void nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) { // int number_passes; NOT USED png_uint_32 width, height; int bit_depth, color_type, interlace_type, compression_type, filter_type; unsigned int channels; png_bytep trans = nullptr; int num_trans = 0; nsPNGDecoder* decoder = static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr)); // Always decode to 24-bit RGB or 32-bit RGBA png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, &compression_type, &filter_type); // Are we too big? if (width > MOZ_PNG_MAX_DIMENSION || height > MOZ_PNG_MAX_DIMENSION) { png_longjmp(decoder->mPNG, 1); } // Post our size to the superclass decoder->PostSize(width, height); if (decoder->HasError()) { // Setting the size led to an error. png_longjmp(decoder->mPNG, 1); } if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_expand(png_ptr); } if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { png_set_expand(png_ptr); } if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { png_color_16p trans_values; png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values); // libpng doesn't reject a tRNS chunk with out-of-range samples // so we check it here to avoid setting up a useless opacity // channel or producing unexpected transparent pixels (bug #428045) if (bit_depth < 16) { png_uint_16 sample_max = (1 << bit_depth) - 1; if ((color_type == PNG_COLOR_TYPE_GRAY && trans_values->gray > sample_max) || (color_type == PNG_COLOR_TYPE_RGB && (trans_values->red > sample_max || trans_values->green > sample_max || trans_values->blue > sample_max))) { // clear the tRNS valid flag and release tRNS memory png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0); num_trans = 0; } } if (num_trans != 0) { png_set_expand(png_ptr); } } if (bit_depth == 16) { png_set_scale_16(png_ptr); } qcms_data_type inType = QCMS_DATA_RGBA_8; uint32_t intent = -1; uint32_t pIntent; if (decoder->mCMSMode != eCMSMode_Off) { intent = gfxPlatform::GetRenderingIntent(); decoder->mInProfile = PNGGetColorProfile(png_ptr, info_ptr, color_type, &inType, &pIntent); // If we're not mandating an intent, use the one from the image. if (intent == uint32_t(-1)) { intent = pIntent; } } if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) { qcms_data_type outType; if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) { outType = QCMS_DATA_RGBA_8; } else { outType = QCMS_DATA_RGB_8; } decoder->mTransform = qcms_transform_create(decoder->mInProfile, inType, gfxPlatform::GetCMSOutputProfile(), outType, (qcms_intent)intent); } else { png_set_gray_to_rgb(png_ptr); // only do gamma correction if CMS isn't entirely disabled if (decoder->mCMSMode != eCMSMode_Off) { PNGDoGammaCorrection(png_ptr, info_ptr); } if (decoder->mCMSMode == eCMSMode_All) { if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) { decoder->mTransform = gfxPlatform::GetCMSRGBATransform(); } else { decoder->mTransform = gfxPlatform::GetCMSRGBTransform(); } } } // let libpng expand interlaced images if (interlace_type == PNG_INTERLACE_ADAM7) { // number_passes = png_set_interlace_handling(png_ptr); } // now all of those things we set above are used to update various struct // members and whatnot, after which we can get channels, rowbytes, etc. png_read_update_info(png_ptr, info_ptr); decoder->mChannels = channels = png_get_channels(png_ptr, info_ptr); //---------------------------------------------------------------// // copy PNG info into imagelib structs (formerly png_set_dims()) // //---------------------------------------------------------------// if (channels == 1 || channels == 3) { decoder->format = gfx::SurfaceFormat::B8G8R8X8; } else if (channels == 2 || channels == 4) { decoder->format = gfx::SurfaceFormat::B8G8R8A8; } else { png_longjmp(decoder->mPNG, 1); // invalid number of channels } #ifdef PNG_APNG_SUPPORTED if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) { png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback, nullptr); } if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) { decoder->mFrameIsHidden = true; } else { #endif decoder->CreateFrame(0, 0, width, height, decoder->format); #ifdef PNG_APNG_SUPPORTED } #endif if (decoder->mTransform && (channels <= 2 || interlace_type == PNG_INTERLACE_ADAM7)) { uint32_t bpp[] = { 0, 3, 4, 3, 4 }; decoder->mCMSLine = (uint8_t*)malloc(bpp[channels] * width); if (!decoder->mCMSLine) { png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY } } if (interlace_type == PNG_INTERLACE_ADAM7) { if (height < INT32_MAX / (width * channels)) { decoder->interlacebuf = (uint8_t*)malloc(channels * width * height); } if (!decoder->interlacebuf) { png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY } } if (decoder->NeedsNewFrame()) { // We know that we need a new frame, so pause input so the decoder // infrastructure can give it to us. png_process_data_pause(png_ptr, /* save = */ 1); } }
static void readAnimated( imageCache &cache, PngInfo& png ){ auto width = png.width(); auto height = png.height(); png_uint_32 x_offset=0, y_offset=0; png_uint_16 delay_num=0, delay_den=0; png_byte dispose_op = PNG_DISPOSE_OP_NONE, blend_op = PNG_BLEND_OP_SOURCE; QImage canvas( width, height, QImage::Format_ARGB32 ); canvas.fill( qRgba( 0,0,0,0 ) ); AnimCombiner combiner( canvas ); if( setjmp( png_jmpbuf( png.png ) ) ) return; unsigned repeats = png_get_num_plays( png.png, png.info ); unsigned frames = png_get_num_frames( png.png, png.info ); //NOTE: We discard the frame if it is not a part of the animation if( png_get_first_frame_is_hidden( png.png, png.info ) ){ readImage( png, width, height ); --frames; //libpng appears to tell the total amount of images } cache.set_info( frames, true, repeats>0 ? repeats-1 : -1 ); for( unsigned i=0; i < frames; ++i ){ png_read_frame_head( png.png, png.info ); if( png_get_valid( png.png, png.info, PNG_INFO_fcTL ) ){ png_get_next_frame_fcTL( png.png, png.info , &width, &height , &x_offset, &y_offset , &delay_num, &delay_den , &dispose_op, &blend_op ); } else{ width = png.width(); height = png.height(); } readImage( png, width, height, i==0 ); //Calculate delay delay_den = delay_den==0 ? 100 : delay_den; unsigned delay = std::ceil( (double)delay_num / delay_den * 1000 ); if( delay == 0 ) delay = 1; //Fastest speed we support //Compose and add auto blend_mode = blend_op == PNG_BLEND_OP_SOURCE ? BlendMode::OVERLAY : BlendMode::REPLACE; auto dispose_mode = [=](){ switch( dispose_op ){ case PNG_DISPOSE_OP_NONE: return DisposeMode::NONE; case PNG_DISPOSE_OP_BACKGROUND: return DisposeMode::BACKGROUND; case PNG_DISPOSE_OP_PREVIOUS: return DisposeMode::REVERT; default: return DisposeMode::NONE; //TODO: add error } }(); QImage output = combiner.combine( png.frame, x_offset, y_offset, blend_mode, dispose_mode ); cache.add_frame( output, delay ); } }
void nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) { /* int number_passes; NOT USED */ png_uint_32 width, height; int bit_depth, color_type, interlace_type, compression_type, filter_type; unsigned int channels; png_bytep trans = NULL; int num_trans = 0; nsPNGDecoder *decoder = static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr)); /* always decode to 24-bit RGB or 32-bit RGBA */ png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, &compression_type, &filter_type); /* Are we too big? */ if (width > MOZ_PNG_MAX_DIMENSION || height > MOZ_PNG_MAX_DIMENSION) longjmp(png_jmpbuf(decoder->mPNG), 1); // Post our size to the superclass decoder->PostSize(width, height); if (decoder->HasError()) { // Setting the size lead to an error; this can happen when for example // a multipart channel sends an image of a different size. longjmp(png_jmpbuf(decoder->mPNG), 1); } if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_expand(png_ptr); if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand(png_ptr); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { int sample_max = (1 << bit_depth); png_color_16p trans_values; png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values); /* libpng doesn't reject a tRNS chunk with out-of-range samples so we check it here to avoid setting up a useless opacity channel or producing unexpected transparent pixels when using libpng-1.2.19 through 1.2.26 (bug #428045) */ if ((color_type == PNG_COLOR_TYPE_GRAY && (int)trans_values->gray > sample_max) || (color_type == PNG_COLOR_TYPE_RGB && ((int)trans_values->red > sample_max || (int)trans_values->green > sample_max || (int)trans_values->blue > sample_max))) { /* clear the tRNS valid flag and release tRNS memory */ png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0); } else png_set_expand(png_ptr); } if (bit_depth == 16) png_set_strip_16(png_ptr); qcms_data_type inType; PRUint32 intent = -1; PRUint32 pIntent; if (decoder->mCMSMode != eCMSMode_Off) { intent = gfxPlatform::GetRenderingIntent(); decoder->mInProfile = PNGGetColorProfile(png_ptr, info_ptr, color_type, &inType, &pIntent); /* If we're not mandating an intent, use the one from the image. */ if (intent == PRUint32(-1)) intent = pIntent; } if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) { qcms_data_type outType; if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) outType = QCMS_DATA_RGBA_8; else outType = QCMS_DATA_RGB_8; decoder->mTransform = qcms_transform_create(decoder->mInProfile, inType, gfxPlatform::GetCMSOutputProfile(), outType, (qcms_intent)intent); } else { png_set_gray_to_rgb(png_ptr); // only do gamma correction if CMS isn't entirely disabled if (decoder->mCMSMode != eCMSMode_Off) PNGDoGammaCorrection(png_ptr, info_ptr); if (decoder->mCMSMode == eCMSMode_All) { if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) decoder->mTransform = gfxPlatform::GetCMSRGBATransform(); else decoder->mTransform = gfxPlatform::GetCMSRGBTransform(); } } /* let libpng expand interlaced images */ if (interlace_type == PNG_INTERLACE_ADAM7) { /* number_passes = */ png_set_interlace_handling(png_ptr); } /* now all of those things we set above are used to update various struct * members and whatnot, after which we can get channels, rowbytes, etc. */ png_read_update_info(png_ptr, info_ptr); decoder->mChannels = channels = png_get_channels(png_ptr, info_ptr); /*---------------------------------------------------------------*/ /* copy PNG info into imagelib structs (formerly png_set_dims()) */ /*---------------------------------------------------------------*/ PRInt32 alpha_bits = 1; if (channels == 2 || channels == 4) { /* check if alpha is coming from a tRNS chunk and is binary */ if (num_trans) { /* if it's not an indexed color image, tRNS means binary */ if (color_type == PNG_COLOR_TYPE_PALETTE) { for (int i=0; i<num_trans; i++) { if ((trans[i] != 0) && (trans[i] != 255)) { alpha_bits = 8; break; } } } } else { alpha_bits = 8; } } if (channels == 1 || channels == 3) decoder->format = gfxASurface::ImageFormatRGB24; else if (channels == 2 || channels == 4) decoder->format = gfxASurface::ImageFormatARGB32; #ifdef PNG_APNG_SUPPORTED if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback, NULL); if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) { decoder->mFrameIsHidden = PR_TRUE; } else { #endif decoder->CreateFrame(0, 0, width, height, decoder->format); #ifdef PNG_APNG_SUPPORTED } #endif if (decoder->mTransform && (channels <= 2 || interlace_type == PNG_INTERLACE_ADAM7)) { PRUint32 bpp[] = { 0, 3, 4, 3, 4 }; decoder->mCMSLine = (PRUint8 *)moz_malloc(bpp[channels] * width); if (!decoder->mCMSLine) { longjmp(png_jmpbuf(decoder->mPNG), 5); // NS_ERROR_OUT_OF_MEMORY } } if (interlace_type == PNG_INTERLACE_ADAM7) { if (height < PR_INT32_MAX / (width * channels)) decoder->interlacebuf = (PRUint8 *)moz_malloc(channels * width * height); if (!decoder->interlacebuf) { longjmp(png_jmpbuf(decoder->mPNG), 5); // NS_ERROR_OUT_OF_MEMORY } } /* Reject any ancillary chunk after IDAT with a bad CRC (bug #397593). * It would be better to show the default frame (if one has already been * successfully decoded) before bailing, but it's simpler to just bail * out with an error message. */ png_set_crc_action(png_ptr, PNG_CRC_NO_CHANGE, PNG_CRC_ERROR_QUIT); return; }
void* PNG_decode(JNIEnv* env, PatchHeadInputStream* patch_head_input_stream, bool partially) { PNG *png = NULL; png_structp png_ptr = NULL; png_infop info_ptr = NULL; bool apng; unsigned int width; unsigned int height; int color_type; int bit_depth; bool is_opaque; unsigned char* buffer = NULL; unsigned int frame_count = 0; bool hide_first_frame = false; PNG_FRAME_INFO* frame_info_array = NULL; int i; png = (PNG *) malloc(sizeof(PNG)); if (png == NULL) { WTF_OM; close_patch_head_input_stream(get_env(), patch_head_input_stream); destroy_patch_head_input_stream(get_env(), &patch_head_input_stream); return NULL; } png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, &user_error_fn, &user_warn_fn); if (png_ptr == NULL) { free(png); png = NULL; close_patch_head_input_stream(get_env(), patch_head_input_stream); destroy_patch_head_input_stream(get_env(), &patch_head_input_stream); return NULL; } info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { png_destroy_read_struct(&png_ptr, NULL, NULL); free(png); png = NULL; close_patch_head_input_stream(get_env(), patch_head_input_stream); destroy_patch_head_input_stream(get_env(), &patch_head_input_stream); return NULL; } if (setjmp(png_jmpbuf(png_ptr))) { LOGE(EMSG("Error in png decode")); free_frame_info_array(frame_info_array, frame_count); frame_info_array = NULL; free(buffer); buffer = NULL; png_destroy_read_struct(&png_ptr, &info_ptr, NULL); free(png); png = NULL; close_patch_head_input_stream(get_env(), patch_head_input_stream); destroy_patch_head_input_stream(get_env(), &patch_head_input_stream); return NULL; } // Set custom read function png_set_read_fn(png_ptr, patch_head_input_stream, &user_read_fn); // Get png info png_read_info(png_ptr, info_ptr); // Check apng if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) { apng = true; } else { apng = false; } // PNG info width = png_get_image_width(png_ptr, info_ptr); height = png_get_image_height(png_ptr, info_ptr); color_type = png_get_color_type(png_ptr, info_ptr); bit_depth = png_get_bit_depth(png_ptr, info_ptr); // Create buffer buffer = (unsigned char*) malloc(width * height * 4); if (buffer == NULL) { WTF_OM; png_destroy_read_struct(&png_ptr, &info_ptr, NULL); free(png); png = NULL; close_patch_head_input_stream(get_env(), patch_head_input_stream); destroy_patch_head_input_stream(get_env(), &patch_head_input_stream); return NULL; } if (apng) { // Get frame count frame_count = png_get_num_frames(png_ptr, info_ptr); hide_first_frame = png_get_first_frame_is_hidden(png_ptr, info_ptr); if (hide_first_frame) { frame_count--; } // Create frame info array frame_info_array = (PNG_FRAME_INFO*) calloc(frame_count, sizeof(PNG_FRAME_INFO)); if (frame_info_array == NULL) { WTF_OM; free(buffer); buffer = NULL; png_destroy_read_struct(&png_ptr, &info_ptr, NULL); free(png); png = NULL; close_patch_head_input_stream(get_env(), patch_head_input_stream); destroy_patch_head_input_stream(get_env(), &patch_head_input_stream); return NULL; } } // Configure to ARGB png_set_expand(png_ptr); if (bit_depth == 16) { png_set_scale_16(png_ptr); } if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { png_set_gray_to_rgb(png_ptr); } if (!(color_type & PNG_COLOR_MASK_ALPHA)) { is_opaque = true; png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER); } else { is_opaque = false; } if (apng) { if (hide_first_frame) { // Skip first frame read_image(png_ptr, buffer, width, height); } // Read first frame read_frame(png_ptr, info_ptr, frame_info_array); // Fix dop if (frame_info_array->dop == PNG_DISPOSE_OP_PREVIOUS) { frame_info_array->dop = PNG_DISPOSE_OP_BACKGROUND; } if (!partially || frame_count == 1) { // Read all frame for (i = 1; i < frame_count; read_frame(png_ptr, info_ptr, frame_info_array + i++)); // Generate pop generate_pop(frame_info_array, frame_count); // End read png_read_end(png_ptr, info_ptr); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); // Close input stream close_patch_head_input_stream(env, patch_head_input_stream); destroy_patch_head_input_stream(env, &patch_head_input_stream); png->partially = false; png->png_ptr = NULL; png->info_ptr = NULL; png->patch_head_input_stream = NULL; } else { png->partially = true; png->png_ptr = png_ptr; png->info_ptr = info_ptr; png->patch_head_input_stream = patch_head_input_stream; } // Fill PNG png->width = width; png->height = height; png->is_opaque = is_opaque; png->buffer = buffer; png->apng = true; png->buffer_index = -1; png->frame_info_array = frame_info_array; png->frame_count = frame_count; png->backup = NULL; // Render first frame PNG_advance(png); } else { read_image(png_ptr, buffer, width, height); // End read png_read_end(png_ptr, info_ptr); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); // Close input stream close_patch_head_input_stream(env, patch_head_input_stream); destroy_patch_head_input_stream(env, &patch_head_input_stream); // Fill PNG png->width = width; png->height = height; png->buffer = buffer; png->apng = false; png->buffer_index = 0; png->frame_info_array = NULL; png->frame_count = 0; png->backup = NULL; png->partially = false; png->png_ptr = NULL; png->info_ptr = NULL; png->patch_head_input_stream = NULL; } return png; }