mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-28 00:35:47 +00:00
397 lines
20 KiB
C++
397 lines
20 KiB
C++
#include "Decoder/Png.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <optional>
|
|
#include <stdexcept>
|
|
#include <vector>
|
|
|
|
#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<typename TFunction>
|
|
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<std::uint8_t>& bytes, std::uint32_t value) {
|
|
for (int shift = 24; shift >= 0; shift -= 8) bytes.push_back(static_cast<std::uint8_t>(value >> shift));
|
|
}
|
|
|
|
static std::vector<std::uint8_t> HeaderOnly(std::uint32_t width, std::uint32_t height, std::uint8_t bitDepth,
|
|
std::uint8_t colorType, std::uint8_t interlace) {
|
|
std::vector<std::uint8_t> 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<const std::uint8_t> 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<const std::uint8_t> 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<std::uint8_t>& png, const char* type, std::span<const std::uint8_t> data) {
|
|
AppendBigEndian32(png, static_cast<std::uint32_t>(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<const std::uint8_t>(png.data() + start, png.size() - start)));
|
|
}
|
|
|
|
static std::vector<std::uint8_t> ZlibStore(std::span<const std::uint8_t> bytes) {
|
|
Require(bytes.size() <= 0xFFFF);
|
|
const std::uint16_t length = static_cast<std::uint16_t>(bytes.size());
|
|
const std::uint16_t inverse = static_cast<std::uint16_t>(~length);
|
|
std::vector<std::uint8_t> output{0x78, 0x01, 0x01, static_cast<std::uint8_t>(length), static_cast<std::uint8_t>(length >> 8),
|
|
static_cast<std::uint8_t>(inverse), static_cast<std::uint8_t>(inverse >> 8)};
|
|
output.insert(output.end(), bytes.begin(), bytes.end());
|
|
AppendBigEndian32(output, Adler32(bytes));
|
|
return output;
|
|
}
|
|
|
|
static std::vector<std::uint8_t> GrayLowBitPng(std::uint8_t bitDepth, std::span<const std::uint8_t> samples,
|
|
std::optional<std::uint16_t> transparency,
|
|
std::optional<std::uint16_t> precedingTransparency = std::nullopt) {
|
|
Require(bitDepth == 1 || bitDepth == 2 || bitDepth == 4);
|
|
std::vector<std::uint8_t> 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<std::uint8_t>(sample << (8 - bitDepth - bit % 8));
|
|
bit += bitDepth;
|
|
}
|
|
|
|
std::vector<std::uint8_t> ihdr;
|
|
AppendBigEndian32(ihdr, static_cast<std::uint32_t>(samples.size()));
|
|
AppendBigEndian32(ihdr, 1);
|
|
ihdr.insert(ihdr.end(), {bitDepth, 0, 0, 0, 0});
|
|
|
|
std::vector<std::uint8_t> png(PNG_SIGNATURE, PNG_SIGNATURE + sizeof(PNG_SIGNATURE));
|
|
AppendTestChunk(png, "IHDR", ihdr);
|
|
if (precedingTransparency.has_value()) {
|
|
const std::uint8_t key[2] = {static_cast<std::uint8_t>(*precedingTransparency >> 8), static_cast<std::uint8_t>(*precedingTransparency)};
|
|
AppendTestChunk(png, "tRNS", key);
|
|
}
|
|
if (transparency.has_value()) {
|
|
const std::uint8_t key[2] = {static_cast<std::uint8_t>(*transparency >> 8), static_cast<std::uint8_t>(*transparency)};
|
|
AppendTestChunk(png, "tRNS", key);
|
|
}
|
|
AppendTestChunk(png, "IDAT", ZlibStore(rows));
|
|
AppendTestChunk(png, "IEND", std::span<const std::uint8_t>());
|
|
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<const std::uint8_t> samples, std::uint16_t key,
|
|
std::uint8_t transparentSample, std::optional<std::uint16_t> precedingKey = std::nullopt) {
|
|
const std::vector<std::uint8_t> png = GrayLowBitPng(bitDepth, samples, key, precedingKey);
|
|
const std::vector<std::uint8_t> original = png;
|
|
const auto header = Decoder::Png::ParseHeader(png);
|
|
Require(header.has_value());
|
|
Require(header->width == static_cast<std::uint32_t>(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<std::uint8_t>(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<const std::uint8_t> samples) {
|
|
const std::vector<std::uint8_t> png = GrayLowBitPng(bitDepth, samples, std::nullopt);
|
|
const std::vector<std::uint8_t> 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<std::uint8_t>(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<std::uint8_t> png = GrayLowBitPng(1, samples, 0x0002);
|
|
const std::size_t transparencyOffset = sizeof(PNG_SIGNATURE) + 25;
|
|
const std::vector<std::uint8_t> truncated(png.begin(), png.begin() + transparencyOffset + 9);
|
|
const std::vector<std::uint8_t> 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<std::uint8_t> pixels(width * height * channels);
|
|
for (std::size_t i = 0; i < pixels.size(); ++i) pixels[i] = static_cast<std::uint8_t>(i * 37 + 11);
|
|
|
|
const std::vector<std::uint8_t> 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<std::uint8_t> RowFilters(const std::vector<std::uint8_t>& png, std::uint32_t height, std::uint32_t rowSize) {
|
|
std::vector<std::uint8_t> stream;
|
|
for (std::size_t offset = 8; offset + 12 <= png.size();) {
|
|
const std::size_t length = static_cast<std::size_t>(png[offset]) << 24 | static_cast<std::size_t>(png[offset + 1]) << 16
|
|
| static_cast<std::size_t>(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<const char*>(stream.data()), static_cast<int>(stream.size()), &size);
|
|
Require(rows && static_cast<std::size_t>(size) == static_cast<std::size_t>(height) * (rowSize + 1));
|
|
std::vector<std::uint8_t> filters;
|
|
for (std::uint32_t y = 0; y < height; ++y) filters.push_back(static_cast<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t>(noise >> 16) : static_cast<std::uint8_t>(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<std::uint8_t> 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<std::uint8_t> 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<const std::uint8_t>(PALETTE_TRNS, size));
|
|
Require(truncated.has_value() && !truncated->hasTransparency);
|
|
}
|
|
const auto completeTransparency = Decoder::Png::ParseHeader(std::span<const std::uint8_t>(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<std::uint8_t>({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<std::uint8_t>({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<std::uint8_t>({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<std::uint8_t> badSignature = HeaderOnly(3, 4, 8, 6, 0);
|
|
badSignature[1] = 'X';
|
|
Require(!Decoder::Png::ParseHeader(badSignature).has_value());
|
|
std::vector<std::uint8_t> 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<const std::uint8_t>(PALETTE_TRNS, 32)).has_value());
|
|
|
|
Require(!Decoder::Png::Decode({}).has_value());
|
|
Require(!Decoder::Png::Decode(std::span<const std::uint8_t>(PALETTE_TRNS, 60)).has_value());
|
|
const std::vector<std::uint8_t> garbage{1, 2, 3, 4, 5, 6, 7, 8};
|
|
Require(!Decoder::Png::Decode(garbage).has_value());
|
|
|
|
const std::vector<std::uint8_t> 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<std::uint8_t> gradient(64 * 64 * 3);
|
|
for (std::size_t i = 0; i < gradient.size(); ++i) gradient[i] = static_cast<std::uint8_t>(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;
|
|
}
|