Skip to content

Commit

Permalink
[Data/ImageFormat] Images can be loaded from a byte array
Browse files Browse the repository at this point in the history
- Either a vector, or a pointer and a size, can be given
  • Loading branch information
Razakhel committed Dec 20, 2023
1 parent f82df35 commit ee7fce4
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 11 deletions.
15 changes: 15 additions & 0 deletions include/RaZ/Data/ImageFormat.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#ifndef RAZ_IMAGEFORMAT_HPP
#define RAZ_IMAGEFORMAT_HPP

#include <vector>

namespace Raz {

class FilePath;
Expand All @@ -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<unsigned char>& 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.
Expand Down
47 changes: 42 additions & 5 deletions src/RaZ/Data/ImageFormat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ ImageColorspace recoverColorspace(int channelCount) {
}
}

Image createImageFromData(int width, int height, int channelCount, bool isHdr, const std::unique_ptr<void, ImageDataDeleter>& 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<float*>(data.get()), valueCount, static_cast<float*>(img.getDataPtr()));
else
std::copy_n(static_cast<uint8_t*>(data.get()), valueCount, static_cast<uint8_t*>(img.getDataPtr()));

return img;
}

} // namespace

Image load(const FilePath& filePath, bool flipVertically) {
Expand All @@ -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<unsigned char>& 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<int>(dataSize));

int width {};
int height {};
int channelCount {};
std::unique_ptr<void, ImageDataDeleter> data;

if (isHdr)
std::copy_n(static_cast<float*>(data.get()), valueCount, static_cast<float*>(img.getDataPtr()));
data.reset(stbi_loadf_from_memory(imgData, static_cast<int>(dataSize), &width, &height, &channelCount, 0));
else
std::copy_n(static_cast<uint8_t*>(data.get()), valueCount, static_cast<uint8_t*>(img.getDataPtr()));
data.reset(stbi_load_from_memory(imgData, static_cast<int>(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<uint8_t>(channelCount), isHdr, data);

Logger::debug("[ImageFormat] Loaded image from data");

return img;
}
Expand Down
14 changes: 9 additions & 5 deletions src/RaZ/Script/LuaFileFormat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,15 @@ void LuaWrapper::registerFileFormatTypes() {
}

{
sol::table imageFormat = state["ImageFormat"].get_or_create<sol::table>();
imageFormat["load"] = sol::overload([] (const FilePath& p) { return ImageFormat::load(p); },
PickOverload<const FilePath&, bool>(&ImageFormat::load));
imageFormat["save"] = sol::overload([] (const FilePath& p, const Image& i) { ImageFormat::save(p, i); },
PickOverload<const FilePath&, const Image&, bool>(&ImageFormat::save));
sol::table imageFormat = state["ImageFormat"].get_or_create<sol::table>();
imageFormat["load"] = sol::overload([] (const FilePath& p) { return ImageFormat::load(p); },
PickOverload<const FilePath&, bool>(&ImageFormat::load));
imageFormat["loadFromData"] = sol::overload([] (const std::vector<unsigned char>& d) { return ImageFormat::loadFromData(d); },
PickOverload<const std::vector<unsigned char>&, bool>(&ImageFormat::loadFromData),
[] (const unsigned char* d, std::size_t s) { return ImageFormat::loadFromData(d, s); },
PickOverload<const unsigned char*, std::size_t, bool>(&ImageFormat::loadFromData));
imageFormat["save"] = sol::overload([] (const FilePath& p, const Image& i) { ImageFormat::save(p, i); },
PickOverload<const FilePath&, const Image&, bool>(&ImageFormat::save));
}

{
Expand Down
11 changes: 11 additions & 0 deletions tests/src/RaZ/Data/ImageFormat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "RaZ/Data/Image.hpp"
#include "RaZ/Data/ImageFormat.hpp"
#include "RaZ/Utils/FileUtils.hpp"

#include <array>

Expand Down Expand Up @@ -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<unsigned char> 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
Expand Down
2 changes: 1 addition & 1 deletion tests/src/RaZ/Script/LuaData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
)"));
}

Expand Down

0 comments on commit ee7fce4

Please sign in to comment.