From d6ff7d065e61897609c5b5131a4e18a2ca3c96fe Mon Sep 17 00:00:00 2001 From: Lynix Date: Tue, 8 Mar 2016 13:11:09 +0100 Subject: [PATCH] Utility/Image: Add saver (allowing to save images) Former-commit-id: df78d657256f8a6b7dad5ab11877aae7402608b3 --- include/Nazara/Renderer/Texture.hpp | 5 +- include/Nazara/Utility/Image.hpp | 15 ++ src/Nazara/Renderer/Texture.cpp | 24 +++ src/Nazara/Utility/Formats/STBLoader.cpp | 4 +- src/Nazara/Utility/Formats/STBLoader.hpp | 10 +- src/Nazara/Utility/Formats/STBSaver.cpp | 242 +++++++++++++++++++++++ src/Nazara/Utility/Formats/STBSaver.hpp | 21 ++ src/Nazara/Utility/Image.cpp | 95 +++++++++ src/Nazara/Utility/Utility.cpp | 7 +- 9 files changed, 413 insertions(+), 10 deletions(-) create mode 100644 src/Nazara/Utility/Formats/STBSaver.cpp create mode 100644 src/Nazara/Utility/Formats/STBSaver.hpp diff --git a/include/Nazara/Renderer/Texture.hpp b/include/Nazara/Renderer/Texture.hpp index 15f3c11ec..73069aa37 100644 --- a/include/Nazara/Renderer/Texture.hpp +++ b/include/Nazara/Renderer/Texture.hpp @@ -22,7 +22,6 @@ namespace Nz { - class Texture; using TextureConstRef = ObjectRef; @@ -94,6 +93,10 @@ namespace Nz bool LoadFaceFromMemory(CubemapFace face, const void* data, std::size_t size, const ImageParams& params = ImageParams()); bool LoadFaceFromStream(CubemapFace face, Stream& stream, const ImageParams& params = ImageParams()); + // Save + bool SaveToFile(const String& filePath, const ImageParams& params = ImageParams()); + bool SaveToStream(Stream& stream, const String& format, const ImageParams& params = ImageParams()); + bool SetMipmapRange(UInt8 minLevel, UInt8 maxLevel); bool Update(const Image& image, UInt8 level = 0); diff --git a/include/Nazara/Utility/Image.hpp b/include/Nazara/Utility/Image.hpp index ed9772a88..5ef32f2e7 100644 --- a/include/Nazara/Utility/Image.hpp +++ b/include/Nazara/Utility/Image.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -43,12 +44,14 @@ namespace Nz using ImageLoader = ResourceLoader; using ImageManager = ResourceManager; using ImageRef = ObjectRef; + using ImageSaver = ResourceSaver; class NAZARA_UTILITY_API Image : public AbstractImage, public RefCounted, public Resource { friend ImageLibrary; friend ImageLoader; friend ImageManager; + friend ImageSaver; friend class Utility; public: @@ -107,6 +110,17 @@ namespace Nz bool LoadCubemapFromMemory(const void* data, std::size_t size, const ImageParams& imageParams = ImageParams(), const CubemapParams& cubemapParams = CubemapParams()); bool LoadCubemapFromStream(Stream& stream, const ImageParams& imageParams = ImageParams(), const CubemapParams& cubemapParams = CubemapParams()); + // LoadFace + bool LoadFaceFromFile(CubemapFace face, const String& filePath, const ImageParams& params = ImageParams()); + bool LoadFaceFromMemory(CubemapFace face, const void* data, std::size_t size, const ImageParams& params = ImageParams()); + bool LoadFaceFromStream(CubemapFace face, Stream& stream, const ImageParams& params = ImageParams()); + + // Save + bool SaveToFile(const String& filePath, const ImageParams& params = ImageParams()); + bool SaveToStream(Stream& stream, const String& format, const ImageParams& params = ImageParams()); + + //TODO: SaveArray, SaveCubemap, SaveFace + void SetLevelCount(UInt8 levelCount); bool SetPixelColor(const Color& color, unsigned int x, unsigned int y = 0, unsigned int z = 0); @@ -165,6 +179,7 @@ namespace Nz static ImageLoader::LoaderList s_loaders; static ImageManager::ManagerMap s_managerMap; static ImageManager::ManagerParams s_managerParameters; + static ImageSaver::SaverList s_savers; }; } diff --git a/src/Nazara/Renderer/Texture.cpp b/src/Nazara/Renderer/Texture.cpp index fea1e2d6b..83b04ee33 100644 --- a/src/Nazara/Renderer/Texture.cpp +++ b/src/Nazara/Renderer/Texture.cpp @@ -825,6 +825,30 @@ namespace Nz return Update(image, Rectui(0, 0, faceSize, faceSize), face); } + bool Texture::SaveToFile(const String& filePath, const ImageParams& params) + { + Image image; + if (!Download(&image)) + { + NazaraError("Failed to download texture"); + return false; + } + + return image.SaveToFile(filePath, params); + } + + bool Texture::SaveToStream(Stream& stream, const String& format, const ImageParams& params) + { + Image image; + if (!Download(&image)) + { + NazaraError("Failed to download texture"); + return false; + } + + return image.SaveToStream(stream, format, params); + } + bool Texture::SetMipmapRange(UInt8 minLevel, UInt8 maxLevel) { #if NAZARA_RENDERER_SAFE diff --git a/src/Nazara/Utility/Formats/STBLoader.cpp b/src/Nazara/Utility/Formats/STBLoader.cpp index ef1a815b4..6954bd2b3 100644 --- a/src/Nazara/Utility/Formats/STBLoader.cpp +++ b/src/Nazara/Utility/Formats/STBLoader.cpp @@ -87,12 +87,12 @@ namespace Nz namespace Loaders { - void RegisterSTB() + void RegisterSTBLoader() { ImageLoader::RegisterLoader(IsSupported, Check, Load); } - void UnregisterSTB() + void UnregisterSTBLoader() { ImageLoader::UnregisterLoader(IsSupported, Check, Load); } diff --git a/src/Nazara/Utility/Formats/STBLoader.hpp b/src/Nazara/Utility/Formats/STBLoader.hpp index 4d8917ef5..03e08c72f 100644 --- a/src/Nazara/Utility/Formats/STBLoader.hpp +++ b/src/Nazara/Utility/Formats/STBLoader.hpp @@ -4,8 +4,8 @@ #pragma once -#ifndef NAZARA_LOADERS_STB_HPP -#define NAZARA_LOADERS_STB_HPP +#ifndef NAZARA_FORMATS_STBLOADER_HPP +#define NAZARA_FORMATS_STBLOADER_HPP #include @@ -13,9 +13,9 @@ namespace Nz { namespace Loaders { - void RegisterSTB(); - void UnregisterSTB(); + void RegisterSTBLoader(); + void UnregisterSTBLoader(); } } -#endif // NAZARA_LOADERS_STB_HPP +#endif // NAZARA_FORMATS_STBLOADER_HPP diff --git a/src/Nazara/Utility/Formats/STBSaver.cpp b/src/Nazara/Utility/Formats/STBSaver.cpp new file mode 100644 index 000000000..e68210d93 --- /dev/null +++ b/src/Nazara/Utility/Formats/STBSaver.cpp @@ -0,0 +1,242 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + namespace + { + using FormatHandler = bool(*)(const Image& image, const ImageParams& parameters, Stream& stream); + + std::map s_formatHandlers; + + int ConvertToFloatFormat(Image& image) + { + switch (image.GetFormat()) + { + case PixelFormatType_R32F: + return 1; + + case PixelFormatType_RG32F: + return 2; + + case PixelFormatType_RGB32F: + return 3; + + case PixelFormatType_RGBA32F: + return 4; + + default: + { + if (PixelFormat::HasAlpha(image.GetFormat())) + { + if (!image.Convert(PixelFormatType_RGBA32F)) + break; + + return 4; + } + else + { + if (!image.Convert(PixelFormatType_RGB32F)) + break; + + return 3; + } + } + } + + return 0; + } + + int ConvertToIntegerFormat(Image& image) + { + switch (image.GetFormat()) + { + case PixelFormatType_L8: + case PixelFormatType_R8: + return 1; + + case PixelFormatType_LA8: + case PixelFormatType_RG8: + return 2; + + case PixelFormatType_RGB8: + return 3; + + case PixelFormatType_RGBA8: + return 4; + + default: + { + if (PixelFormat::HasAlpha(image.GetFormat())) + { + if (!image.Convert(PixelFormatType_RGBA8)) + break; + + return 4; + } + else + { + if (!image.Convert(PixelFormatType_RGB8)) + break; + + return 3; + } + } + } + + return 0; + } + + void WriteToStream(void* userdata, void* data, int size) + { + Stream* stream = static_cast(userdata); + if (stream->Write(data, size) != size) + throw std::runtime_error("Failed to write to stream"); + } + + bool FormatQuerier(const String& extension) + { + return s_formatHandlers.find(extension) != s_formatHandlers.end(); + } + + bool SaveToStream(const Image& image, const String& format, Stream& stream, const ImageParams& parameters) + { + NazaraUnused(parameters); + + if (!image.IsValid()) + { + NazaraError("Invalid image"); + return false; + } + + ImageType type = image.GetType(); + if (type != ImageType_1D && type != ImageType_2D) + { + NazaraError("Image type 0x" + String::Number(type, 16) + " is not "); + return false; + } + + auto it = s_formatHandlers.find(format); + NazaraAssert(it != s_formatHandlers.end(), "Invalid handler"); + + const FormatHandler& handler = it->second; + try + { + return handler(image, parameters, stream); + } + catch (const std::exception& e) + { + NazaraError(e.what()); + return false; + } + } + + bool SaveBMP(const Image& image, const ImageParams& parameters, Stream& stream) + { + Image tempImage(image); //< We're using COW here to prevent Image copy unless required + + int componentCount = ConvertToIntegerFormat(tempImage); + if (componentCount == 0) + { + NazaraError("Failed to convert image to suitable format"); + return false; + } + + if (!stbi_write_bmp_to_func(&WriteToStream, &stream, tempImage.GetWidth(), tempImage.GetHeight(), componentCount, tempImage.GetConstPixels())) + { + NazaraError("Failed to write BMP to stream"); + return false; + } + + return true; + } + + bool SaveHDR(const Image& image, const ImageParams& parameters, Stream& stream) + { + Image tempImage(image); //< We're using COW here to prevent Image copy unless required + + int componentCount = ConvertToFloatFormat(tempImage); + if (componentCount == 0) + { + NazaraError("Failed to convert image to suitable format"); + return false; + } + + if (!stbi_write_hdr_to_func(&WriteToStream, &stream, tempImage.GetWidth(), tempImage.GetHeight(), componentCount, reinterpret_cast(tempImage.GetConstPixels()))) + { + NazaraError("Failed to write BMP to stream"); + return false; + } + + return true; + } + + bool SavePNG(const Image& image, const ImageParams& parameters, Stream& stream) + { + Image tempImage(image); //< We're using COW here to prevent Image copy unless required + + int componentCount = ConvertToIntegerFormat(tempImage); + if (componentCount == 0) + { + NazaraError("Failed to convert image to suitable format"); + return false; + } + + if (!stbi_write_png_to_func(&WriteToStream, &stream, tempImage.GetWidth(), tempImage.GetHeight(), componentCount, tempImage.GetConstPixels(), 0)) + { + NazaraError("Failed to write BMP to stream"); + return false; + } + + return true; + } + + bool SaveTGA(const Image& image, const ImageParams& parameters, Stream& stream) + { + Image tempImage(image); //< We're using COW here to prevent Image copy unless required + + int componentCount = ConvertToIntegerFormat(tempImage); + if (componentCount == 0) + { + NazaraError("Failed to convert image to suitable format"); + return false; + } + + if (!stbi_write_tga_to_func(&WriteToStream, &stream, tempImage.GetWidth(), tempImage.GetHeight(), componentCount, tempImage.GetConstPixels())) + { + NazaraError("Failed to write BMP to stream"); + return false; + } + + return true; + } + } + + namespace Loaders + { + void RegisterSTBSaver() + { + s_formatHandlers["bmp"] = &SaveBMP; + s_formatHandlers["hdr"] = &SaveHDR; + s_formatHandlers["png"] = &SavePNG; + s_formatHandlers["tga"] = &SaveTGA; + + ImageSaver::RegisterSaver(FormatQuerier, SaveToStream); + } + + void UnregisterSTBSaver() + { + ImageSaver::UnregisterSaver(FormatQuerier, SaveToStream); + s_formatHandlers.clear(); + } + } +} diff --git a/src/Nazara/Utility/Formats/STBSaver.hpp b/src/Nazara/Utility/Formats/STBSaver.hpp new file mode 100644 index 000000000..9a511c07e --- /dev/null +++ b/src/Nazara/Utility/Formats/STBSaver.hpp @@ -0,0 +1,21 @@ +// Copyright (C) 2015 Jérôme Leclercq +// This file is part of the "Nazara Engine - Utility module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_FORMATS_STBSAVER_HPP +#define NAZARA_FORMATS_STBSAVER_HPP + +#include + +namespace Nz +{ + namespace Loaders + { + void RegisterSTBSaver(); + void UnregisterSTBSaver(); + } +} + +#endif // NAZARA_FORMATS_STBSAVER_HPP diff --git a/src/Nazara/Utility/Image.cpp b/src/Nazara/Utility/Image.cpp index c8845f808..0965ed06e 100644 --- a/src/Nazara/Utility/Image.cpp +++ b/src/Nazara/Utility/Image.cpp @@ -1066,6 +1066,100 @@ namespace Nz return LoadCubemapFromImage(image, cubemapParams); } + bool Image::LoadFaceFromFile(CubemapFace face, const String& filePath, const ImageParams& params) + { + NazaraAssert(IsValid() && IsCubemap(), "Texture must be a valid cubemap"); + + Image image; + if (!image.LoadFromFile(filePath, params)) + { + NazaraError("Failed to load image"); + return false; + } + + if (!image.Convert(GetFormat())) + { + NazaraError("Failed to convert image to texture format"); + return false; + } + + unsigned int faceSize = GetWidth(); + if (image.GetWidth() != faceSize || image.GetHeight() != faceSize) + { + NazaraError("Image size must match texture face size"); + return false; + } + + Copy(image, Rectui(0, 0, faceSize, faceSize), Vector3ui(0, 0, face)); + return true; + } + + bool Image::LoadFaceFromMemory(CubemapFace face, const void* data, std::size_t size, const ImageParams& params) + { + NazaraAssert(IsValid() && IsCubemap(), "Texture must be a valid cubemap"); + + Image image; + if (!image.LoadFromMemory(data, size, params)) + { + NazaraError("Failed to load image"); + return false; + } + + if (!image.Convert(GetFormat())) + { + NazaraError("Failed to convert image to texture format"); + return false; + } + + unsigned int faceSize = GetWidth(); + if (image.GetWidth() != faceSize || image.GetHeight() != faceSize) + { + NazaraError("Image size must match texture face size"); + return false; + } + + Copy(image, Rectui(0, 0, faceSize, faceSize), Vector3ui(0, 0, face)); + return true; + } + + bool Image::LoadFaceFromStream(CubemapFace face, Stream& stream, const ImageParams& params) + { + NazaraAssert(IsValid() && IsCubemap(), "Texture must be a valid cubemap"); + + Image image; + if (!image.LoadFromStream(stream, params)) + { + NazaraError("Failed to load image"); + return false; + } + + if (!image.Convert(GetFormat())) + { + NazaraError("Failed to convert image to texture format"); + return false; + } + + unsigned int faceSize = GetWidth(); + if (image.GetWidth() != faceSize || image.GetHeight() != faceSize) + { + NazaraError("Image size must match texture face size"); + return false; + } + + Copy(image, Rectui(0, 0, faceSize, faceSize), Vector3ui(0, 0, face)); + return true; + } + + bool Image::SaveToFile(const String& filePath, const ImageParams& params) + { + return ImageSaver::SaveToFile(*this, filePath, params); + } + + bool Image::SaveToStream(Stream& stream, const String& format, const ImageParams& params) + { + return ImageSaver::SaveToStream(*this, stream, format, params); + } + void Image::SetLevelCount(UInt8 levelCount) { #if NAZARA_UTILITY_SAFE @@ -1377,4 +1471,5 @@ namespace Nz ImageLoader::LoaderList Image::s_loaders; ImageManager::ManagerMap Image::s_managerMap; ImageManager::ManagerParams Image::s_managerParameters; + ImageSaver::SaverList Image::s_savers; } diff --git a/src/Nazara/Utility/Utility.cpp b/src/Nazara/Utility/Utility.cpp index cc4687197..17a1bd049 100644 --- a/src/Nazara/Utility/Utility.cpp +++ b/src/Nazara/Utility/Utility.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include namespace Nz @@ -112,7 +113,8 @@ namespace Nz Loaders::RegisterFreeType(); // Image - Loaders::RegisterSTB(); // Loader générique (STB) + Loaders::RegisterSTBLoader(); // Generic loader (STB) + Loaders::RegisterSTBSaver(); // Generic saver (STB) /// Loaders spécialisés // Animation @@ -155,7 +157,8 @@ namespace Nz Loaders::UnregisterMD5Anim(); Loaders::UnregisterMD5Mesh(); Loaders::UnregisterPCX(); - Loaders::UnregisterSTB(); + Loaders::UnregisterSTBLoader(); + Loaders::UnregisterSTBSaver(); Window::Uninitialize(); VertexDeclaration::Uninitialize();