libs: libScePngEnc on stb_image_write

This commit is contained in:
Senaxx
2026-09-30 03:55:59 +05:30
committed by nmzik
parent 840b9f5753
commit 48b52d31b5
5 changed files with 2328 additions and 0 deletions
+1724
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -500,6 +500,10 @@ add_executable(save_data_memory_tests EXCLUDE_FROM_ALL
target_link_libraries(save_data_memory_tests common fmt::fmt)
target_include_directories(save_data_memory_tests PRIVATE ${inc_headers})
add_executable(png_enc_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/PngEncTests.cpp")
target_link_libraries(png_enc_tests common fmt::fmt)
target_include_directories(png_enc_tests PRIVATE ${inc_headers})
add_executable(ces_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/CesTests.cpp")
target_link_libraries(ces_tests common fmt::fmt SDL3::SDL3-static)
target_include_directories(ces_tests PRIVATE ${inc_headers})
@@ -605,6 +609,7 @@ if(BUILD_TESTING)
add_test(NAME ngs2_sampler COMMAND $<TARGET_FILE:ngs2_sampler_tests>)
add_test(NAME pad_haptics COMMAND $<TARGET_FILE:pad_haptics_tests>)
add_test(NAME save_data_memory COMMAND $<TARGET_FILE:save_data_memory_tests>)
add_test(NAME png_enc COMMAND $<TARGET_FILE:png_enc_tests>)
add_test(NAME ces COMMAND $<TARGET_FILE:ces_tests>)
add_test(NAME save_data_dialog COMMAND $<TARGET_FILE:save_data_dialog_tests>)
add_test(NAME http_uri_parse COMMAND $<TARGET_FILE:http_uri_parse_tests>)
@@ -680,6 +685,7 @@ if(BUILD_TESTING)
ngs2_sampler_tests
pad_haptics_tests
save_data_memory_tests
png_enc_tests
ces_tests
save_data_dialog_tests
http_uri_parse_tests
+349
View File
@@ -0,0 +1,349 @@
#include "common/abi.h"
#include "common/logging/log.h"
#include "libs/libs.h"
#include "loader/symbolDatabase.h"
#include <bit>
#include <cstddef>
#include <cstring>
#include <limits>
#include <memory>
#include <mutex>
#include <new>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_WRITE_STATIC
#define STBIWDEF static inline
#define STBI_WRITE_NO_STDIO
#include "stb_image_write.h"
namespace Libs {
LIB_VERSION("PngEnc", 1, "PngEnc", 1, 1);
namespace PngEnc {
constexpr int32_t PNG_ENC_ERROR_INVALID_ADDR = -2140602111; // 0x80690101
constexpr int32_t PNG_ENC_ERROR_INVALID_SIZE = -2140602110; // 0x80690102
constexpr int32_t PNG_ENC_ERROR_INVALID_PARAM = -2140602109; // 0x80690103
constexpr int32_t PNG_ENC_ERROR_INVALID_HANDLE = -2140602108; // 0x80690104
constexpr int32_t PNG_ENC_ERROR_DATA_OVERFLOW = -2140602096; // 0x80690110
constexpr int32_t PNG_ENC_ERROR_FATAL = -2140602080; // 0x80690120
constexpr uint32_t PNG_ENC_MAX_IMAGE_WIDTH = 1000000;
constexpr uint32_t PNG_ENC_MAX_IMAGE_HEIGHT = 1000000;
constexpr uint32_t PNG_ENC_MAX_FILTER_NUMBER = 4;
constexpr uint16_t PNG_ENC_COLOR_SPACE_RGB = 3;
constexpr uint16_t PNG_ENC_COLOR_SPACE_RGBA = 19;
constexpr uint16_t PNG_ENC_PIXEL_FORMAT_R8G8B8A8 = 0;
constexpr uint16_t PNG_ENC_PIXEL_FORMAT_B8G8R8A8 = 1;
constexpr uint16_t PNG_ENC_FILTER_TYPE_SUB = 1;
constexpr uint16_t PNG_ENC_FILTER_TYPE_UP = 2;
constexpr uint16_t PNG_ENC_FILTER_TYPE_AVERAGE = 4;
constexpr uint16_t PNG_ENC_FILTER_TYPE_PAETH = 8;
struct PngEncCreateParam {
uint32_t this_size;
uint32_t attribute;
uint32_t max_image_width;
uint32_t max_filter_number;
};
struct PngEncEncodeParam {
const uint8_t* image_mem_addr;
uint8_t* png_mem_addr;
uint32_t image_mem_size;
uint32_t png_mem_size;
uint32_t image_width;
uint32_t image_height;
uint32_t image_pitch;
uint16_t pixel_format;
uint16_t color_space;
uint16_t bit_depth;
uint16_t clut_number;
uint16_t filter_type;
uint16_t compression_level;
};
struct PngEncOutputInfo {
uint32_t data_size;
uint32_t processed_height;
};
static_assert(sizeof(PngEncCreateParam) == 16);
static_assert(sizeof(PngEncEncodeParam) == 48);
struct PngEncContext {
uint64_t magic;
uint32_t max_image_width;
uint32_t max_filter_number;
};
constexpr uint64_t PNG_ENC_CONTEXT_MAGIC = 0x4b595459504e4745ull; // KYTYPNGE
struct PngEncWriter {
uint8_t* dst;
uint32_t capacity;
uint32_t size;
bool overflow;
};
// stb_image_write keeps its compression level and filter choice in globals
static std::mutex g_png_enc_mutex;
static void PngEncWrite(void* context, void* data, int size) {
auto* writer = static_cast<PngEncWriter*>(context);
if (writer->overflow || size < 0 ||
static_cast<uint64_t>(writer->size) + static_cast<uint32_t>(size) > writer->capacity) {
writer->overflow = true;
return;
}
std::memcpy(writer->dst + writer->size, data, static_cast<size_t>(size));
writer->size += static_cast<uint32_t>(size);
}
static int PngEncForcedFilter(uint16_t filter_type) {
if (filter_type == (PNG_ENC_FILTER_TYPE_SUB | PNG_ENC_FILTER_TYPE_UP |
PNG_ENC_FILTER_TYPE_AVERAGE | PNG_ENC_FILTER_TYPE_PAETH)) {
return -1;
}
// stb's adaptive mode cannot restrict itself to the guest's filter mask.
if ((filter_type & PNG_ENC_FILTER_TYPE_SUB) != 0) {
return 1;
}
if ((filter_type & PNG_ENC_FILTER_TYPE_UP) != 0) {
return 2;
}
if ((filter_type & PNG_ENC_FILTER_TYPE_AVERAGE) != 0) {
return 3;
}
if ((filter_type & PNG_ENC_FILTER_TYPE_PAETH) != 0) {
return 4;
}
return 0;
}
static int32_t ValidateCreateParam(const PngEncCreateParam* param) {
if (param == nullptr) {
return PNG_ENC_ERROR_INVALID_ADDR;
}
LOGF("\t this_size = %" PRIu32 "\n", param->this_size);
LOGF("\t attribute = %" PRIu32 "\n", param->attribute);
LOGF("\t max_image_width = %" PRIu32 "\n", param->max_image_width);
LOGF("\t max_filter_number = %" PRIu32 "\n", param->max_filter_number);
if (param->this_size != sizeof(PngEncCreateParam) || param->attribute != 0 ||
param->max_filter_number > PNG_ENC_MAX_FILTER_NUMBER) {
return PNG_ENC_ERROR_INVALID_PARAM;
}
if (param->max_image_width == 0 || param->max_image_width > PNG_ENC_MAX_IMAGE_WIDTH) {
return PNG_ENC_ERROR_INVALID_SIZE;
}
return 0;
}
static int32_t KYTY_SYSV_ABI PngEncQueryMemorySize(const PngEncCreateParam* param) {
PRINT_NAME();
if (int32_t result = ValidateCreateParam(param); result != 0) {
return result;
}
return sizeof(PngEncContext);
}
static int32_t KYTY_SYSV_ABI PngEncCreate(const PngEncCreateParam* param, void* memory_address,
uint32_t memory_size, void** handle) {
PRINT_NAME();
LOGF("\t memory_address = %p\n", memory_address);
LOGF("\t memory_size = %" PRIu32 "\n", memory_size);
if (int32_t result = ValidateCreateParam(param); result != 0) {
return result;
}
if (memory_address == nullptr || handle == nullptr) {
return PNG_ENC_ERROR_INVALID_ADDR;
}
if (memory_size < sizeof(PngEncContext)) {
return PNG_ENC_ERROR_INVALID_SIZE;
}
auto* ctx = static_cast<PngEncContext*>(memory_address);
ctx->magic = PNG_ENC_CONTEXT_MAGIC;
ctx->max_image_width = param->max_image_width;
ctx->max_filter_number = param->max_filter_number;
*handle = ctx;
return 0;
}
static int32_t KYTY_SYSV_ABI PngEncEncode(void* handle, const PngEncEncodeParam* param,
PngEncOutputInfo* output_info) {
PRINT_NAME();
if (handle == nullptr) {
return PNG_ENC_ERROR_INVALID_HANDLE;
}
const auto* ctx = static_cast<const PngEncContext*>(handle);
if (ctx->magic != PNG_ENC_CONTEXT_MAGIC) {
return PNG_ENC_ERROR_INVALID_HANDLE;
}
if (param == nullptr) {
return PNG_ENC_ERROR_INVALID_PARAM;
}
LOGF("\t image_mem_addr = %p\n", static_cast<const void*>(param->image_mem_addr));
LOGF("\t png_mem_addr = %p\n", static_cast<const void*>(param->png_mem_addr));
LOGF("\t image_mem_size = %" PRIu32 "\n", param->image_mem_size);
LOGF("\t png_mem_size = %" PRIu32 "\n", param->png_mem_size);
LOGF("\t image_width = %" PRIu32 "\n", param->image_width);
LOGF("\t image_height = %" PRIu32 "\n", param->image_height);
LOGF("\t image_pitch = %" PRIu32 "\n", param->image_pitch);
LOGF("\t pixel_format = %" PRIu16 "\n", param->pixel_format);
LOGF("\t color_space = %" PRIu16 "\n", param->color_space);
LOGF("\t bit_depth = %" PRIu16 "\n", param->bit_depth);
LOGF("\t filter_type = %" PRIu16 "\n", param->filter_type);
LOGF("\t compression_level = %" PRIu16 "\n", param->compression_level);
if (param->image_mem_addr == nullptr || param->png_mem_addr == nullptr ||
(reinterpret_cast<uintptr_t>(param->image_mem_addr) & 3u) != 0) {
return PNG_ENC_ERROR_INVALID_ADDR;
}
if (param->pixel_format != PNG_ENC_PIXEL_FORMAT_R8G8B8A8 &&
param->pixel_format != PNG_ENC_PIXEL_FORMAT_B8G8R8A8) {
return PNG_ENC_ERROR_INVALID_PARAM;
}
if (param->color_space != PNG_ENC_COLOR_SPACE_RGB &&
param->color_space != PNG_ENC_COLOR_SPACE_RGBA) {
return PNG_ENC_ERROR_INVALID_PARAM;
}
if (param->bit_depth != 8 || param->clut_number != 0 || param->compression_level > 9) {
return PNG_ENC_ERROR_INVALID_PARAM;
}
constexpr uint16_t filter_mask = PNG_ENC_FILTER_TYPE_SUB | PNG_ENC_FILTER_TYPE_UP |
PNG_ENC_FILTER_TYPE_AVERAGE | PNG_ENC_FILTER_TYPE_PAETH;
if ((param->filter_type & ~filter_mask) != 0 ||
static_cast<uint32_t>(std::popcount(param->filter_type)) > ctx->max_filter_number) {
return PNG_ENC_ERROR_INVALID_PARAM;
}
const uint32_t width = param->image_width;
const uint32_t height = param->image_height;
if (width == 0 || height == 0 || width > ctx->max_image_width ||
width > PNG_ENC_MAX_IMAGE_WIDTH || height > PNG_ENC_MAX_IMAGE_HEIGHT) {
return PNG_ENC_ERROR_INVALID_SIZE;
}
const int components = (param->color_space == PNG_ENC_COLOR_SPACE_RGBA ? 4 : 3);
const uint32_t row_size = width * static_cast<uint32_t>(components);
const uint32_t min_pitch = width * 4u;
const uint32_t pitch = param->image_pitch;
// Leave room for deflate expansion and stb's signed-int buffer-capacity doubling.
constexpr uint64_t max_filtered_size = std::numeric_limits<int>::max() / 2u;
if (static_cast<uint64_t>(row_size + 1u) * height > max_filtered_size ||
param->png_mem_size == 0 || pitch < min_pitch || (pitch & 3u) != 0 ||
static_cast<uint64_t>(pitch) * height > param->image_mem_size) {
return PNG_ENC_ERROR_INVALID_SIZE;
}
const bool bgr = (param->pixel_format == PNG_ENC_PIXEL_FORMAT_B8G8R8A8);
std::unique_ptr<uint8_t[]> pixels(new (std::nothrow)
uint8_t[static_cast<size_t>(row_size) * height]);
if (pixels == nullptr) {
return PNG_ENC_ERROR_FATAL;
}
for (uint32_t y = 0; y < height; y++) {
const auto* src = param->image_mem_addr + static_cast<size_t>(y) * pitch;
auto* dst = pixels.get() + static_cast<size_t>(y) * row_size;
for (uint32_t x = 0; x < width; x++) {
dst[0] = src[bgr ? 2 : 0];
dst[1] = src[1];
dst[2] = src[bgr ? 0 : 2];
if (components == 4) {
dst[3] = src[3];
}
src += 4;
dst += components;
}
}
PngEncWriter writer {param->png_mem_addr, param->png_mem_size, 0, false};
int ok = 0;
{
std::scoped_lock lock(g_png_enc_mutex);
stbi_write_png_compression_level = param->compression_level;
stbi_write_force_png_filter = PngEncForcedFilter(param->filter_type);
ok = stbi_write_png_to_func(PngEncWrite, &writer, static_cast<int>(width),
static_cast<int>(height), components, pixels.get(),
static_cast<int>(row_size));
}
if (output_info != nullptr) {
output_info->data_size = (writer.overflow ? 0 : writer.size);
output_info->processed_height = (ok != 0 && !writer.overflow ? height : 0);
}
if (ok == 0) {
LOGF("\t encode failed\n");
return PNG_ENC_ERROR_FATAL;
}
if (writer.overflow) {
LOGF("\t output buffer too small\n");
return PNG_ENC_ERROR_DATA_OVERFLOW;
}
LOGF("\t data_size = %" PRIu32 "\n", writer.size);
return static_cast<int32_t>(writer.size);
}
static int32_t KYTY_SYSV_ABI PngEncDelete(void* handle) {
PRINT_NAME();
if (handle == nullptr) {
return PNG_ENC_ERROR_INVALID_HANDLE;
}
auto* ctx = static_cast<PngEncContext*>(handle);
if (ctx->magic != PNG_ENC_CONTEXT_MAGIC) {
return PNG_ENC_ERROR_INVALID_HANDLE;
}
ctx->magic = 0;
return 0;
}
} // namespace PngEnc
LIB_DEFINE(InitPngEnc_1) {
PRINT_NAME_ENABLE(true);
LIB_FUNC("9030RnBDoh4", PngEnc::PngEncQueryMemorySize);
LIB_FUNC("7aGTPfrqT9s", PngEnc::PngEncCreate);
LIB_FUNC("xgDjJKpcyHo", PngEnc::PngEncEncode);
LIB_FUNC("RUrWdwTWZy8", PngEnc::PngEncDelete);
}
} // namespace Libs
+2
View File
@@ -91,6 +91,7 @@ LIB_DEFINE(InitNet_1);
LIB_DEFINE(InitPad_1);
LIB_DEFINE(InitPlayGo_1);
LIB_DEFINE(InitPngDec_1);
LIB_DEFINE(InitPngEnc_1);
LIB_DEFINE(InitPlatform_1);
LIB_DEFINE(InitRudp_1);
LIB_DEFINE(InitSaveData_1);
@@ -129,6 +130,7 @@ void InitAll(Loader::SymbolDatabase* s) {
InitPlayGo_1(s);
LibPsml::InitPsml_1(s);
InitPngDec_1(s);
InitPngEnc_1(s);
InitPlatform_1(s);
InitRudp_1(s);
LibRtc::InitRtc_1(s);
+247
View File
@@ -0,0 +1,247 @@
#include "libs/libPngEnc.cpp"
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_PNG
#define STBI_NO_STDIO
#include "stb_image.h"
#include <algorithm>
#include <array>
#include <cstdio>
#include <cstdlib>
#include <vector>
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<uint8_t, 16> 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<uint8_t> source(pitch * 2, 0xee);
std::vector<uint8_t> 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<uint8_t, 1024> 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, &param, &info);
CHECK(size > 0 && info.data_size == static_cast<uint32_t>(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<uint8_t, 16> pixels {};
std::array<uint8_t, 1024> 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, &param, nullptr);
CHECK(size > 0);
CHECK(std::memcmp(png.data() + 37, "IDAT", 4) == 0);
std::array<char, 18> filtered {};
CHECK(stbi_zlib_decode_buffer(filtered.data(), filtered.size(),
reinterpret_cast<const char*>(png.data() + 41),
ReadBe32(png.data() + 33)) == filtered.size());
for (size_t row = 0; row < 2; ++row) {
const auto filter = static_cast<uint8_t>(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, &param, 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, &param, nullptr);
CHECK(std::popcount(mask) <= count ? result > 0
: result == PNG_ENC_ERROR_INVALID_PARAM);
}
}
}
void TestOutputCapacity() {
Encoder encoder;
alignas(4) std::array<uint8_t, 16> pixels {};
std::array<uint8_t, 1024> png {};
auto param = EncodeParam(pixels.data(), pixels.size(), png.data(), png.size());
const auto size = PngEncEncode(encoder.handle, &param, nullptr);
CHECK(size > 0);
std::vector<uint8_t> guarded(size + 2, 0xa5);
param.png_mem_addr = guarded.data() + 1;
param.png_mem_size = size;
PngEncOutputInfo info {};
CHECK(PngEncEncode(encoder.handle, &param, &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, &param, &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<uint8_t, 16> pixels {};
std::array<uint8_t, 1024> 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, &param, 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, &param, 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, &param, 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, &param, nullptr) == PNG_ENC_ERROR_INVALID_SIZE);
}
auto param = valid;
param.image_pitch = 12;
param.image_mem_size = 20;
CHECK(PngEncEncode(encoder.handle, &param, nullptr) == PNG_ENC_ERROR_INVALID_SIZE);
param = valid;
param.image_mem_size--;
CHECK(PngEncEncode(encoder.handle, &param, nullptr) == PNG_ENC_ERROR_INVALID_SIZE);
param = valid;
param.image_mem_addr = nullptr;
CHECK(PngEncEncode(encoder.handle, &param, nullptr) == PNG_ENC_ERROR_INVALID_ADDR);
param.image_mem_addr = pixels.data() + 1;
CHECK(PngEncEncode(encoder.handle, &param, nullptr) == PNG_ENC_ERROR_INVALID_ADDR);
param = valid;
param.bit_depth = 16;
CHECK(PngEncEncode(encoder.handle, &param, nullptr) == PNG_ENC_ERROR_INVALID_PARAM);
param = valid;
param.clut_number = 1;
CHECK(PngEncEncode(encoder.handle, &param, nullptr) == PNG_ENC_ERROR_INVALID_PARAM);
param = valid;
param.compression_level = 10;
CHECK(PngEncEncode(encoder.handle, &param, nullptr) == PNG_ENC_ERROR_INVALID_PARAM);
}
} // namespace
int main() {
TestPixels();
TestFilterMasks();
TestOutputCapacity();
TestValidation();
std::puts("PNG encoder tests passed");
}