From 0211fcb6b88295d6e980054b43c61f86512513f3 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 20 Nov 2024 08:17:34 +0200 Subject: [PATCH] rewrote png encoder with libpng --- src/core/libraries/libpng/pngdec.cpp | 252 +++++++++++++++++++-------- 1 file changed, 180 insertions(+), 72 deletions(-) diff --git a/src/core/libraries/libpng/pngdec.cpp b/src/core/libraries/libpng/pngdec.cpp index 3a5d1ba71..e2946c303 100644 --- a/src/core/libraries/libpng/pngdec.cpp +++ b/src/core/libraries/libpng/pngdec.cpp @@ -1,55 +1,47 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include "common/assert.h" #include "common/logging/log.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" -#include "externals/stb_image.h" #include "pngdec.h" namespace Libraries::PngDec { -void setImageInfoParams(OrbisPngDecImageInfo* imageInfo, int width, int height, int channels, - bool isInterlaced, bool isTransparent) { - if (imageInfo != nullptr) { - imageInfo->imageWidth = width; - imageInfo->imageHeight = height; - imageInfo->bitDepth = 8; // always 8? - switch (channels) { // clut missing - case 1: - imageInfo->colorSpace = OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_GRAYSCALE; - break; - case 2: - imageInfo->colorSpace = - OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_GRAYSCALE_ALPHA; - break; - case 3: - imageInfo->colorSpace = OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_RGB; - break; - case 4: - imageInfo->colorSpace = OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_RGBA; - break; - default: - imageInfo->colorSpace = OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_RGB; - break; - } - imageInfo->imageFlag = 0; - if (isInterlaced) { - imageInfo->imageFlag |= ORBIS_PNG_DEC_IMAGE_FLAG_ADAM7_INTERLACE; - } - if (isTransparent) { - imageInfo->imageFlag |= ORBIS_PNG_DEC_IMAGE_FLAG_TRNS_CHUNK_EXIST; - } +struct PngHandler { + png_structp png_ptr; + png_infop info_ptr; +}; + +static inline OrbisPngDecColorSpace MapPngColor(int color) { + switch (color) { + case PNG_COLOR_TYPE_GRAY: + return OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_GRAYSCALE; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + return OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_GRAYSCALE_ALPHA; + + case PNG_COLOR_TYPE_PALETTE: + return OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_CLUT; + + case PNG_COLOR_TYPE_RGB: + return OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_RGB; + + case PNG_COLOR_TYPE_RGB_ALPHA: + return OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_RGBA; } + + UNREACHABLE_MSG("unknown png color type"); } -bool checktRNS(const u8* png_raw, int size) { - for (int i = 30; i < size - 4; i += 4) { - if (std::memcmp(png_raw + i, "tRNS", 4) == 0) { - return true; - } - } - return false; +void PngDecError(png_structp png_ptr, png_const_charp error_message) { + LOG_ERROR(Lib_Png, "PNG error {}", error_message); +} + +void PngDecWarning(png_structp png_ptr, png_const_charp error_message) { + LOG_ERROR(Lib_Png, "PNG warning {}", error_message); } s32 PS4_SYSV_ABI scePngDecCreate(const OrbisPngDecCreateParam* param, void* memoryAddress, @@ -66,11 +58,21 @@ s32 PS4_SYSV_ABI scePngDecCreate(const OrbisPngDecCreateParam* param, void* memo LOG_ERROR(Lib_Png, "Invalid size! width = {}", param->maxImageWidth); return ORBIS_PNG_DEC_ERROR_INVALID_SIZE; } - const OrbisPngDecCreateParam* nextParam = param + 1; - int ret = (8 << (reinterpret_cast(nextParam) & 0x1f)) * - (param->maxImageWidth + 0x47U & 0xfffffff8) + - 0xd000; - *handle = reinterpret_cast(ret); + auto pngh = (PngHandler*)memoryAddress; + + pngh->png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, PngDecError, PngDecWarning); + + if (pngh->png_ptr == nullptr) + return ORBIS_PNG_DEC_ERROR_FATAL; + + pngh->info_ptr = png_create_info_struct(pngh->png_ptr); + if (pngh->info_ptr == nullptr) { + png_destroy_read_struct(&pngh->png_ptr, nullptr, nullptr); + return false; + } + + *handle = pngh; return ORBIS_OK; } @@ -89,21 +91,86 @@ s32 PS4_SYSV_ABI scePngDecDecode(OrbisPngDecHandle handle, const OrbisPngDecDeco return ORBIS_PNG_DEC_ERROR_INVALID_ADDR; } - int width, height, channels; - const u8* png_raw = (const u8*)param->pngMemAddr; - u8* img = stbi_load_from_memory(png_raw, param->pngMemSize, &width, &height, &channels, - STBI_rgb_alpha); // STBI_rgb_alpha? - if (!img) { - LOG_ERROR(Lib_Png, "Decoding failed!"); - return ORBIS_PNG_DEC_ERROR_DECODE_ERROR; + auto pngh = (PngHandler*)handle; + + struct pngstruct { + const u8* data; + size_t size; + u64 offset; + } pngdata = { + .data = (const u8*)param->pngMemAddr, + .size = param->pngMemSize, + .offset = 0, + }; + + // Read png from memory + png_set_read_fn(pngh->png_ptr, (void*)&pngdata, + [](png_structp ps, png_bytep data, png_size_t len) { + if (len == 0) + return; + auto pngdata = (pngstruct*)png_get_io_ptr(ps); + ::memcpy(data, pngdata->data + pngdata->offset, len); + pngdata->offset += len; + }); + + u32 weight, height; + int color_type, bit_depth, interlace_method; + png_read_info(pngh->png_ptr, pngh->info_ptr); + png_get_IHDR(pngh->png_ptr, pngh->info_ptr, &weight, &height, &bit_depth, &color_type, + &interlace_method, nullptr, nullptr); + + if (imageInfo != nullptr) { + imageInfo->bitDepth = bit_depth; + imageInfo->imageWidth = weight; + imageInfo->imageHeight = height; + imageInfo->colorSpace = MapPngColor(color_type); + imageInfo->imageFlag = 0; + if (interlace_method == 1) + imageInfo->imageFlag |= OrbisPngDecImageFlag::ORBIS_PNG_DEC_IMAGE_FLAG_ADAM7_INTERLACE; + if (png_get_valid(pngh->png_ptr, pngh->info_ptr, PNG_INFO_tRNS)) + imageInfo->imageFlag |= OrbisPngDecImageFlag::ORBIS_PNG_DEC_IMAGE_FLAG_TRNS_CHUNK_EXIST; } - bool isInterlaced = (png_raw[28] == 1); - bool isTransparent = checktRNS(png_raw, param->pngMemSize); - setImageInfoParams(imageInfo, width, height, channels, isInterlaced, isTransparent); - u8* imageBuffer = (u8*)(param->imageMemAddr); - memcpy(imageBuffer, img, width * height * 4); // copy/pass decoded data - stbi_image_free(img); - return 0; + + if (bit_depth == 16) + png_set_strip_16(pngh->png_ptr); + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(pngh->png_ptr); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) + png_set_expand_gray_1_2_4_to_8(pngh->png_ptr); + if (png_get_valid(pngh->png_ptr, pngh->info_ptr, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(pngh->png_ptr); + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(pngh->png_ptr); + if (param->pixelFormat == OrbisPngDecPixelFormat::ORBIS_PNG_DEC_PIXEL_FORMAT_B8G8R8A8) + png_set_bgr(pngh->png_ptr); + if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_PALETTE) + png_set_add_alpha(pngh->png_ptr, 0xFF, PNG_FILLER_AFTER); + png_read_update_info(pngh->png_ptr, pngh->info_ptr); + + png_bytep* row_pointers = NULL; + row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height); + for (int y = 0; y < height; y++) { + row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(pngh->png_ptr, pngh->info_ptr)); + } + + png_read_image(pngh->png_ptr, row_pointers); + + auto ptr = (png_bytep)param->imageMemAddr; + + auto const numChannels = png_get_channels(pngh->png_ptr, pngh->info_ptr); + + int stride = param->imagePitch > 0 ? (param->imagePitch - weight) : 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < numChannels * weight; x++) { + *ptr++ = row_pointers[y][x]; + } + // ptr += stride;//doesn't work?? + png_free(pngh->png_ptr, row_pointers[y]); + } + png_free(pngh->png_ptr, row_pointers); + + return (weight > 32767 || height > 32767) ? 0 : ((u32)weight << 16) | (u32)height; } s32 PS4_SYSV_ABI scePngDecDecodeWithInputControl() { @@ -112,8 +179,8 @@ s32 PS4_SYSV_ABI scePngDecDecodeWithInputControl() { } s32 PS4_SYSV_ABI scePngDecDelete(OrbisPngDecHandle handle) { - handle = nullptr; // ? - LOG_ERROR(Lib_Png, "(STUBBED)called"); + auto pngh = *(PngHandler**)handle; + png_destroy_read_struct(&pngh->png_ptr, &pngh->info_ptr, nullptr); return ORBIS_OK; } @@ -123,16 +190,59 @@ s32 PS4_SYSV_ABI scePngDecParseHeader(const OrbisPngDecParseParam* param, LOG_ERROR(Lib_Png, "Invalid param!"); return ORBIS_PNG_DEC_ERROR_INVALID_PARAM; } - int width, height, channels; - const u8* png_raw = (const u8*)(param->pngMemAddr); - int img = stbi_info_from_memory(png_raw, param->pngMemSize, &width, &height, &channels); - if (img == 0) { - LOG_ERROR(Lib_Png, "Decoding failed!"); - return ORBIS_PNG_DEC_ERROR_DECODE_ERROR; + + u8 header[8]; + memcpy(header, param->pngMemAddr, 8); + // Check if the header indicates a valid PNG file + if (png_sig_cmp(header, 0, 8)) { + LOG_ERROR(Lib_Png, "Memory doesn't contain a valid png file"); + return ORBIS_PNG_DEC_ERROR_INVALID_DATA; } - bool isInterlaced = (png_raw[28] == 1); - bool isTransparent = checktRNS(png_raw, param->pngMemSize); - setImageInfoParams(imageInfo, width, height, channels, isInterlaced, isTransparent); + // Create a libpng structure, also pass our custom error/warning functions + auto png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, PngDecError, PngDecWarning); + + // Create a libpng info structure + auto info_ptr = png_create_info_struct(png_ptr); + + struct pngstruct { + const u8* data; + size_t size; + u64 offset; + } pngdata = { + .data = (const u8*)param->pngMemAddr, + .size = param->pngMemSize, + .offset = 0, + }; + + png_set_read_fn(png_ptr, (void*)&pngdata, [](png_structp ps, png_bytep data, png_size_t len) { + auto pngdata = (pngstruct*)png_get_io_ptr(ps); + ::memcpy(data, pngdata->data + pngdata->offset, len); + pngdata->offset += len; + }); + + // Now call png_read_info with our pngPtr as image handle, and infoPtr to receive the file info. + png_read_info(png_ptr, info_ptr); + + imageInfo->imageWidth = png_get_image_width(png_ptr, info_ptr); + imageInfo->imageHeight = png_get_image_height(png_ptr, info_ptr); + imageInfo->colorSpace = MapPngColor(png_get_color_type(png_ptr, info_ptr)); + imageInfo->bitDepth = png_get_bit_depth(png_ptr, info_ptr); + imageInfo->imageFlag = 0; + if (png_get_interlace_type(png_ptr, info_ptr) == 1) { + imageInfo->imageFlag |= OrbisPngDecImageFlag::ORBIS_PNG_DEC_IMAGE_FLAG_ADAM7_INTERLACE; + } + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + + imageInfo->imageFlag |= ORBIS_PNG_DEC_IMAGE_FLAG_TRNS_CHUNK_EXIST; + } + + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + LOG_ERROR( + Lib_Png, + "imageWidth = {} , imageHeight = {} , colorSpace = {} , bitDepth = {} , imageFlag = {}", + imageInfo->imageWidth, imageInfo->imageHeight, imageInfo->colorSpace, imageInfo->bitDepth, + imageInfo->imageFlag); return ORBIS_OK; } @@ -149,9 +259,7 @@ s32 PS4_SYSV_ABI scePngDecQueryMemorySize(const OrbisPngDecCreateParam* param) { LOG_ERROR(Lib_Png, "Invalid size! width = {}", param->maxImageWidth); return ORBIS_PNG_DEC_ERROR_INVALID_SIZE; } - int ret = - (8 << ((u8)param->attribute & 0x1f)) * (param->maxImageWidth + 0x47U & 0xfffffff8) + 0xd090; - return ret; + return sizeof(PngHandler); } void RegisterlibScePngDec(Core::Loader::SymbolsResolver* sym) {