diff --git a/rpcs3/Emu/Cell/Modules/cellPngDec.cpp b/rpcs3/Emu/Cell/Modules/cellPngDec.cpp index c7fb7869b9..9864ac3c4c 100644 --- a/rpcs3/Emu/Cell/Modules/cellPngDec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPngDec.cpp @@ -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(stream->cbDispParam->nextOutputImage.get_ptr()); + else + data = static_cast(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 void pngDecError(png_structp png_ptr, png_const_charp 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 @@ -114,7 +179,7 @@ void pngDecWarning(png_structp png_ptr, png_const_charp error_message) // 14 - sCAL // 15 - IDAT // 16:30 - reserved -be_t pngDecGetChunkInformation(PStream stream, bool IDAT = false) +be_t pngDecGetChunkInformation(PngStream* stream, bool IDAT = false) { // The end result of the chunk information (bigger-endian) be_t 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) { - // Check if partial image decoding is used - if (control_stream || open_param) + // partial decoding only supported with buffer type + 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 @@ -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 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 *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); } - // 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 open_info->initSpaceAllocated += sizeof(PngBuffer); - // Init the IO for either reading from a file or a buffer if (source->srcSelect == CELL_PNGDEC_BUFFER) { - // Set the data pointer and the file size - buffer->length = stream->source.fileSize; + buffer->length = stream->source.streamSize; buffer->data = stream->source.streamPtr; - - // Since we already read the header, we start reading from position 8 buffer->cursor = 8; } // Set the custom read function for decoding - png_set_read_fn(stream->png_ptr, buffer.get_ptr(), pngDecReadBuffer); + if (control_stream) + { + if (open_param && open_param->selectChunk != 0) + fmt::throw_exception("Partial Decoding with selectChunk not supported yet."); - // We need to tell libpng, that we already read 8 bytes - png_set_sig_bytes(stream->png_ptr, 8); + stream->cbCtrlStream.cbCtrlStrmArg = control_stream->cbCtrlStrmArg; + stream->cbCtrlStream.cbCtrlStrmFunc = control_stream->cbCtrlStrmFunc; - // Read the basic info of the PNG file - png_read_info(stream->png_ptr, stream->info_ptr); + png_set_progressive_read_fn(stream->png_ptr, (void *)stream.get_ptr(), pngDecInfoCallback, pngDecRowCallback, pngDecEndCallback); - // 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); + // 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); + + // We need to tell libpng, that we already read 8 bytes + png_set_sig_bytes(stream->png_ptr, 8); + } return CELL_OK; } @@ -473,83 +537,139 @@ s32 pngDecClose(ppu_thread& ppu, PHandle handle, PStream stream) 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 - *info = stream->info; - - // Set the reserved value to 0, if passed to the function. - if (extra_info) - { - extra_info->reserved = 0; - } - - return CELL_OK; + 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); } 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) { 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.outputHeight = stream->info.imageHeight; + stream->out_param.outputBitDepth = in_param->outputBitDepth; stream->out_param.outputColorSpace = in_param->outputColorSpace; - stream->out_param.outputBitDepth = stream->info.bitDepth; stream->out_param.outputMode = in_param->outputMode; + 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; - // 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. 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; 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 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 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; - // Whether to recaculate bytes per row - bool recalculate_bytes_per_row = false; - - // 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) + // Log this for now + if (bytes_per_line < stream->out_param.outputWidthByte) { - // Check if the packing is really 1 byte per 1 pixel - if (stream->packing != CELL_PNGDEC_1BYTE_PER_1PIXEL) + fmt::throw_exception("Bytes per line less than expected output! Got: %d, expected: %d" HERE, bytes_per_line, stream->out_param.outputWidthByte); + } + + // partial decoding + if (cb_control_disp && stream->outputCounts > 0) + { + // get data from cb + auto streamInfo = vm::ptr::make(handle->malloc_(ppu, sizeof(CellPngDecStrmInfo), handle->malloc_arg).addr()); + auto streamParam = vm::ptr::make(handle->malloc_(ppu, sizeof(CellPngDecStrmParam), handle->malloc_arg).addr()); + stream->cbDispInfo = vm::ptr::make(handle->malloc_(ppu, sizeof(CellPngDecDispInfo), handle->malloc_arg).addr()); + stream->cbDispParam = vm::ptr::make(handle->malloc_(ppu, sizeof(CellPngDecDispParam), handle->malloc_arg).addr()); + + auto freeMem = [&]() { - fmt::throw_exception("Unexpected packing value! (%d)" HERE, stream->packing); - } + 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); + }; - // 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. - png_set_strip_16(stream->png_ptr); - recalculate_bytes_per_row = true; - } - // 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. - 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) - if (!stream->fixed_alpha) + // 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) { - stream->fixed_alpha_colour = 0xFF; - } - - // We need to fill alpha (before or after, depending on the output colour format) using the fixed alpha value passed by the game. - png_set_add_alpha(stream->png_ptr, stream->fixed_alpha_colour, stream->out_param.outputColorSpace == CELL_PNGDEC_RGBA ? PNG_FILLER_AFTER : PNG_FILLER_BEFORE); - recalculate_bytes_per_row = 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 - png_set_swap_alpha(stream->png_ptr); - } - // 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_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; - } - - if (recalculate_bytes_per_row) - { - // 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 png(image_size); - - // Make an unique pointer for the row pointers - std::vector 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++) + u8* data = static_cast(stream->buffer->data.get_ptr()) + stream->buffer->cursor; + try { - 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); + png_process_data(stream->png_ptr, stream->info_ptr, data, stream->buffer->length - stream->buffer->cursor); + } + catch (LibPngCustomException&) + { + freeMem(); + return CELL_PNGDEC_ERROR_FATAL; + } + streamInfo->decodedStrmSize = stream->buffer->length; + } + + // todo: commandPtr + // then just loop until the end, the callbacks should take care of the rest + while (stream->endOfFile != true) + { + stream->cbCtrlStream.cbCtrlStrmFunc(ppu, streamInfo, streamParam, stream->cbCtrlStream.cbCtrlStrmArg); + streamInfo->decodedStrmSize += streamParam->strmSize; + try + { + png_process_data(stream->png_ptr, stream->info_ptr, static_cast(streamParam->strmPtr.get_ptr()), streamParam->strmSize); + } + catch (LibPngCustomException&) + { + freeMem(); + return CELL_PNGDEC_ERROR_FATAL; } } - 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 - memcpy(data.get_ptr(), png.data(), image_size); - } - break; - } - default: fmt::throw_exception("Unsupported color space (%d)" HERE, stream->out_param.outputColorSpace); + freeMem(); + } + else + { + // Check if the image needs to be flipped + const bool flip = stream->out_param.outputMode == CELL_PNGDEC_BOTTOM_TO_TOP; + + // 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 - s32 text_chunks = 0; - png_get_text(stream->png_ptr, stream->info_ptr, nullptr, &text_chunks); + const s32 text_chunks = png_get_text(stream->png_ptr, stream->info_ptr, nullptr, nullptr); // Set the chunk information and the previously obtained number of text chunks data_out_info->numText = (u32)text_chunks; - data_out_info->chunkInformation = pngDecGetChunkInformation(stream, true); - data_out_info->numUnknownChunk = 0; // TODO: Get this somehow. Does anything even use or need this? + data_out_info->chunkInformation = pngDecGetChunkInformation(stream.get_ptr(), true); + 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 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) { - 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); } @@ -742,13 +840,51 @@ s32 cellPngDecClose(ppu_thread& ppu, PHandle handle, PStream stream) s32 cellPngDecReadHeader(PHandle handle, PStream stream, PInfo 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) { 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(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) diff --git a/rpcs3/Emu/Cell/Modules/cellPngDec.h b/rpcs3/Emu/Cell/Modules/cellPngDec.h index a56d9457da..85fc9de79b 100644 --- a/rpcs3/Emu/Cell/Modules/cellPngDec.h +++ b/rpcs3/Emu/Cell/Modules/cellPngDec.h @@ -1,5 +1,4 @@ #pragma once - namespace vm { using namespace ps3; } #include "cellPng.h" @@ -289,16 +288,22 @@ struct PngStream CellPngDecInfo info; CellPngDecOutParam out_param; CellPngDecSrc source; - CellPngDecStrmInfo streamInfo; - CellPngDecStrmParam streamParam; - // Fixed alpha value and flag - bool fixed_alpha; - be_t fixed_alpha_colour; + // Partial decoding + CellPngDecCbCtrlStrm cbCtrlStream; + CellPngDecCbCtrlDisp cbCtrlDisp; + vm::ptr cbDispInfo; + vm::ptr cbDispParam; + ppu_thread* ppuContext; + + u32 outputCounts = 0; + u32 nextRow = 0; + bool endOfFile = false; // Pixel packing value be_t packing; - + u32 passes; + // PNG custom read function structure, for decoding from a buffer vm::ptr buffer; @@ -320,3 +325,24 @@ static s32 getPngDecColourType(u8 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) {} +}; \ No newline at end of file