mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-04 13:07:32 +00:00
RequireTruncatedTransparency called Decode and ignored the result, so any result passed. Assert that Decode returns no image for the input cut in the middle of its tRNS chunk, which is what the decoder returns today. Checked with a mutant Decode that returns an image for this input: the test aborts on the mutant, and the previous version of the test passes it.
395 lines
20 KiB
C++
395 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 auto header = Decoder::Png::ParseHeader(truncated);
|
|
Require(header.has_value() && !header->hasTransparency);
|
|
Require(!Decoder::Png::Decode(truncated).has_value());
|
|
}
|
|
|
|
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;
|
|
}
|