From ee7fce4500c714703bf924d65ca1c5209649fcc8 Mon Sep 17 00:00:00 2001 From: Razakhel Date: Wed, 20 Dec 2023 22:58:51 +0100 Subject: [PATCH] [Data/ImageFormat] Images can be loaded from a byte array - Either a vector, or a pointer and a size, can be given --- include/RaZ/Data/ImageFormat.hpp | 15 ++++++++++ src/RaZ/Data/ImageFormat.cpp | 47 ++++++++++++++++++++++++++---- src/RaZ/Script/LuaFileFormat.cpp | 14 +++++---- tests/src/RaZ/Data/ImageFormat.cpp | 11 +++++++ tests/src/RaZ/Script/LuaData.cpp | 2 +- 5 files changed, 78 insertions(+), 11 deletions(-) diff --git a/include/RaZ/Data/ImageFormat.hpp b/include/RaZ/Data/ImageFormat.hpp index 2d8a4f61..3ce00ec6 100644 --- a/include/RaZ/Data/ImageFormat.hpp +++ b/include/RaZ/Data/ImageFormat.hpp @@ -3,6 +3,8 @@ #ifndef RAZ_IMAGEFORMAT_HPP #define RAZ_IMAGEFORMAT_HPP +#include + namespace Raz { class FilePath; @@ -16,6 +18,19 @@ namespace ImageFormat { /// \return Loaded image's data. Image load(const FilePath& filePath, bool flipVertically = false); +/// Loads an image from a byte array. +/// \param imgData Data to be loaded as image. +/// \param flipVertically Flip vertically the image when loading. +/// \return Loaded image's data. +Image loadFromData(const std::vector& imgData, bool flipVertically = false); + +/// Loads an image from a byte array. +/// \param imgData Data to be loaded as image. +/// \param dataSize Size of the data to be loaded. +/// \param flipVertically Flip vertically the image when loading. +/// \return Loaded image's data. +Image loadFromData(const unsigned char* imgData, std::size_t dataSize, bool flipVertically = false); + /// Saves an image to a file. /// \param filePath File to which to save the image. /// \param flipVertically Flip vertically the image when saving. diff --git a/src/RaZ/Data/ImageFormat.cpp b/src/RaZ/Data/ImageFormat.cpp index cdbf3d8d..48f09fb9 100644 --- a/src/RaZ/Data/ImageFormat.cpp +++ b/src/RaZ/Data/ImageFormat.cpp @@ -29,6 +29,19 @@ ImageColorspace recoverColorspace(int channelCount) { } } +Image createImageFromData(int width, int height, int channelCount, bool isHdr, const std::unique_ptr& data) { + const std::size_t valueCount = width * height * channelCount; + + Image img(width, height, recoverColorspace(channelCount), (isHdr ? ImageDataType::FLOAT : ImageDataType::BYTE)); + + if (isHdr) + std::copy_n(static_cast(data.get()), valueCount, static_cast(img.getDataPtr())); + else + std::copy_n(static_cast(data.get()), valueCount, static_cast(img.getDataPtr())); + + return img; +} + } // namespace Image load(const FilePath& filePath, bool flipVertically) { @@ -52,16 +65,40 @@ Image load(const FilePath& filePath, bool flipVertically) { if (data == nullptr) throw std::invalid_argument("[ImageFormat] Cannot load image '" + filePath + "': " + stbi_failure_reason()); - const std::size_t valueCount = width * height * channelCount; + Image img = createImageFromData(width, height, channelCount, isHdr, data); - Image img(width, height, recoverColorspace(channelCount), (isHdr ? ImageDataType::FLOAT : ImageDataType::BYTE)); + Logger::debug("[ImageFormat] Loaded image"); + + return img; +} + +Image loadFromData(const std::vector& imgData, bool flipVertically) { + return loadFromData(imgData.data(), imgData.size(), flipVertically); +} + +Image loadFromData(const unsigned char* imgData, std::size_t dataSize, bool flipVertically) { + Logger::debug("[ImageFormat] Loading image from data..."); + + stbi_set_flip_vertically_on_load(flipVertically); + + const bool isHdr = stbi_is_hdr_from_memory(imgData, static_cast(dataSize)); + + int width {}; + int height {}; + int channelCount {}; + std::unique_ptr data; if (isHdr) - std::copy_n(static_cast(data.get()), valueCount, static_cast(img.getDataPtr())); + data.reset(stbi_loadf_from_memory(imgData, static_cast(dataSize), &width, &height, &channelCount, 0)); else - std::copy_n(static_cast(data.get()), valueCount, static_cast(img.getDataPtr())); + data.reset(stbi_load_from_memory(imgData, static_cast(dataSize), &width, &height, &channelCount, 0)); - Logger::debug("[ImageFormat] Loaded image"); + if (data == nullptr) + throw std::invalid_argument("[ImageFormat] Cannot load image from data: " + std::string(stbi_failure_reason())); + + Image img = createImageFromData(width, height, static_cast(channelCount), isHdr, data); + + Logger::debug("[ImageFormat] Loaded image from data"); return img; } diff --git a/src/RaZ/Script/LuaFileFormat.cpp b/src/RaZ/Script/LuaFileFormat.cpp index 71c28cbe..a09df671 100644 --- a/src/RaZ/Script/LuaFileFormat.cpp +++ b/src/RaZ/Script/LuaFileFormat.cpp @@ -49,11 +49,15 @@ void LuaWrapper::registerFileFormatTypes() { } { - sol::table imageFormat = state["ImageFormat"].get_or_create(); - imageFormat["load"] = sol::overload([] (const FilePath& p) { return ImageFormat::load(p); }, - PickOverload(&ImageFormat::load)); - imageFormat["save"] = sol::overload([] (const FilePath& p, const Image& i) { ImageFormat::save(p, i); }, - PickOverload(&ImageFormat::save)); + sol::table imageFormat = state["ImageFormat"].get_or_create(); + imageFormat["load"] = sol::overload([] (const FilePath& p) { return ImageFormat::load(p); }, + PickOverload(&ImageFormat::load)); + imageFormat["loadFromData"] = sol::overload([] (const std::vector& d) { return ImageFormat::loadFromData(d); }, + PickOverload&, bool>(&ImageFormat::loadFromData), + [] (const unsigned char* d, std::size_t s) { return ImageFormat::loadFromData(d, s); }, + PickOverload(&ImageFormat::loadFromData)); + imageFormat["save"] = sol::overload([] (const FilePath& p, const Image& i) { ImageFormat::save(p, i); }, + PickOverload(&ImageFormat::save)); } { diff --git a/tests/src/RaZ/Data/ImageFormat.cpp b/tests/src/RaZ/Data/ImageFormat.cpp index 2c735e8f..52681c78 100644 --- a/tests/src/RaZ/Data/ImageFormat.cpp +++ b/tests/src/RaZ/Data/ImageFormat.cpp @@ -2,6 +2,7 @@ #include "RaZ/Data/Image.hpp" #include "RaZ/Data/ImageFormat.hpp" +#include "RaZ/Utils/FileUtils.hpp" #include @@ -66,6 +67,16 @@ void checkImage(const Raz::FilePath& filePath, uint8_t expectedChannelCount, con expectedChannelCount, expectedColorspace, { expectedValues[2], expectedValues[3], expectedValues[0], expectedValues[1] }); + + const std::vector fileContent = Raz::FileUtils::readFileToArray(filePath); + checkImageData(Raz::ImageFormat::loadFromData(fileContent), + expectedChannelCount, + expectedColorspace, + expectedValues); + checkImageData(Raz::ImageFormat::loadFromData(fileContent, true), + expectedChannelCount, + expectedColorspace, + { expectedValues[2], expectedValues[3], expectedValues[0], expectedValues[1] }); } } // namespace diff --git a/tests/src/RaZ/Script/LuaData.cpp b/tests/src/RaZ/Script/LuaData.cpp index ac53b0a1..86e6c884 100644 --- a/tests/src/RaZ/Script/LuaData.cpp +++ b/tests/src/RaZ/Script/LuaData.cpp @@ -102,7 +102,7 @@ TEST_CASE("LuaData Image") { local pngPath = FilePath.new(RAZ_TESTS_ROOT .. "assets/images/dëfàùltTêst.png") local tgaPath = FilePath.new(RAZ_TESTS_ROOT .. "assets/images/dëfàùltTêst.tga") assert(ImageFormat.load(pngPath) == PngFormat.load(pngPath)) - assert(ImageFormat.load(tgaPath) == TgaFormat.load(tgaPath)) + assert(ImageFormat.loadFromData(FileUtils.readFileToArray(tgaPath), true) == TgaFormat.load(tgaPath, true)) )")); }