#include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "Decoder/Jpeg.hpp" #include #include #include #include #include extern "C" { std::int32_t APS5_VABI sceJpegEncQueryMemorySize(const JpegEncCreateParam*); std::int32_t APS5_VABI sceJpegEncCreate(const JpegEncCreateParam*, void*, std::uint32_t, void**); std::int32_t APS5_VABI sceJpegEncDelete(void*); std::int32_t APS5_VABI sceJpegEncEncode(void*, const JpegEncEncodeParam*, JpegEncOutputInfo*); } static void Require(bool value) { if (!value) std::abort(); } alignas(4) static unsigned char image[16 * 16 * 4]; static unsigned char jpeg[4096]; static bool IsJpeg(const unsigned char* data, std::uint32_t size) { return size > 4 && data[0] == 0xFF && data[1] == 0xD8 && data[size - 2] == 0xFF && data[size - 1] == 0xD9; } static int AverageError(const std::vector& expected, const std::vector& actual) { long total = 0; for (std::size_t i = 0; i < expected.size(); ++i) total += expected[i] > actual[i] ? expected[i] - actual[i] : actual[i] - expected[i]; return static_cast(total / static_cast(expected.size())); } static std::vector DecodeOutput(const JpegEncOutputInfo& info) { Require(IsJpeg(jpeg, info.size)); const auto decoded = Decoder::Jpeg::Decode({jpeg, info.size}); Require(decoded.has_value()); Require(decoded->width == 16 && decoded->height == 16 && decoded->channels == 3); return decoded->pixels; } template static bool ThrowsRuntimeError(TFunction function) { try { function(); } catch (const std::runtime_error&) { return true; } return false; } static JpegEncEncodeParam ValidEncodeParam() { JpegEncEncodeParam param{}; param.image = image; param.jpeg = jpeg; param.image_size = sizeof(image); param.jpeg_size = sizeof(jpeg); param.image_width = 16; param.image_height = 16; param.image_pitch = 16 * 4; param.pixel_format = 0; param.encode_mode = 0; param.color_space = 1; param.sampling_type = 2; param.compression_ratio = 80; param.restart_interval = 0; return param; } int main() { constexpr std::int32_t invalidAddr = static_cast(0x80650101); constexpr std::int32_t invalidSize = static_cast(0x80650102); constexpr std::int32_t invalidParam = static_cast(0x80650103); constexpr std::int32_t invalidHandle = static_cast(0x80650104); JpegEncCreateParam param{sizeof(JpegEncCreateParam), 0}; Require(sceJpegEncQueryMemorySize(¶m) == 0x800); Require(sceJpegEncQueryMemorySize(nullptr) == invalidAddr); JpegEncCreateParam badSize{sizeof(JpegEncCreateParam) - 1, 0}; Require(sceJpegEncQueryMemorySize(&badSize) == invalidSize); JpegEncCreateParam badAttr{sizeof(JpegEncCreateParam), 1}; Require(sceJpegEncQueryMemorySize(&badAttr) == invalidParam); alignas(32) static unsigned char memory[0x800 + 32]; unsigned char* unaligned = memory + 1; void* handle = nullptr; Require(sceJpegEncCreate(nullptr, unaligned, 0x800, &handle) == invalidAddr); Require(sceJpegEncCreate(&badSize, unaligned, 0x800, &handle) == invalidSize); Require(sceJpegEncCreate(&badAttr, unaligned, 0x800, &handle) == invalidParam); Require(sceJpegEncCreate(¶m, nullptr, 0x800, &handle) == invalidAddr); Require(sceJpegEncCreate(¶m, unaligned, 0x7FF, &handle) == invalidSize); Require(sceJpegEncCreate(¶m, unaligned, 0x800, nullptr) == invalidAddr); Require(handle == nullptr); Require(sceJpegEncCreate(¶m, unaligned, 0x800, &handle) == 0); const auto address = reinterpret_cast(handle); Require(address % 32 == 0); Require(address >= reinterpret_cast(unaligned)); Require(address < reinterpret_cast(unaligned) + 32); Require(sceJpegEncDelete(nullptr) == invalidHandle); Require(sceJpegEncDelete(static_cast(handle) + 1) == invalidHandle); alignas(32) static unsigned char garbage[64] = {}; Require(sceJpegEncDelete(garbage) == invalidHandle); Require(sceJpegEncDelete(handle) == 0); Require(sceJpegEncDelete(handle) == invalidHandle); JpegEncOutputInfo info{}; const JpegEncEncodeParam valid = ValidEncodeParam(); Require(sceJpegEncEncode(handle, &valid, &info) == invalidHandle); Require(sceJpegEncEncode(nullptr, &valid, &info) == invalidHandle); Require(sceJpegEncCreate(¶m, unaligned, 0x800, &handle) == 0); Require(sceJpegEncEncode(handle, nullptr, &info) == invalidAddr); auto encodeWith = [&](auto change) { JpegEncEncodeParam changed = ValidEncodeParam(); change(changed); return sceJpegEncEncode(handle, &changed, &info); }; Require(encodeWith([](JpegEncEncodeParam& p) { p.image = nullptr; }) == invalidAddr); Require(encodeWith([](JpegEncEncodeParam& p) { p.image = image + 1; }) == invalidAddr); Require(encodeWith([](JpegEncEncodeParam& p) { p.jpeg = nullptr; }) == invalidAddr); Require(encodeWith([](JpegEncEncodeParam& p) { p.image_size = 0; }) == invalidSize); Require(encodeWith([](JpegEncEncodeParam& p) { p.jpeg_size = 0; }) == invalidSize); Require(encodeWith([](JpegEncEncodeParam& p) { p.image_width = 0x10000; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.image_height = 0x10000; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.image_pitch = 0; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.image_pitch = 66; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.image_size = sizeof(image) - 1; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.image_height = 0xFFFF; p.image_pitch = 0xFFFFFFC; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.encode_mode = 2; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.color_space = 0; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.sampling_type = 3; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.restart_interval = 0x10000; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.pixel_format = 2; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.image_width = 17; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.color_space = 2; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.sampling_type = 0; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.pixel_format = 10; p.image_width = 33; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.pixel_format = 11; p.color_space = 2; p.sampling_type = 0; p.image_width = 65; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.pixel_format = 11; p.color_space = 2; p.sampling_type = 2; }) == invalidParam); Require(encodeWith([](JpegEncEncodeParam& p) { p.pixel_format = 11; p.sampling_type = 0; }) == invalidParam); constexpr std::uint32_t pitch = 16 * 4 + 16; alignas(4) static unsigned char padded[16 * pitch]; std::vector expected(16 * 16 * 3); for (std::uint32_t y = 0; y < 16; ++y) { for (std::uint32_t x = 0; x < 16; ++x) { const std::uint8_t color[3] = {static_cast(x * 16), static_cast(y * 16), 96}; unsigned char* pixel = padded + y * pitch + x * 4; pixel[0] = color[0]; pixel[1] = color[1]; pixel[2] = color[2]; pixel[3] = 255; for (int c = 0; c < 3; ++c) expected[(y * 16 + x) * 3 + c] = color[c]; } for (std::uint32_t x = 16 * 4; x < pitch; ++x) padded[y * pitch + x] = 0xEE; } JpegEncEncodeParam rgba = ValidEncodeParam(); rgba.image = padded; rgba.image_size = sizeof(padded); rgba.image_pitch = pitch; rgba.compression_ratio = 0; info = {}; Require(sceJpegEncEncode(handle, &rgba, &info) == 0); Require(info.height == 16); Require(AverageError(expected, DecodeOutput(info)) < 6); for (std::uint32_t y = 0; y < 16; ++y) { for (std::uint32_t x = 0; x < 16; ++x) { unsigned char* pixel = padded + y * pitch + x * 4; const unsigned char red = pixel[0]; pixel[0] = pixel[2]; pixel[2] = red; } } JpegEncEncodeParam bgra = rgba; bgra.pixel_format = 1; info = {}; Require(sceJpegEncEncode(handle, &bgra, &info) == 0); Require(AverageError(expected, DecodeOutput(info)) < 6); alignas(4) static unsigned char yuyv[16 * 16 * 2]; for (std::size_t i = 0; i < sizeof(yuyv); i += 4) { yuyv[i + 0] = 100; yuyv[i + 1] = 128; yuyv[i + 2] = 100; yuyv[i + 3] = 128; } JpegEncEncodeParam yuv = ValidEncodeParam(); yuv.image = yuyv; yuv.image_size = sizeof(yuyv); yuv.image_pitch = 16 * 2; yuv.pixel_format = 10; info = {}; Require(sceJpegEncEncode(handle, &yuv, &info) == 0); Require(AverageError(std::vector(16 * 16 * 3, 100), DecodeOutput(info)) < 3); static unsigned char gray[16 * 16]; std::vector expectedGray(16 * 16 * 3); for (std::size_t i = 0; i < sizeof(gray); ++i) { gray[i] = static_cast(((i % 16) + (i / 16)) * 8); for (int c = 0; c < 3; ++c) expectedGray[i * 3 + c] = gray[i]; } JpegEncEncodeParam y8 = ValidEncodeParam(); y8.image = gray; y8.image_size = sizeof(gray); y8.image_pitch = 16; y8.pixel_format = 11; y8.color_space = 2; y8.sampling_type = 0; info = {}; Require(sceJpegEncEncode(handle, &y8, &info) == 0); Require(AverageError(expectedGray, DecodeOutput(info)) < 6); std::memset(jpeg, 0, sizeof(jpeg)); Require(sceJpegEncEncode(handle, &rgba, nullptr) == 0); Require(jpeg[0] == 0xFF && jpeg[1] == 0xD8); JpegEncEncodeParam tooSmall = rgba; tooSmall.jpeg_size = 16; Require(sceJpegEncEncode(handle, &tooSmall, &info) == invalidSize); Require(encodeWith([](JpegEncEncodeParam& p) { p.image_width = 0; }) == invalidParam); Require(ThrowsRuntimeError([&] { encodeWith([](JpegEncEncodeParam& p) { p.encode_mode = 1; }); })); Require(ThrowsRuntimeError([&] { encodeWith([](JpegEncEncodeParam& p) { p.restart_interval = 1; }); })); Require(sceJpegEncDelete(handle) == 0); return 0; }