#include "Decoder/Png.hpp" #include #include #include #include #include #include #define STB_IMAGE_STATIC #define STB_IMAGE_IMPLEMENTATION #define STBI_ONLY_PNG #define STBI_NO_STDIO #include "stb_image.h" static void Require(bool value) { if (!value) std::abort(); } template static bool ThrowsInvalidArgument(TFunction function) { try { function(); } catch (const std::invalid_argument&) { return true; } return false; } static const std::uint8_t PALETTE_TRNS[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x02, 0x03, 0x00, 0x00, 0x00, 0x0F, 0xD8, 0xE5, 0xB7, 0x00, 0x00, 0x00, 0x0C, 0x50, 0x4C, 0x54, 0x45, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xD6, 0x02, 0x8F, 0x7B, 0x00, 0x00, 0x00, 0x04, 0x74, 0x52, 0x4E, 0x53, 0xFF, 0x00, 0xFF, 0x80, 0x13, 0x0A, 0x1E, 0x39, 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0x10, 0x60, 0xD8, 0x00, 0x00, 0x00, 0xE4, 0x00, 0xC1, 0x27, 0xA8, 0xE8, 0x57, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, }; static const std::uint8_t GRAY16[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x81, 0xD9, 0xFC, 0x15, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0x10, 0x32, 0x59, 0x7D, 0x16, 0x00, 0x03, 0x0C, 0x01, 0xBF, 0x6E, 0xB9, 0xC6, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, }; static const std::uint8_t GRAY1[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0xCB, 0x7B, 0xD2, 0xEE, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0x88, 0x04, 0x00, 0x00, 0x5B, 0x00, 0x5A, 0x7C, 0xA5, 0x93, 0x54, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, }; static const std::uint8_t GRAY1_TRNS[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x59, 0x42, 0x27, 0x00, 0x00, 0x00, 0x02, 0x74, 0x52, 0x4E, 0x53, 0x00, 0x02, 0x98, 0x9D, 0xAC, 0x14, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0x63, 0x70, 0x00, 0x00, 0x00, 0x42, 0x00, 0x41, 0x84, 0xBF, 0x8E, 0x62, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, }; static const std::uint8_t PNG_SIGNATURE[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; static void AppendBigEndian32(std::vector& bytes, std::uint32_t value) { for (int shift = 24; shift >= 0; shift -= 8) bytes.push_back(static_cast(value >> shift)); } static std::vector HeaderOnly(std::uint32_t width, std::uint32_t height, std::uint8_t bitDepth, std::uint8_t colorType, std::uint8_t interlace) { std::vector bytes{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0, 0, 0, 13, 'I', 'H', 'D', 'R'}; AppendBigEndian32(bytes, width); AppendBigEndian32(bytes, height); bytes.insert(bytes.end(), {bitDepth, colorType, 0, 0, interlace, 0, 0, 0, 0}); bytes.insert(bytes.end(), {0, 0, 0, 0, 'I', 'E', 'N', 'D', 0, 0, 0, 0}); return bytes; } static std::uint32_t Crc32(std::span bytes) { std::uint32_t crc = 0xFFFFFFFFu; for (const std::uint8_t byte : bytes) { crc ^= byte; for (int bit = 0; bit < 8; ++bit) crc = (crc >> 1) ^ ((crc & 1) != 0 ? 0xEDB88320u : 0u); } return ~crc; } static std::uint32_t Adler32(std::span bytes) { std::uint32_t low = 1; std::uint32_t high = 0; for (const std::uint8_t byte : bytes) { low = (low + byte) % 65521; high = (high + low) % 65521; } return high << 16 | low; } static void AppendTestChunk(std::vector& png, const char* type, std::span data) { AppendBigEndian32(png, static_cast(data.size())); const std::size_t start = png.size(); png.insert(png.end(), type, type + 4); png.insert(png.end(), data.begin(), data.end()); AppendBigEndian32(png, Crc32(std::span(png.data() + start, png.size() - start))); } static std::vector ZlibStore(std::span bytes) { Require(bytes.size() <= 0xFFFF); const std::uint16_t length = static_cast(bytes.size()); const std::uint16_t inverse = static_cast(~length); std::vector output{0x78, 0x01, 0x01, static_cast(length), static_cast(length >> 8), static_cast(inverse), static_cast(inverse >> 8)}; output.insert(output.end(), bytes.begin(), bytes.end()); AppendBigEndian32(output, Adler32(bytes)); return output; } static std::vector GrayLowBitPng(std::uint8_t bitDepth, std::span samples, std::optional transparency, std::optional precedingTransparency = std::nullopt) { Require(bitDepth == 1 || bitDepth == 2 || bitDepth == 4); std::vector rows(1 + (samples.size() * bitDepth + 7) / 8, 0); std::size_t bit = 0; for (const std::uint8_t sample : samples) { Require(sample < (1u << bitDepth)); rows[1 + bit / 8] |= static_cast(sample << (8 - bitDepth - bit % 8)); bit += bitDepth; } std::vector ihdr; AppendBigEndian32(ihdr, static_cast(samples.size())); AppendBigEndian32(ihdr, 1); ihdr.insert(ihdr.end(), {bitDepth, 0, 0, 0, 0}); std::vector png(PNG_SIGNATURE, PNG_SIGNATURE + sizeof(PNG_SIGNATURE)); AppendTestChunk(png, "IHDR", ihdr); if (precedingTransparency.has_value()) { const std::uint8_t key[2] = {static_cast(*precedingTransparency >> 8), static_cast(*precedingTransparency)}; AppendTestChunk(png, "tRNS", key); } if (transparency.has_value()) { const std::uint8_t key[2] = {static_cast(*transparency >> 8), static_cast(*transparency)}; AppendTestChunk(png, "tRNS", key); } AppendTestChunk(png, "IDAT", ZlibStore(rows)); AppendTestChunk(png, "IEND", std::span()); return png; } static std::uint8_t GrayScale(std::uint8_t bitDepth) { if (bitDepth == 1) return 255; if (bitDepth == 2) return 85; return 17; } static void RequireGrayTransparency(std::uint8_t bitDepth, std::span samples, std::uint16_t key, std::uint8_t transparentSample, std::optional precedingKey = std::nullopt) { const std::vector png = GrayLowBitPng(bitDepth, samples, key, precedingKey); const std::vector original = png; const auto header = Decoder::Png::ParseHeader(png); Require(header.has_value()); Require(header->width == static_cast(samples.size()) && header->height == 1 && header->bitDepth == bitDepth); Require(header->colorType == Decoder::Png::ColorType::Grayscale && header->hasTransparency); const auto image = Decoder::Png::Decode(png); Require(image.has_value() && image->pixels.size() == samples.size() * 4); const std::uint8_t scale = GrayScale(bitDepth); for (std::size_t i = 0; i < samples.size(); ++i) { const std::uint8_t gray = static_cast(samples[i] * scale); Require(image->pixels[i * 4] == gray && image->pixels[i * 4 + 1] == gray && image->pixels[i * 4 + 2] == gray); Require(image->pixels[i * 4 + 3] == (samples[i] == transparentSample ? 0 : 255)); } Require(png == original); } static void RequireGrayOpaque(std::uint8_t bitDepth, std::span samples) { const std::vector png = GrayLowBitPng(bitDepth, samples, std::nullopt); const std::vector original = png; const auto header = Decoder::Png::ParseHeader(png); Require(header.has_value() && !header->hasTransparency); const auto image = Decoder::Png::Decode(png); Require(image.has_value() && image->pixels.size() == samples.size() * 4); const std::uint8_t scale = GrayScale(bitDepth); for (std::size_t i = 0; i < samples.size(); ++i) { const std::uint8_t gray = static_cast(samples[i] * scale); Require(image->pixels[i * 4] == gray && image->pixels[i * 4 + 1] == gray && image->pixels[i * 4 + 2] == gray); Require(image->pixels[i * 4 + 3] == 255); } Require(png == original); } static void RequireTruncatedTransparency() { const std::uint8_t samples[] = {0, 1}; const std::vector png = GrayLowBitPng(1, samples, 0x0002); const std::size_t transparencyOffset = sizeof(PNG_SIGNATURE) + 25; const std::vector truncated(png.begin(), png.begin() + transparencyOffset + 9); const std::vector original = truncated; const auto header = Decoder::Png::ParseHeader(truncated); Require(header.has_value() && !header->hasTransparency); (void)Decoder::Png::Decode(truncated); Require(truncated == original); } static void RequireRoundTrip(std::uint32_t channels, Decoder::Png::ColorType colorType) { constexpr std::uint32_t width = 20; constexpr std::uint32_t height = 10; std::vector pixels(width * height * channels); for (std::size_t i = 0; i < pixels.size(); ++i) pixels[i] = static_cast(i * 37 + 11); const std::vector png = Decoder::Png::Encode(pixels, width, height, channels); const auto header = Decoder::Png::ParseHeader(png); Require(header.has_value()); Require(header->width == width && header->height == height && header->bitDepth == 8); Require(header->colorType == colorType && !header->interlaced && !header->hasTransparency); const auto image = Decoder::Png::Decode(png); Require(image.has_value()); Require(image->width == width && image->height == height && image->pixels.size() == width * height * 4); for (std::size_t pixel = 0; pixel < width * height; ++pixel) { const std::uint8_t* in = &pixels[pixel * channels]; const std::uint8_t* out = &image->pixels[pixel * 4]; const bool gray = channels <= 2; Require(out[0] == in[0]); Require(out[1] == (gray ? in[0] : in[1])); Require(out[2] == (gray ? in[0] : in[2])); const bool hasAlpha = channels == 2 || channels == 4; Require(out[3] == (hasAlpha ? in[channels - 1] : 255)); } } static std::vector RowFilters(const std::vector& png, std::uint32_t height, std::uint32_t rowSize) { std::vector stream; for (std::size_t offset = 8; offset + 12 <= png.size();) { const std::size_t length = static_cast(png[offset]) << 24 | static_cast(png[offset + 1]) << 16 | static_cast(png[offset + 2]) << 8 | png[offset + 3]; Require(length <= png.size() - offset - 12); const std::uint8_t* data = png.data() + offset + 8; if (std::equal(data - 4, data, "IDAT")) stream.insert(stream.end(), data, data + length); offset += 12 + length; } int size = 0; char* rows = stbi_zlib_decode_malloc(reinterpret_cast(stream.data()), static_cast(stream.size()), &size); Require(rows && static_cast(size) == static_cast(height) * (rowSize + 1)); std::vector filters; for (std::uint32_t y = 0; y < height; ++y) filters.push_back(static_cast(rows[y * (rowSize + 1)])); stbi_image_free(rows); return filters; } static void RequireFilterSets(std::uint32_t channels) { constexpr std::uint32_t width = 16; constexpr std::uint32_t height = 6; const std::uint32_t rowSize = width * channels; std::vector pixels(rowSize * height); std::uint32_t noise = 12345; for (std::uint32_t y = 0; y < height; ++y) { for (std::uint32_t i = 0; i < rowSize; ++i) { noise = noise * 1103515245u + 12345u; pixels[y * rowSize + i] = y == 2 ? static_cast(noise >> 16) : static_cast(i / channels * 8); } } std::copy_n(pixels.begin() + 2 * rowSize, rowSize, pixels.begin() + 3 * rowSize); for (std::uint8_t filters = 1; filters <= Decoder::Png::FILTER_ALL; ++filters) { const std::vector png = Decoder::Png::Encode(pixels, width, height, channels, {6, filters}); for (const std::uint8_t filter : RowFilters(png, height, rowSize)) Require(filter < 5 && (filters >> filter & 1) != 0); const auto image = Decoder::Png::Decode(png); Require(image.has_value() && image->width == width && image->height == height); for (std::size_t pixel = 0; pixel < width * height; ++pixel) { Require(image->pixels[pixel * 4] == pixels[pixel * channels]); Require(image->pixels[pixel * 4 + 3] == (channels % 2 == 0 ? pixels[pixel * channels + channels - 1] : 255)); } } const std::vector subOrUp = RowFilters( Decoder::Png::Encode(pixels, width, height, channels, {6, Decoder::Png::FILTER_SUB | Decoder::Png::FILTER_UP}), height, rowSize); Require(subOrUp[0] == 1 && subOrUp[1] == 2 && subOrUp[3] == 2 && subOrUp[4] == 1 && subOrUp[5] == 2); } int main() { RequireRoundTrip(1, Decoder::Png::ColorType::Grayscale); RequireRoundTrip(2, Decoder::Png::ColorType::GrayscaleAlpha); RequireRoundTrip(3, Decoder::Png::ColorType::Rgb); RequireRoundTrip(4, Decoder::Png::ColorType::Rgba); for (const std::uint32_t channels : {1, 2, 3, 4}) RequireFilterSets(channels); const auto paletteHeader = Decoder::Png::ParseHeader(PALETTE_TRNS); Require(paletteHeader.has_value()); Require(paletteHeader->width == 2 && paletteHeader->height == 2 && paletteHeader->bitDepth == 2); Require(paletteHeader->colorType == Decoder::Png::ColorType::Palette && paletteHeader->hasTransparency); for (const std::size_t size : {std::size_t{69}, std::size_t{71}}) { const auto truncated = Decoder::Png::ParseHeader(std::span(PALETTE_TRNS, size)); Require(truncated.has_value() && !truncated->hasTransparency); } const auto completeTransparency = Decoder::Png::ParseHeader(std::span(PALETTE_TRNS, 73)); Require(completeTransparency.has_value() && completeTransparency->hasTransparency); const auto palette = Decoder::Png::Decode(PALETTE_TRNS); Require(palette.has_value()); Require(palette->pixels == std::vector({255, 0, 0, 255, 0, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 128})); const auto gray16Header = Decoder::Png::ParseHeader(GRAY16); Require(gray16Header.has_value() && gray16Header->bitDepth == 16 && gray16Header->colorType == Decoder::Png::ColorType::Grayscale); const auto gray16 = Decoder::Png::Decode(GRAY16); Require(gray16.has_value()); Require(gray16->pixels == std::vector({0x12, 0x12, 0x12, 255, 0xAB, 0xAB, 0xAB, 255})); const auto gray1Header = Decoder::Png::ParseHeader(GRAY1); Require(gray1Header.has_value() && gray1Header->bitDepth == 1 && gray1Header->width == 8); const auto gray1 = Decoder::Png::Decode(GRAY1); Require(gray1.has_value()); const std::uint8_t levels[8] = {0, 255, 0, 255, 255, 0, 0, 255}; for (int i = 0; i < 8; ++i) Require(gray1->pixels[i * 4] == levels[i] && gray1->pixels[i * 4 + 3] == 255); const auto gray1TrnsHeader = Decoder::Png::ParseHeader(GRAY1_TRNS); Require(gray1TrnsHeader.has_value()); Require(gray1TrnsHeader->width == 2 && gray1TrnsHeader->height == 1 && gray1TrnsHeader->bitDepth == 1); Require(gray1TrnsHeader->colorType == Decoder::Png::ColorType::Grayscale && gray1TrnsHeader->hasTransparency); const auto gray1Trns = Decoder::Png::Decode(GRAY1_TRNS); Require(gray1Trns.has_value()); Require(gray1Trns->pixels == std::vector({0, 0, 0, 0, 255, 255, 255, 255})); const std::uint8_t gray1Samples[] = {0, 1, 0, 1, 1, 0, 0, 1}; RequireGrayTransparency(1, gray1Samples, 0x0000, 0); RequireGrayTransparency(1, gray1Samples, 0x0002, 0); RequireGrayTransparency(1, gray1Samples, 0x0100, 0); RequireGrayTransparency(1, gray1Samples, 0xFF02, 0); RequireGrayTransparency(1, gray1Samples, 0xFFFF, 1); RequireGrayTransparency(1, gray1Samples, 0x0001, 1, 0x0002); RequireGrayOpaque(1, gray1Samples); const std::uint8_t gray2Samples[] = {0, 1, 2, 3}; RequireGrayTransparency(2, gray2Samples, 0x0001, 1); RequireGrayTransparency(2, gray2Samples, 0x0005, 1); RequireGrayTransparency(2, gray2Samples, 0x0100, 0); RequireGrayTransparency(2, gray2Samples, 0x0101, 1); RequireGrayTransparency(2, gray2Samples, 0xFF05, 1); RequireGrayTransparency(2, gray2Samples, 0xFFFF, 3); RequireGrayOpaque(2, gray2Samples); const std::uint8_t gray4Samples[] = {0, 3, 10, 15, 1}; RequireGrayTransparency(4, gray4Samples, 0x0003, 3); RequireGrayTransparency(4, gray4Samples, 0x0013, 3); RequireGrayTransparency(4, gray4Samples, 0x0100, 0); RequireGrayTransparency(4, gray4Samples, 0x0103, 3); RequireGrayTransparency(4, gray4Samples, 0xFF13, 3); RequireGrayTransparency(4, gray4Samples, 0xFFFF, 15); RequireGrayOpaque(4, gray4Samples); RequireTruncatedTransparency(); const auto interlaced = Decoder::Png::ParseHeader(HeaderOnly(3, 4, 8, 6, 1)); Require(interlaced.has_value() && interlaced->interlaced && !interlaced->hasTransparency); Require(!Decoder::Png::ParseHeader(HeaderOnly(0, 4, 8, 6, 0)).has_value()); Require(!Decoder::Png::ParseHeader(HeaderOnly(3, 4, 8, 5, 0)).has_value()); Require(!Decoder::Png::ParseHeader(HeaderOnly(3, 4, 16, 3, 0)).has_value()); Require(!Decoder::Png::ParseHeader(HeaderOnly(3, 4, 8, 6, 2)).has_value()); std::vector badSignature = HeaderOnly(3, 4, 8, 6, 0); badSignature[1] = 'X'; Require(!Decoder::Png::ParseHeader(badSignature).has_value()); std::vector hugeChunk = HeaderOnly(3, 4, 8, 6, 0); hugeChunk[33] = 0xFF; hugeChunk[37] = 't'; hugeChunk[38] = 'E'; hugeChunk[39] = 'X'; hugeChunk[40] = 't'; const auto hugeChunkHeader = Decoder::Png::ParseHeader(hugeChunk); Require(hugeChunkHeader.has_value() && !hugeChunkHeader->hasTransparency); Require(!Decoder::Png::ParseHeader(std::span(PALETTE_TRNS, 32)).has_value()); Require(!Decoder::Png::Decode({}).has_value()); Require(!Decoder::Png::Decode(std::span(PALETTE_TRNS, 60)).has_value()); const std::vector garbage{1, 2, 3, 4, 5, 6, 7, 8}; Require(!Decoder::Png::Decode(garbage).has_value()); const std::vector pixels(16 * 4); Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 0); })); Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 5); })); Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 0, 4, 4); })); Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 5, 4); })); Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 4, {10, Decoder::Png::FILTER_ALL}); })); Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 4, {8, 0}); })); Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 4, {8, Decoder::Png::FILTER_ALL + 1}); })); std::vector gradient(64 * 64 * 3); for (std::size_t i = 0; i < gradient.size(); ++i) gradient[i] = static_cast(i / 3 % 64 * 4); const auto stored = Decoder::Png::Encode(gradient, 64, 64, 3, {0, Decoder::Png::FILTER_NONE}); const auto compressed = Decoder::Png::Encode(gradient, 64, 64, 3, {9, Decoder::Png::FILTER_SUB}); Require(compressed.size() < stored.size()); for (const auto& png : {stored, compressed}) { const auto image = Decoder::Png::Decode(png); Require(image.has_value() && image->width == 64 && image->height == 64); for (std::size_t pixel = 0; pixel < 64 * 64; ++pixel) Require(image->pixels[pixel * 4] == gradient[pixel * 3]); } return 0; }