AnyPS5/core/libs/tests/GuestPngEnc.cpp

147 lines
7.5 KiB
C++

#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "Decoder/Png.hpp"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <utility>
#include <vector>
extern "C" {
int APS5_VABI scePngEncQueryMemorySize(const PngEncCreateParam*);
int APS5_VABI scePngEncCreate(const PngEncCreateParam*, void*, std::uint32_t, void**);
int APS5_VABI scePngEncDelete(void*);
int APS5_VABI scePngEncEncode(void*, const PngEncEncodeParam*, PngEncOutputInfo*);
}
static void Require(bool value) { if (!value) std::abort(); }
int main() {
constexpr int invalidAddr = static_cast<int>(0x80690101);
constexpr int invalidSize = static_cast<int>(0x80690102);
constexpr int invalidParam = static_cast<int>(0x80690103);
constexpr int invalidHandle = static_cast<int>(0x80690104);
constexpr int dataOverflow = static_cast<int>(0x80690110);
constexpr std::uint32_t width = 5;
constexpr std::uint32_t height = 3;
constexpr std::uint32_t pitch = 24;
PngEncCreateParam create{sizeof(PngEncCreateParam), 0, 16, 5};
const int memorySize = scePngEncQueryMemorySize(&create);
Require(memorySize > 0);
Require(scePngEncQueryMemorySize(nullptr) == invalidAddr);
PngEncCreateParam bad = create;
bad.attribute = 1;
Require(scePngEncQueryMemorySize(&bad) == invalidParam);
bad = create;
bad.max_filter_number = 6;
Require(scePngEncQueryMemorySize(&bad) == invalidParam);
bad = create;
bad.max_image_width = 0;
Require(scePngEncQueryMemorySize(&bad) == invalidSize);
bad.max_image_width = 1000001;
Require(scePngEncQueryMemorySize(&bad) == invalidSize);
std::vector<std::uint64_t> memory((memorySize + 7) / 8);
void* handle = nullptr;
Require(scePngEncCreate(&create, nullptr, memorySize, &handle) == invalidAddr);
Require(scePngEncCreate(&create, memory.data(), memorySize - 1, &handle) == invalidSize);
Require(scePngEncCreate(&create, memory.data(), memorySize, &handle) == 0 && handle == memory.data());
std::vector<std::uint8_t> image(pitch * height, 0xEE);
for (std::uint32_t y = 0; y < height; ++y) {
for (std::uint32_t x = 0; x < width; ++x) {
std::uint8_t* pixel = &image[y * pitch + x * 4];
pixel[0] = static_cast<std::uint8_t>(x * 40);
pixel[1] = static_cast<std::uint8_t>(y * 80);
pixel[2] = static_cast<std::uint8_t>(200 - x * 10);
pixel[3] = static_cast<std::uint8_t>(100 + x + y);
}
}
std::vector<std::uint8_t> png(4096);
PngEncEncodeParam encode{image.data(), png.data(), static_cast<std::uint32_t>(image.size()), static_cast<std::uint32_t>(png.size()),
width, height, pitch, 0, 19, 8, 0, 15, 6};
for (const std::uint16_t pixelFormat : {0, 1}) {
for (const std::uint16_t colorSpace : {3, 19}) {
encode.pixel_format = pixelFormat;
encode.color_space = colorSpace;
PngEncOutputInfo info{};
const int size = scePngEncEncode(handle, &encode, &info);
Require(size > 0 && info.data_size == static_cast<std::uint32_t>(size) && info.processed_height == height);
const auto header = Decoder::Png::ParseHeader(std::span<const std::uint8_t>(png.data(), size));
Require(header.has_value());
Require(header->colorType == (colorSpace == 19 ? Decoder::Png::ColorType::Rgba : Decoder::Png::ColorType::Rgb));
const auto decoded = Decoder::Png::Decode(std::span<const std::uint8_t>(png.data(), size));
Require(decoded.has_value() && decoded->width == width && decoded->height == height);
for (std::uint32_t y = 0; y < height; ++y) {
for (std::uint32_t x = 0; x < width; ++x) {
const std::uint8_t* source = &image[y * pitch + x * 4];
const std::uint8_t* result = &decoded->pixels[(y * width + x) * 4];
Require(result[0] == source[pixelFormat == 1 ? 2 : 0] && result[1] == source[1]);
Require(result[2] == source[pixelFormat == 1 ? 0 : 2]);
Require(result[3] == (colorSpace == 19 ? source[3] : 0xFF));
}
}
}
}
constexpr std::uint32_t rowSize = 16 * 4;
std::vector<std::uint8_t> rows(rowSize * 6);
std::uint32_t noise = 12345;
for (std::uint32_t y = 0; y < 6; ++y) {
for (std::uint32_t i = 0; i < rowSize; ++i) {
noise = noise * 1103515245u + 12345u;
rows[y * rowSize + i] = y == 2 ? static_cast<std::uint8_t>(noise >> 16) : static_cast<std::uint8_t>(i / 4 * 8);
}
}
std::copy_n(rows.begin() + 2 * rowSize, rowSize, rows.begin() + 3 * rowSize);
PngEncEncodeParam filtered{rows.data(), png.data(), static_cast<std::uint32_t>(rows.size()), static_cast<std::uint32_t>(png.size()),
16, 6, rowSize, 0, 19, 8, 0, 0, 6};
const auto encodeWith = [&](std::uint16_t filterType) {
filtered.filter_type = filterType;
const int size = scePngEncEncode(handle, &filtered, nullptr);
Require(size > 0);
return std::vector<std::uint8_t>(png.begin(), png.begin() + size);
};
using namespace Decoder::Png;
const std::pair<std::uint16_t, std::uint8_t> filterSets[] = {
{0, FILTER_NONE}, {1, FILTER_SUB}, {2, FILTER_UP}, {4, FILTER_AVERAGE}, {8, FILTER_PAETH},
{3, FILTER_SUB | FILTER_UP}, {6, FILTER_UP | FILTER_AVERAGE}, {9, FILTER_SUB | FILTER_PAETH},
{14, FILTER_UP | FILTER_AVERAGE | FILTER_PAETH}, {15, FILTER_ALL}};
for (const auto& [filterType, filters] : filterSets) Require(encodeWith(filterType) == Encode(rows, 16, 6, 4, {6, filters}));
Require(encodeWith(3) != encodeWith(1) && encodeWith(3) != encodeWith(2) && encodeWith(15) != encodeWith(14));
encode.png_mem_size = 20;
PngEncOutputInfo info{1, 1};
Require(scePngEncEncode(handle, &encode, &info) == dataOverflow && info.data_size == 0 && info.processed_height == 0);
encode.png_mem_size = static_cast<std::uint32_t>(png.size());
PngEncEncodeParam invalid = encode;
Require(scePngEncEncode(handle, nullptr, nullptr) == invalidParam);
invalid.image_mem_addr = nullptr;
Require(scePngEncEncode(handle, &invalid, nullptr) == invalidAddr);
for (auto change : {+[](PngEncEncodeParam& p) { p.pixel_format = 2; }, +[](PngEncEncodeParam& p) { p.color_space = 4; },
+[](PngEncEncodeParam& p) { p.bit_depth = 16; }, +[](PngEncEncodeParam& p) { p.clut_number = 1; },
+[](PngEncEncodeParam& p) { p.filter_type = 16; }, +[](PngEncEncodeParam& p) { p.compression_level = 10; }}) {
invalid = encode;
change(invalid);
Require(scePngEncEncode(handle, &invalid, nullptr) == invalidParam);
}
for (auto change : {+[](PngEncEncodeParam& p) { p.image_width = 0; }, +[](PngEncEncodeParam& p) { p.image_width = 17; },
+[](PngEncEncodeParam& p) { p.image_pitch = 19; }, +[](PngEncEncodeParam& p) { p.image_mem_size = 67; },
+[](PngEncEncodeParam& p) { p.png_mem_size = 0; }}) {
invalid = encode;
change(invalid);
Require(scePngEncEncode(handle, &invalid, nullptr) == invalidSize);
}
invalid = encode;
invalid.image_mem_size = pitch * (height - 1) + width * 4;
Require(scePngEncEncode(handle, &invalid, nullptr) > 0);
Require(scePngEncDelete(handle) == 0);
Require(scePngEncDelete(handle) == invalidHandle);
Require(scePngEncEncode(handle, &encode, nullptr) == invalidHandle);
Require(scePngEncDelete(nullptr) == invalidHandle);
}