#include "assets/image_metadata.h" #include namespace pp::assets { namespace { constexpr std::byte png_signature[] { std::byte { 0x89 }, std::byte { 0x50 }, std::byte { 0x4e }, std::byte { 0x47 }, std::byte { 0x0d }, std::byte { 0x0a }, std::byte { 0x1a }, std::byte { 0x0a }, }; [[nodiscard]] bool has_png_signature(std::span bytes) noexcept { if (bytes.size() < 8U) { return false; } for (std::size_t i = 0; i < 8U; ++i) { if (bytes[i] != png_signature[i]) { return false; } } return true; } [[nodiscard]] std::uint32_t read_u32_be(std::span bytes, std::size_t offset) noexcept { return (static_cast(std::to_integer(bytes[offset])) << 24U) | (static_cast(std::to_integer(bytes[offset + 1U])) << 16U) | (static_cast(std::to_integer(bytes[offset + 2U])) << 8U) | static_cast(std::to_integer(bytes[offset + 3U])); } [[nodiscard]] pp::foundation::Result parse_png_color_type(std::byte value) noexcept { switch (std::to_integer(value)) { case 0: return pp::foundation::Result::success(ImageColorType::grayscale); case 2: return pp::foundation::Result::success(ImageColorType::rgb); case 3: return pp::foundation::Result::success(ImageColorType::indexed); case 4: return pp::foundation::Result::success(ImageColorType::grayscale_alpha); case 6: return pp::foundation::Result::success(ImageColorType::rgba); default: return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("PNG color type is unsupported")); } } [[nodiscard]] std::uint8_t component_count(ImageColorType color_type) noexcept { switch (color_type) { case ImageColorType::grayscale: case ImageColorType::indexed: return 1; case ImageColorType::grayscale_alpha: return 2; case ImageColorType::rgb: return 3; case ImageColorType::rgba: return 4; } return 0; } } pp::foundation::Result parse_png_metadata(std::span bytes) noexcept { constexpr std::size_t png_ihdr_end = 33; if (bytes.size() < png_ihdr_end) { return pp::foundation::Result::failure( pp::foundation::Status::out_of_range("PNG metadata is truncated")); } if (!has_png_signature(bytes)) { return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("PNG signature is invalid")); } const auto ihdr_length = read_u32_be(bytes, 8); if (ihdr_length != 13U || bytes[12] != std::byte { 'I' } || bytes[13] != std::byte { 'H' } || bytes[14] != std::byte { 'D' } || bytes[15] != std::byte { 'R' }) { return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("PNG IHDR chunk is invalid")); } const auto width = read_u32_be(bytes, 16); const auto height = read_u32_be(bytes, 20); if (width == 0 || height == 0 || width > max_image_dimension || height > max_image_dimension) { return pp::foundation::Result::failure( pp::foundation::Status::out_of_range("PNG dimensions are outside the configured range")); } const auto bit_depth = std::to_integer(bytes[24]); if (bit_depth == 0U) { return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("PNG bit depth is invalid")); } const auto color_type = parse_png_color_type(bytes[25]); if (!color_type) { return pp::foundation::Result::failure(color_type.status()); } return pp::foundation::Result::success(ImageMetadata { .width = width, .height = height, .bit_depth = bit_depth, .components = component_count(color_type.value()), .color_type = color_type.value(), }); } const char* image_color_type_name(ImageColorType color_type) noexcept { switch (color_type) { case ImageColorType::grayscale: return "grayscale"; case ImageColorType::rgb: return "rgb"; case ImageColorType::indexed: return "indexed"; case ImageColorType::grayscale_alpha: return "grayscale_alpha"; case ImageColorType::rgba: return "rgba"; } return "unknown"; } }