michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ImageLogging.h" michael@0: #include "nsPNGDecoder.h" michael@0: michael@0: #include "nsMemory.h" michael@0: #include "nsRect.h" michael@0: michael@0: #include "nsIInputStream.h" michael@0: michael@0: #include "RasterImage.h" michael@0: michael@0: #include "gfxColor.h" michael@0: #include "nsColor.h" michael@0: michael@0: #include "nspr.h" michael@0: #include "png.h" michael@0: michael@0: #include "gfxPlatform.h" michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: namespace image { michael@0: michael@0: #ifdef PR_LOGGING michael@0: static PRLogModuleInfo * michael@0: GetPNGLog() michael@0: { michael@0: static PRLogModuleInfo *sPNGLog; michael@0: if (!sPNGLog) michael@0: sPNGLog = PR_NewLogModule("PNGDecoder"); michael@0: return sPNGLog; michael@0: } michael@0: michael@0: static PRLogModuleInfo * michael@0: GetPNGDecoderAccountingLog() michael@0: { michael@0: static PRLogModuleInfo *sPNGDecoderAccountingLog; michael@0: if (!sPNGDecoderAccountingLog) michael@0: sPNGDecoderAccountingLog = PR_NewLogModule("PNGDecoderAccounting"); michael@0: return sPNGDecoderAccountingLog; michael@0: } michael@0: #endif michael@0: michael@0: /* limit image dimensions (bug #251381, #591822, and #967656) */ michael@0: #ifndef MOZ_PNG_MAX_DIMENSION michael@0: # define MOZ_PNG_MAX_DIMENSION 32767 michael@0: #endif michael@0: michael@0: // For size decodes michael@0: #define WIDTH_OFFSET 16 michael@0: #define HEIGHT_OFFSET (WIDTH_OFFSET + 4) michael@0: #define BYTES_NEEDED_FOR_DIMENSIONS (HEIGHT_OFFSET + 4) michael@0: michael@0: nsPNGDecoder::AnimFrameInfo::AnimFrameInfo() michael@0: : mDispose(FrameBlender::kDisposeKeep) michael@0: , mBlend(FrameBlender::kBlendOver) michael@0: , mTimeout(0) michael@0: {} michael@0: michael@0: #ifdef PNG_APNG_SUPPORTED michael@0: nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo) michael@0: : mDispose(FrameBlender::kDisposeKeep) michael@0: , mBlend(FrameBlender::kBlendOver) michael@0: , mTimeout(0) michael@0: { michael@0: png_uint_16 delay_num, delay_den; michael@0: /* delay, in seconds is delay_num/delay_den */ michael@0: png_byte dispose_op; michael@0: png_byte blend_op; michael@0: delay_num = png_get_next_frame_delay_num(aPNG, aInfo); michael@0: delay_den = png_get_next_frame_delay_den(aPNG, aInfo); michael@0: dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo); michael@0: blend_op = png_get_next_frame_blend_op(aPNG, aInfo); michael@0: michael@0: if (delay_num == 0) { michael@0: mTimeout = 0; // SetFrameTimeout() will set to a minimum michael@0: } else { michael@0: if (delay_den == 0) michael@0: delay_den = 100; // so says the APNG spec michael@0: michael@0: // Need to cast delay_num to float to have a proper division and michael@0: // the result to int to avoid compiler warning michael@0: mTimeout = static_cast(static_cast(delay_num) * 1000 / delay_den); michael@0: } michael@0: michael@0: if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) { michael@0: mDispose = FrameBlender::kDisposeRestorePrevious; michael@0: } else if (dispose_op == PNG_DISPOSE_OP_BACKGROUND) { michael@0: mDispose = FrameBlender::kDisposeClear; michael@0: } else { michael@0: mDispose = FrameBlender::kDisposeKeep; michael@0: } michael@0: michael@0: if (blend_op == PNG_BLEND_OP_SOURCE) { michael@0: mBlend = FrameBlender::kBlendSource; michael@0: } else { michael@0: mBlend = FrameBlender::kBlendOver; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: // First 8 bytes of a PNG file michael@0: const uint8_t michael@0: nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 }; michael@0: michael@0: nsPNGDecoder::nsPNGDecoder(RasterImage &aImage) michael@0: : Decoder(aImage), michael@0: mPNG(nullptr), mInfo(nullptr), michael@0: mCMSLine(nullptr), interlacebuf(nullptr), michael@0: mInProfile(nullptr), mTransform(nullptr), michael@0: mHeaderBytesRead(0), mCMSMode(0), michael@0: mChannels(0), mFrameIsHidden(false), michael@0: mDisablePremultipliedAlpha(false), michael@0: mNumFrames(0) michael@0: { michael@0: } michael@0: michael@0: nsPNGDecoder::~nsPNGDecoder() michael@0: { michael@0: if (mPNG) michael@0: png_destroy_read_struct(&mPNG, mInfo ? &mInfo : nullptr, nullptr); michael@0: if (mCMSLine) michael@0: nsMemory::Free(mCMSLine); michael@0: if (interlacebuf) michael@0: nsMemory::Free(interlacebuf); michael@0: if (mInProfile) { michael@0: qcms_profile_release(mInProfile); michael@0: michael@0: /* mTransform belongs to us only if mInProfile is non-null */ michael@0: if (mTransform) michael@0: qcms_transform_release(mTransform); michael@0: } michael@0: } michael@0: michael@0: // CreateFrame() is used for both simple and animated images michael@0: void nsPNGDecoder::CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset, michael@0: int32_t width, int32_t height, michael@0: gfxImageFormat format) michael@0: { michael@0: // Our first full frame is automatically created by the image decoding michael@0: // infrastructure. Just use it as long as it matches up. michael@0: MOZ_ASSERT(HasSize()); michael@0: if (mNumFrames != 0 || michael@0: !GetCurrentFrame()->GetRect().IsEqualEdges(nsIntRect(x_offset, y_offset, width, height))) { michael@0: NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format); michael@0: } else if (mNumFrames == 0) { michael@0: // Our preallocated frame matches up, with the possible exception of alpha. michael@0: if (format == gfxImageFormat::RGB24) { michael@0: GetCurrentFrame()->SetHasNoAlpha(); michael@0: } michael@0: } michael@0: michael@0: mFrameRect.x = x_offset; michael@0: mFrameRect.y = y_offset; michael@0: mFrameRect.width = width; michael@0: mFrameRect.height = height; michael@0: michael@0: PR_LOG(GetPNGDecoderAccountingLog(), PR_LOG_DEBUG, michael@0: ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created " michael@0: "image frame with %dx%d pixels in container %p", michael@0: width, height, michael@0: &mImage)); michael@0: michael@0: mFrameHasNoAlpha = true; michael@0: michael@0: #ifdef PNG_APNG_SUPPORTED michael@0: if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) { michael@0: mAnimInfo = AnimFrameInfo(mPNG, mInfo); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: // set timeout and frame disposal method for the current frame michael@0: void nsPNGDecoder::EndImageFrame() michael@0: { michael@0: if (mFrameIsHidden) michael@0: return; michael@0: michael@0: mNumFrames++; michael@0: michael@0: FrameBlender::FrameAlpha alpha; michael@0: if (mFrameHasNoAlpha) michael@0: alpha = FrameBlender::kFrameOpaque; michael@0: else michael@0: alpha = FrameBlender::kFrameHasAlpha; michael@0: michael@0: #ifdef PNG_APNG_SUPPORTED michael@0: uint32_t numFrames = GetFrameCount(); michael@0: michael@0: // We can't use mPNG->num_frames_read as it may be one ahead. michael@0: if (numFrames > 1) { michael@0: PostInvalidation(mFrameRect); michael@0: } michael@0: #endif michael@0: michael@0: PostFrameStop(alpha, mAnimInfo.mDispose, mAnimInfo.mTimeout, mAnimInfo.mBlend); michael@0: } michael@0: michael@0: void michael@0: nsPNGDecoder::InitInternal() michael@0: { michael@0: // For size decodes, we don't need to initialize the png decoder michael@0: if (IsSizeDecode()) { michael@0: return; michael@0: } michael@0: michael@0: mCMSMode = gfxPlatform::GetCMSMode(); michael@0: if ((mDecodeFlags & DECODER_NO_COLORSPACE_CONVERSION) != 0) michael@0: mCMSMode = eCMSMode_Off; michael@0: mDisablePremultipliedAlpha = (mDecodeFlags & DECODER_NO_PREMULTIPLY_ALPHA) != 0; michael@0: michael@0: #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED michael@0: static png_byte color_chunks[]= michael@0: { 99, 72, 82, 77, '\0', /* cHRM */ michael@0: 105, 67, 67, 80, '\0'}; /* iCCP */ michael@0: static png_byte unused_chunks[]= michael@0: { 98, 75, 71, 68, '\0', /* bKGD */ michael@0: 104, 73, 83, 84, '\0', /* hIST */ michael@0: 105, 84, 88, 116, '\0', /* iTXt */ michael@0: 111, 70, 70, 115, '\0', /* oFFs */ michael@0: 112, 67, 65, 76, '\0', /* pCAL */ michael@0: 115, 67, 65, 76, '\0', /* sCAL */ michael@0: 112, 72, 89, 115, '\0', /* pHYs */ michael@0: 115, 66, 73, 84, '\0', /* sBIT */ michael@0: 115, 80, 76, 84, '\0', /* sPLT */ michael@0: 116, 69, 88, 116, '\0', /* tEXt */ michael@0: 116, 73, 77, 69, '\0', /* tIME */ michael@0: 122, 84, 88, 116, '\0'}; /* zTXt */ michael@0: #endif michael@0: michael@0: /* For full decodes, do png init stuff */ michael@0: michael@0: /* Initialize the container's source image header. */ michael@0: /* Always decode to 24 bit pixdepth */ michael@0: michael@0: mPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING, michael@0: nullptr, nsPNGDecoder::error_callback, michael@0: nsPNGDecoder::warning_callback); michael@0: if (!mPNG) { michael@0: PostDecoderError(NS_ERROR_OUT_OF_MEMORY); michael@0: return; michael@0: } michael@0: michael@0: mInfo = png_create_info_struct(mPNG); michael@0: if (!mInfo) { michael@0: PostDecoderError(NS_ERROR_OUT_OF_MEMORY); michael@0: png_destroy_read_struct(&mPNG, nullptr, nullptr); michael@0: return; michael@0: } michael@0: michael@0: #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED michael@0: /* Ignore unused chunks */ michael@0: if (mCMSMode == eCMSMode_Off) michael@0: png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2); michael@0: michael@0: png_set_keep_unknown_chunks(mPNG, 1, unused_chunks, michael@0: (int)sizeof(unused_chunks)/5); michael@0: #endif michael@0: michael@0: #ifdef PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED michael@0: if (mCMSMode != eCMSMode_Off) michael@0: png_set_chunk_malloc_max(mPNG, 4000000L); michael@0: #endif michael@0: michael@0: #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED michael@0: #ifndef PR_LOGGING michael@0: /* Disallow palette-index checking, for speed; we would ignore the warning michael@0: * anyhow unless we have defined PR_LOGGING. This feature was added at michael@0: * libpng version 1.5.10 and is disabled in the embedded libpng but enabled michael@0: * by default in the system libpng. This call also disables it in the michael@0: * system libpng, for decoding speed. Bug #745202. michael@0: */ michael@0: png_set_check_for_invalid_index(mPNG, 0); michael@0: #endif michael@0: #endif michael@0: michael@0: /* use this as libpng "progressive pointer" (retrieve in callbacks) */ michael@0: png_set_progressive_read_fn(mPNG, static_cast(this), michael@0: nsPNGDecoder::info_callback, michael@0: nsPNGDecoder::row_callback, michael@0: nsPNGDecoder::end_callback); michael@0: michael@0: } michael@0: michael@0: void michael@0: nsPNGDecoder::WriteInternal(const char *aBuffer, uint32_t aCount, DecodeStrategy) michael@0: { michael@0: NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!"); michael@0: michael@0: // If we only want width/height, we don't need to go through libpng michael@0: if (IsSizeDecode()) { michael@0: michael@0: // Are we done? michael@0: if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) michael@0: return; michael@0: michael@0: // Scan the header for the width and height bytes michael@0: uint32_t pos = 0; michael@0: const uint8_t *bptr = (uint8_t *)aBuffer; michael@0: michael@0: while (pos < aCount && mHeaderBytesRead < BYTES_NEEDED_FOR_DIMENSIONS) { michael@0: // Verify the signature bytes michael@0: if (mHeaderBytesRead < sizeof(pngSignatureBytes)) { michael@0: if (bptr[pos] != nsPNGDecoder::pngSignatureBytes[mHeaderBytesRead]) { michael@0: PostDataError(); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Get width and height bytes into the buffer michael@0: if ((mHeaderBytesRead >= WIDTH_OFFSET) && michael@0: (mHeaderBytesRead < BYTES_NEEDED_FOR_DIMENSIONS)) { michael@0: mSizeBytes[mHeaderBytesRead - WIDTH_OFFSET] = bptr[pos]; michael@0: } michael@0: pos ++; michael@0: mHeaderBytesRead ++; michael@0: } michael@0: michael@0: // If we're done now, verify the data and set up the container michael@0: if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) { michael@0: michael@0: // Grab the width and height, accounting for endianness (thanks libpng!) michael@0: uint32_t width = png_get_uint_32(mSizeBytes); michael@0: uint32_t height = png_get_uint_32(mSizeBytes + 4); michael@0: michael@0: // Too big? michael@0: if ((width > MOZ_PNG_MAX_DIMENSION) || (height > MOZ_PNG_MAX_DIMENSION)) { michael@0: PostDataError(); michael@0: return; michael@0: } michael@0: michael@0: // Post our size to the superclass michael@0: PostSize(width, height); michael@0: } michael@0: } michael@0: michael@0: // Otherwise, we're doing a standard decode michael@0: else { michael@0: michael@0: // libpng uses setjmp/longjmp for error handling - set the buffer michael@0: if (setjmp(png_jmpbuf(mPNG))) { michael@0: michael@0: // We might not really know what caused the error, but it makes more michael@0: // sense to blame the data. michael@0: if (!HasError()) michael@0: PostDataError(); michael@0: michael@0: png_destroy_read_struct(&mPNG, &mInfo, nullptr); michael@0: return; michael@0: } michael@0: michael@0: // Pass the data off to libpng michael@0: png_process_data(mPNG, mInfo, (unsigned char *)aBuffer, aCount); michael@0: michael@0: } michael@0: } michael@0: michael@0: // Sets up gamma pre-correction in libpng before our callback gets called. michael@0: // We need to do this if we don't end up with a CMS profile. michael@0: static void michael@0: PNGDoGammaCorrection(png_structp png_ptr, png_infop info_ptr) michael@0: { michael@0: double aGamma; michael@0: michael@0: if (png_get_gAMA(png_ptr, info_ptr, &aGamma)) { michael@0: if ((aGamma <= 0.0) || (aGamma > 21474.83)) { michael@0: aGamma = 0.45455; michael@0: png_set_gAMA(png_ptr, info_ptr, aGamma); michael@0: } michael@0: png_set_gamma(png_ptr, 2.2, aGamma); michael@0: } michael@0: else michael@0: png_set_gamma(png_ptr, 2.2, 0.45455); michael@0: michael@0: } michael@0: michael@0: // Adapted from http://www.littlecms.com/pngchrm.c example code michael@0: static qcms_profile * michael@0: PNGGetColorProfile(png_structp png_ptr, png_infop info_ptr, michael@0: int color_type, qcms_data_type *inType, uint32_t *intent) michael@0: { michael@0: qcms_profile *profile = nullptr; michael@0: *intent = QCMS_INTENT_PERCEPTUAL; // Our default michael@0: michael@0: // First try to see if iCCP chunk is present michael@0: if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) { michael@0: png_uint_32 profileLen; michael@0: #if (PNG_LIBPNG_VER < 10500) michael@0: char *profileData, *profileName; michael@0: #else michael@0: png_bytep profileData; michael@0: png_charp profileName; michael@0: #endif michael@0: int compression; michael@0: michael@0: png_get_iCCP(png_ptr, info_ptr, &profileName, &compression, michael@0: &profileData, &profileLen); michael@0: michael@0: profile = qcms_profile_from_memory( michael@0: #if (PNG_LIBPNG_VER < 10500) michael@0: profileData, michael@0: #else michael@0: (char *)profileData, michael@0: #endif michael@0: profileLen); michael@0: if (profile) { michael@0: uint32_t profileSpace = qcms_profile_get_color_space(profile); michael@0: michael@0: bool mismatch = false; michael@0: if (color_type & PNG_COLOR_MASK_COLOR) { michael@0: if (profileSpace != icSigRgbData) michael@0: mismatch = true; michael@0: } else { michael@0: if (profileSpace == icSigRgbData) michael@0: png_set_gray_to_rgb(png_ptr); michael@0: else if (profileSpace != icSigGrayData) michael@0: mismatch = true; michael@0: } michael@0: michael@0: if (mismatch) { michael@0: qcms_profile_release(profile); michael@0: profile = nullptr; michael@0: } else { michael@0: *intent = qcms_profile_get_rendering_intent(profile); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Check sRGB chunk michael@0: if (!profile && png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { michael@0: profile = qcms_profile_sRGB(); michael@0: michael@0: if (profile) { michael@0: int fileIntent; michael@0: png_set_gray_to_rgb(png_ptr); michael@0: png_get_sRGB(png_ptr, info_ptr, &fileIntent); michael@0: uint32_t map[] = { QCMS_INTENT_PERCEPTUAL, michael@0: QCMS_INTENT_RELATIVE_COLORIMETRIC, michael@0: QCMS_INTENT_SATURATION, michael@0: QCMS_INTENT_ABSOLUTE_COLORIMETRIC }; michael@0: *intent = map[fileIntent]; michael@0: } michael@0: } michael@0: michael@0: // Check gAMA/cHRM chunks michael@0: if (!profile && michael@0: png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) && michael@0: png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM)) { michael@0: qcms_CIE_xyYTRIPLE primaries; michael@0: qcms_CIE_xyY whitePoint; michael@0: michael@0: png_get_cHRM(png_ptr, info_ptr, michael@0: &whitePoint.x, &whitePoint.y, michael@0: &primaries.red.x, &primaries.red.y, michael@0: &primaries.green.x, &primaries.green.y, michael@0: &primaries.blue.x, &primaries.blue.y); michael@0: whitePoint.Y = michael@0: primaries.red.Y = primaries.green.Y = primaries.blue.Y = 1.0; michael@0: michael@0: double gammaOfFile; michael@0: michael@0: png_get_gAMA(png_ptr, info_ptr, &gammaOfFile); michael@0: michael@0: profile = qcms_profile_create_rgb_with_gamma(whitePoint, primaries, michael@0: 1.0/gammaOfFile); michael@0: michael@0: if (profile) michael@0: png_set_gray_to_rgb(png_ptr); michael@0: } michael@0: michael@0: if (profile) { michael@0: uint32_t profileSpace = qcms_profile_get_color_space(profile); michael@0: if (profileSpace == icSigGrayData) { michael@0: if (color_type & PNG_COLOR_MASK_ALPHA) michael@0: *inType = QCMS_DATA_GRAYA_8; michael@0: else michael@0: *inType = QCMS_DATA_GRAY_8; michael@0: } else { michael@0: if (color_type & PNG_COLOR_MASK_ALPHA || michael@0: png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) michael@0: *inType = QCMS_DATA_RGBA_8; michael@0: else michael@0: *inType = QCMS_DATA_RGB_8; michael@0: } michael@0: } michael@0: michael@0: return profile; michael@0: } michael@0: michael@0: void michael@0: nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) michael@0: { michael@0: /* int number_passes; NOT USED */ michael@0: png_uint_32 width, height; michael@0: int bit_depth, color_type, interlace_type, compression_type, filter_type; michael@0: unsigned int channels; michael@0: michael@0: png_bytep trans = nullptr; michael@0: int num_trans = 0; michael@0: michael@0: nsPNGDecoder *decoder = michael@0: static_cast(png_get_progressive_ptr(png_ptr)); michael@0: michael@0: /* always decode to 24-bit RGB or 32-bit RGBA */ michael@0: png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, michael@0: &interlace_type, &compression_type, &filter_type); michael@0: michael@0: /* Are we too big? */ michael@0: if (width > MOZ_PNG_MAX_DIMENSION || height > MOZ_PNG_MAX_DIMENSION) michael@0: longjmp(png_jmpbuf(decoder->mPNG), 1); michael@0: michael@0: // Post our size to the superclass michael@0: decoder->PostSize(width, height); michael@0: if (decoder->HasError()) { michael@0: // Setting the size led to an error. michael@0: longjmp(png_jmpbuf(decoder->mPNG), 1); michael@0: } michael@0: michael@0: if (color_type == PNG_COLOR_TYPE_PALETTE) michael@0: png_set_expand(png_ptr); michael@0: michael@0: if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) michael@0: png_set_expand(png_ptr); michael@0: michael@0: if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { michael@0: int sample_max = (1 << bit_depth); michael@0: png_color_16p trans_values; michael@0: png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values); michael@0: /* libpng doesn't reject a tRNS chunk with out-of-range samples michael@0: so we check it here to avoid setting up a useless opacity michael@0: channel or producing unexpected transparent pixels when using michael@0: libpng-1.2.19 through 1.2.26 (bug #428045) */ michael@0: if ((color_type == PNG_COLOR_TYPE_GRAY && michael@0: (int)trans_values->gray > sample_max) || michael@0: (color_type == PNG_COLOR_TYPE_RGB && michael@0: ((int)trans_values->red > sample_max || michael@0: (int)trans_values->green > sample_max || michael@0: (int)trans_values->blue > sample_max))) michael@0: { michael@0: /* clear the tRNS valid flag and release tRNS memory */ michael@0: png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0); michael@0: } michael@0: else michael@0: png_set_expand(png_ptr); michael@0: } michael@0: michael@0: if (bit_depth == 16) michael@0: png_set_scale_16(png_ptr); michael@0: michael@0: qcms_data_type inType = QCMS_DATA_RGBA_8; michael@0: uint32_t intent = -1; michael@0: uint32_t pIntent; michael@0: if (decoder->mCMSMode != eCMSMode_Off) { michael@0: intent = gfxPlatform::GetRenderingIntent(); michael@0: decoder->mInProfile = PNGGetColorProfile(png_ptr, info_ptr, michael@0: color_type, &inType, &pIntent); michael@0: /* If we're not mandating an intent, use the one from the image. */ michael@0: if (intent == uint32_t(-1)) michael@0: intent = pIntent; michael@0: } michael@0: if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) { michael@0: qcms_data_type outType; michael@0: michael@0: if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) michael@0: outType = QCMS_DATA_RGBA_8; michael@0: else michael@0: outType = QCMS_DATA_RGB_8; michael@0: michael@0: decoder->mTransform = qcms_transform_create(decoder->mInProfile, michael@0: inType, michael@0: gfxPlatform::GetCMSOutputProfile(), michael@0: outType, michael@0: (qcms_intent)intent); michael@0: } else { michael@0: png_set_gray_to_rgb(png_ptr); michael@0: michael@0: // only do gamma correction if CMS isn't entirely disabled michael@0: if (decoder->mCMSMode != eCMSMode_Off) michael@0: PNGDoGammaCorrection(png_ptr, info_ptr); michael@0: michael@0: if (decoder->mCMSMode == eCMSMode_All) { michael@0: if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) michael@0: decoder->mTransform = gfxPlatform::GetCMSRGBATransform(); michael@0: else michael@0: decoder->mTransform = gfxPlatform::GetCMSRGBTransform(); michael@0: } michael@0: } michael@0: michael@0: /* let libpng expand interlaced images */ michael@0: if (interlace_type == PNG_INTERLACE_ADAM7) { michael@0: /* number_passes = */ michael@0: png_set_interlace_handling(png_ptr); michael@0: } michael@0: michael@0: /* now all of those things we set above are used to update various struct michael@0: * members and whatnot, after which we can get channels, rowbytes, etc. */ michael@0: png_read_update_info(png_ptr, info_ptr); michael@0: decoder->mChannels = channels = png_get_channels(png_ptr, info_ptr); michael@0: michael@0: /*---------------------------------------------------------------*/ michael@0: /* copy PNG info into imagelib structs (formerly png_set_dims()) */ michael@0: /*---------------------------------------------------------------*/ michael@0: michael@0: // This code is currently unused, but it will be needed for bug 517713. michael@0: #if 0 michael@0: int32_t alpha_bits = 1; michael@0: michael@0: if (channels == 2 || channels == 4) { michael@0: /* check if alpha is coming from a tRNS chunk and is binary */ michael@0: if (num_trans) { michael@0: /* if it's not an indexed color image, tRNS means binary */ michael@0: if (color_type == PNG_COLOR_TYPE_PALETTE) { michael@0: for (int i=0; iformat = gfxImageFormat::RGB24; michael@0: else if (channels == 2 || channels == 4) michael@0: decoder->format = gfxImageFormat::ARGB32; michael@0: michael@0: #ifdef PNG_APNG_SUPPORTED michael@0: if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) michael@0: png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback, michael@0: nullptr); michael@0: michael@0: if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) { michael@0: decoder->mFrameIsHidden = true; michael@0: } else { michael@0: #endif michael@0: decoder->CreateFrame(0, 0, width, height, decoder->format); michael@0: #ifdef PNG_APNG_SUPPORTED michael@0: } michael@0: #endif michael@0: michael@0: if (decoder->mTransform && michael@0: (channels <= 2 || interlace_type == PNG_INTERLACE_ADAM7)) { michael@0: uint32_t bpp[] = { 0, 3, 4, 3, 4 }; michael@0: decoder->mCMSLine = michael@0: (uint8_t *)moz_malloc(bpp[channels] * width); michael@0: if (!decoder->mCMSLine) { michael@0: longjmp(png_jmpbuf(decoder->mPNG), 5); // NS_ERROR_OUT_OF_MEMORY michael@0: } michael@0: } michael@0: michael@0: if (interlace_type == PNG_INTERLACE_ADAM7) { michael@0: if (height < INT32_MAX / (width * channels)) michael@0: decoder->interlacebuf = (uint8_t *)moz_malloc(channels * width * height); michael@0: if (!decoder->interlacebuf) { michael@0: longjmp(png_jmpbuf(decoder->mPNG), 5); // NS_ERROR_OUT_OF_MEMORY michael@0: } michael@0: } michael@0: michael@0: if (decoder->NeedsNewFrame()) { michael@0: /* We know that we need a new frame, so pause input so the decoder michael@0: * infrastructure can give it to us. michael@0: */ michael@0: png_process_data_pause(png_ptr, /* save = */ 1); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row, michael@0: png_uint_32 row_num, int pass) michael@0: { michael@0: /* libpng comments: michael@0: * michael@0: * this function is called for every row in the image. If the michael@0: * image is interlacing, and you turned on the interlace handler, michael@0: * this function will be called for every row in every pass. michael@0: * Some of these rows will not be changed from the previous pass. michael@0: * When the row is not changed, the new_row variable will be michael@0: * nullptr. The rows and passes are called in order, so you don't michael@0: * really need the row_num and pass, but I'm supplying them michael@0: * because it may make your life easier. michael@0: * michael@0: * For the non-nullptr rows of interlaced images, you must call michael@0: * png_progressive_combine_row() passing in the row and the michael@0: * old row. You can call this function for nullptr rows (it will michael@0: * just return) and for non-interlaced images (it just does the michael@0: * memcpy for you) if it will make the code easier. Thus, you michael@0: * can just do this for all cases: michael@0: * michael@0: * png_progressive_combine_row(png_ptr, old_row, new_row); michael@0: * michael@0: * where old_row is what was displayed for previous rows. Note michael@0: * that the first pass (pass == 0 really) will completely cover michael@0: * the old row, so the rows do not have to be initialized. After michael@0: * the first pass (and only for interlaced images), you will have michael@0: * to pass the current row, and the function will combine the michael@0: * old row and the new row. michael@0: */ michael@0: nsPNGDecoder *decoder = michael@0: static_cast(png_get_progressive_ptr(png_ptr)); michael@0: michael@0: // skip this frame michael@0: if (decoder->mFrameIsHidden) michael@0: return; michael@0: michael@0: if (row_num >= (png_uint_32) decoder->mFrameRect.height) michael@0: return; michael@0: michael@0: if (new_row) { michael@0: int32_t width = decoder->mFrameRect.width; michael@0: uint32_t iwidth = decoder->mFrameRect.width; michael@0: michael@0: png_bytep line = new_row; michael@0: if (decoder->interlacebuf) { michael@0: line = decoder->interlacebuf + (row_num * decoder->mChannels * width); michael@0: png_progressive_combine_row(png_ptr, line, new_row); michael@0: } michael@0: michael@0: uint32_t bpr = width * sizeof(uint32_t); michael@0: uint32_t *cptr32 = (uint32_t*)(decoder->mImageData + (row_num*bpr)); michael@0: bool rowHasNoAlpha = true; michael@0: michael@0: if (decoder->mTransform) { michael@0: if (decoder->mCMSLine) { michael@0: qcms_transform_data(decoder->mTransform, line, decoder->mCMSLine, michael@0: iwidth); michael@0: /* copy alpha over */ michael@0: uint32_t channels = decoder->mChannels; michael@0: if (channels == 2 || channels == 4) { michael@0: for (uint32_t i = 0; i < iwidth; i++) michael@0: decoder->mCMSLine[4 * i + 3] = line[channels * i + channels - 1]; michael@0: } michael@0: line = decoder->mCMSLine; michael@0: } else { michael@0: qcms_transform_data(decoder->mTransform, line, line, iwidth); michael@0: } michael@0: } michael@0: michael@0: switch (decoder->format) { michael@0: case gfxImageFormat::RGB24: michael@0: { michael@0: // counter for while() loops below michael@0: uint32_t idx = iwidth; michael@0: michael@0: // copy as bytes until source pointer is 32-bit-aligned michael@0: for (; (NS_PTR_TO_UINT32(line) & 0x3) && idx; --idx) { michael@0: *cptr32++ = gfxPackedPixel(0xFF, line[0], line[1], line[2]); michael@0: line += 3; michael@0: } michael@0: michael@0: // copy pixels in blocks of 4 michael@0: while (idx >= 4) { michael@0: GFX_BLOCK_RGB_TO_FRGB(line, cptr32); michael@0: idx -= 4; michael@0: line += 12; michael@0: cptr32 += 4; michael@0: } michael@0: michael@0: // copy remaining pixel(s) michael@0: while (idx--) { michael@0: // 32-bit read of final pixel will exceed buffer, so read bytes michael@0: *cptr32++ = gfxPackedPixel(0xFF, line[0], line[1], line[2]); michael@0: line += 3; michael@0: } michael@0: } michael@0: break; michael@0: case gfxImageFormat::ARGB32: michael@0: { michael@0: if (!decoder->mDisablePremultipliedAlpha) { michael@0: for (uint32_t x=width; x>0; --x) { michael@0: *cptr32++ = gfxPackedPixel(line[3], line[0], line[1], line[2]); michael@0: if (line[3] != 0xff) michael@0: rowHasNoAlpha = false; michael@0: line += 4; michael@0: } michael@0: } else { michael@0: for (uint32_t x=width; x>0; --x) { michael@0: *cptr32++ = gfxPackedPixelNoPreMultiply(line[3], line[0], line[1], line[2]); michael@0: if (line[3] != 0xff) michael@0: rowHasNoAlpha = false; michael@0: line += 4; michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: default: michael@0: longjmp(png_jmpbuf(decoder->mPNG), 1); michael@0: } michael@0: michael@0: if (!rowHasNoAlpha) michael@0: decoder->mFrameHasNoAlpha = false; michael@0: michael@0: if (decoder->mNumFrames <= 1) { michael@0: // Only do incremental image display for the first frame michael@0: // XXXbholley - this check should be handled in the superclass michael@0: nsIntRect r(0, row_num, width, 1); michael@0: decoder->PostInvalidation(r); michael@0: } michael@0: } michael@0: } michael@0: michael@0: #ifdef PNG_APNG_SUPPORTED michael@0: // got the header of a new frame that's coming michael@0: void michael@0: nsPNGDecoder::frame_info_callback(png_structp png_ptr, png_uint_32 frame_num) michael@0: { michael@0: png_uint_32 x_offset, y_offset; michael@0: int32_t width, height; michael@0: michael@0: nsPNGDecoder *decoder = michael@0: static_cast(png_get_progressive_ptr(png_ptr)); michael@0: michael@0: // old frame is done michael@0: decoder->EndImageFrame(); michael@0: michael@0: // Only the first frame can be hidden, so unhide unconditionally here. michael@0: decoder->mFrameIsHidden = false; michael@0: michael@0: x_offset = png_get_next_frame_x_offset(png_ptr, decoder->mInfo); michael@0: y_offset = png_get_next_frame_y_offset(png_ptr, decoder->mInfo); michael@0: width = png_get_next_frame_width(png_ptr, decoder->mInfo); michael@0: height = png_get_next_frame_height(png_ptr, decoder->mInfo); michael@0: michael@0: decoder->CreateFrame(x_offset, y_offset, width, height, decoder->format); michael@0: michael@0: if (decoder->NeedsNewFrame()) { michael@0: /* We know that we need a new frame, so pause input so the decoder michael@0: * infrastructure can give it to us. michael@0: */ michael@0: png_process_data_pause(png_ptr, /* save = */ 1); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: nsPNGDecoder::end_callback(png_structp png_ptr, png_infop info_ptr) michael@0: { michael@0: /* libpng comments: michael@0: * michael@0: * this function is called when the whole image has been read, michael@0: * including any chunks after the image (up to and including michael@0: * the IEND). You will usually have the same info chunk as you michael@0: * had in the header, although some data may have been added michael@0: * to the comments and time fields. michael@0: * michael@0: * Most people won't do much here, perhaps setting a flag that michael@0: * marks the image as finished. michael@0: */ michael@0: michael@0: nsPNGDecoder *decoder = michael@0: static_cast(png_get_progressive_ptr(png_ptr)); michael@0: michael@0: // We shouldn't get here if we've hit an error michael@0: NS_ABORT_IF_FALSE(!decoder->HasError(), "Finishing up PNG but hit error!"); michael@0: michael@0: int32_t loop_count = 0; michael@0: #ifdef PNG_APNG_SUPPORTED michael@0: if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) { michael@0: int32_t num_plays = png_get_num_plays(png_ptr, info_ptr); michael@0: loop_count = num_plays - 1; michael@0: } michael@0: #endif michael@0: michael@0: // Send final notifications michael@0: decoder->EndImageFrame(); michael@0: decoder->PostDecodeDone(loop_count); michael@0: } michael@0: michael@0: michael@0: void michael@0: nsPNGDecoder::error_callback(png_structp png_ptr, png_const_charp error_msg) michael@0: { michael@0: PR_LOG(GetPNGLog(), PR_LOG_ERROR, ("libpng error: %s\n", error_msg)); michael@0: longjmp(png_jmpbuf(png_ptr), 1); michael@0: } michael@0: michael@0: michael@0: void michael@0: nsPNGDecoder::warning_callback(png_structp png_ptr, png_const_charp warning_msg) michael@0: { michael@0: PR_LOG(GetPNGLog(), PR_LOG_WARNING, ("libpng warning: %s\n", warning_msg)); michael@0: } michael@0: michael@0: Telemetry::ID michael@0: nsPNGDecoder::SpeedHistogram() michael@0: { michael@0: return Telemetry::IMAGE_DECODE_SPEED_PNG; michael@0: } michael@0: michael@0: michael@0: } // namespace image michael@0: } // namespace mozilla