image/decoders/nsPNGDecoder.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     2  *
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "ImageLogging.h"
     8 #include "nsPNGDecoder.h"
    10 #include "nsMemory.h"
    11 #include "nsRect.h"
    13 #include "nsIInputStream.h"
    15 #include "RasterImage.h"
    17 #include "gfxColor.h"
    18 #include "nsColor.h"
    20 #include "nspr.h"
    21 #include "png.h"
    23 #include "gfxPlatform.h"
    24 #include <algorithm>
    26 namespace mozilla {
    27 namespace image {
    29 #ifdef PR_LOGGING
    30 static PRLogModuleInfo *
    31 GetPNGLog()
    32 {
    33   static PRLogModuleInfo *sPNGLog;
    34   if (!sPNGLog)
    35     sPNGLog = PR_NewLogModule("PNGDecoder");
    36   return sPNGLog;
    37 }
    39 static PRLogModuleInfo *
    40 GetPNGDecoderAccountingLog()
    41 {
    42   static PRLogModuleInfo *sPNGDecoderAccountingLog;
    43   if (!sPNGDecoderAccountingLog)
    44     sPNGDecoderAccountingLog = PR_NewLogModule("PNGDecoderAccounting");
    45   return sPNGDecoderAccountingLog;
    46 }
    47 #endif
    49 /* limit image dimensions (bug #251381, #591822, and #967656) */
    50 #ifndef MOZ_PNG_MAX_DIMENSION
    51 #  define MOZ_PNG_MAX_DIMENSION 32767
    52 #endif
    54 // For size decodes
    55 #define WIDTH_OFFSET 16
    56 #define HEIGHT_OFFSET (WIDTH_OFFSET + 4)
    57 #define BYTES_NEEDED_FOR_DIMENSIONS (HEIGHT_OFFSET + 4)
    59 nsPNGDecoder::AnimFrameInfo::AnimFrameInfo()
    60  : mDispose(FrameBlender::kDisposeKeep)
    61  , mBlend(FrameBlender::kBlendOver)
    62  , mTimeout(0)
    63 {}
    65 #ifdef PNG_APNG_SUPPORTED
    66 nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo)
    67  : mDispose(FrameBlender::kDisposeKeep)
    68  , mBlend(FrameBlender::kBlendOver)
    69  , mTimeout(0)
    70 {
    71   png_uint_16 delay_num, delay_den;
    72   /* delay, in seconds is delay_num/delay_den */
    73   png_byte dispose_op;
    74   png_byte blend_op;
    75   delay_num = png_get_next_frame_delay_num(aPNG, aInfo);
    76   delay_den = png_get_next_frame_delay_den(aPNG, aInfo);
    77   dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo);
    78   blend_op = png_get_next_frame_blend_op(aPNG, aInfo);
    80   if (delay_num == 0) {
    81     mTimeout = 0; // SetFrameTimeout() will set to a minimum
    82   } else {
    83     if (delay_den == 0)
    84       delay_den = 100; // so says the APNG spec
    86     // Need to cast delay_num to float to have a proper division and
    87     // the result to int to avoid compiler warning
    88     mTimeout = static_cast<int32_t>(static_cast<double>(delay_num) * 1000 / delay_den);
    89   }
    91   if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) {
    92     mDispose = FrameBlender::kDisposeRestorePrevious;
    93   } else if (dispose_op == PNG_DISPOSE_OP_BACKGROUND) {
    94     mDispose = FrameBlender::kDisposeClear;
    95   } else {
    96     mDispose = FrameBlender::kDisposeKeep;
    97   }
    99   if (blend_op == PNG_BLEND_OP_SOURCE) {
   100     mBlend = FrameBlender::kBlendSource;
   101   } else {
   102     mBlend = FrameBlender::kBlendOver;
   103   }
   104 }
   105 #endif
   107 // First 8 bytes of a PNG file
   108 const uint8_t
   109 nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
   111 nsPNGDecoder::nsPNGDecoder(RasterImage &aImage)
   112  : Decoder(aImage),
   113    mPNG(nullptr), mInfo(nullptr),
   114    mCMSLine(nullptr), interlacebuf(nullptr),
   115    mInProfile(nullptr), mTransform(nullptr),
   116    mHeaderBytesRead(0), mCMSMode(0),
   117    mChannels(0), mFrameIsHidden(false),
   118    mDisablePremultipliedAlpha(false),
   119    mNumFrames(0)
   120 {
   121 }
   123 nsPNGDecoder::~nsPNGDecoder()
   124 {
   125   if (mPNG)
   126     png_destroy_read_struct(&mPNG, mInfo ? &mInfo : nullptr, nullptr);
   127   if (mCMSLine)
   128     nsMemory::Free(mCMSLine);
   129   if (interlacebuf)
   130     nsMemory::Free(interlacebuf);
   131   if (mInProfile) {
   132     qcms_profile_release(mInProfile);
   134     /* mTransform belongs to us only if mInProfile is non-null */
   135     if (mTransform)
   136       qcms_transform_release(mTransform);
   137   }
   138 }
   140 // CreateFrame() is used for both simple and animated images
   141 void nsPNGDecoder::CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
   142                                int32_t width, int32_t height,
   143                                gfxImageFormat format)
   144 {
   145   // Our first full frame is automatically created by the image decoding
   146   // infrastructure. Just use it as long as it matches up.
   147   MOZ_ASSERT(HasSize());
   148   if (mNumFrames != 0 ||
   149       !GetCurrentFrame()->GetRect().IsEqualEdges(nsIntRect(x_offset, y_offset, width, height))) {
   150     NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format);
   151   } else if (mNumFrames == 0) {
   152     // Our preallocated frame matches up, with the possible exception of alpha.
   153     if (format == gfxImageFormat::RGB24) {
   154       GetCurrentFrame()->SetHasNoAlpha();
   155     }
   156   }
   158   mFrameRect.x = x_offset;
   159   mFrameRect.y = y_offset;
   160   mFrameRect.width = width;
   161   mFrameRect.height = height;
   163   PR_LOG(GetPNGDecoderAccountingLog(), PR_LOG_DEBUG,
   164          ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created "
   165           "image frame with %dx%d pixels in container %p",
   166           width, height,
   167           &mImage));
   169   mFrameHasNoAlpha = true;
   171 #ifdef PNG_APNG_SUPPORTED
   172   if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) {
   173     mAnimInfo = AnimFrameInfo(mPNG, mInfo);
   174   }
   175 #endif
   176 }
   178 // set timeout and frame disposal method for the current frame
   179 void nsPNGDecoder::EndImageFrame()
   180 {
   181   if (mFrameIsHidden)
   182     return;
   184   mNumFrames++;
   186   FrameBlender::FrameAlpha alpha;
   187   if (mFrameHasNoAlpha)
   188     alpha = FrameBlender::kFrameOpaque;
   189   else
   190     alpha = FrameBlender::kFrameHasAlpha;
   192 #ifdef PNG_APNG_SUPPORTED
   193   uint32_t numFrames = GetFrameCount();
   195   // We can't use mPNG->num_frames_read as it may be one ahead.
   196   if (numFrames > 1) {
   197     PostInvalidation(mFrameRect);
   198   }
   199 #endif
   201   PostFrameStop(alpha, mAnimInfo.mDispose, mAnimInfo.mTimeout, mAnimInfo.mBlend);
   202 }
   204 void
   205 nsPNGDecoder::InitInternal()
   206 {
   207   // For size decodes, we don't need to initialize the png decoder
   208   if (IsSizeDecode()) {
   209     return;
   210   }
   212   mCMSMode = gfxPlatform::GetCMSMode();
   213   if ((mDecodeFlags & DECODER_NO_COLORSPACE_CONVERSION) != 0)
   214     mCMSMode = eCMSMode_Off;
   215   mDisablePremultipliedAlpha = (mDecodeFlags & DECODER_NO_PREMULTIPLY_ALPHA) != 0;
   217 #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
   218   static png_byte color_chunks[]=
   219        { 99,  72,  82,  77, '\0',   /* cHRM */
   220         105,  67,  67,  80, '\0'};  /* iCCP */
   221   static png_byte unused_chunks[]=
   222        { 98,  75,  71,  68, '\0',   /* bKGD */
   223         104,  73,  83,  84, '\0',   /* hIST */
   224         105,  84,  88, 116, '\0',   /* iTXt */
   225         111,  70,  70, 115, '\0',   /* oFFs */
   226         112,  67,  65,  76, '\0',   /* pCAL */
   227         115,  67,  65,  76, '\0',   /* sCAL */
   228         112,  72,  89, 115, '\0',   /* pHYs */
   229         115,  66,  73,  84, '\0',   /* sBIT */
   230         115,  80,  76,  84, '\0',   /* sPLT */
   231         116,  69,  88, 116, '\0',   /* tEXt */
   232         116,  73,  77,  69, '\0',   /* tIME */
   233         122,  84,  88, 116, '\0'};  /* zTXt */
   234 #endif
   236   /* For full decodes, do png init stuff */
   238   /* Initialize the container's source image header. */
   239   /* Always decode to 24 bit pixdepth */
   241   mPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING,
   242                                 nullptr, nsPNGDecoder::error_callback,
   243                                 nsPNGDecoder::warning_callback);
   244   if (!mPNG) {
   245     PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
   246     return;
   247   }
   249   mInfo = png_create_info_struct(mPNG);
   250   if (!mInfo) {
   251     PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
   252     png_destroy_read_struct(&mPNG, nullptr, nullptr);
   253     return;
   254   }
   256 #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
   257   /* Ignore unused chunks */
   258   if (mCMSMode == eCMSMode_Off)
   259     png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2);
   261   png_set_keep_unknown_chunks(mPNG, 1, unused_chunks,
   262                               (int)sizeof(unused_chunks)/5);
   263 #endif
   265 #ifdef PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED
   266   if (mCMSMode != eCMSMode_Off)
   267     png_set_chunk_malloc_max(mPNG, 4000000L);
   268 #endif
   270 #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED
   271 #ifndef PR_LOGGING
   272   /* Disallow palette-index checking, for speed; we would ignore the warning
   273    * anyhow unless we have defined PR_LOGGING.  This feature was added at
   274    * libpng version 1.5.10 and is disabled in the embedded libpng but enabled
   275    * by default in the system libpng.  This call also disables it in the
   276    * system libpng, for decoding speed.  Bug #745202.
   277    */
   278     png_set_check_for_invalid_index(mPNG, 0);
   279 #endif
   280 #endif
   282   /* use this as libpng "progressive pointer" (retrieve in callbacks) */
   283   png_set_progressive_read_fn(mPNG, static_cast<png_voidp>(this),
   284                               nsPNGDecoder::info_callback,
   285                               nsPNGDecoder::row_callback,
   286                               nsPNGDecoder::end_callback);
   288 }
   290 void
   291 nsPNGDecoder::WriteInternal(const char *aBuffer, uint32_t aCount, DecodeStrategy)
   292 {
   293   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
   295   // If we only want width/height, we don't need to go through libpng
   296   if (IsSizeDecode()) {
   298     // Are we done?
   299     if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS)
   300       return;
   302     // Scan the header for the width and height bytes
   303     uint32_t pos = 0;
   304     const uint8_t *bptr = (uint8_t *)aBuffer;
   306     while (pos < aCount && mHeaderBytesRead < BYTES_NEEDED_FOR_DIMENSIONS) {
   307       // Verify the signature bytes
   308       if (mHeaderBytesRead < sizeof(pngSignatureBytes)) {
   309         if (bptr[pos] != nsPNGDecoder::pngSignatureBytes[mHeaderBytesRead]) {
   310           PostDataError();
   311           return;
   312         }
   313       }
   315       // Get width and height bytes into the buffer
   316       if ((mHeaderBytesRead >= WIDTH_OFFSET) &&
   317           (mHeaderBytesRead < BYTES_NEEDED_FOR_DIMENSIONS)) {
   318         mSizeBytes[mHeaderBytesRead - WIDTH_OFFSET] = bptr[pos];
   319       }
   320       pos ++;
   321       mHeaderBytesRead ++;
   322     }
   324     // If we're done now, verify the data and set up the container
   325     if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) {
   327       // Grab the width and height, accounting for endianness (thanks libpng!)
   328       uint32_t width = png_get_uint_32(mSizeBytes);
   329       uint32_t height = png_get_uint_32(mSizeBytes + 4);
   331       // Too big?
   332       if ((width > MOZ_PNG_MAX_DIMENSION) || (height > MOZ_PNG_MAX_DIMENSION)) {
   333         PostDataError();
   334         return;
   335       }
   337       // Post our size to the superclass
   338       PostSize(width, height);
   339     }
   340   }
   342   // Otherwise, we're doing a standard decode
   343   else {
   345     // libpng uses setjmp/longjmp for error handling - set the buffer
   346     if (setjmp(png_jmpbuf(mPNG))) {
   348       // We might not really know what caused the error, but it makes more
   349       // sense to blame the data.
   350       if (!HasError())
   351         PostDataError();
   353       png_destroy_read_struct(&mPNG, &mInfo, nullptr);
   354       return;
   355     }
   357     // Pass the data off to libpng
   358     png_process_data(mPNG, mInfo, (unsigned char *)aBuffer, aCount);
   360   }
   361 }
   363 // Sets up gamma pre-correction in libpng before our callback gets called.
   364 // We need to do this if we don't end up with a CMS profile.
   365 static void
   366 PNGDoGammaCorrection(png_structp png_ptr, png_infop info_ptr)
   367 {
   368   double aGamma;
   370   if (png_get_gAMA(png_ptr, info_ptr, &aGamma)) {
   371     if ((aGamma <= 0.0) || (aGamma > 21474.83)) {
   372       aGamma = 0.45455;
   373       png_set_gAMA(png_ptr, info_ptr, aGamma);
   374     }
   375     png_set_gamma(png_ptr, 2.2, aGamma);
   376   }
   377   else
   378     png_set_gamma(png_ptr, 2.2, 0.45455);
   380 }
   382 // Adapted from http://www.littlecms.com/pngchrm.c example code
   383 static qcms_profile *
   384 PNGGetColorProfile(png_structp png_ptr, png_infop info_ptr,
   385                    int color_type, qcms_data_type *inType, uint32_t *intent)
   386 {
   387   qcms_profile *profile = nullptr;
   388   *intent = QCMS_INTENT_PERCEPTUAL; // Our default
   390   // First try to see if iCCP chunk is present
   391   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) {
   392     png_uint_32 profileLen;
   393 #if (PNG_LIBPNG_VER < 10500)
   394     char *profileData, *profileName;
   395 #else
   396     png_bytep profileData;
   397     png_charp profileName;
   398 #endif
   399     int compression;
   401     png_get_iCCP(png_ptr, info_ptr, &profileName, &compression,
   402                  &profileData, &profileLen);
   404     profile = qcms_profile_from_memory(
   405 #if (PNG_LIBPNG_VER < 10500)
   406                                        profileData,
   407 #else
   408                                        (char *)profileData,
   409 #endif
   410                                        profileLen);
   411     if (profile) {
   412       uint32_t profileSpace = qcms_profile_get_color_space(profile);
   414       bool mismatch = false;
   415       if (color_type & PNG_COLOR_MASK_COLOR) {
   416         if (profileSpace != icSigRgbData)
   417           mismatch = true;
   418       } else {
   419         if (profileSpace == icSigRgbData)
   420           png_set_gray_to_rgb(png_ptr);
   421         else if (profileSpace != icSigGrayData)
   422           mismatch = true;
   423       }
   425       if (mismatch) {
   426         qcms_profile_release(profile);
   427         profile = nullptr;
   428       } else {
   429         *intent = qcms_profile_get_rendering_intent(profile);
   430       }
   431     }
   432   }
   434   // Check sRGB chunk
   435   if (!profile && png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) {
   436     profile = qcms_profile_sRGB();
   438     if (profile) {
   439       int fileIntent;
   440       png_set_gray_to_rgb(png_ptr);
   441       png_get_sRGB(png_ptr, info_ptr, &fileIntent);
   442       uint32_t map[] = { QCMS_INTENT_PERCEPTUAL,
   443                          QCMS_INTENT_RELATIVE_COLORIMETRIC,
   444                          QCMS_INTENT_SATURATION,
   445                          QCMS_INTENT_ABSOLUTE_COLORIMETRIC };
   446       *intent = map[fileIntent];
   447     }
   448   }
   450   // Check gAMA/cHRM chunks
   451   if (!profile &&
   452        png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) &&
   453        png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM)) {
   454     qcms_CIE_xyYTRIPLE primaries;
   455     qcms_CIE_xyY whitePoint;
   457     png_get_cHRM(png_ptr, info_ptr,
   458                  &whitePoint.x, &whitePoint.y,
   459                  &primaries.red.x,   &primaries.red.y,
   460                  &primaries.green.x, &primaries.green.y,
   461                  &primaries.blue.x,  &primaries.blue.y);
   462     whitePoint.Y =
   463       primaries.red.Y = primaries.green.Y = primaries.blue.Y = 1.0;
   465     double gammaOfFile;
   467     png_get_gAMA(png_ptr, info_ptr, &gammaOfFile);
   469     profile = qcms_profile_create_rgb_with_gamma(whitePoint, primaries,
   470                                                  1.0/gammaOfFile);
   472     if (profile)
   473       png_set_gray_to_rgb(png_ptr);
   474   }
   476   if (profile) {
   477     uint32_t profileSpace = qcms_profile_get_color_space(profile);
   478     if (profileSpace == icSigGrayData) {
   479       if (color_type & PNG_COLOR_MASK_ALPHA)
   480         *inType = QCMS_DATA_GRAYA_8;
   481       else
   482         *inType = QCMS_DATA_GRAY_8;
   483     } else {
   484       if (color_type & PNG_COLOR_MASK_ALPHA ||
   485           png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
   486         *inType = QCMS_DATA_RGBA_8;
   487       else
   488         *inType = QCMS_DATA_RGB_8;
   489     }
   490   }
   492   return profile;
   493 }
   495 void
   496 nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr)
   497 {
   498 /*  int number_passes;   NOT USED  */
   499   png_uint_32 width, height;
   500   int bit_depth, color_type, interlace_type, compression_type, filter_type;
   501   unsigned int channels;
   503   png_bytep trans = nullptr;
   504   int num_trans = 0;
   506   nsPNGDecoder *decoder =
   507                static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
   509   /* always decode to 24-bit RGB or 32-bit RGBA  */
   510   png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
   511                &interlace_type, &compression_type, &filter_type);
   513   /* Are we too big? */
   514   if (width > MOZ_PNG_MAX_DIMENSION || height > MOZ_PNG_MAX_DIMENSION)
   515     longjmp(png_jmpbuf(decoder->mPNG), 1);
   517   // Post our size to the superclass
   518   decoder->PostSize(width, height);
   519   if (decoder->HasError()) {
   520     // Setting the size led to an error.
   521     longjmp(png_jmpbuf(decoder->mPNG), 1);
   522   }
   524   if (color_type == PNG_COLOR_TYPE_PALETTE)
   525     png_set_expand(png_ptr);
   527   if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
   528     png_set_expand(png_ptr);
   530   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
   531     int sample_max = (1 << bit_depth);
   532     png_color_16p trans_values;
   533     png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values);
   534     /* libpng doesn't reject a tRNS chunk with out-of-range samples
   535        so we check it here to avoid setting up a useless opacity
   536        channel or producing unexpected transparent pixels when using
   537        libpng-1.2.19 through 1.2.26 (bug #428045) */
   538     if ((color_type == PNG_COLOR_TYPE_GRAY &&
   539        (int)trans_values->gray > sample_max) ||
   540        (color_type == PNG_COLOR_TYPE_RGB &&
   541        ((int)trans_values->red > sample_max ||
   542        (int)trans_values->green > sample_max ||
   543        (int)trans_values->blue > sample_max)))
   544       {
   545         /* clear the tRNS valid flag and release tRNS memory */
   546         png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0);
   547       }
   548     else
   549       png_set_expand(png_ptr);
   550   }
   552   if (bit_depth == 16)
   553     png_set_scale_16(png_ptr);
   555   qcms_data_type inType = QCMS_DATA_RGBA_8;
   556   uint32_t intent = -1;
   557   uint32_t pIntent;
   558   if (decoder->mCMSMode != eCMSMode_Off) {
   559     intent = gfxPlatform::GetRenderingIntent();
   560     decoder->mInProfile = PNGGetColorProfile(png_ptr, info_ptr,
   561                                              color_type, &inType, &pIntent);
   562     /* If we're not mandating an intent, use the one from the image. */
   563     if (intent == uint32_t(-1))
   564       intent = pIntent;
   565   }
   566   if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) {
   567     qcms_data_type outType;
   569     if (color_type & PNG_COLOR_MASK_ALPHA || num_trans)
   570       outType = QCMS_DATA_RGBA_8;
   571     else
   572       outType = QCMS_DATA_RGB_8;
   574     decoder->mTransform = qcms_transform_create(decoder->mInProfile,
   575                                            inType,
   576                                            gfxPlatform::GetCMSOutputProfile(),
   577                                            outType,
   578                                            (qcms_intent)intent);
   579   } else {
   580     png_set_gray_to_rgb(png_ptr);
   582     // only do gamma correction if CMS isn't entirely disabled
   583     if (decoder->mCMSMode != eCMSMode_Off)
   584       PNGDoGammaCorrection(png_ptr, info_ptr);
   586     if (decoder->mCMSMode == eCMSMode_All) {
   587       if (color_type & PNG_COLOR_MASK_ALPHA || num_trans)
   588         decoder->mTransform = gfxPlatform::GetCMSRGBATransform();
   589       else
   590         decoder->mTransform = gfxPlatform::GetCMSRGBTransform();
   591     }
   592   }
   594   /* let libpng expand interlaced images */
   595   if (interlace_type == PNG_INTERLACE_ADAM7) {
   596     /* number_passes = */
   597     png_set_interlace_handling(png_ptr);
   598   }
   600   /* now all of those things we set above are used to update various struct
   601    * members and whatnot, after which we can get channels, rowbytes, etc. */
   602   png_read_update_info(png_ptr, info_ptr);
   603   decoder->mChannels = channels = png_get_channels(png_ptr, info_ptr);
   605   /*---------------------------------------------------------------*/
   606   /* copy PNG info into imagelib structs (formerly png_set_dims()) */
   607   /*---------------------------------------------------------------*/
   609   // This code is currently unused, but it will be needed for bug 517713.
   610 #if 0
   611   int32_t alpha_bits = 1;
   613   if (channels == 2 || channels == 4) {
   614     /* check if alpha is coming from a tRNS chunk and is binary */
   615     if (num_trans) {
   616       /* if it's not an indexed color image, tRNS means binary */
   617       if (color_type == PNG_COLOR_TYPE_PALETTE) {
   618         for (int i=0; i<num_trans; i++) {
   619           if ((trans[i] != 0) && (trans[i] != 255)) {
   620             alpha_bits = 8;
   621             break;
   622           }
   623         }
   624       }
   625     } else {
   626       alpha_bits = 8;
   627     }
   628   }
   629 #endif
   631   if (channels == 1 || channels == 3)
   632     decoder->format = gfxImageFormat::RGB24;
   633   else if (channels == 2 || channels == 4)
   634     decoder->format = gfxImageFormat::ARGB32;
   636 #ifdef PNG_APNG_SUPPORTED
   637   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL))
   638     png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback,
   639                                  nullptr);
   641   if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) {
   642     decoder->mFrameIsHidden = true;
   643   } else {
   644 #endif
   645     decoder->CreateFrame(0, 0, width, height, decoder->format);
   646 #ifdef PNG_APNG_SUPPORTED
   647   }
   648 #endif
   650   if (decoder->mTransform &&
   651       (channels <= 2 || interlace_type == PNG_INTERLACE_ADAM7)) {
   652     uint32_t bpp[] = { 0, 3, 4, 3, 4 };
   653     decoder->mCMSLine =
   654       (uint8_t *)moz_malloc(bpp[channels] * width);
   655     if (!decoder->mCMSLine) {
   656       longjmp(png_jmpbuf(decoder->mPNG), 5); // NS_ERROR_OUT_OF_MEMORY
   657     }
   658   }
   660   if (interlace_type == PNG_INTERLACE_ADAM7) {
   661     if (height < INT32_MAX / (width * channels))
   662       decoder->interlacebuf = (uint8_t *)moz_malloc(channels * width * height);
   663     if (!decoder->interlacebuf) {
   664       longjmp(png_jmpbuf(decoder->mPNG), 5); // NS_ERROR_OUT_OF_MEMORY
   665     }
   666   }
   668   if (decoder->NeedsNewFrame()) {
   669     /* We know that we need a new frame, so pause input so the decoder
   670      * infrastructure can give it to us.
   671      */
   672     png_process_data_pause(png_ptr, /* save = */ 1);
   673   }
   674 }
   676 void
   677 nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row,
   678                            png_uint_32 row_num, int pass)
   679 {
   680   /* libpng comments:
   681    *
   682    * this function is called for every row in the image.  If the
   683    * image is interlacing, and you turned on the interlace handler,
   684    * this function will be called for every row in every pass.
   685    * Some of these rows will not be changed from the previous pass.
   686    * When the row is not changed, the new_row variable will be
   687    * nullptr. The rows and passes are called in order, so you don't
   688    * really need the row_num and pass, but I'm supplying them
   689    * because it may make your life easier.
   690    *
   691    * For the non-nullptr rows of interlaced images, you must call
   692    * png_progressive_combine_row() passing in the row and the
   693    * old row.  You can call this function for nullptr rows (it will
   694    * just return) and for non-interlaced images (it just does the
   695    * memcpy for you) if it will make the code easier.  Thus, you
   696    * can just do this for all cases:
   697    *
   698    *    png_progressive_combine_row(png_ptr, old_row, new_row);
   699    *
   700    * where old_row is what was displayed for previous rows.  Note
   701    * that the first pass (pass == 0 really) will completely cover
   702    * the old row, so the rows do not have to be initialized.  After
   703    * the first pass (and only for interlaced images), you will have
   704    * to pass the current row, and the function will combine the
   705    * old row and the new row.
   706    */
   707   nsPNGDecoder *decoder =
   708                static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
   710   // skip this frame
   711   if (decoder->mFrameIsHidden)
   712     return;
   714   if (row_num >= (png_uint_32) decoder->mFrameRect.height)
   715     return;
   717   if (new_row) {
   718     int32_t width = decoder->mFrameRect.width;
   719     uint32_t iwidth = decoder->mFrameRect.width;
   721     png_bytep line = new_row;
   722     if (decoder->interlacebuf) {
   723       line = decoder->interlacebuf + (row_num * decoder->mChannels * width);
   724       png_progressive_combine_row(png_ptr, line, new_row);
   725     }
   727     uint32_t bpr = width * sizeof(uint32_t);
   728     uint32_t *cptr32 = (uint32_t*)(decoder->mImageData + (row_num*bpr));
   729     bool rowHasNoAlpha = true;
   731     if (decoder->mTransform) {
   732       if (decoder->mCMSLine) {
   733         qcms_transform_data(decoder->mTransform, line, decoder->mCMSLine,
   734                             iwidth);
   735         /* copy alpha over */
   736         uint32_t channels = decoder->mChannels;
   737         if (channels == 2 || channels == 4) {
   738           for (uint32_t i = 0; i < iwidth; i++)
   739             decoder->mCMSLine[4 * i + 3] = line[channels * i + channels - 1];
   740         }
   741         line = decoder->mCMSLine;
   742       } else {
   743         qcms_transform_data(decoder->mTransform, line, line, iwidth);
   744        }
   745      }
   747     switch (decoder->format) {
   748       case gfxImageFormat::RGB24:
   749       {
   750         // counter for while() loops below
   751         uint32_t idx = iwidth;
   753         // copy as bytes until source pointer is 32-bit-aligned
   754         for (; (NS_PTR_TO_UINT32(line) & 0x3) && idx; --idx) {
   755           *cptr32++ = gfxPackedPixel(0xFF, line[0], line[1], line[2]);
   756           line += 3;
   757         }
   759         // copy pixels in blocks of 4
   760         while (idx >= 4) {
   761           GFX_BLOCK_RGB_TO_FRGB(line, cptr32);
   762           idx    -=  4;
   763           line   += 12;
   764           cptr32 +=  4;
   765         }
   767         // copy remaining pixel(s)
   768         while (idx--) {
   769           // 32-bit read of final pixel will exceed buffer, so read bytes
   770           *cptr32++ = gfxPackedPixel(0xFF, line[0], line[1], line[2]);
   771           line += 3;
   772         }
   773       }
   774       break;
   775       case gfxImageFormat::ARGB32:
   776       {
   777         if (!decoder->mDisablePremultipliedAlpha) {
   778           for (uint32_t x=width; x>0; --x) {
   779             *cptr32++ = gfxPackedPixel(line[3], line[0], line[1], line[2]);
   780             if (line[3] != 0xff)
   781               rowHasNoAlpha = false;
   782             line += 4;
   783           }
   784         } else {
   785           for (uint32_t x=width; x>0; --x) {
   786             *cptr32++ = gfxPackedPixelNoPreMultiply(line[3], line[0], line[1], line[2]);
   787             if (line[3] != 0xff)
   788               rowHasNoAlpha = false;
   789             line += 4;
   790           }
   791         }
   792       }
   793       break;
   794       default:
   795         longjmp(png_jmpbuf(decoder->mPNG), 1);
   796     }
   798     if (!rowHasNoAlpha)
   799       decoder->mFrameHasNoAlpha = false;
   801     if (decoder->mNumFrames <= 1) {
   802       // Only do incremental image display for the first frame
   803       // XXXbholley - this check should be handled in the superclass
   804       nsIntRect r(0, row_num, width, 1);
   805       decoder->PostInvalidation(r);
   806     }
   807   }
   808 }
   810 #ifdef PNG_APNG_SUPPORTED
   811 // got the header of a new frame that's coming
   812 void
   813 nsPNGDecoder::frame_info_callback(png_structp png_ptr, png_uint_32 frame_num)
   814 {
   815   png_uint_32 x_offset, y_offset;
   816   int32_t width, height;
   818   nsPNGDecoder *decoder =
   819                static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
   821   // old frame is done
   822   decoder->EndImageFrame();
   824   // Only the first frame can be hidden, so unhide unconditionally here.
   825   decoder->mFrameIsHidden = false;
   827   x_offset = png_get_next_frame_x_offset(png_ptr, decoder->mInfo);
   828   y_offset = png_get_next_frame_y_offset(png_ptr, decoder->mInfo);
   829   width = png_get_next_frame_width(png_ptr, decoder->mInfo);
   830   height = png_get_next_frame_height(png_ptr, decoder->mInfo);
   832   decoder->CreateFrame(x_offset, y_offset, width, height, decoder->format);
   834   if (decoder->NeedsNewFrame()) {
   835     /* We know that we need a new frame, so pause input so the decoder
   836      * infrastructure can give it to us.
   837      */
   838     png_process_data_pause(png_ptr, /* save = */ 1);
   839   }
   840 }
   841 #endif
   843 void
   844 nsPNGDecoder::end_callback(png_structp png_ptr, png_infop info_ptr)
   845 {
   846   /* libpng comments:
   847    *
   848    * this function is called when the whole image has been read,
   849    * including any chunks after the image (up to and including
   850    * the IEND).  You will usually have the same info chunk as you
   851    * had in the header, although some data may have been added
   852    * to the comments and time fields.
   853    *
   854    * Most people won't do much here, perhaps setting a flag that
   855    * marks the image as finished.
   856    */
   858   nsPNGDecoder *decoder =
   859                static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
   861   // We shouldn't get here if we've hit an error
   862   NS_ABORT_IF_FALSE(!decoder->HasError(), "Finishing up PNG but hit error!");
   864   int32_t loop_count = 0;
   865 #ifdef PNG_APNG_SUPPORTED
   866   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) {
   867     int32_t num_plays = png_get_num_plays(png_ptr, info_ptr);
   868     loop_count = num_plays - 1;
   869   }
   870 #endif
   872   // Send final notifications
   873   decoder->EndImageFrame();
   874   decoder->PostDecodeDone(loop_count);
   875 }
   878 void
   879 nsPNGDecoder::error_callback(png_structp png_ptr, png_const_charp error_msg)
   880 {
   881   PR_LOG(GetPNGLog(), PR_LOG_ERROR, ("libpng error: %s\n", error_msg));
   882   longjmp(png_jmpbuf(png_ptr), 1);
   883 }
   886 void
   887 nsPNGDecoder::warning_callback(png_structp png_ptr, png_const_charp warning_msg)
   888 {
   889   PR_LOG(GetPNGLog(), PR_LOG_WARNING, ("libpng warning: %s\n", warning_msg));
   890 }
   892 Telemetry::ID
   893 nsPNGDecoder::SpeedHistogram()
   894 {
   895   return Telemetry::IMAGE_DECODE_SPEED_PNG;
   896 }
   899 } // namespace image
   900 } // namespace mozilla

mercurial