#include "libs/libPngEnc.cpp" #define STB_IMAGE_IMPLEMENTATION #define STBI_ONLY_PNG #define STBI_NO_STDIO #include "stb_image.h" #include #include #include #include #include namespace Loader { void SymbolDatabase::Add(const SymbolResolve&, uint64_t, const std::string&) {} namespace Timer { double GetTimeMs() { return 0.0; } } // namespace Timer } // namespace Loader namespace { using namespace Libs::PngEnc; #define CHECK(condition) \ do { \ if (!(condition)) { \ std::fprintf(stderr, "PngEncTests:%d: %s\n", __LINE__, #condition); \ std::abort(); \ } \ } while (false) struct Encoder { PngEncContext context {}; void* handle = nullptr; explicit Encoder(uint32_t max_filters = 4) { const PngEncCreateParam create {sizeof(PngEncCreateParam), 0, PNG_ENC_MAX_IMAGE_WIDTH, max_filters}; CHECK(PngEncQueryMemorySize(&create) == sizeof(context)); CHECK(PngEncCreate(&create, &context, sizeof(context), &handle) == 0); } ~Encoder() { CHECK(PngEncDelete(handle) == 0); CHECK(PngEncDelete(handle) == PNG_ENC_ERROR_INVALID_HANDLE); } }; PngEncEncodeParam EncodeParam(const uint8_t* source, uint32_t source_size, uint8_t* destination, uint32_t destination_size) { PngEncEncodeParam param {}; param.image_mem_addr = source; param.png_mem_addr = destination; param.image_mem_size = source_size; param.png_mem_size = destination_size; param.image_width = 2; param.image_height = 2; param.image_pitch = 8; param.color_space = PNG_ENC_COLOR_SPACE_RGBA; param.bit_depth = 8; param.compression_level = 6; return param; } uint32_t ReadBe32(const uint8_t* bytes) { return (uint32_t {bytes[0]} << 24u) | (uint32_t {bytes[1]} << 16u) | (uint32_t {bytes[2]} << 8u) | bytes[3]; } void TestPixels() { Encoder encoder; constexpr std::array rgba {255, 0, 13, 0, 4, 210, 6, 255, 7, 8, 220, 64, 111, 12, 113, 128}; for (const bool bgr: {false, true}) { for (const bool alpha: {false, true}) { for (const bool padded: {false, true}) { const uint32_t pitch = padded ? 12 : 8; std::vector source(pitch * 2, 0xee); std::vector expected; for (uint32_t y = 0; y < 2; ++y) { for (uint32_t x = 0; x < 2; ++x) { const auto* pixel = rgba.data() + (y * 2 + x) * 4; auto* input = source.data() + y * pitch + x * 4; input[0] = pixel[bgr ? 2 : 0]; input[1] = pixel[1]; input[2] = pixel[bgr ? 0 : 2]; input[3] = pixel[3]; expected.insert(expected.end(), pixel, pixel + (alpha ? 4 : 3)); } } std::array png {}; auto param = EncodeParam(source.data(), source.size(), png.data(), png.size()); param.pixel_format = bgr ? PNG_ENC_PIXEL_FORMAT_B8G8R8A8 : PNG_ENC_PIXEL_FORMAT_R8G8B8A8; param.color_space = alpha ? PNG_ENC_COLOR_SPACE_RGBA : PNG_ENC_COLOR_SPACE_RGB; param.image_pitch = pitch; PngEncOutputInfo info {}; const auto size = PngEncEncode(encoder.handle, ¶m, &info); CHECK(size > 0 && info.data_size == static_cast(size)); CHECK(info.processed_height == 2); int width = 0, height = 0, components = 0; auto* decoded = stbi_load_from_memory(png.data(), size, &width, &height, &components, 0); CHECK(decoded != nullptr); CHECK(width == 2 && height == 2 && components == (alpha ? 4 : 3)); CHECK(std::memcmp(decoded, expected.data(), expected.size()) == 0); stbi_image_free(decoded); } } } } void TestFilterMasks() { Encoder encoder; alignas(4) std::array pixels {}; std::array png {}; auto param = EncodeParam(pixels.data(), pixels.size(), png.data(), png.size()); for (uint16_t mask = 0; mask <= 15; ++mask) { param.filter_type = mask; const auto size = PngEncEncode(encoder.handle, ¶m, nullptr); CHECK(size > 0); CHECK(std::memcmp(png.data() + 37, "IDAT", 4) == 0); std::array filtered {}; CHECK(stbi_zlib_decode_buffer(filtered.data(), filtered.size(), reinterpret_cast(png.data() + 41), ReadBe32(png.data() + 33)) == filtered.size()); for (size_t row = 0; row < 2; ++row) { const auto filter = static_cast(filtered[row * 9]); CHECK(filter <= 4); CHECK(filter == 0 ? (mask == 0 || mask == 15) : (mask & (1u << (filter - 1u))) != 0); } } param.filter_type = 16; CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_PARAM); for (uint32_t count = 0; count <= 4; ++count) { Encoder limited(count); for (uint16_t mask = 0; mask <= 15; ++mask) { param.filter_type = mask; const auto result = PngEncEncode(limited.handle, ¶m, nullptr); CHECK(std::popcount(mask) <= count ? result > 0 : result == PNG_ENC_ERROR_INVALID_PARAM); } } } void TestOutputCapacity() { Encoder encoder; alignas(4) std::array pixels {}; std::array png {}; auto param = EncodeParam(pixels.data(), pixels.size(), png.data(), png.size()); const auto size = PngEncEncode(encoder.handle, ¶m, nullptr); CHECK(size > 0); std::vector guarded(size + 2, 0xa5); param.png_mem_addr = guarded.data() + 1; param.png_mem_size = size; PngEncOutputInfo info {}; CHECK(PngEncEncode(encoder.handle, ¶m, &info) == size); CHECK(guarded.front() == 0xa5 && guarded.back() == 0xa5); CHECK(std::memcmp(guarded.data() + 1, png.data(), size) == 0); std::fill(guarded.begin(), guarded.end(), 0xa5); param.png_mem_size = size - 1; CHECK(PngEncEncode(encoder.handle, ¶m, &info) == PNG_ENC_ERROR_DATA_OVERFLOW); CHECK(info.data_size == 0 && info.processed_height == 0); CHECK(std::all_of(guarded.begin(), guarded.end(), [](uint8_t byte) { return byte == 0xa5; })); } void TestValidation() { Encoder encoder; PngEncCreateParam create {sizeof(PngEncCreateParam), 0, 2, 4}; CHECK(PngEncQueryMemorySize(nullptr) == PNG_ENC_ERROR_INVALID_ADDR); CHECK(PngEncCreate(&create, nullptr, sizeof(PngEncContext), &encoder.handle) == PNG_ENC_ERROR_INVALID_ADDR); CHECK(PngEncCreate(&create, &encoder.context, 0, &encoder.handle) == PNG_ENC_ERROR_INVALID_SIZE); create.max_image_width = 0; CHECK(PngEncQueryMemorySize(&create) == PNG_ENC_ERROR_INVALID_SIZE); create.max_image_width = PNG_ENC_MAX_IMAGE_WIDTH + 1; CHECK(PngEncQueryMemorySize(&create) == PNG_ENC_ERROR_INVALID_SIZE); create.max_image_width = 2; create.this_size = 0; CHECK(PngEncQueryMemorySize(&create) == PNG_ENC_ERROR_INVALID_PARAM); create.this_size = sizeof(create); create.max_filter_number = 5; CHECK(PngEncQueryMemorySize(&create) == PNG_ENC_ERROR_INVALID_PARAM); alignas(4) std::array pixels {}; std::array png {}; const auto valid = EncodeParam(pixels.data(), pixels.size(), png.data(), png.size()); CHECK(PngEncEncode(nullptr, &valid, nullptr) == PNG_ENC_ERROR_INVALID_HANDLE); CHECK(PngEncEncode(encoder.handle, nullptr, nullptr) == PNG_ENC_ERROR_INVALID_PARAM); for (const auto width: {0u, PNG_ENC_MAX_IMAGE_WIDTH + 1, 0x40000000u, 0xffffffffu}) { auto param = valid; param.image_width = width; CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_SIZE); } for (const auto height: {0u, PNG_ENC_MAX_IMAGE_HEIGHT + 1, 0xffffffffu}) { auto param = valid; param.image_width = 1; param.image_height = height; param.image_mem_size = 0xffffffffu; CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_SIZE); } for (const auto height: {262144u, 400000u, 0x7ffffu}) { auto param = valid; param.image_width = 1024; param.image_height = height; param.image_pitch = 4096; param.image_mem_size = 0xffffffffu; CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_SIZE); } for (const auto pitch: {0u, 7u, 9u, 0xfffffffcu}) { auto param = valid; param.image_pitch = pitch; CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_SIZE); } auto param = valid; param.image_pitch = 12; param.image_mem_size = 20; CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_SIZE); param = valid; param.image_mem_size--; CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_SIZE); param = valid; param.image_mem_addr = nullptr; CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_ADDR); param.image_mem_addr = pixels.data() + 1; CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_ADDR); param = valid; param.bit_depth = 16; CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_PARAM); param = valid; param.clut_number = 1; CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_PARAM); param = valid; param.compression_level = 10; CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_PARAM); } } // namespace int main() { TestPixels(); TestFilterMasks(); TestOutputCapacity(); TestValidation(); std::puts("PNG encoder tests passed"); }