mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-08-04 16:32:39 +00:00
rewrote png encoder with libpng
This commit is contained in:
parent
adda221618
commit
0211fcb6b8
@ -1,55 +1,47 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <png.h>
|
||||||
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/libraries/error_codes.h"
|
#include "core/libraries/error_codes.h"
|
||||||
#include "core/libraries/libs.h"
|
#include "core/libraries/libs.h"
|
||||||
#include "externals/stb_image.h"
|
|
||||||
#include "pngdec.h"
|
#include "pngdec.h"
|
||||||
|
|
||||||
namespace Libraries::PngDec {
|
namespace Libraries::PngDec {
|
||||||
|
|
||||||
void setImageInfoParams(OrbisPngDecImageInfo* imageInfo, int width, int height, int channels,
|
struct PngHandler {
|
||||||
bool isInterlaced, bool isTransparent) {
|
png_structp png_ptr;
|
||||||
if (imageInfo != nullptr) {
|
png_infop info_ptr;
|
||||||
imageInfo->imageWidth = width;
|
};
|
||||||
imageInfo->imageHeight = height;
|
|
||||||
imageInfo->bitDepth = 8; // always 8?
|
static inline OrbisPngDecColorSpace MapPngColor(int color) {
|
||||||
switch (channels) { // clut missing
|
switch (color) {
|
||||||
case 1:
|
case PNG_COLOR_TYPE_GRAY:
|
||||||
imageInfo->colorSpace = OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_GRAYSCALE;
|
return OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_GRAYSCALE;
|
||||||
break;
|
|
||||||
case 2:
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
||||||
imageInfo->colorSpace =
|
return OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_GRAYSCALE_ALPHA;
|
||||||
OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_GRAYSCALE_ALPHA;
|
|
||||||
break;
|
case PNG_COLOR_TYPE_PALETTE:
|
||||||
case 3:
|
return OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_CLUT;
|
||||||
imageInfo->colorSpace = OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_RGB;
|
|
||||||
break;
|
case PNG_COLOR_TYPE_RGB:
|
||||||
case 4:
|
return OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_RGB;
|
||||||
imageInfo->colorSpace = OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_RGBA;
|
|
||||||
break;
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
||||||
default:
|
return OrbisPngDecColorSpace::ORBIS_PNG_DEC_COLOR_SPACE_RGBA;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checktRNS(const u8* png_raw, int size) {
|
UNREACHABLE_MSG("unknown png color type");
|
||||||
for (int i = 30; i < size - 4; i += 4) {
|
|
||||||
if (std::memcmp(png_raw + i, "tRNS", 4) == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PngDecError(png_structp png_ptr, png_const_charp error_message) {
|
||||||
|
LOG_ERROR(Lib_Png, "PNG error {}", error_message);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
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,
|
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);
|
LOG_ERROR(Lib_Png, "Invalid size! width = {}", param->maxImageWidth);
|
||||||
return ORBIS_PNG_DEC_ERROR_INVALID_SIZE;
|
return ORBIS_PNG_DEC_ERROR_INVALID_SIZE;
|
||||||
}
|
}
|
||||||
const OrbisPngDecCreateParam* nextParam = param + 1;
|
auto pngh = (PngHandler*)memoryAddress;
|
||||||
int ret = (8 << (reinterpret_cast<uintptr_t>(nextParam) & 0x1f)) *
|
|
||||||
(param->maxImageWidth + 0x47U & 0xfffffff8) +
|
pngh->png_ptr =
|
||||||
0xd000;
|
png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, PngDecError, PngDecWarning);
|
||||||
*handle = reinterpret_cast<OrbisPngDecHandle>(ret);
|
|
||||||
|
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;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,21 +91,86 @@ s32 PS4_SYSV_ABI scePngDecDecode(OrbisPngDecHandle handle, const OrbisPngDecDeco
|
|||||||
return ORBIS_PNG_DEC_ERROR_INVALID_ADDR;
|
return ORBIS_PNG_DEC_ERROR_INVALID_ADDR;
|
||||||
}
|
}
|
||||||
|
|
||||||
int width, height, channels;
|
auto pngh = (PngHandler*)handle;
|
||||||
const u8* png_raw = (const u8*)param->pngMemAddr;
|
|
||||||
u8* img = stbi_load_from_memory(png_raw, param->pngMemSize, &width, &height, &channels,
|
struct pngstruct {
|
||||||
STBI_rgb_alpha); // STBI_rgb_alpha?
|
const u8* data;
|
||||||
if (!img) {
|
size_t size;
|
||||||
LOG_ERROR(Lib_Png, "Decoding failed!");
|
u64 offset;
|
||||||
return ORBIS_PNG_DEC_ERROR_DECODE_ERROR;
|
} 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);
|
if (bit_depth == 16)
|
||||||
setImageInfoParams(imageInfo, width, height, channels, isInterlaced, isTransparent);
|
png_set_strip_16(pngh->png_ptr);
|
||||||
u8* imageBuffer = (u8*)(param->imageMemAddr);
|
if (color_type == PNG_COLOR_TYPE_PALETTE)
|
||||||
memcpy(imageBuffer, img, width * height * 4); // copy/pass decoded data
|
png_set_palette_to_rgb(pngh->png_ptr);
|
||||||
stbi_image_free(img);
|
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
|
||||||
return 0;
|
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() {
|
s32 PS4_SYSV_ABI scePngDecDecodeWithInputControl() {
|
||||||
@ -112,8 +179,8 @@ s32 PS4_SYSV_ABI scePngDecDecodeWithInputControl() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s32 PS4_SYSV_ABI scePngDecDelete(OrbisPngDecHandle handle) {
|
s32 PS4_SYSV_ABI scePngDecDelete(OrbisPngDecHandle handle) {
|
||||||
handle = nullptr; // ?
|
auto pngh = *(PngHandler**)handle;
|
||||||
LOG_ERROR(Lib_Png, "(STUBBED)called");
|
png_destroy_read_struct(&pngh->png_ptr, &pngh->info_ptr, nullptr);
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,16 +190,59 @@ s32 PS4_SYSV_ABI scePngDecParseHeader(const OrbisPngDecParseParam* param,
|
|||||||
LOG_ERROR(Lib_Png, "Invalid param!");
|
LOG_ERROR(Lib_Png, "Invalid param!");
|
||||||
return ORBIS_PNG_DEC_ERROR_INVALID_PARAM;
|
return ORBIS_PNG_DEC_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
int width, height, channels;
|
|
||||||
const u8* png_raw = (const u8*)(param->pngMemAddr);
|
u8 header[8];
|
||||||
int img = stbi_info_from_memory(png_raw, param->pngMemSize, &width, &height, &channels);
|
memcpy(header, param->pngMemAddr, 8);
|
||||||
if (img == 0) {
|
// Check if the header indicates a valid PNG file
|
||||||
LOG_ERROR(Lib_Png, "Decoding failed!");
|
if (png_sig_cmp(header, 0, 8)) {
|
||||||
return ORBIS_PNG_DEC_ERROR_DECODE_ERROR;
|
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);
|
// Create a libpng structure, also pass our custom error/warning functions
|
||||||
bool isTransparent = checktRNS(png_raw, param->pngMemSize);
|
auto png_ptr =
|
||||||
setImageInfoParams(imageInfo, width, height, channels, isInterlaced, isTransparent);
|
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;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,9 +259,7 @@ s32 PS4_SYSV_ABI scePngDecQueryMemorySize(const OrbisPngDecCreateParam* param) {
|
|||||||
LOG_ERROR(Lib_Png, "Invalid size! width = {}", param->maxImageWidth);
|
LOG_ERROR(Lib_Png, "Invalid size! width = {}", param->maxImageWidth);
|
||||||
return ORBIS_PNG_DEC_ERROR_INVALID_SIZE;
|
return ORBIS_PNG_DEC_ERROR_INVALID_SIZE;
|
||||||
}
|
}
|
||||||
int ret =
|
return sizeof(PngHandler);
|
||||||
(8 << ((u8)param->attribute & 0x1f)) * (param->maxImageWidth + 0x47U & 0xfffffff8) + 0xd090;
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RegisterlibScePngDec(Core::Loader::SymbolsResolver* sym) {
|
void RegisterlibScePngDec(Core::Loader::SymbolsResolver* sym) {
|
||||||
|
Loading…
Reference in New Issue
Block a user