feat(libScePngEnc): implement PNG encoding
scePngEncQueryMemorySize, scePngEncCreate, scePngEncEncode and scePngEncDelete encode R8G8B8A8 and B8G8R8A8 images with a pitch into RGB or RGBA PNGs using the shared encoder in core/Decoder/Png. Decoder::Png::Encode takes optional compression level and filter options so the encode parameters are honoured. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
01eaae0bf9
commit
58f90eea9d
|
|
@ -35,8 +35,13 @@ std::optional<Header> ParseHeader(std::span<const std::uint8_t> png);
|
|||
|
||||
std::optional<Image> Decode(std::span<const std::uint8_t> png);
|
||||
|
||||
struct EncodeOptions {
|
||||
int compressionLevel = 8;
|
||||
int filter = -1;
|
||||
};
|
||||
|
||||
std::vector<std::uint8_t> Encode(std::span<const std::uint8_t> pixels, std::uint32_t width, std::uint32_t height,
|
||||
std::uint32_t channels);
|
||||
std::uint32_t channels, EncodeOptions options = {});
|
||||
|
||||
} // namespace Decoder::Png
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <array>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
|
||||
#define STB_IMAGE_WRITE_STATIC
|
||||
|
|
@ -102,8 +103,11 @@ std::optional<Image> Decode(std::span<const std::uint8_t> png) {
|
|||
}
|
||||
|
||||
std::vector<std::uint8_t> Encode(std::span<const std::uint8_t> pixels, std::uint32_t width, std::uint32_t height,
|
||||
std::uint32_t channels) {
|
||||
std::uint32_t channels, EncodeOptions options) {
|
||||
if (channels < 1 || channels > 4) throw std::invalid_argument("Png::Encode: channels must be 1-4");
|
||||
if (options.compressionLevel < 0 || options.compressionLevel > 9 || options.filter < -1 || options.filter > 4) {
|
||||
throw std::invalid_argument("Png::Encode: unsupported options");
|
||||
}
|
||||
if (width == 0 || height == 0 || static_cast<std::uint64_t>(width) * channels > INT_MAX || height > INT_MAX) {
|
||||
throw std::invalid_argument("Png::Encode: unsupported image size");
|
||||
}
|
||||
|
|
@ -113,6 +117,10 @@ std::vector<std::uint8_t> Encode(std::span<const std::uint8_t> pixels, std::uint
|
|||
|
||||
std::vector<std::uint8_t> output;
|
||||
const int stride = static_cast<int>(width * channels);
|
||||
static std::mutex optionsMutex;
|
||||
std::lock_guard lock(optionsMutex);
|
||||
stbi_write_png_compression_level = options.compressionLevel;
|
||||
stbi_write_force_png_filter = options.filter;
|
||||
const int written = stbi_write_png_to_func(appendBytes, &output, static_cast<int>(width), static_cast<int>(height),
|
||||
static_cast<int>(channels), pixels.data(), stride);
|
||||
if (written == 0) throw std::runtime_error("Png::Encode: encoding failed");
|
||||
|
|
|
|||
|
|
@ -140,6 +140,19 @@ int main() {
|
|||
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, -1}); }));
|
||||
Require(ThrowsInvalidArgument([&] { Decoder::Png::Encode(pixels, 4, 4, 4, {8, 5}); }));
|
||||
|
||||
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, 0});
|
||||
const auto compressed = Decoder::Png::Encode(gradient, 64, 64, 3, {9, 1});
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1406,6 +1406,34 @@ struct ContentExportInitParam2 {
|
|||
|
||||
struct ContentSearchInitParam { std::size_t memory_size; };
|
||||
|
||||
struct PngEncCreateParam {
|
||||
std::uint32_t this_size;
|
||||
std::uint32_t attribute;
|
||||
std::uint32_t max_image_width;
|
||||
std::uint32_t max_filter_number;
|
||||
};
|
||||
|
||||
struct PngEncEncodeParam {
|
||||
const std::uint8_t* image_mem_addr;
|
||||
std::uint8_t* png_mem_addr;
|
||||
std::uint32_t image_mem_size;
|
||||
std::uint32_t png_mem_size;
|
||||
std::uint32_t image_width;
|
||||
std::uint32_t image_height;
|
||||
std::uint32_t image_pitch;
|
||||
std::uint16_t pixel_format;
|
||||
std::uint16_t color_space;
|
||||
std::uint16_t bit_depth;
|
||||
std::uint16_t clut_number;
|
||||
std::uint16_t filter_type;
|
||||
std::uint16_t compression_level;
|
||||
};
|
||||
|
||||
struct PngEncOutputInfo {
|
||||
std::uint32_t data_size;
|
||||
std::uint32_t processed_height;
|
||||
};
|
||||
|
||||
struct ContentDeleteInitParam {
|
||||
char reserved1[4];
|
||||
std::size_t heap_size;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
add_library(libScePngEnc SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp)
|
||||
target_include_directories(libScePngEnc PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
set_target_properties(libScePngEnc PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(libScePngEnc)
|
||||
target_link_libraries(libScePngEnc PRIVATE decoder_png)
|
||||
|
||||
if(MINGW)
|
||||
foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static)
|
||||
string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_png_enc_tests ${LIBS_INCLUDE_DIR}/tests/GuestPngEnc.cpp)
|
||||
target_include_directories(guest_png_enc_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_png_enc_tests PRIVATE libScePngEnc decoder_png libc)
|
||||
configure_windows_unwind(guest_png_enc_tests)
|
||||
add_test(NAME guest_png_enc COMMAND guest_png_enc_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_png_enc PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libScePngEnc>")
|
||||
endif()
|
||||
endif()
|
||||
|
|
@ -1,28 +1,135 @@
|
|||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <exception>
|
||||
#include <vector>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "Decoder/Png.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int PNG_ENC_ERROR_INVALID_ADDR = static_cast<int>(0x80690101);
|
||||
constexpr int PNG_ENC_ERROR_INVALID_SIZE = static_cast<int>(0x80690102);
|
||||
constexpr int PNG_ENC_ERROR_INVALID_PARAM = static_cast<int>(0x80690103);
|
||||
constexpr int PNG_ENC_ERROR_INVALID_HANDLE = static_cast<int>(0x80690104);
|
||||
constexpr int PNG_ENC_ERROR_DATA_OVERFLOW = static_cast<int>(0x80690110);
|
||||
constexpr int PNG_ENC_ERROR_FATAL = static_cast<int>(0x80690120);
|
||||
|
||||
constexpr std::uint32_t MAX_IMAGE_WIDTH = 1000000;
|
||||
constexpr std::uint32_t MAX_IMAGE_HEIGHT = 1000000;
|
||||
constexpr std::uint32_t MAX_FILTER_NUMBER = 5;
|
||||
constexpr std::uint16_t COLOR_SPACE_RGB = 3;
|
||||
constexpr std::uint16_t COLOR_SPACE_RGBA = 19;
|
||||
constexpr std::uint16_t PIXEL_FORMAT_R8G8B8A8 = 0;
|
||||
constexpr std::uint16_t PIXEL_FORMAT_B8G8R8A8 = 1;
|
||||
constexpr std::uint16_t FILTER_SUB = 1;
|
||||
constexpr std::uint16_t FILTER_UP = 2;
|
||||
constexpr std::uint16_t FILTER_AVERAGE = 4;
|
||||
constexpr std::uint16_t FILTER_PAETH = 8;
|
||||
constexpr std::uint16_t FILTER_ALL = FILTER_SUB | FILTER_UP | FILTER_AVERAGE | FILTER_PAETH;
|
||||
constexpr std::uint64_t CONTEXT_MAGIC = 0x434E45474E505341ull;
|
||||
|
||||
struct PngEncContext {
|
||||
std::uint64_t magic;
|
||||
std::uint32_t maxImageWidth;
|
||||
std::uint32_t reserved;
|
||||
};
|
||||
|
||||
static_assert(sizeof(PngEncCreateParam) == 16);
|
||||
static_assert(sizeof(PngEncEncodeParam) == 48);
|
||||
static_assert(sizeof(PngEncOutputInfo) == 8);
|
||||
|
||||
int validateCreateParam(const PngEncCreateParam* param) {
|
||||
if (!param) return PNG_ENC_ERROR_INVALID_ADDR;
|
||||
if (param->attribute != 0 || param->max_filter_number > MAX_FILTER_NUMBER) return PNG_ENC_ERROR_INVALID_PARAM;
|
||||
if (param->max_image_width == 0 || param->max_image_width > MAX_IMAGE_WIDTH) return PNG_ENC_ERROR_INVALID_SIZE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
PngEncContext* context(void* handle) {
|
||||
auto* ctx = static_cast<PngEncContext*>(handle);
|
||||
return ctx && ctx->magic == CONTEXT_MAGIC ? ctx : nullptr;
|
||||
}
|
||||
|
||||
int filterMode(std::uint16_t filterType) {
|
||||
if (filterType == FILTER_ALL) return -1;
|
||||
if (filterType & FILTER_SUB) return 1;
|
||||
if (filterType & FILTER_UP) return 2;
|
||||
if (filterType & FILTER_AVERAGE) return 3;
|
||||
if (filterType & FILTER_PAETH) return 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI scePngEncCreate() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
int APS5_VABI scePngEncQueryMemorySize(const PngEncCreateParam* param) {
|
||||
if (const int result = validateCreateParam(param); result != 0) return result;
|
||||
return sizeof(PngEncContext);
|
||||
}
|
||||
|
||||
int APS5_VABI scePngEncDelete() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
int APS5_VABI scePngEncCreate(const PngEncCreateParam* param, void* memoryAddress, uint32_t memorySize, void** handle) {
|
||||
if (const int result = validateCreateParam(param); result != 0) return result;
|
||||
if (!memoryAddress || !handle) return PNG_ENC_ERROR_INVALID_ADDR;
|
||||
if (memorySize < sizeof(PngEncContext)) return PNG_ENC_ERROR_INVALID_SIZE;
|
||||
*static_cast<PngEncContext*>(memoryAddress) = {CONTEXT_MAGIC, param->max_image_width, 0};
|
||||
*handle = memoryAddress;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePngEncEncode() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
int APS5_VABI scePngEncDelete(void* handle) {
|
||||
PngEncContext* ctx = context(handle);
|
||||
if (!ctx) return PNG_ENC_ERROR_INVALID_HANDLE;
|
||||
ctx->magic = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI scePngEncQueryMemorySize() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
int APS5_VABI scePngEncEncode(void* handle, const PngEncEncodeParam* param, PngEncOutputInfo* outputInfo) {
|
||||
const PngEncContext* ctx = context(handle);
|
||||
if (!ctx) return PNG_ENC_ERROR_INVALID_HANDLE;
|
||||
if (!param) return PNG_ENC_ERROR_INVALID_PARAM;
|
||||
if (!param->image_mem_addr || !param->png_mem_addr) return PNG_ENC_ERROR_INVALID_ADDR;
|
||||
if ((param->pixel_format != PIXEL_FORMAT_R8G8B8A8 && param->pixel_format != PIXEL_FORMAT_B8G8R8A8)
|
||||
|| (param->color_space != COLOR_SPACE_RGB && param->color_space != COLOR_SPACE_RGBA) || param->bit_depth != 8
|
||||
|| param->clut_number != 0 || (param->filter_type & ~FILTER_ALL) != 0 || param->compression_level > 9) {
|
||||
return PNG_ENC_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
const std::uint32_t width = param->image_width;
|
||||
const std::uint32_t height = param->image_height;
|
||||
if (width == 0 || height == 0 || width > ctx->maxImageWidth || height > MAX_IMAGE_HEIGHT || param->png_mem_size == 0
|
||||
|| param->image_pitch < width * 4 || static_cast<std::uint64_t>(param->image_pitch) * (height - 1) + width * 4 > param->image_mem_size) {
|
||||
return PNG_ENC_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
const std::uint32_t channels = param->color_space == COLOR_SPACE_RGBA ? 4 : 3;
|
||||
const bool bgr = param->pixel_format == PIXEL_FORMAT_B8G8R8A8;
|
||||
std::vector<std::uint8_t> pixels;
|
||||
std::vector<std::uint8_t> png;
|
||||
try {
|
||||
pixels.resize(static_cast<std::size_t>(width) * height * channels);
|
||||
std::uint8_t* destination = pixels.data();
|
||||
for (std::uint32_t y = 0; y < height; ++y) {
|
||||
const std::uint8_t* source = param->image_mem_addr + static_cast<std::size_t>(y) * param->image_pitch;
|
||||
for (std::uint32_t x = 0; x < width; ++x, source += 4) {
|
||||
*destination++ = source[bgr ? 2 : 0];
|
||||
*destination++ = source[1];
|
||||
*destination++ = source[bgr ? 0 : 2];
|
||||
if (channels == 4) *destination++ = source[3];
|
||||
}
|
||||
}
|
||||
png = Decoder::Png::Encode(pixels, width, height, channels, {param->compression_level, filterMode(param->filter_type)});
|
||||
} catch (const std::exception&) {
|
||||
return PNG_ENC_ERROR_FATAL;
|
||||
}
|
||||
|
||||
const bool overflow = png.size() > param->png_mem_size;
|
||||
if (outputInfo) *outputInfo = {overflow ? 0 : static_cast<std::uint32_t>(png.size()), overflow ? 0 : height};
|
||||
if (overflow) return PNG_ENC_ERROR_DATA_OVERFLOW;
|
||||
std::copy(png.begin(), png.end(), param->png_mem_addr);
|
||||
return static_cast<int>(png.size());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "Decoder/Png.hpp"
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -47,4 +47,5 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- `--to-intel` guest module trampolines are covered only by a synthetic relinker test; no game title has been verified with them on Linux or Windows.
|
||||
- [sceKeyboardGetKey2Char](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) (libSceKeyboard) translates only the 101-key (US) arrangement and throws for the 106-key (Japanese) one; Ctrl and Alt do not change the character.
|
||||
- [libSceAudiodec](../../core/libs/prx/libSceAudiodec/Export.cpp) throws for the 24-bit PCM word size (`iBwPcm` 0): its sample layout is unknown. ATRAC9 decoding is covered only by configuration and error tests, as no ATRAC9 encoder is available for a fixture.
|
||||
- [libScePngEnc](../../core/libs/prx/libScePngEnc/Export.cpp) honours `filter_type` only as all filters (adaptive) or a single filter: the [PNG encoder](../../core/Decoder/Png/src/Png.cpp) (stb) cannot restrict adaptive filtering to a subset, so the first filter in the mask is used.
|
||||
- [libScePlayerInvitationDialog](../../core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.cpp) simulates dialog completion without displaying UI or sending invitations; its parameter ABI remains unverified.
|
||||
|
|
|
|||
Loading…
Reference in New Issue