feat(libSceJpegDec): implement JPEG decoding
This commit is contained in:
parent
e1654a97e5
commit
8bd9670ab0
|
|
@ -15,9 +15,17 @@ struct Image {
|
|||
std::vector<std::uint8_t> pixels;
|
||||
};
|
||||
|
||||
struct Header {
|
||||
std::uint32_t width;
|
||||
std::uint32_t height;
|
||||
std::uint32_t channels;
|
||||
};
|
||||
|
||||
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, int quality);
|
||||
|
||||
std::optional<Header> ParseHeader(std::span<const std::uint8_t> jpeg);
|
||||
|
||||
std::optional<Image> Decode(std::span<const std::uint8_t> jpeg);
|
||||
|
||||
} // namespace Decoder::Jpeg
|
||||
|
|
|
|||
|
|
@ -47,6 +47,17 @@ std::vector<std::uint8_t> Encode(std::span<const std::uint8_t> pixels, std::uint
|
|||
return output;
|
||||
}
|
||||
|
||||
std::optional<Header> ParseHeader(std::span<const std::uint8_t> jpeg) {
|
||||
if (jpeg.empty() || jpeg.size() > INT_MAX) return std::nullopt;
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int channels = 0;
|
||||
if (!stbi_info_from_memory(jpeg.data(), static_cast<int>(jpeg.size()), &width, &height, &channels)) return std::nullopt;
|
||||
if (width <= 0 || height <= 0 || channels <= 0) return std::nullopt;
|
||||
return Header{static_cast<std::uint32_t>(width), static_cast<std::uint32_t>(height), static_cast<std::uint32_t>(channels)};
|
||||
}
|
||||
|
||||
std::optional<Image> Decode(std::span<const std::uint8_t> jpeg) {
|
||||
if (jpeg.empty() || jpeg.size() > INT_MAX) return std::nullopt;
|
||||
|
||||
|
|
|
|||
|
|
@ -1468,6 +1468,35 @@ struct JpegEncOutputInfo {
|
|||
std::uint32_t height;
|
||||
};
|
||||
|
||||
struct JpegDecCreateParam {
|
||||
std::uint32_t size;
|
||||
std::uint32_t attribute;
|
||||
};
|
||||
|
||||
struct JpegDecParseParam {
|
||||
const void* jpeg_mem_addr;
|
||||
std::uint32_t jpeg_mem_size;
|
||||
std::uint32_t reserved0;
|
||||
};
|
||||
|
||||
struct JpegDecDecodeParam {
|
||||
const void* jpeg_mem_addr;
|
||||
void* image_mem_addr;
|
||||
std::uint32_t jpeg_mem_size;
|
||||
std::uint32_t image_mem_size;
|
||||
std::uint16_t pixel_format;
|
||||
std::uint16_t reserved0;
|
||||
std::uint32_t image_pitch;
|
||||
};
|
||||
|
||||
struct JpegDecImageInfo {
|
||||
std::uint32_t image_width;
|
||||
std::uint32_t image_height;
|
||||
std::uint16_t color_space;
|
||||
std::uint16_t bit_depth;
|
||||
std::uint32_t image_flag;
|
||||
};
|
||||
|
||||
struct PlayGoInitParams {
|
||||
const void* buf_addr;
|
||||
std::uint32_t buf_size;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
if(NOT TARGET decoder_jpeg)
|
||||
add_jpeg_decoder(decoder_jpeg libc)
|
||||
endif()
|
||||
|
||||
add_library(libSceJpegDec SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp)
|
||||
target_include_directories(libSceJpegDec PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
set_target_properties(libSceJpegDec PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(libSceJpegDec)
|
||||
target_link_libraries(libSceJpegDec PRIVATE decoder_jpeg)
|
||||
|
||||
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_jpeg_dec_tests ${LIBS_INCLUDE_DIR}/tests/GuestJpegDec.cpp)
|
||||
target_include_directories(guest_jpeg_dec_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_jpeg_dec_tests PRIVATE libSceJpegDec decoder_jpeg libc)
|
||||
configure_windows_unwind(guest_jpeg_dec_tests)
|
||||
add_test(NAME guest_jpeg_dec COMMAND guest_jpeg_dec_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_jpeg_dec PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceJpegDec>")
|
||||
endif()
|
||||
endif()
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include "Decoder/Jpeg.hpp"
|
||||
#include "SceTypes.hpp"
|
||||
|
||||
static_assert(sizeof(JpegDecCreateParam) == 0x8);
|
||||
static_assert(sizeof(JpegDecParseParam) == 0x10);
|
||||
static_assert(sizeof(JpegDecDecodeParam) == 0x20);
|
||||
static_assert(sizeof(JpegDecImageInfo) == 0x10);
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_ADDR = static_cast<std::int32_t>(0x80690001);
|
||||
constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_SIZE = static_cast<std::int32_t>(0x80690002);
|
||||
constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_PARAM = static_cast<std::int32_t>(0x80690003);
|
||||
constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_HANDLE = static_cast<std::int32_t>(0x80690004);
|
||||
constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_WORK_MEMORY = static_cast<std::int32_t>(0x80690005);
|
||||
constexpr std::int32_t SCE_JPEG_DEC_ERROR_INVALID_DATA = static_cast<std::int32_t>(0x80690010);
|
||||
constexpr std::int32_t SCE_JPEG_DEC_ERROR_DECODE_ERROR = static_cast<std::int32_t>(0x80690012);
|
||||
|
||||
constexpr std::uint32_t ATTRIBUTE_NONE = 0;
|
||||
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_RGB = 3;
|
||||
|
||||
constexpr std::uint32_t BYTES_PER_PIXEL = 4;
|
||||
constexpr std::uint32_t MAX_PACKED_DIMENSION = 32767;
|
||||
|
||||
struct Context {
|
||||
Context* self;
|
||||
std::uint32_t attribute;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Context) + HANDLE_ALIGNMENT - 1 <= MEMORY_SIZE);
|
||||
|
||||
std::int32_t validateCreateParam(const JpegDecCreateParam* param) {
|
||||
if (!param) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
if (param->size != sizeof(JpegDecCreateParam)) return SCE_JPEG_DEC_ERROR_INVALID_SIZE;
|
||||
if (param->attribute != ATTRIBUTE_NONE) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
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;
|
||||
}
|
||||
|
||||
void fillImageInfo(const Decoder::Jpeg::Header& header, JpegDecImageInfo* info) {
|
||||
info->image_width = header.width;
|
||||
info->image_height = header.height;
|
||||
info->color_space = COLOR_SPACE_RGB;
|
||||
info->bit_depth = 8;
|
||||
info->image_flag = 0;
|
||||
}
|
||||
|
||||
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 sceJpegDecCreate(const JpegDecCreateParam* param, void* memory_address, uint32_t memory_size, void** handle) {
|
||||
const std::int32_t result = validateCreateParam(param);
|
||||
if (result != 0) return result;
|
||||
if (!memory_address || !handle) return SCE_JPEG_DEC_ERROR_INVALID_ADDR;
|
||||
if (memory_size < MEMORY_SIZE) return SCE_JPEG_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;
|
||||
*handle = context;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t APS5_VABI sceJpegDecDecode(void* handle, const JpegDecDecodeParam* param, JpegDecImageInfo* image_info) {
|
||||
const Context* context = toContext(handle);
|
||||
if (!context) return SCE_JPEG_DEC_ERROR_INVALID_HANDLE;
|
||||
(void)context;
|
||||
if (!param) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
if (!param->jpeg_mem_addr || !param->image_mem_addr) return SCE_JPEG_DEC_ERROR_INVALID_ADDR;
|
||||
if (param->jpeg_mem_size == 0 || param->image_mem_size == 0) return SCE_JPEG_DEC_ERROR_INVALID_SIZE;
|
||||
if (param->pixel_format != PIXEL_FORMAT_R8G8B8A8 && param->pixel_format != PIXEL_FORMAT_B8G8R8A8) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
|
||||
const std::span<const std::uint8_t> jpeg = toBytes(param->jpeg_mem_addr, param->jpeg_mem_size);
|
||||
const std::optional<Decoder::Jpeg::Header> header = Decoder::Jpeg::ParseHeader(jpeg);
|
||||
if (!header) return SCE_JPEG_DEC_ERROR_INVALID_DATA;
|
||||
|
||||
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_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
if ((header->height - 1) * pitch + rowSize > param->image_mem_size) return SCE_JPEG_DEC_ERROR_INVALID_SIZE;
|
||||
|
||||
const std::optional<Decoder::Jpeg::Image> image = Decoder::Jpeg::Decode(jpeg);
|
||||
if (!image || image->width != header->width || image->height != header->height) return SCE_JPEG_DEC_ERROR_DECODE_ERROR;
|
||||
|
||||
const bool swapRedBlue = param->pixel_format == PIXEL_FORMAT_B8G8R8A8;
|
||||
auto* output = static_cast<std::uint8_t*>(param->image_mem_addr);
|
||||
for (std::uint32_t y = 0; y < image->height; ++y) {
|
||||
std::uint8_t* out = output + y * pitch;
|
||||
for (std::uint32_t x = 0; x < image->width; ++x) {
|
||||
const std::uint8_t* in = image->pixels.data() + (static_cast<std::size_t>(y) * image->width + x) * image->channels;
|
||||
std::uint8_t* pixel = out + x * BYTES_PER_PIXEL;
|
||||
if (image->channels == 1) {
|
||||
pixel[0] = in[0];
|
||||
pixel[1] = in[0];
|
||||
pixel[2] = in[0];
|
||||
} else {
|
||||
pixel[0] = in[0];
|
||||
pixel[1] = in[1];
|
||||
pixel[2] = in[2];
|
||||
}
|
||||
if (swapRedBlue) std::swap(pixel[0], pixel[2]);
|
||||
pixel[3] = 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
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 sceJpegDecDelete(void* handle) {
|
||||
Context* context = toContext(handle);
|
||||
if (!context) return SCE_JPEG_DEC_ERROR_INVALID_HANDLE;
|
||||
context->self = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t APS5_VABI sceJpegDecParseHeader(const JpegDecParseParam* param, JpegDecImageInfo* image_info) {
|
||||
if (!param) return SCE_JPEG_DEC_ERROR_INVALID_PARAM;
|
||||
if (!param->jpeg_mem_addr || !image_info) return SCE_JPEG_DEC_ERROR_INVALID_ADDR;
|
||||
if (param->jpeg_mem_size == 0) return SCE_JPEG_DEC_ERROR_INVALID_SIZE;
|
||||
const std::optional<Decoder::Jpeg::Header> header = Decoder::Jpeg::ParseHeader(toBytes(param->jpeg_mem_addr, param->jpeg_mem_size));
|
||||
if (!header) return SCE_JPEG_DEC_ERROR_INVALID_DATA;
|
||||
fillImageInfo(*header, image_info);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t APS5_VABI sceJpegDecQueryMemorySize(const JpegDecCreateParam* param) {
|
||||
const std::int32_t result = validateCreateParam(param);
|
||||
if (result != 0) return result;
|
||||
return static_cast<std::int32_t>(MEMORY_SIZE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
add_jpeg_decoder(decoder_jpeg libc)
|
||||
if(NOT TARGET decoder_jpeg)
|
||||
add_jpeg_decoder(decoder_jpeg libc)
|
||||
endif()
|
||||
|
||||
add_library(libSceJpegEnc SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp)
|
||||
target_include_directories(libSceJpegEnc PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "Decoder/Jpeg.hpp"
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
std::int32_t APS5_VABI sceJpegDecQueryMemorySize(const JpegDecCreateParam*);
|
||||
std::int32_t APS5_VABI sceJpegDecCreate(const JpegDecCreateParam*, void*, std::uint32_t, void**);
|
||||
std::int32_t APS5_VABI sceJpegDecDelete(void*);
|
||||
std::int32_t APS5_VABI sceJpegDecParseHeader(const JpegDecParseParam*, JpegDecImageInfo*);
|
||||
std::int32_t APS5_VABI sceJpegDecDecode(void*, const JpegDecDecodeParam*, JpegDecImageInfo*);
|
||||
}
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
|
||||
static std::vector<std::uint8_t> TestJpeg(std::uint32_t width, std::uint32_t height) {
|
||||
std::vector<std::uint8_t> rgb(static_cast<std::size_t>(width) * height * 3);
|
||||
for (std::size_t i = 0; i < rgb.size(); ++i) rgb[i] = static_cast<std::uint8_t>(10 + i * 13);
|
||||
return Decoder::Jpeg::Encode(rgb, width, height, 3, 90);
|
||||
}
|
||||
|
||||
static JpegDecDecodeParam DecodeParam(const std::vector<std::uint8_t>& jpeg, std::vector<std::uint8_t>& image) {
|
||||
JpegDecDecodeParam param{};
|
||||
param.jpeg_mem_addr = jpeg.data();
|
||||
param.image_mem_addr = image.data();
|
||||
param.jpeg_mem_size = static_cast<std::uint32_t>(jpeg.size());
|
||||
param.image_mem_size = static_cast<std::uint32_t>(image.size());
|
||||
param.pixel_format = 0;
|
||||
param.reserved0 = 0;
|
||||
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);
|
||||
|
||||
const JpegDecCreateParam param{sizeof(JpegDecCreateParam), 0};
|
||||
const std::int32_t memorySize = sceJpegDecQueryMemorySize(¶m);
|
||||
Require(memorySize > 0);
|
||||
Require(sceJpegDecQueryMemorySize(nullptr) == invalidParam);
|
||||
const JpegDecCreateParam badSize{sizeof(JpegDecCreateParam) - 1, 0};
|
||||
Require(sceJpegDecQueryMemorySize(&badSize) == invalidSize);
|
||||
const JpegDecCreateParam badAttribute{sizeof(JpegDecCreateParam), 1};
|
||||
Require(sceJpegDecQueryMemorySize(&badAttribute) == invalidParam);
|
||||
|
||||
std::vector<std::uint8_t> memory(static_cast<std::size_t>(memorySize) + 8);
|
||||
void* handle = nullptr;
|
||||
Require(sceJpegDecCreate(¶m, memory.data(), 0, &handle) == invalidWorkMemory);
|
||||
Require(sceJpegDecCreate(¶m, memory.data(), static_cast<std::uint32_t>(memorySize), nullptr) == invalidAddr);
|
||||
Require(sceJpegDecCreate(¶m, memory.data(), static_cast<std::uint32_t>(memorySize), &handle) == 0);
|
||||
Require(handle != nullptr);
|
||||
Require(sceJpegDecDelete(nullptr) == invalidHandle);
|
||||
Require(sceJpegDecDelete(handle) == 0);
|
||||
|
||||
Require(sceJpegDecCreate(¶m, memory.data(), static_cast<std::uint32_t>(memorySize), &handle) == 0);
|
||||
|
||||
const std::vector<std::uint8_t> jpeg = TestJpeg(7, 5);
|
||||
JpegDecImageInfo info{};
|
||||
const JpegDecParseParam parseParam{jpeg.data(), static_cast<std::uint32_t>(jpeg.size()), 0};
|
||||
Require(sceJpegDecParseHeader(nullptr, &info) == invalidParam);
|
||||
Require(sceJpegDecParseHeader(&parseParam, nullptr) == invalidAddr);
|
||||
const JpegDecParseParam emptyParam{nullptr, 0, 0};
|
||||
Require(sceJpegDecParseHeader(&emptyParam, &info) == invalidAddr);
|
||||
Require(sceJpegDecParseHeader(&parseParam, &info) == 0);
|
||||
Require(info.image_width == 7);
|
||||
Require(info.image_height == 5);
|
||||
|
||||
std::vector<std::uint8_t> image(7 * 5 * 4, 0xCC);
|
||||
JpegDecDecodeParam decodeParam = DecodeParam(jpeg, image);
|
||||
Require(sceJpegDecDecode(nullptr, &decodeParam, &info) == invalidHandle);
|
||||
Require(sceJpegDecDecode(handle, nullptr, &info) == invalidParam);
|
||||
JpegDecDecodeParam badFormat = decodeParam;
|
||||
badFormat.pixel_format = 2;
|
||||
Require(sceJpegDecDecode(handle, &badFormat, &info) == invalidParam);
|
||||
const std::int32_t packed = sceJpegDecDecode(handle, &decodeParam, &info);
|
||||
Require(packed == static_cast<std::int32_t>(7 << 16 | 5));
|
||||
Require(info.image_width == 7);
|
||||
Require(info.image_height == 5);
|
||||
Require(image[3] == 0xFF);
|
||||
|
||||
std::vector<std::uint8_t> bgra(7 * 5 * 4, 0);
|
||||
JpegDecDecodeParam bgraParam = DecodeParam(jpeg, bgra);
|
||||
bgraParam.pixel_format = 1;
|
||||
Require(sceJpegDecDecode(handle, &bgraParam, nullptr) == static_cast<std::int32_t>(7 << 16 | 5));
|
||||
Require(bgra[0] == image[2]);
|
||||
Require(bgra[2] == image[0]);
|
||||
|
||||
const std::vector<std::uint8_t> garbage{0x00, 0x01, 0x02, 0x03};
|
||||
JpegDecParseParam garbageParam{garbage.data(), static_cast<std::uint32_t>(garbage.size()), 0};
|
||||
Require(sceJpegDecParseHeader(&garbageParam, &info) == invalidData);
|
||||
std::vector<std::uint8_t> garbageOut(64, 0);
|
||||
JpegDecDecodeParam garbageDecode = DecodeParam(garbage, garbageOut);
|
||||
Require(sceJpegDecDecode(handle, &garbageDecode, &info) == invalidData);
|
||||
|
||||
Require(sceJpegDecDelete(handle) == 0);
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in New Issue