#include "assets/image_pixels.h" #include "assets/image_metadata.h" #include #include #define STB_IMAGE_STATIC #define STB_IMAGE_IMPLEMENTATION #include #define STB_IMAGE_WRITE_STATIC #define STB_IMAGE_WRITE_IMPLEMENTATION #include namespace pp::assets { namespace { [[nodiscard]] pp::foundation::Result rgba_byte_size( std::uint32_t width, std::uint32_t height) noexcept { const auto pixels = static_cast(width) * static_cast(height); constexpr auto channels = 4ULL; if (pixels > std::numeric_limits::max() / channels) { return pp::foundation::Result::failure( pp::foundation::Status::out_of_range("RGBA byte size overflows")); } const auto bytes = pixels * channels; if (bytes > static_cast(std::numeric_limits::max())) { return pp::foundation::Result::failure( pp::foundation::Status::out_of_range("RGBA byte size exceeds addressable memory")); } return pp::foundation::Result::success(static_cast(bytes)); } void append_png_bytes(void* context, void* data, int size) { if (context == nullptr || data == nullptr || size <= 0) { return; } auto* bytes = static_cast*>(context); const auto* begin = static_cast(data); bytes->insert(bytes->end(), begin, begin + size); } } pp::foundation::Result decode_png_rgba8(std::span bytes) { const auto metadata = parse_png_metadata(bytes); if (!metadata) { return pp::foundation::Result::failure(metadata.status()); } if (bytes.size() > static_cast(std::numeric_limits::max())) { return pp::foundation::Result::failure( pp::foundation::Status::out_of_range("PNG payload is too large for the decoder")); } int width = 0; int height = 0; int source_components = 0; auto* decoded = stbi_load_from_memory( reinterpret_cast(bytes.data()), static_cast(bytes.size()), &width, &height, &source_components, 4); if (decoded == nullptr) { return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("PNG payload could not be decoded")); } const auto cleanup = [decoded]() noexcept { stbi_image_free(decoded); }; if (width <= 0 || height <= 0 || static_cast(width) != metadata.value().width || static_cast(height) != metadata.value().height) { cleanup(); return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("decoded PNG dimensions are inconsistent")); } const auto byte_count = rgba_byte_size(metadata.value().width, metadata.value().height); if (!byte_count) { cleanup(); return pp::foundation::Result::failure(byte_count.status()); } Rgba8Image image { .width = metadata.value().width, .height = metadata.value().height, .pixels = {}, }; image.pixels.assign(decoded, decoded + byte_count.value()); cleanup(); return pp::foundation::Result::success(std::move(image)); } pp::foundation::Result> encode_png_rgba8( std::uint32_t width, std::uint32_t height, std::span pixels) { if (width == 0 || height == 0) { return pp::foundation::Result>::failure( pp::foundation::Status::invalid_argument("PNG dimensions must be greater than zero")); } if (width > static_cast(std::numeric_limits::max()) || height > static_cast(std::numeric_limits::max())) { return pp::foundation::Result>::failure( pp::foundation::Status::out_of_range("PNG dimensions exceed encoder limits")); } const auto byte_count = rgba_byte_size(width, height); if (!byte_count) { return pp::foundation::Result>::failure(byte_count.status()); } if (pixels.size() != byte_count.value()) { return pp::foundation::Result>::failure( pp::foundation::Status::invalid_argument("RGBA pixel payload size does not match dimensions")); } const auto stride = static_cast(width) * 4ULL; if (stride > static_cast(std::numeric_limits::max())) { return pp::foundation::Result>::failure( pp::foundation::Status::out_of_range("PNG row stride exceeds encoder limits")); } std::vector encoded; const auto result = stbi_write_png_to_func( append_png_bytes, &encoded, static_cast(width), static_cast(height), 4, pixels.data(), static_cast(stride)); if (result == 0 || encoded.empty()) { return pp::foundation::Result>::failure( pp::foundation::Status::invalid_argument("RGBA pixels could not be encoded as PNG")); } return pp::foundation::Result>::success(std::move(encoded)); } }