feat(pngdec): implement libScePngDec on the shared PNG decoder

This commit is contained in:
Qznad 2026-09-30 11:10:00 +01:00
parent 5853fec906
commit a0e2f88048
4 changed files with 382 additions and 22 deletions

View File

@ -425,6 +425,15 @@ if(BUILD_TESTING)
set_tests_properties(guest_jpeg_enc PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceJpegEnc>")
endif()
add_executable(guest_png_dec_tests tests/GuestPngDec.cpp)
target_include_directories(guest_png_dec_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_png_dec_tests PRIVATE libScePngDec decoder_png libc)
configure_windows_unwind(guest_png_dec_tests)
add_test(NAME guest_png_dec COMMAND guest_png_dec_tests)
if(WIN32)
set_tests_properties(guest_png_dec PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libScePngDec>")
endif()
add_executable(guest_font_tests tests/GuestFont.cpp)
target_include_directories(guest_font_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_font_tests PRIVATE libSceFont libSceFontFt libc)

View File

@ -0,0 +1,5 @@
add_library(libScePngDec SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp)
target_include_directories(libScePngDec PRIVATE ${LIBS_INCLUDE_DIR})
set_target_properties(libScePngDec PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(libScePngDec)
target_link_libraries(libScePngDec PRIVATE decoder_png)

View File

@ -1,44 +1,188 @@
#include <cstdint>
#include <cstddef>
#include <cstring>
#include <new>
#include <optional>
#include <span>
#include <stdexcept>
#include <utility>
#include "Decoder/Png.hpp"
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
static_assert(sizeof(PngDecCreateParam) == 0xC);
static_assert(sizeof(PngDecParseParam) == 0x10);
static_assert(sizeof(PngDecDecodeParam) == 0x20);
static_assert(sizeof(PngDecImageInfo) == 0x10);
namespace {
constexpr std::int32_t SCE_PNG_DEC_ERROR_INVALID_ADDR = static_cast<std::int32_t>(0x80690001);
constexpr std::int32_t SCE_PNG_DEC_ERROR_INVALID_SIZE = static_cast<std::int32_t>(0x80690002);
constexpr std::int32_t SCE_PNG_DEC_ERROR_INVALID_PARAM = static_cast<std::int32_t>(0x80690003);
constexpr std::int32_t SCE_PNG_DEC_ERROR_INVALID_HANDLE = static_cast<std::int32_t>(0x80690004);
constexpr std::int32_t SCE_PNG_DEC_ERROR_INVALID_WORK_MEMORY = static_cast<std::int32_t>(0x80690005);
constexpr std::int32_t SCE_PNG_DEC_ERROR_INVALID_DATA = static_cast<std::int32_t>(0x80690010);
constexpr std::int32_t SCE_PNG_DEC_ERROR_DECODE_ERROR = static_cast<std::int32_t>(0x80690012);
constexpr std::uint32_t ATTRIBUTE_NONE = 0;
constexpr std::uint32_t ATTRIBUTE_BIT_DEPTH_16 = 1;
constexpr std::uint32_t MAX_IMAGE_WIDTH = 1000000;
constexpr std::uint32_t MEMORY_SIZE = 0x20;
constexpr std::uintptr_t HANDLE_ALIGNMENT = 8;
constexpr std::uint16_t PIXEL_FORMAT_R8G8B8A8 = 0;
constexpr std::uint16_t PIXEL_FORMAT_B8G8R8A8 = 1;
constexpr std::uint16_t COLOR_SPACE_GRAYSCALE = 2;
constexpr std::uint16_t COLOR_SPACE_RGB = 3;
constexpr std::uint16_t COLOR_SPACE_CLUT = 4;
constexpr std::uint16_t COLOR_SPACE_GRAYSCALE_ALPHA = 18;
constexpr std::uint16_t COLOR_SPACE_RGBA = 19;
constexpr std::uint32_t IMAGE_FLAG_ADAM7_INTERLACE = 1;
constexpr std::uint32_t IMAGE_FLAG_TRNS_CHUNK_EXIST = 2;
constexpr std::uint32_t BYTES_PER_PIXEL = 4;
constexpr std::uint32_t MAX_PACKED_DIMENSION = 32767;
struct Context {
Context* self;
std::uint32_t attribute;
std::uint32_t maxImageWidth;
};
static_assert(sizeof(Context) + HANDLE_ALIGNMENT - 1 <= MEMORY_SIZE);
std::int32_t validateCreateParam(const PngDecCreateParam* param) {
if (!param) return SCE_PNG_DEC_ERROR_INVALID_PARAM;
if (param->attribute != ATTRIBUTE_NONE && param->attribute != ATTRIBUTE_BIT_DEPTH_16) return SCE_PNG_DEC_ERROR_INVALID_PARAM;
if (param->max_image_width == 0 || param->max_image_width - 1 > MAX_IMAGE_WIDTH) return SCE_PNG_DEC_ERROR_INVALID_SIZE;
return 0;
}
Context* toContext(void* handle) {
const auto address = reinterpret_cast<std::uintptr_t>(handle);
if (address == 0 || address % HANDLE_ALIGNMENT != 0) return nullptr;
auto* context = reinterpret_cast<Context*>(handle);
return context->self == context ? context : nullptr;
}
std::uint16_t toColorSpace(Decoder::Png::ColorType colorType) {
switch (colorType) {
case Decoder::Png::ColorType::Grayscale:
return COLOR_SPACE_GRAYSCALE;
case Decoder::Png::ColorType::Rgb:
return COLOR_SPACE_RGB;
case Decoder::Png::ColorType::Palette:
return COLOR_SPACE_CLUT;
case Decoder::Png::ColorType::GrayscaleAlpha:
return COLOR_SPACE_GRAYSCALE_ALPHA;
case Decoder::Png::ColorType::Rgba:
return COLOR_SPACE_RGBA;
}
throw std::runtime_error("libScePngDec: unknown PNG color type");
}
void fillImageInfo(const Decoder::Png::Header& header, PngDecImageInfo* info) {
info->image_width = header.width;
info->image_height = header.height;
info->color_space = toColorSpace(header.colorType);
info->bit_depth = header.bitDepth;
info->image_flag = (header.interlaced ? IMAGE_FLAG_ADAM7_INTERLACE : 0) | (header.hasTransparency ? IMAGE_FLAG_TRNS_CHUNK_EXIST : 0);
}
bool hasAlpha(const Decoder::Png::Header& header) {
return header.hasTransparency || header.colorType == Decoder::Png::ColorType::GrayscaleAlpha
|| header.colorType == Decoder::Png::ColorType::Rgba;
}
std::span<const std::uint8_t> toBytes(const void* data, std::uint32_t size) {
return {static_cast<const std::uint8_t*>(data), size};
}
} // namespace
extern "C" {
int32_t APS5_VABI scePngDecCreate(const PngDecCreateParam* param, void* memory_address, uint32_t memory_size, void** handle) {
(void)param;
(void)memory_address;
(void)memory_size;
(void)handle;
NotImplemented_nid_no_patch(__func__);
return 0;
const std::int32_t result = validateCreateParam(param);
if (result != 0) return result;
if (!memory_address || !handle) return SCE_PNG_DEC_ERROR_INVALID_ADDR;
if (memory_size < MEMORY_SIZE) return SCE_PNG_DEC_ERROR_INVALID_WORK_MEMORY;
const auto address = reinterpret_cast<std::uintptr_t>(memory_address);
const std::uintptr_t aligned = (address + HANDLE_ALIGNMENT - 1) & ~(HANDLE_ALIGNMENT - 1);
auto* context = new (reinterpret_cast<void*>(aligned)) Context{};
context->self = context;
context->attribute = param->attribute;
context->maxImageWidth = param->max_image_width;
*handle = context;
return 0;
}
int32_t APS5_VABI scePngDecDecode(void* handle, const PngDecDecodeParam* param, PngDecImageInfo* image_info) {
(void)handle;
(void)param;
(void)image_info;
NotImplemented_nid_no_patch(__func__);
return 0;
const Context* context = toContext(handle);
if (!context) return SCE_PNG_DEC_ERROR_INVALID_HANDLE;
if (!param) return SCE_PNG_DEC_ERROR_INVALID_PARAM;
if (!param->png_mem_addr || !param->image_mem_addr) return SCE_PNG_DEC_ERROR_INVALID_ADDR;
if (param->png_mem_size == 0 || param->image_mem_size == 0) return SCE_PNG_DEC_ERROR_INVALID_SIZE;
if (param->pixel_format != PIXEL_FORMAT_R8G8B8A8 && param->pixel_format != PIXEL_FORMAT_B8G8R8A8) return SCE_PNG_DEC_ERROR_INVALID_PARAM;
const std::span<const std::uint8_t> png = toBytes(param->png_mem_addr, param->png_mem_size);
const std::optional<Decoder::Png::Header> header = Decoder::Png::ParseHeader(png);
if (!header) return SCE_PNG_DEC_ERROR_INVALID_DATA;
if (header->bitDepth == 16 && context->attribute == ATTRIBUTE_BIT_DEPTH_16) {
throw std::runtime_error("scePngDecDecode: 16-bit output is not implemented");
}
const std::uint64_t rowSize = static_cast<std::uint64_t>(header->width) * BYTES_PER_PIXEL;
const std::uint64_t pitch = param->image_pitch == 0 ? rowSize : param->image_pitch;
if (pitch < rowSize) return SCE_PNG_DEC_ERROR_INVALID_PARAM;
if ((header->height - 1) * pitch + rowSize > param->image_mem_size) return SCE_PNG_DEC_ERROR_INVALID_SIZE;
const std::optional<Decoder::Png::Image> image = Decoder::Png::Decode(png);
if (!image || image->width != header->width || image->height != header->height) return SCE_PNG_DEC_ERROR_DECODE_ERROR;
const bool swapRedBlue = param->pixel_format == PIXEL_FORMAT_B8G8R8A8;
const bool fillAlpha = !hasAlpha(*header);
const auto alpha = static_cast<std::uint8_t>(param->alpha_value);
auto* output = static_cast<std::uint8_t*>(param->image_mem_addr);
for (std::uint32_t y = 0; y < image->height; ++y) {
const std::uint8_t* in = image->pixels.data() + y * rowSize;
std::uint8_t* out = output + y * pitch;
std::memcpy(out, in, rowSize);
for (std::uint32_t x = 0; x < image->width; ++x) {
std::uint8_t* pixel = out + x * BYTES_PER_PIXEL;
if (swapRedBlue) std::swap(pixel[0], pixel[2]);
if (fillAlpha) pixel[3] = alpha;
}
}
if (image_info) fillImageInfo(*header, image_info);
if (header->width > MAX_PACKED_DIMENSION || header->height > MAX_PACKED_DIMENSION) return 0;
return static_cast<std::int32_t>(header->width << 16 | header->height);
}
int32_t APS5_VABI scePngDecDelete(void* handle) {
(void)handle;
NotImplemented_nid_no_patch(__func__);
return 0;
Context* context = toContext(handle);
if (!context) return SCE_PNG_DEC_ERROR_INVALID_HANDLE;
context->self = nullptr;
return 0;
}
int32_t APS5_VABI scePngDecParseHeader(const PngDecParseParam* param, PngDecImageInfo* image_info) {
(void)param;
(void)image_info;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!param) return SCE_PNG_DEC_ERROR_INVALID_PARAM;
if (!param->png_mem_addr || !image_info) return SCE_PNG_DEC_ERROR_INVALID_ADDR;
if (param->png_mem_size == 0) return SCE_PNG_DEC_ERROR_INVALID_SIZE;
const std::optional<Decoder::Png::Header> header = Decoder::Png::ParseHeader(toBytes(param->png_mem_addr, param->png_mem_size));
if (!header) return SCE_PNG_DEC_ERROR_INVALID_DATA;
fillImageInfo(*header, image_info);
return 0;
}
int32_t APS5_VABI scePngDecQueryMemorySize(const PngDecCreateParam* param) {
(void)param;
NotImplemented_nid_no_patch(__func__);
return 0;
const std::int32_t result = validateCreateParam(param);
if (result != 0) return result;
return static_cast<std::int32_t>(MEMORY_SIZE);
}
}

View File

@ -0,0 +1,202 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "Decoder/Png.hpp"
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
#include <vector>
extern "C" {
std::int32_t APS5_VABI scePngDecQueryMemorySize(const PngDecCreateParam*);
std::int32_t APS5_VABI scePngDecCreate(const PngDecCreateParam*, void*, std::uint32_t, void**);
std::int32_t APS5_VABI scePngDecDelete(void*);
std::int32_t APS5_VABI scePngDecParseHeader(const PngDecParseParam*, PngDecImageInfo*);
std::int32_t APS5_VABI scePngDecDecode(void*, const PngDecDecodeParam*, PngDecImageInfo*);
}
static void Require(bool value) { if (!value) std::abort(); }
template<typename TFunction>
static bool ThrowsRuntimeError(TFunction function) {
try {
function();
} catch (const std::runtime_error&) {
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 std::vector<std::uint8_t> RgbPng() {
std::vector<std::uint8_t> rgb(3 * 2 * 3);
for (std::size_t i = 0; i < rgb.size(); ++i) rgb[i] = static_cast<std::uint8_t>(10 + i * 13);
return Decoder::Png::Encode(rgb, 3, 2, 3);
}
static PngDecDecodeParam DecodeParam(const std::vector<std::uint8_t>& png, std::vector<std::uint8_t>& image) {
PngDecDecodeParam param{};
param.png_mem_addr = png.data();
param.image_mem_addr = image.data();
param.png_mem_size = static_cast<std::uint32_t>(png.size());
param.image_mem_size = static_cast<std::uint32_t>(image.size());
param.pixel_format = 0;
param.alpha_value = 0x77;
param.image_pitch = 0;
return param;
}
int main() {
constexpr std::int32_t invalidAddr = static_cast<std::int32_t>(0x80690001);
constexpr std::int32_t invalidSize = static_cast<std::int32_t>(0x80690002);
constexpr std::int32_t invalidParam = static_cast<std::int32_t>(0x80690003);
constexpr std::int32_t invalidHandle = static_cast<std::int32_t>(0x80690004);
constexpr std::int32_t invalidWorkMemory = static_cast<std::int32_t>(0x80690005);
constexpr std::int32_t invalidData = static_cast<std::int32_t>(0x80690010);
constexpr std::int32_t decodeError = static_cast<std::int32_t>(0x80690012);
const PngDecCreateParam param{sizeof(PngDecCreateParam), 0, 1920};
const std::int32_t memorySize = scePngDecQueryMemorySize(&param);
Require(memorySize > 0);
Require(scePngDecQueryMemorySize(nullptr) == invalidParam);
const PngDecCreateParam badAttribute{sizeof(PngDecCreateParam), 2, 1920};
Require(scePngDecQueryMemorySize(&badAttribute) == invalidParam);
const PngDecCreateParam zeroWidth{sizeof(PngDecCreateParam), 0, 0};
Require(scePngDecQueryMemorySize(&zeroWidth) == invalidSize);
const PngDecCreateParam hugeWidth{sizeof(PngDecCreateParam), 0, 1000002};
Require(scePngDecQueryMemorySize(&hugeWidth) == invalidSize);
const PngDecCreateParam maxWidth{sizeof(PngDecCreateParam), 1, 1000001};
Require(scePngDecQueryMemorySize(&maxWidth) == memorySize);
std::vector<unsigned char> memory(static_cast<std::size_t>(memorySize) + 1);
unsigned char* unaligned = memory.data() + 1;
void* handle = nullptr;
Require(scePngDecCreate(nullptr, unaligned, static_cast<std::uint32_t>(memorySize), &handle) == invalidParam);
Require(scePngDecCreate(&param, nullptr, static_cast<std::uint32_t>(memorySize), &handle) == invalidAddr);
Require(scePngDecCreate(&param, unaligned, static_cast<std::uint32_t>(memorySize), nullptr) == invalidAddr);
Require(scePngDecCreate(&param, unaligned, static_cast<std::uint32_t>(memorySize) - 1, &handle) == invalidWorkMemory);
Require(handle == nullptr);
Require(scePngDecCreate(&param, unaligned, static_cast<std::uint32_t>(memorySize), &handle) == 0);
Require(reinterpret_cast<std::uintptr_t>(handle) % 8 == 0);
Require(static_cast<unsigned char*>(handle) >= unaligned && static_cast<unsigned char*>(handle) < unaligned + 8);
const std::vector<std::uint8_t> rgbPng = RgbPng();
PngDecImageInfo info{};
const PngDecParseParam parse{rgbPng.data(), static_cast<std::uint32_t>(rgbPng.size()), 0};
Require(scePngDecParseHeader(&parse, &info) == 0);
Require(info.image_width == 3 && info.image_height == 2 && info.color_space == 3 && info.bit_depth == 8 && info.image_flag == 0);
const PngDecParseParam parsePalette{PALETTE_TRNS, sizeof(PALETTE_TRNS), 0};
Require(scePngDecParseHeader(&parsePalette, &info) == 0);
Require(info.color_space == 4 && info.bit_depth == 2 && info.image_flag == 2);
Require(scePngDecParseHeader(nullptr, &info) == invalidParam);
Require(scePngDecParseHeader(&parse, nullptr) == invalidAddr);
const PngDecParseParam parseNull{nullptr, 16, 0};
Require(scePngDecParseHeader(&parseNull, &info) == invalidAddr);
const PngDecParseParam parseEmpty{rgbPng.data(), 0, 0};
Require(scePngDecParseHeader(&parseEmpty, &info) == invalidSize);
const std::uint8_t garbage[40] = {1, 2, 3};
const PngDecParseParam parseGarbage{garbage, sizeof(garbage), 0};
Require(scePngDecParseHeader(&parseGarbage, &info) == invalidData);
std::vector<std::uint8_t> image(3 * 2 * 4);
PngDecDecodeParam decode = DecodeParam(rgbPng, image);
info = {};
Require(scePngDecDecode(handle, &decode, &info) == (3 << 16 | 2));
Require(info.image_width == 3 && info.image_height == 2 && info.color_space == 3);
for (std::size_t pixel = 0; pixel < 6; ++pixel) {
for (std::size_t c = 0; c < 3; ++c) Require(image[pixel * 4 + c] == static_cast<std::uint8_t>(10 + (pixel * 3 + c) * 13));
Require(image[pixel * 4 + 3] == 0x77);
}
constexpr std::uint32_t pitch = 3 * 4 + 8;
std::vector<std::uint8_t> padded(pitch + 3 * 4, 0xEE);
decode = DecodeParam(rgbPng, padded);
decode.pixel_format = 1;
decode.image_pitch = pitch;
Require(scePngDecDecode(handle, &decode, nullptr) == (3 << 16 | 2));
for (std::size_t y = 0; y < 2; ++y) {
for (std::size_t x = 0; x < 3; ++x) {
const std::uint8_t* pixel = &padded[y * pitch + x * 4];
const std::size_t source = (y * 3 + x) * 3;
Require(pixel[0] == static_cast<std::uint8_t>(10 + (source + 2) * 13));
Require(pixel[2] == static_cast<std::uint8_t>(10 + source * 13));
}
}
for (std::size_t x = 3 * 4; x < pitch; ++x) Require(padded[x] == 0xEE);
const std::vector<std::uint8_t> palettePng(PALETTE_TRNS, PALETTE_TRNS + sizeof(PALETTE_TRNS));
std::vector<std::uint8_t> paletteImage(2 * 2 * 4);
decode = DecodeParam(palettePng, paletteImage);
Require(scePngDecDecode(handle, &decode, &info) == (2 << 16 | 2));
Require(paletteImage == std::vector<std::uint8_t>({255, 0, 0, 255, 0, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 128}));
Require(info.image_flag == 2);
const std::vector<std::uint8_t> gray16Png(GRAY16, GRAY16 + sizeof(GRAY16));
std::vector<std::uint8_t> gray16Image(2 * 4);
decode = DecodeParam(gray16Png, gray16Image);
Require(scePngDecDecode(handle, &decode, &info) == (2 << 16 | 1));
Require(gray16Image == std::vector<std::uint8_t>({0x12, 0x12, 0x12, 0x77, 0xAB, 0xAB, 0xAB, 0x77}));
Require(info.bit_depth == 16);
decode = DecodeParam(rgbPng, image);
Require(scePngDecDecode(nullptr, &decode, &info) == invalidHandle);
Require(scePngDecDecode(handle, nullptr, &info) == invalidParam);
decode.png_mem_addr = nullptr;
Require(scePngDecDecode(handle, &decode, &info) == invalidAddr);
decode = DecodeParam(rgbPng, image);
decode.image_mem_addr = nullptr;
Require(scePngDecDecode(handle, &decode, &info) == invalidAddr);
decode = DecodeParam(rgbPng, image);
decode.png_mem_size = 0;
Require(scePngDecDecode(handle, &decode, &info) == invalidSize);
decode = DecodeParam(rgbPng, image);
decode.pixel_format = 2;
Require(scePngDecDecode(handle, &decode, &info) == invalidParam);
decode = DecodeParam(rgbPng, image);
decode.image_pitch = 3 * 4 - 1;
Require(scePngDecDecode(handle, &decode, &info) == invalidParam);
decode = DecodeParam(rgbPng, image);
decode.image_mem_size = 3 * 2 * 4 - 1;
Require(scePngDecDecode(handle, &decode, &info) == invalidSize);
const std::vector<std::uint8_t> garbagePng(garbage, garbage + sizeof(garbage));
decode = DecodeParam(garbagePng, image);
Require(scePngDecDecode(handle, &decode, &info) == invalidData);
const std::vector<std::uint8_t> truncatedPng(rgbPng.begin(), rgbPng.begin() + 40);
decode = DecodeParam(truncatedPng, image);
Require(scePngDecDecode(handle, &decode, &info) == decodeError);
void* deepHandle = nullptr;
std::vector<unsigned char> deepMemory(static_cast<std::size_t>(memorySize));
const PngDecCreateParam deepParam{sizeof(PngDecCreateParam), 1, 1920};
Require(scePngDecCreate(&deepParam, deepMemory.data(), static_cast<std::uint32_t>(memorySize), &deepHandle) == 0);
decode = DecodeParam(gray16Png, gray16Image);
Require(ThrowsRuntimeError([&] { scePngDecDecode(deepHandle, &decode, &info); }));
decode = DecodeParam(rgbPng, image);
Require(scePngDecDecode(deepHandle, &decode, &info) == (3 << 16 | 2));
Require(scePngDecDelete(deepHandle) == 0);
Require(scePngDecDelete(nullptr) == invalidHandle);
Require(scePngDecDelete(static_cast<unsigned char*>(handle) + 1) == invalidHandle);
Require(scePngDecDelete(handle) == 0);
Require(scePngDecDelete(handle) == invalidHandle);
Require(scePngDecDecode(handle, &decode, &info) == invalidHandle);
return 0;
}