Files
panopainter/src/assets/image_metadata.cpp

147 lines
4.6 KiB
C++

#include "assets/image_metadata.h"
#include <cstddef>
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<const std::byte> 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<const std::byte> bytes, std::size_t offset) noexcept
{
return (static_cast<std::uint32_t>(std::to_integer<std::uint8_t>(bytes[offset])) << 24U)
| (static_cast<std::uint32_t>(std::to_integer<std::uint8_t>(bytes[offset + 1U])) << 16U)
| (static_cast<std::uint32_t>(std::to_integer<std::uint8_t>(bytes[offset + 2U])) << 8U)
| static_cast<std::uint32_t>(std::to_integer<std::uint8_t>(bytes[offset + 3U]));
}
[[nodiscard]] pp::foundation::Result<ImageColorType> parse_png_color_type(std::byte value) noexcept
{
switch (std::to_integer<std::uint8_t>(value)) {
case 0:
return pp::foundation::Result<ImageColorType>::success(ImageColorType::grayscale);
case 2:
return pp::foundation::Result<ImageColorType>::success(ImageColorType::rgb);
case 3:
return pp::foundation::Result<ImageColorType>::success(ImageColorType::indexed);
case 4:
return pp::foundation::Result<ImageColorType>::success(ImageColorType::grayscale_alpha);
case 6:
return pp::foundation::Result<ImageColorType>::success(ImageColorType::rgba);
default:
return pp::foundation::Result<ImageColorType>::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<ImageMetadata> parse_png_metadata(std::span<const std::byte> bytes) noexcept
{
constexpr std::size_t png_ihdr_end = 33;
if (bytes.size() < png_ihdr_end) {
return pp::foundation::Result<ImageMetadata>::failure(
pp::foundation::Status::out_of_range("PNG metadata is truncated"));
}
if (!has_png_signature(bytes)) {
return pp::foundation::Result<ImageMetadata>::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<ImageMetadata>::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<ImageMetadata>::failure(
pp::foundation::Status::out_of_range("PNG dimensions are outside the configured range"));
}
const auto bit_depth = std::to_integer<std::uint8_t>(bytes[24]);
if (bit_depth == 0U) {
return pp::foundation::Result<ImageMetadata>::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<ImageMetadata>::failure(color_type.status());
}
return pp::foundation::Result<ImageMetadata>::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";
}
}