mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-07-14 18:58:36 +12:00
Improve cellPngDec (#2394)
* Improve cellPngDec - ReadHeader and SetParam corrected - Partial loading Implemented - Interlace Support added - Improve error handling * Use custom exception * Change to catch ref
This commit is contained in:
parent
22c0f0d635
commit
47fdaf6902
2 changed files with 380 additions and 218 deletions
|
@ -83,10 +83,75 @@ void pngDecReadBuffer(png_structp png_ptr, png_bytep out, png_size_t length)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pngDecRowCallback(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass)
|
||||||
|
{
|
||||||
|
PngStream* stream = (PngStream*)png_get_progressive_ptr(png_ptr);
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
cellPngDec.error("Failed to obtain streamPtr in rowCallback.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have to check this everytime as this func can be called multiple times per row, and/or only once per row
|
||||||
|
if (stream->nextRow + stream->outputCounts == row_num)
|
||||||
|
stream->nextRow = row_num;
|
||||||
|
|
||||||
|
if (stream->ppuContext && (stream->nextRow == row_num || pass > 0))
|
||||||
|
{
|
||||||
|
if (pass > 0 )
|
||||||
|
{
|
||||||
|
stream->cbDispInfo->scanPassCount = pass;
|
||||||
|
stream->cbDispInfo->nextOutputStartY = row_num;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stream->cbDispInfo->scanPassCount = 0;
|
||||||
|
stream->cbDispInfo->nextOutputStartY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->cbDispInfo->outputImage = stream->cbDispParam->nextOutputImage;
|
||||||
|
stream->cbCtrlDisp.cbCtrlDispFunc(*stream->ppuContext, stream->cbDispInfo, stream->cbDispParam, stream->cbCtrlDisp.cbCtrlDispArg);
|
||||||
|
stream->cbDispInfo->outputStartY = row_num;
|
||||||
|
}
|
||||||
|
u8* data;
|
||||||
|
if (pass > 0)
|
||||||
|
data = static_cast<u8*>(stream->cbDispParam->nextOutputImage.get_ptr());
|
||||||
|
else
|
||||||
|
data = static_cast<u8*>(stream->cbDispParam->nextOutputImage.get_ptr()) + ((row_num - stream->cbDispInfo->outputStartY) * stream->cbDispInfo->outputFrameWidthByte);
|
||||||
|
|
||||||
|
png_progressive_combine_row(png_ptr, data, new_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pngDecInfoCallback(png_structp png_ptr, png_infop info)
|
||||||
|
{
|
||||||
|
PngStream* stream = (PngStream*)png_get_progressive_ptr(png_ptr);
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
cellPngDec.error("Failed to obtain streamPtr in rowCallback.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t remaining = png_process_data_pause(png_ptr, false);
|
||||||
|
stream->buffer->cursor += (stream->buffer->length - remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pngDecEndCallback(png_structp png_ptr, png_infop info)
|
||||||
|
{
|
||||||
|
PngStream* stream = (PngStream*)png_get_progressive_ptr(png_ptr);
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
cellPngDec.error("Failed to obtain streamPtr in endCallback.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->endOfFile = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Custom error handler for libpng
|
// Custom error handler for libpng
|
||||||
void pngDecError(png_structp png_ptr, png_const_charp error_message)
|
void pngDecError(png_structp png_ptr, png_const_charp error_message)
|
||||||
{
|
{
|
||||||
cellPngDec.error(error_message);
|
cellPngDec.error(error_message);
|
||||||
|
// we can't return here or libpng blows up
|
||||||
|
throw LibPngCustomException("Fatal Error in libpng");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom warning handler for libpng
|
// Custom warning handler for libpng
|
||||||
|
@ -114,7 +179,7 @@ void pngDecWarning(png_structp png_ptr, png_const_charp error_message)
|
||||||
// 14 - sCAL
|
// 14 - sCAL
|
||||||
// 15 - IDAT
|
// 15 - IDAT
|
||||||
// 16:30 - reserved
|
// 16:30 - reserved
|
||||||
be_t<u32> pngDecGetChunkInformation(PStream stream, bool IDAT = false)
|
be_t<u32> pngDecGetChunkInformation(PngStream* stream, bool IDAT = false)
|
||||||
{
|
{
|
||||||
// The end result of the chunk information (bigger-endian)
|
// The end result of the chunk information (bigger-endian)
|
||||||
be_t<u32> chunk_information = 0;
|
be_t<u32> chunk_information = 0;
|
||||||
|
@ -291,10 +356,11 @@ s32 pngDecDestroy(ppu_thread& ppu, PHandle handle)
|
||||||
|
|
||||||
s32 pngDecOpen(ppu_thread& ppu, PHandle handle, PPStream png_stream, PSrc source, POpenInfo open_info, PCbControlStream control_stream = vm::null, POpenParam open_param = vm::null)
|
s32 pngDecOpen(ppu_thread& ppu, PHandle handle, PPStream png_stream, PSrc source, POpenInfo open_info, PCbControlStream control_stream = vm::null, POpenParam open_param = vm::null)
|
||||||
{
|
{
|
||||||
// Check if partial image decoding is used
|
// partial decoding only supported with buffer type
|
||||||
if (control_stream || open_param)
|
if (source->srcSelect != CELL_PNGDEC_BUFFER && control_stream)
|
||||||
{
|
{
|
||||||
fmt::throw_exception("Partial image decoding is not supported." HERE);
|
cellPngDec.error("Attempted partial image decode with file.");
|
||||||
|
return CELL_PNGDEC_ERROR_STREAM_FORMAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate memory for the stream structure
|
// Allocate memory for the stream structure
|
||||||
|
@ -313,9 +379,6 @@ s32 pngDecOpen(ppu_thread& ppu, PHandle handle, PPStream png_stream, PSrc source
|
||||||
// Set the stream source to the source give by the game
|
// Set the stream source to the source give by the game
|
||||||
stream->source = *source;
|
stream->source = *source;
|
||||||
|
|
||||||
// Indicate that a fixed alpha value isn't used, if not specified otherwise
|
|
||||||
stream->fixed_alpha = false;
|
|
||||||
|
|
||||||
// Use virtual memory address as a handle
|
// Use virtual memory address as a handle
|
||||||
*png_stream = stream;
|
*png_stream = stream;
|
||||||
|
|
||||||
|
@ -401,43 +464,44 @@ s32 pngDecOpen(ppu_thread& ppu, PHandle handle, PPStream png_stream, PSrc source
|
||||||
fmt::throw_exception("Creation of png_infop failed." HERE);
|
fmt::throw_exception("Creation of png_infop failed." HERE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a point to return to when an error occurs in libpng
|
|
||||||
if (setjmp(png_jmpbuf(stream->png_ptr)))
|
|
||||||
{
|
|
||||||
fmt::throw_exception("Fatal error in libpng." HERE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We must indicate, that we allocated more memory
|
// We must indicate, that we allocated more memory
|
||||||
open_info->initSpaceAllocated += sizeof(PngBuffer);
|
open_info->initSpaceAllocated += sizeof(PngBuffer);
|
||||||
|
|
||||||
// Init the IO for either reading from a file or a buffer
|
|
||||||
if (source->srcSelect == CELL_PNGDEC_BUFFER)
|
if (source->srcSelect == CELL_PNGDEC_BUFFER)
|
||||||
{
|
{
|
||||||
// Set the data pointer and the file size
|
buffer->length = stream->source.streamSize;
|
||||||
buffer->length = stream->source.fileSize;
|
|
||||||
buffer->data = stream->source.streamPtr;
|
buffer->data = stream->source.streamPtr;
|
||||||
|
|
||||||
// Since we already read the header, we start reading from position 8
|
|
||||||
buffer->cursor = 8;
|
buffer->cursor = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the custom read function for decoding
|
// Set the custom read function for decoding
|
||||||
|
if (control_stream)
|
||||||
|
{
|
||||||
|
if (open_param && open_param->selectChunk != 0)
|
||||||
|
fmt::throw_exception("Partial Decoding with selectChunk not supported yet.");
|
||||||
|
|
||||||
|
stream->cbCtrlStream.cbCtrlStrmArg = control_stream->cbCtrlStrmArg;
|
||||||
|
stream->cbCtrlStream.cbCtrlStrmFunc = control_stream->cbCtrlStrmFunc;
|
||||||
|
|
||||||
|
png_set_progressive_read_fn(stream->png_ptr, (void *)stream.get_ptr(), pngDecInfoCallback, pngDecRowCallback, pngDecEndCallback);
|
||||||
|
|
||||||
|
// push header tag to libpng to keep us in sync
|
||||||
|
try
|
||||||
|
{
|
||||||
|
png_process_data(stream->png_ptr, stream->info_ptr, header, 8);
|
||||||
|
}
|
||||||
|
catch (LibPngCustomException&)
|
||||||
|
{
|
||||||
|
return CELL_PNGDEC_ERROR_HEADER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
png_set_read_fn(stream->png_ptr, buffer.get_ptr(), pngDecReadBuffer);
|
png_set_read_fn(stream->png_ptr, buffer.get_ptr(), pngDecReadBuffer);
|
||||||
|
|
||||||
// We need to tell libpng, that we already read 8 bytes
|
// We need to tell libpng, that we already read 8 bytes
|
||||||
png_set_sig_bytes(stream->png_ptr, 8);
|
png_set_sig_bytes(stream->png_ptr, 8);
|
||||||
|
}
|
||||||
// Read the basic info of the PNG file
|
|
||||||
png_read_info(stream->png_ptr, stream->info_ptr);
|
|
||||||
|
|
||||||
// Read the header info for future use
|
|
||||||
stream->info.imageWidth = png_get_image_width(stream->png_ptr, stream->info_ptr);
|
|
||||||
stream->info.imageHeight = png_get_image_height(stream->png_ptr, stream->info_ptr);
|
|
||||||
stream->info.numComponents = png_get_channels(stream->png_ptr, stream->info_ptr);
|
|
||||||
stream->info.colorSpace = getPngDecColourType(png_get_color_type(stream->png_ptr, stream->info_ptr));
|
|
||||||
stream->info.bitDepth = png_get_bit_depth(stream->png_ptr, stream->info_ptr);
|
|
||||||
stream->info.interlaceMethod = png_get_interlace_type(stream->png_ptr, stream->info_ptr);
|
|
||||||
stream->info.chunkInformation = pngDecGetChunkInformation(stream);
|
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
@ -473,83 +537,139 @@ s32 pngDecClose(ppu_thread& ppu, PHandle handle, PStream stream)
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 pngReadHeader(PStream stream, PInfo info, PExtInfo extra_info = vm::null)
|
void pngSetHeader(PngStream* stream)
|
||||||
{
|
{
|
||||||
// Set the pointer to stream info - we already get the header info, when opening the decoder
|
stream->info.imageWidth = png_get_image_width(stream->png_ptr, stream->info_ptr);
|
||||||
*info = stream->info;
|
stream->info.imageHeight = png_get_image_height(stream->png_ptr, stream->info_ptr);
|
||||||
|
stream->info.numComponents = png_get_channels(stream->png_ptr, stream->info_ptr);
|
||||||
// Set the reserved value to 0, if passed to the function.
|
stream->info.colorSpace = getPngDecColourType(png_get_color_type(stream->png_ptr, stream->info_ptr));
|
||||||
if (extra_info)
|
stream->info.bitDepth = png_get_bit_depth(stream->png_ptr, stream->info_ptr);
|
||||||
{
|
stream->info.interlaceMethod = png_get_interlace_type(stream->png_ptr, stream->info_ptr);
|
||||||
extra_info->reserved = 0;
|
stream->info.chunkInformation = pngDecGetChunkInformation(stream);
|
||||||
}
|
|
||||||
|
|
||||||
return CELL_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 pngDecSetParameter(PStream stream, PInParam in_param, POutParam out_param, PExtInParam extra_in_param = vm::null, PExtOutParam extra_out_param = vm::null)
|
s32 pngDecSetParameter(PStream stream, PInParam in_param, POutParam out_param, PExtInParam extra_in_param = vm::null, PExtOutParam extra_out_param = vm::null)
|
||||||
{
|
{
|
||||||
// Partial image decoding is not supported. Need to find games to test with.
|
|
||||||
if (extra_in_param || extra_out_param)
|
|
||||||
{
|
|
||||||
fmt::throw_exception("Partial image decoding is not supported" HERE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_param->outputPackFlag == CELL_PNGDEC_1BYTE_PER_NPIXEL)
|
if (in_param->outputPackFlag == CELL_PNGDEC_1BYTE_PER_NPIXEL)
|
||||||
{
|
{
|
||||||
fmt::throw_exception("Packing not supported! (%d)" HERE, in_param->outputPackFlag);
|
fmt::throw_exception("Packing not supported! (%d)" HERE, in_param->outputPackFlag);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We already grab the basic info, when opening the stream, so we simply need to pass most of the values
|
// flag to keep unknown chunks
|
||||||
|
png_set_keep_unknown_chunks(stream->png_ptr, PNG_HANDLE_CHUNK_IF_SAFE, 0, 0);
|
||||||
|
|
||||||
|
// Scale 16 bit depth down to 8 bit depth.
|
||||||
|
if (stream->info.bitDepth == 16 && in_param->outputBitDepth == 8)
|
||||||
|
{
|
||||||
|
// PS3 uses png_set_strip_16, since png_set_scale_16 wasn't available back then.
|
||||||
|
png_set_strip_16(stream->png_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This shouldnt ever happen, but not sure what to do if it does, just want it logged for now
|
||||||
|
if (stream->info.bitDepth != 16 && in_param->outputBitDepth == 16)
|
||||||
|
cellPngDec.error("Output depth of 16 with non input depth of 16 specified!");
|
||||||
|
if (in_param->commandPtr != vm::null)
|
||||||
|
cellPngDec.warning("Ignoring CommandPtr.");
|
||||||
|
|
||||||
|
if (stream->info.colorSpace != in_param->outputColorSpace)
|
||||||
|
{
|
||||||
|
// check if we need to set alpha
|
||||||
|
const bool inputHasAlpha = cellPngColorSpaceHasAlpha(stream->info.colorSpace);
|
||||||
|
const bool outputWantsAlpha = cellPngColorSpaceHasAlpha(in_param->outputColorSpace);
|
||||||
|
|
||||||
|
if (outputWantsAlpha && !inputHasAlpha)
|
||||||
|
{
|
||||||
|
if (in_param->outputAlphaSelect == CELL_PNGDEC_FIX_ALPHA)
|
||||||
|
png_set_add_alpha(stream->png_ptr, in_param->outputColorAlpha, in_param->outputColorSpace == CELL_PNGDEC_ARGB ? PNG_FILLER_BEFORE : PNG_FILLER_AFTER);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Check if we can steal the alpha from a trns block
|
||||||
|
if (png_get_valid(stream->png_ptr, stream->info_ptr, PNG_INFO_tRNS))
|
||||||
|
png_set_tRNS_to_alpha(stream->png_ptr);
|
||||||
|
// if not, just set default of 0xff
|
||||||
|
else
|
||||||
|
png_set_add_alpha(stream->png_ptr, 0xff, in_param->outputColorSpace == CELL_PNGDEC_ARGB ? PNG_FILLER_BEFORE : PNG_FILLER_AFTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (inputHasAlpha && !outputWantsAlpha)
|
||||||
|
png_set_strip_alpha(stream->png_ptr);
|
||||||
|
else if (in_param->outputColorSpace == CELL_PNGDEC_ARGB && stream->info.colorSpace == CELL_PNGDEC_RGBA)
|
||||||
|
png_set_swap_alpha(stream->png_ptr);
|
||||||
|
|
||||||
|
// Handle gray<->rgb colorspace conversions
|
||||||
|
// rgb output
|
||||||
|
if (in_param->outputColorSpace == CELL_PNGDEC_ARGB
|
||||||
|
|| in_param->outputColorSpace == CELL_PNGDEC_RGBA
|
||||||
|
|| in_param->outputColorSpace == CELL_PNGDEC_RGB)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (stream->info.colorSpace == CELL_PNGDEC_PALETTE)
|
||||||
|
png_set_palette_to_rgb(stream->png_ptr);
|
||||||
|
if ((stream->info.colorSpace == CELL_PNGDEC_GRAYSCALE || stream->info.colorSpace == CELL_PNGDEC_GRAYSCALE_ALPHA)
|
||||||
|
&& stream->info.bitDepth < 8)
|
||||||
|
png_set_expand_gray_1_2_4_to_8(stream->png_ptr);
|
||||||
|
}
|
||||||
|
// grayscale output
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (stream->info.colorSpace == CELL_PNGDEC_ARGB
|
||||||
|
|| stream->info.colorSpace == CELL_PNGDEC_RGBA
|
||||||
|
|| stream->info.colorSpace == CELL_PNGDEC_RGB)
|
||||||
|
{
|
||||||
|
|
||||||
|
png_set_rgb_to_gray(stream->png_ptr, PNG_ERROR_ACTION_NONE, PNG_RGB_TO_GRAY_DEFAULT, PNG_RGB_TO_GRAY_DEFAULT);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// not sure what to do here
|
||||||
|
cellPngDec.error("Grayscale / Palette to Grayscale / Palette conversion currently unsupported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->passes = png_set_interlace_handling(stream->png_ptr);
|
||||||
|
|
||||||
|
// Update the info structure
|
||||||
|
png_read_update_info(stream->png_ptr, stream->info_ptr);
|
||||||
|
|
||||||
stream->out_param.outputWidth = stream->info.imageWidth;
|
stream->out_param.outputWidth = stream->info.imageWidth;
|
||||||
stream->out_param.outputHeight = stream->info.imageHeight;
|
stream->out_param.outputHeight = stream->info.imageHeight;
|
||||||
|
stream->out_param.outputBitDepth = in_param->outputBitDepth;
|
||||||
stream->out_param.outputColorSpace = in_param->outputColorSpace;
|
stream->out_param.outputColorSpace = in_param->outputColorSpace;
|
||||||
stream->out_param.outputBitDepth = stream->info.bitDepth;
|
|
||||||
stream->out_param.outputMode = in_param->outputMode;
|
stream->out_param.outputMode = in_param->outputMode;
|
||||||
|
|
||||||
stream->out_param.outputWidthByte = png_get_rowbytes(stream->png_ptr, stream->info_ptr);
|
stream->out_param.outputWidthByte = png_get_rowbytes(stream->png_ptr, stream->info_ptr);
|
||||||
|
stream->out_param.outputComponents = png_get_channels(stream->png_ptr, stream->info_ptr);
|
||||||
|
|
||||||
stream->packing = in_param->outputPackFlag;
|
stream->packing = in_param->outputPackFlag;
|
||||||
|
|
||||||
// Check if a fixed alpha value is specified
|
|
||||||
if (in_param->outputAlphaSelect == CELL_PNGDEC_FIX_ALPHA)
|
|
||||||
{
|
|
||||||
// We set the fixed alpha value in the stream structure, for padding while decoding
|
|
||||||
stream->fixed_alpha = true;
|
|
||||||
stream->fixed_alpha_colour = in_param->outputColorAlpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remap the number of output components based on the passed colorSpace value
|
|
||||||
switch (stream->out_param.outputColorSpace)
|
|
||||||
{
|
|
||||||
case CELL_PNGDEC_RGBA:
|
|
||||||
case CELL_PNGDEC_ARGB:
|
|
||||||
{
|
|
||||||
stream->out_param.outputComponents = 4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case CELL_PNGDEC_RGB:
|
|
||||||
{
|
|
||||||
stream->out_param.outputComponents = 3;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case CELL_PNGDEC_GRAYSCALE_ALPHA:
|
|
||||||
{
|
|
||||||
stream->out_param.outputComponents = 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case CELL_PNGDEC_PALETTE:
|
|
||||||
case CELL_PNGDEC_GRAYSCALE:
|
|
||||||
{
|
|
||||||
stream->out_param.outputComponents = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the memory usage. We currently don't actually allocate memory for libpng through the callbacks, due to libpng needing a lot more memory compared to PS3 variant.
|
// Set the memory usage. We currently don't actually allocate memory for libpng through the callbacks, due to libpng needing a lot more memory compared to PS3 variant.
|
||||||
stream->out_param.useMemorySpace = 0;
|
stream->out_param.useMemorySpace = 0;
|
||||||
|
|
||||||
// Set the pointer
|
if (extra_in_param)
|
||||||
|
{
|
||||||
|
if (extra_in_param->bufferMode != CELL_PNGDEC_LINE_MODE)
|
||||||
|
{
|
||||||
|
cellPngDec.error("Invalid Buffermode specified.");
|
||||||
|
return CELL_PNGDEC_ERROR_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream->passes > 1)
|
||||||
|
{
|
||||||
|
stream->outputCounts = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
stream->outputCounts = extra_in_param->outputCounts;
|
||||||
|
|
||||||
|
if (extra_out_param)
|
||||||
|
{
|
||||||
|
if (stream->outputCounts == 0)
|
||||||
|
extra_out_param->outputHeight = stream->out_param.outputHeight;
|
||||||
|
else
|
||||||
|
extra_out_param->outputHeight = std::min(stream->outputCounts, stream->out_param.outputHeight.value());
|
||||||
|
extra_out_param->outputWidthByte = stream->out_param.outputWidthByte;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
*out_param = stream->out_param;
|
*out_param = stream->out_param;
|
||||||
|
|
||||||
return CELL_OK;
|
return CELL_OK;
|
||||||
|
@ -557,145 +677,123 @@ s32 pngDecSetParameter(PStream stream, PInParam in_param, POutParam out_param, P
|
||||||
|
|
||||||
s32 pngDecodeData(ppu_thread& ppu, PHandle handle, PStream stream, vm::ptr<u8> data, PDataControlParam data_control_param, PDataOutInfo data_out_info, PCbControlDisp cb_control_disp = vm::null, PDispParam disp_param = vm::null)
|
s32 pngDecodeData(ppu_thread& ppu, PHandle handle, PStream stream, vm::ptr<u8> data, PDataControlParam data_control_param, PDataOutInfo data_out_info, PCbControlDisp cb_control_disp = vm::null, PDispParam disp_param = vm::null)
|
||||||
{
|
{
|
||||||
if (cb_control_disp || disp_param)
|
|
||||||
{
|
|
||||||
fmt::throw_exception("Partial image decoding is not supported" HERE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indicate, that the PNG decoding is stopped/failed. This is incase, we return an error code in the middle of decoding
|
// Indicate, that the PNG decoding is stopped/failed. This is incase, we return an error code in the middle of decoding
|
||||||
data_out_info->status = CELL_PNGDEC_DEC_STATUS_STOP;
|
data_out_info->status = CELL_PNGDEC_DEC_STATUS_STOP;
|
||||||
|
|
||||||
// Possibilities for decoding in different sizes aren't tested, so if anyone finds any of these cases, we'll know about it.
|
|
||||||
if (stream->info.imageWidth != stream->out_param.outputWidth)
|
|
||||||
{
|
|
||||||
fmt::throw_exception("Image width doesn't match output width! (%d/%d)" HERE, stream->out_param.outputWidth, stream->info.imageWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stream->info.imageHeight != stream->out_param.outputHeight)
|
|
||||||
{
|
|
||||||
fmt::throw_exception("Image width doesn't match output height! (%d/%d)" HERE, stream->out_param.outputHeight, stream->info.imageHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the amount of output bytes per line
|
|
||||||
const u32 bytes_per_line = data_control_param->outputBytesPerLine;
|
const u32 bytes_per_line = data_control_param->outputBytesPerLine;
|
||||||
|
|
||||||
// Whether to recaculate bytes per row
|
// Log this for now
|
||||||
bool recalculate_bytes_per_row = false;
|
if (bytes_per_line < stream->out_param.outputWidthByte)
|
||||||
|
|
||||||
// Check if the game is expecting the number of bytes per line to be lower, than the actual bytes per line on the image. (Arkedo Pixel for example)
|
|
||||||
// In such case we strip the bit depth to be lower.
|
|
||||||
if ((bytes_per_line < stream->out_param.outputWidthByte) && stream->out_param.outputBitDepth != 8)
|
|
||||||
{
|
{
|
||||||
// Check if the packing is really 1 byte per 1 pixel
|
fmt::throw_exception("Bytes per line less than expected output! Got: %d, expected: %d" HERE, bytes_per_line, stream->out_param.outputWidthByte);
|
||||||
if (stream->packing != CELL_PNGDEC_1BYTE_PER_1PIXEL)
|
|
||||||
{
|
|
||||||
fmt::throw_exception("Unexpected packing value! (%d)" HERE, stream->packing);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale 16 bit depth down to 8 bit depth. PS3 uses png_set_strip_16, since png_set_scale_16 wasn't available back then.
|
// partial decoding
|
||||||
png_set_strip_16(stream->png_ptr);
|
if (cb_control_disp && stream->outputCounts > 0)
|
||||||
recalculate_bytes_per_row = true;
|
{
|
||||||
|
// get data from cb
|
||||||
|
auto streamInfo = vm::ptr<CellPngDecStrmInfo>::make(handle->malloc_(ppu, sizeof(CellPngDecStrmInfo), handle->malloc_arg).addr());
|
||||||
|
auto streamParam = vm::ptr<CellPngDecStrmParam>::make(handle->malloc_(ppu, sizeof(CellPngDecStrmParam), handle->malloc_arg).addr());
|
||||||
|
stream->cbDispInfo = vm::ptr<CellPngDecDispInfo>::make(handle->malloc_(ppu, sizeof(CellPngDecDispInfo), handle->malloc_arg).addr());
|
||||||
|
stream->cbDispParam = vm::ptr<CellPngDecDispParam>::make(handle->malloc_(ppu, sizeof(CellPngDecDispParam), handle->malloc_arg).addr());
|
||||||
|
|
||||||
|
auto freeMem = [&]()
|
||||||
|
{
|
||||||
|
handle->free_(ppu, streamInfo, handle->free_arg);
|
||||||
|
handle->free_(ppu, streamParam, handle->free_arg);
|
||||||
|
handle->free_(ppu, stream->cbDispInfo, handle->free_arg);
|
||||||
|
handle->free_(ppu, stream->cbDispParam, handle->free_arg);
|
||||||
|
};
|
||||||
|
|
||||||
|
// set things that won't change between callbacks
|
||||||
|
stream->cbDispInfo->outputFrameWidthByte = bytes_per_line;
|
||||||
|
stream->cbDispInfo->outputFrameHeight = stream->out_param.outputHeight;
|
||||||
|
stream->cbDispInfo->outputWidthByte = stream->out_param.outputWidthByte;
|
||||||
|
stream->cbDispInfo->outputBitDepth = stream->out_param.outputBitDepth;
|
||||||
|
stream->cbDispInfo->outputComponents = stream->out_param.outputComponents;
|
||||||
|
stream->cbDispInfo->outputHeight = stream->outputCounts;
|
||||||
|
stream->cbDispInfo->outputStartXByte = 0;
|
||||||
|
stream->cbDispInfo->outputStartY = 0;
|
||||||
|
stream->cbDispInfo->scanPassCount = 0;
|
||||||
|
stream->cbDispInfo->nextOutputStartY = 0;
|
||||||
|
|
||||||
|
stream->ppuContext = &ppu;
|
||||||
|
stream->nextRow = stream->cbDispInfo->outputHeight;
|
||||||
|
stream->cbCtrlDisp.cbCtrlDispArg = cb_control_disp->cbCtrlDispArg;
|
||||||
|
stream->cbCtrlDisp.cbCtrlDispFunc = cb_control_disp->cbCtrlDispFunc;
|
||||||
|
|
||||||
|
stream->cbDispParam->nextOutputImage = disp_param->nextOutputImage;
|
||||||
|
|
||||||
|
streamInfo->decodedStrmSize = stream->buffer->cursor;
|
||||||
|
// push the rest of the buffer we have
|
||||||
|
if (stream->buffer->length > stream->buffer->cursor)
|
||||||
|
{
|
||||||
|
u8* data = static_cast<u8*>(stream->buffer->data.get_ptr()) + stream->buffer->cursor;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
png_process_data(stream->png_ptr, stream->info_ptr, data, stream->buffer->length - stream->buffer->cursor);
|
||||||
}
|
}
|
||||||
// Check if the outputWidthByte is smaller than the intended output length of a line. For example an image might be in RGB, but we need to output 4 components, so we need to perform alpha padding.
|
catch (LibPngCustomException&)
|
||||||
else if (stream->out_param.outputWidthByte < (stream->out_param.outputWidth * stream->out_param.outputComponents))
|
|
||||||
{
|
{
|
||||||
// If fixed alpha is not specified in such a case, the default value for the alpha is 0xFF (255)
|
freeMem();
|
||||||
if (!stream->fixed_alpha)
|
return CELL_PNGDEC_ERROR_FATAL;
|
||||||
{
|
}
|
||||||
stream->fixed_alpha_colour = 0xFF;
|
streamInfo->decodedStrmSize = stream->buffer->length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to fill alpha (before or after, depending on the output colour format) using the fixed alpha value passed by the game.
|
// todo: commandPtr
|
||||||
png_set_add_alpha(stream->png_ptr, stream->fixed_alpha_colour, stream->out_param.outputColorSpace == CELL_PNGDEC_RGBA ? PNG_FILLER_AFTER : PNG_FILLER_BEFORE);
|
// then just loop until the end, the callbacks should take care of the rest
|
||||||
recalculate_bytes_per_row = true;
|
while (stream->endOfFile != true)
|
||||||
}
|
|
||||||
// We decode as RGBA, so we need to swap the alpha
|
|
||||||
else if (stream->out_param.outputColorSpace == CELL_PNGDEC_ARGB)
|
|
||||||
{
|
{
|
||||||
// Swap the alpha channel for the ARGB output format, if the padding isn't needed
|
stream->cbCtrlStream.cbCtrlStrmFunc(ppu, streamInfo, streamParam, stream->cbCtrlStream.cbCtrlStrmArg);
|
||||||
png_set_swap_alpha(stream->png_ptr);
|
streamInfo->decodedStrmSize += streamParam->strmSize;
|
||||||
}
|
try
|
||||||
// Sometimes games pass in a RBG/RGBA image and want it as grayscale
|
|
||||||
else if ((stream->out_param.outputColorSpace == CELL_PNGDEC_GRAYSCALE_ALPHA || stream->out_param.outputColorSpace == CELL_PNGDEC_GRAYSCALE)
|
|
||||||
&& (stream->info.colorSpace == CELL_PNGDEC_RGB || stream->info.colorSpace == CELL_PNGDEC_RGBA))
|
|
||||||
{
|
{
|
||||||
// Tell libpng to convert it to grayscale
|
png_process_data(stream->png_ptr, stream->info_ptr, static_cast<u8*>(streamParam->strmPtr.get_ptr()), streamParam->strmSize);
|
||||||
png_set_rgb_to_gray(stream->png_ptr, PNG_ERROR_ACTION_NONE, PNG_RGB_TO_GRAY_DEFAULT, PNG_RGB_TO_GRAY_DEFAULT);
|
}
|
||||||
recalculate_bytes_per_row = true;
|
catch (LibPngCustomException&)
|
||||||
|
{
|
||||||
|
freeMem();
|
||||||
|
return CELL_PNGDEC_ERROR_FATAL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recalculate_bytes_per_row)
|
freeMem();
|
||||||
{
|
|
||||||
// Update the info structure
|
|
||||||
png_read_update_info(stream->png_ptr, stream->info_ptr);
|
|
||||||
|
|
||||||
// Recalculate the bytes per row
|
|
||||||
stream->out_param.outputWidthByte = png_get_rowbytes(stream->png_ptr, stream->info_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the image size
|
|
||||||
u32 image_size = stream->out_param.outputWidthByte * stream->out_param.outputHeight;
|
|
||||||
|
|
||||||
// Buffer for storing the image
|
|
||||||
std::vector<u8> png(image_size);
|
|
||||||
|
|
||||||
// Make an unique pointer for the row pointers
|
|
||||||
std::vector<u8*> row_pointers(stream->out_param.outputHeight);
|
|
||||||
|
|
||||||
// Allocate memory for rows
|
|
||||||
for (u32 y = 0; y < stream->out_param.outputHeight; y++)
|
|
||||||
{
|
|
||||||
row_pointers[y] = &png[y * stream->out_param.outputWidthByte];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the image
|
|
||||||
png_read_image(stream->png_ptr, row_pointers.data());
|
|
||||||
|
|
||||||
// Check if the image needs to be flipped
|
|
||||||
const bool flip = stream->out_param.outputMode == CELL_PNGDEC_BOTTOM_TO_TOP;
|
|
||||||
|
|
||||||
// Copy the result to the output buffer
|
|
||||||
switch (stream->out_param.outputColorSpace)
|
|
||||||
{
|
|
||||||
case CELL_PNGDEC_RGB:
|
|
||||||
case CELL_PNGDEC_RGBA:
|
|
||||||
case CELL_PNGDEC_ARGB:
|
|
||||||
case CELL_PNGDEC_GRAYSCALE_ALPHA:
|
|
||||||
{
|
|
||||||
// Check if we need to flip the image or need to leave empty bytes at the end of a line
|
|
||||||
if ((bytes_per_line > stream->out_param.outputWidthByte) || flip)
|
|
||||||
{
|
|
||||||
// Get how many bytes per line we need to output - bytesPerLine is total amount of bytes per line, rest is unused and the game can do as it pleases.
|
|
||||||
const u32 line_size = std::min(bytes_per_line, stream->out_param.outputWidth * 4);
|
|
||||||
|
|
||||||
// If the game wants more bytes per line to be output, than the image has, then we simply copy what we have for each line,
|
|
||||||
// and continue on the next line, thus leaving empty bytes at the end of the line.
|
|
||||||
for (u32 i = 0; i < stream->out_param.outputHeight; i++)
|
|
||||||
{
|
|
||||||
const u32 dst = i * bytes_per_line;
|
|
||||||
const u32 src = stream->out_param.outputWidth * 4 * (flip ? stream->out_param.outputHeight - i - 1 : i);
|
|
||||||
memcpy(&data[dst], &png[src], line_size);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// We can simply copy the output to the data pointer specified by the game, since we already do alpha channel transformations in libpng, if needed
|
// Check if the image needs to be flipped
|
||||||
memcpy(data.get_ptr(), png.data(), image_size);
|
const bool flip = stream->out_param.outputMode == CELL_PNGDEC_BOTTOM_TO_TOP;
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: fmt::throw_exception("Unsupported color space (%d)" HERE, stream->out_param.outputColorSpace);
|
// Decode the image
|
||||||
|
// todo: commandptr
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (int j = 0; j < stream->passes; j++)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < stream->out_param.outputHeight; ++i)
|
||||||
|
{
|
||||||
|
const u32 line = flip ? stream->out_param.outputHeight - i - 1 : i;
|
||||||
|
png_read_row(stream->png_ptr, &data[line*bytes_per_line], nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
png_read_end(stream->png_ptr, stream->info_ptr);
|
||||||
|
}
|
||||||
|
catch (LibPngCustomException&)
|
||||||
|
{
|
||||||
|
return CELL_PNGDEC_ERROR_FATAL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the number of iTXt, tEXt and zTXt chunks
|
// Get the number of iTXt, tEXt and zTXt chunks
|
||||||
s32 text_chunks = 0;
|
const s32 text_chunks = png_get_text(stream->png_ptr, stream->info_ptr, nullptr, nullptr);
|
||||||
png_get_text(stream->png_ptr, stream->info_ptr, nullptr, &text_chunks);
|
|
||||||
|
|
||||||
// Set the chunk information and the previously obtained number of text chunks
|
// Set the chunk information and the previously obtained number of text chunks
|
||||||
data_out_info->numText = (u32)text_chunks;
|
data_out_info->numText = (u32)text_chunks;
|
||||||
data_out_info->chunkInformation = pngDecGetChunkInformation(stream, true);
|
data_out_info->chunkInformation = pngDecGetChunkInformation(stream.get_ptr(), true);
|
||||||
data_out_info->numUnknownChunk = 0; // TODO: Get this somehow. Does anything even use or need this?
|
png_unknown_chunkp unknowns;
|
||||||
|
const int num_unknowns = png_get_unknown_chunks(stream->png_ptr, stream->info_ptr, &unknowns);
|
||||||
|
data_out_info->numUnknownChunk = num_unknowns;
|
||||||
|
|
||||||
// Indicate that the decoding succeeded
|
// Indicate that the decoding succeeded
|
||||||
data_out_info->status = CELL_PNGDEC_DEC_STATUS_FINISH;
|
data_out_info->status = CELL_PNGDEC_DEC_STATUS_FINISH;
|
||||||
|
@ -711,7 +809,7 @@ s32 cellPngDecCreate(ppu_thread& ppu, PPHandle handle, PThreadInParam threadInPa
|
||||||
|
|
||||||
s32 cellPngDecExtCreate(ppu_thread& ppu, PPHandle handle, PThreadInParam threadInParam, PThreadOutParam threadOutParam, PExtThreadInParam extThreadInParam, PExtThreadOutParam extThreadOutParam)
|
s32 cellPngDecExtCreate(ppu_thread& ppu, PPHandle handle, PThreadInParam threadInParam, PThreadOutParam threadOutParam, PExtThreadInParam extThreadInParam, PExtThreadOutParam extThreadOutParam)
|
||||||
{
|
{
|
||||||
cellPngDec.warning("cellPngDecCreate(mainHandle=**0x%x, threadInParam=*0x%x, threadOutParam=*0x%x, extThreadInParam=*0x%x, extThreadOutParam=*0x%x)", handle, threadInParam, threadOutParam, extThreadInParam, extThreadOutParam);
|
cellPngDec.warning("cellPngDecExtCreate(mainHandle=**0x%x, threadInParam=*0x%x, threadOutParam=*0x%x, extThreadInParam=*0x%x, extThreadOutParam=*0x%x)", handle, threadInParam, threadOutParam, extThreadInParam, extThreadOutParam);
|
||||||
return pngDecCreate(ppu, handle, threadInParam, threadOutParam, extThreadInParam, extThreadOutParam);
|
return pngDecCreate(ppu, handle, threadInParam, threadOutParam, extThreadInParam, extThreadOutParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -742,13 +840,51 @@ s32 cellPngDecClose(ppu_thread& ppu, PHandle handle, PStream stream)
|
||||||
s32 cellPngDecReadHeader(PHandle handle, PStream stream, PInfo info)
|
s32 cellPngDecReadHeader(PHandle handle, PStream stream, PInfo info)
|
||||||
{
|
{
|
||||||
cellPngDec.warning("cellPngDecReadHeader(handle=*0x%x, stream=*0x%x, info=*0x%x)", handle, stream, info);
|
cellPngDec.warning("cellPngDecReadHeader(handle=*0x%x, stream=*0x%x, info=*0x%x)", handle, stream, info);
|
||||||
return pngReadHeader(stream, info);
|
|
||||||
|
// Read the header info
|
||||||
|
png_read_info(stream->png_ptr, stream->info_ptr);
|
||||||
|
|
||||||
|
pngSetHeader(stream.get_ptr());
|
||||||
|
|
||||||
|
// Set the pointer to stream info
|
||||||
|
*info = stream->info;
|
||||||
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellPngDecExtReadHeader(PHandle handle, PStream stream, PInfo info, PExtInfo extInfo)
|
s32 cellPngDecExtReadHeader(PHandle handle, PStream stream, PInfo info, PExtInfo extInfo)
|
||||||
{
|
{
|
||||||
cellPngDec.warning("cellPngDecExtReadHeader(handle=*0x%x, stream=*0x%x, info=*0x%x, extInfo=*0x%x)", handle, stream, info, extInfo);
|
cellPngDec.warning("cellPngDecExtReadHeader(handle=*0x%x, stream=*0x%x, info=*0x%x, extInfo=*0x%x)", handle, stream, info, extInfo);
|
||||||
return pngReadHeader(stream, info, extInfo);
|
// Set the reserved value to 0, if passed to the function. (Should this be arg error if they dont pass?)
|
||||||
|
if (extInfo)
|
||||||
|
{
|
||||||
|
extInfo->reserved = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lets push what we have so far
|
||||||
|
u8* data = static_cast<u8*>(stream->buffer->data.get_ptr()) + stream->buffer->cursor;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
png_process_data(stream->png_ptr, stream->info_ptr, data, stream->buffer->length);
|
||||||
|
}
|
||||||
|
catch (LibPngCustomException&)
|
||||||
|
{
|
||||||
|
return CELL_PNGDEC_ERROR_HEADER;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lets hope we pushed enough for callback
|
||||||
|
pngSetHeader(stream.get_ptr());
|
||||||
|
|
||||||
|
// png doesnt allow empty image, so quick check for 0 verifys if we got the header
|
||||||
|
// not sure exactly what should happen if we dont have header, ask for more data with callback?
|
||||||
|
if (stream->info.imageWidth == 0)
|
||||||
|
{
|
||||||
|
fmt::throw_exception("Invalid or not enough data sent to get header");
|
||||||
|
return CELL_PNGDEC_ERROR_HEADER;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the pointer to stream info
|
||||||
|
*info = stream->info;
|
||||||
|
return CELL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 cellPngDecSetParameter(PHandle handle, PStream stream, PInParam inParam, POutParam outParam)
|
s32 cellPngDecSetParameter(PHandle handle, PStream stream, PInParam inParam, POutParam outParam)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace vm { using namespace ps3; }
|
namespace vm { using namespace ps3; }
|
||||||
|
|
||||||
#include "cellPng.h"
|
#include "cellPng.h"
|
||||||
|
@ -289,15 +288,21 @@ struct PngStream
|
||||||
CellPngDecInfo info;
|
CellPngDecInfo info;
|
||||||
CellPngDecOutParam out_param;
|
CellPngDecOutParam out_param;
|
||||||
CellPngDecSrc source;
|
CellPngDecSrc source;
|
||||||
CellPngDecStrmInfo streamInfo;
|
|
||||||
CellPngDecStrmParam streamParam;
|
|
||||||
|
|
||||||
// Fixed alpha value and flag
|
// Partial decoding
|
||||||
bool fixed_alpha;
|
CellPngDecCbCtrlStrm cbCtrlStream;
|
||||||
be_t<u32> fixed_alpha_colour;
|
CellPngDecCbCtrlDisp cbCtrlDisp;
|
||||||
|
vm::ptr<CellPngDecDispInfo> cbDispInfo;
|
||||||
|
vm::ptr<CellPngDecDispParam> cbDispParam;
|
||||||
|
ppu_thread* ppuContext;
|
||||||
|
|
||||||
|
u32 outputCounts = 0;
|
||||||
|
u32 nextRow = 0;
|
||||||
|
bool endOfFile = false;
|
||||||
|
|
||||||
// Pixel packing value
|
// Pixel packing value
|
||||||
be_t<s32> packing;
|
be_t<s32> packing;
|
||||||
|
u32 passes;
|
||||||
|
|
||||||
// PNG custom read function structure, for decoding from a buffer
|
// PNG custom read function structure, for decoding from a buffer
|
||||||
vm::ptr<PngBuffer> buffer;
|
vm::ptr<PngBuffer> buffer;
|
||||||
|
@ -320,3 +325,24 @@ static s32 getPngDecColourType(u8 type)
|
||||||
default: fmt::throw_exception("Unknown colour type: %d" HERE, type);
|
default: fmt::throw_exception("Unknown colour type: %d" HERE, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool cellPngColorSpaceHasAlpha(u32 colorspace)
|
||||||
|
{
|
||||||
|
switch (colorspace)
|
||||||
|
{
|
||||||
|
case CELL_PNGDEC_RGBA:
|
||||||
|
case CELL_PNGDEC_ARGB:
|
||||||
|
case CELL_PNGDEC_GRAYSCALE_ALPHA:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom exception for libPng errors
|
||||||
|
|
||||||
|
class LibPngCustomException : public std::runtime_error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LibPngCustomException(char const* const message) : runtime_error(message) {}
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue