LCOV - code coverage report
Current view: directory - image/decoders - nsPNGDecoder.cpp (source / functions) Found Hit Coverage
Test: app.info Lines: 323 160 49.5 %
Date: 2012-06-02 Functions: 20 14 70.0 %

       1                 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
       2                 :  *
       3                 :  * ***** BEGIN LICENSE BLOCK *****
       4                 :  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
       5                 :  *
       6                 :  * The contents of this file are subject to the Mozilla Public License Version
       7                 :  * 1.1 (the "License"); you may not use this file except in compliance with
       8                 :  * the License. You may obtain a copy of the License at
       9                 :  * http://www.mozilla.org/MPL/
      10                 :  *
      11                 :  * Software distributed under the License is distributed on an "AS IS" basis,
      12                 :  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
      13                 :  * for the specific language governing rights and limitations under the
      14                 :  * License.
      15                 :  *
      16                 :  * The Original Code is mozilla.org code.
      17                 :  *
      18                 :  * The Initial Developer of the Original Code is
      19                 :  * Netscape Communications Corporation.
      20                 :  * Portions created by the Initial Developer are Copyright (C) 2001
      21                 :  * the Initial Developer. All Rights Reserved.
      22                 :  *
      23                 :  * Contributor(s):
      24                 :  *   Stuart Parmenter <stuart@mozilla.com>
      25                 :  *   Andrew Smith
      26                 :  *   Federico Mena-Quintero <federico@novell.com>
      27                 :  *   Bobby Holley <bobbyholley@gmail.com>
      28                 :  *   Glenn Randers-Pehrson <glennrp@gmail.com>
      29                 :  *
      30                 :  * Alternatively, the contents of this file may be used under the terms of
      31                 :  * either the GNU General Public License Version 2 or later (the "GPL"), or
      32                 :  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
      33                 :  * in which case the provisions of the GPL or the LGPL are applicable instead
      34                 :  * of those above. If you wish to allow use of your version of this file only
      35                 :  * under the terms of either the GPL or the LGPL, and not to allow others to
      36                 :  * use your version of this file under the terms of the MPL, indicate your
      37                 :  * decision by deleting the provisions above and replace them with the notice
      38                 :  * and other provisions required by the GPL or the LGPL. If you do not delete
      39                 :  * the provisions above, a recipient may use your version of this file under
      40                 :  * the terms of any one of the MPL, the GPL or the LGPL.
      41                 :  *
      42                 :  * ***** END LICENSE BLOCK ***** */
      43                 : 
      44                 : #include "nsPNGDecoder.h"
      45                 : #include "ImageLogging.h"
      46                 : 
      47                 : #include "nsMemory.h"
      48                 : #include "nsRect.h"
      49                 : 
      50                 : #include "nsIInputStream.h"
      51                 : 
      52                 : #include "RasterImage.h"
      53                 : #include "imgIContainerObserver.h"
      54                 : 
      55                 : #include "gfxColor.h"
      56                 : #include "nsColor.h"
      57                 : 
      58                 : #include "nspr.h"
      59                 : #include "png.h"
      60                 : 
      61                 : #include "gfxPlatform.h"
      62                 : 
      63                 : namespace mozilla {
      64                 : namespace image {
      65                 : 
      66                 : #ifdef PR_LOGGING
      67            1464 : static PRLogModuleInfo *gPNGLog = PR_NewLogModule("PNGDecoder");
      68            1464 : static PRLogModuleInfo *gPNGDecoderAccountingLog =
      69            1464 :                         PR_NewLogModule("PNGDecoderAccounting");
      70                 : #endif
      71                 : 
      72                 : /* limit image dimensions (bug #251381) */
      73                 : #define MOZ_PNG_MAX_DIMENSION 1000000L
      74                 : 
      75                 : // For size decodes
      76                 : #define WIDTH_OFFSET 16
      77                 : #define HEIGHT_OFFSET (WIDTH_OFFSET + 4)
      78                 : #define BYTES_NEEDED_FOR_DIMENSIONS (HEIGHT_OFFSET + 4)
      79                 : 
      80                 : // First 8 bytes of a PNG file
      81                 : const PRUint8 
      82                 : nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
      83                 : 
      84               6 : nsPNGDecoder::nsPNGDecoder(RasterImage &aImage, imgIDecoderObserver* aObserver)
      85                 :  : Decoder(aImage, aObserver),
      86                 :    mPNG(nsnull), mInfo(nsnull),
      87                 :    mCMSLine(nsnull), interlacebuf(nsnull),
      88                 :    mInProfile(nsnull), mTransform(nsnull),
      89                 :    mHeaderBuf(nsnull), mHeaderBytesRead(0),
      90                 :    mChannels(0), mFrameIsHidden(false),
      91               6 :    mCMSMode(0), mDisablePremultipliedAlpha(false)
      92                 : {
      93               6 : }
      94                 : 
      95              18 : nsPNGDecoder::~nsPNGDecoder()
      96                 : {
      97               6 :   if (mPNG)
      98               4 :     png_destroy_read_struct(&mPNG, mInfo ? &mInfo : NULL, NULL);
      99               6 :   if (mCMSLine)
     100               0 :     nsMemory::Free(mCMSLine);
     101               6 :   if (interlacebuf)
     102               0 :     nsMemory::Free(interlacebuf);
     103               6 :   if (mInProfile) {
     104               0 :     qcms_profile_release(mInProfile);
     105                 : 
     106                 :     /* mTransform belongs to us only if mInProfile is non-null */
     107               0 :     if (mTransform)
     108               0 :       qcms_transform_release(mTransform);
     109                 :   }
     110               6 :   if (mHeaderBuf)
     111               2 :     nsMemory::Free(mHeaderBuf);
     112              24 : }
     113                 : 
     114                 : // CreateFrame() is used for both simple and animated images
     115               4 : void nsPNGDecoder::CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
     116                 :                                PRInt32 width, PRInt32 height,
     117                 :                                gfxASurface::gfxImageFormat format)
     118                 : {
     119                 :   PRUint32 imageDataLength;
     120                 :   nsresult rv = mImage.EnsureFrame(GetFrameCount(), x_offset, y_offset,
     121                 :                                    width, height, format,
     122               4 :                                    &mImageData, &imageDataLength);
     123               4 :   if (NS_FAILED(rv))
     124               0 :     longjmp(png_jmpbuf(mPNG), 5); // NS_ERROR_OUT_OF_MEMORY
     125                 : 
     126               4 :   mFrameRect.x = x_offset;
     127               4 :   mFrameRect.y = y_offset;
     128               4 :   mFrameRect.width = width;
     129               4 :   mFrameRect.height = height;
     130                 : 
     131                 : #ifdef PNG_APNG_SUPPORTED
     132               4 :   if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL))
     133               0 :     SetAnimFrameInfo();
     134                 : #endif
     135                 : 
     136                 :   // Tell the superclass we're starting a frame
     137               4 :   PostFrameStart();
     138                 : 
     139               4 :   PR_LOG(gPNGDecoderAccountingLog, PR_LOG_DEBUG,
     140                 :          ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created "
     141                 :           "image frame with %dx%d pixels in container %p",
     142                 :           width, height,
     143                 :           &mImage));
     144                 : 
     145               4 :   mFrameHasNoAlpha = true;
     146               4 : }
     147                 : 
     148                 : #ifdef PNG_APNG_SUPPORTED
     149                 : // set timeout and frame disposal method for the current frame
     150               0 : void nsPNGDecoder::SetAnimFrameInfo()
     151                 : {
     152                 :   png_uint_16 delay_num, delay_den;
     153                 :   /* delay, in seconds is delay_num/delay_den */
     154                 :   png_byte dispose_op;
     155                 :   png_byte blend_op;
     156                 :   PRInt32 timeout; /* in milliseconds */
     157                 : 
     158               0 :   delay_num = png_get_next_frame_delay_num(mPNG, mInfo);
     159               0 :   delay_den = png_get_next_frame_delay_den(mPNG, mInfo);
     160               0 :   dispose_op = png_get_next_frame_dispose_op(mPNG, mInfo);
     161               0 :   blend_op = png_get_next_frame_blend_op(mPNG, mInfo);
     162                 : 
     163               0 :   if (delay_num == 0) {
     164               0 :     timeout = 0; // SetFrameTimeout() will set to a minimum
     165                 :   } else {
     166               0 :     if (delay_den == 0)
     167               0 :       delay_den = 100; // so says the APNG spec
     168                 : 
     169                 :     // Need to cast delay_num to float to have a proper division and
     170                 :     // the result to int to avoid compiler warning
     171                 :     timeout = static_cast<PRInt32>
     172               0 :               (static_cast<PRFloat64>(delay_num) * 1000 / delay_den);
     173                 :   }
     174                 : 
     175               0 :   PRUint32 numFrames = mImage.GetNumFrames();
     176                 : 
     177               0 :   mImage.SetFrameTimeout(numFrames - 1, timeout);
     178                 : 
     179               0 :   if (dispose_op == PNG_DISPOSE_OP_PREVIOUS)
     180                 :       mImage.SetFrameDisposalMethod(numFrames - 1,
     181               0 :                                     RasterImage::kDisposeRestorePrevious);
     182               0 :   else if (dispose_op == PNG_DISPOSE_OP_BACKGROUND)
     183                 :       mImage.SetFrameDisposalMethod(numFrames - 1,
     184               0 :                                     RasterImage::kDisposeClear);
     185                 :   else
     186                 :       mImage.SetFrameDisposalMethod(numFrames - 1,
     187               0 :                                     RasterImage::kDisposeKeep);
     188                 : 
     189               0 :   if (blend_op == PNG_BLEND_OP_SOURCE)
     190               0 :       mImage.SetFrameBlendMethod(numFrames - 1, RasterImage::kBlendSource);
     191                 :   /*else // 'over' is the default
     192                 :       mImage.SetFrameBlendMethod(numFrames - 1, RasterImage::kBlendOver); */
     193               0 : }
     194                 : #endif
     195                 : 
     196                 : // set timeout and frame disposal method for the current frame
     197               4 : void nsPNGDecoder::EndImageFrame()
     198                 : {
     199               4 :   if (mFrameIsHidden)
     200               0 :     return;
     201                 : 
     202               4 :   PRUint32 numFrames = 1;
     203                 : #ifdef PNG_APNG_SUPPORTED
     204               4 :   numFrames = mImage.GetNumFrames();
     205                 : 
     206                 :   // We can't use mPNG->num_frames_read as it may be one ahead.
     207               4 :   if (numFrames > 1) {
     208                 :     // Tell the image renderer that the frame is complete
     209               0 :     if (mFrameHasNoAlpha)
     210               0 :       mImage.SetFrameHasNoAlpha(numFrames - 1);
     211                 : 
     212               0 :     PostInvalidation(mFrameRect);
     213                 :   }
     214                 : #endif
     215                 : 
     216               4 :   PostFrameStop();
     217                 : }
     218                 : 
     219                 : void
     220               6 : nsPNGDecoder::InitInternal()
     221                 : {
     222               6 :   mCMSMode = gfxPlatform::GetCMSMode();
     223               6 :   if ((mDecodeFlags & DECODER_NO_COLORSPACE_CONVERSION) != 0)
     224               0 :     mCMSMode = eCMSMode_Off;
     225               6 :   mDisablePremultipliedAlpha = (mDecodeFlags & DECODER_NO_PREMULTIPLY_ALPHA) != 0;
     226                 : 
     227                 : #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
     228                 :   static png_byte color_chunks[]=
     229                 :        { 99,  72,  82,  77, '\0',   /* cHRM */
     230                 :         105,  67,  67,  80, '\0'};  /* iCCP */
     231                 :   static png_byte unused_chunks[]=
     232                 :        { 98,  75,  71,  68, '\0',   /* bKGD */
     233                 :         104,  73,  83,  84, '\0',   /* hIST */
     234                 :         105,  84,  88, 116, '\0',   /* iTXt */
     235                 :         111,  70,  70, 115, '\0',   /* oFFs */
     236                 :         112,  67,  65,  76, '\0',   /* pCAL */
     237                 :         115,  67,  65,  76, '\0',   /* sCAL */
     238                 :         112,  72,  89, 115, '\0',   /* pHYs */
     239                 :         115,  66,  73,  84, '\0',   /* sBIT */
     240                 :         115,  80,  76,  84, '\0',   /* sPLT */
     241                 :         116,  69,  88, 116, '\0',   /* tEXt */
     242                 :         116,  73,  77,  69, '\0',   /* tIME */
     243                 :         122,  84,  88, 116, '\0'};  /* zTXt */
     244                 : #endif
     245                 : 
     246                 :   // For size decodes, we only need a small buffer
     247               6 :   if (IsSizeDecode()) {
     248               2 :     mHeaderBuf = (PRUint8 *)moz_xmalloc(BYTES_NEEDED_FOR_DIMENSIONS);
     249               2 :     return;
     250                 :   }
     251                 : 
     252                 :   /* For full decodes, do png init stuff */
     253                 : 
     254                 :   /* Initialize the container's source image header. */
     255                 :   /* Always decode to 24 bit pixdepth */
     256                 : 
     257                 :   mPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING,
     258                 :                                 NULL, nsPNGDecoder::error_callback,
     259               4 :                                 nsPNGDecoder::warning_callback);
     260               4 :   if (!mPNG) {
     261               0 :     PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
     262               0 :     return;
     263                 :   }
     264                 : 
     265               4 :   mInfo = png_create_info_struct(mPNG);
     266               4 :   if (!mInfo) {
     267               0 :     PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
     268               0 :     png_destroy_read_struct(&mPNG, NULL, NULL);
     269               0 :     return;
     270                 :   }
     271                 : 
     272                 : #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
     273                 :   /* Ignore unused chunks */
     274                 :   if (mCMSMode == eCMSMode_Off)
     275                 :     png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2);
     276                 : 
     277                 :   png_set_keep_unknown_chunks(mPNG, 1, unused_chunks,
     278                 :                               (int)sizeof(unused_chunks)/5);   
     279                 : #endif
     280                 : 
     281                 : #ifdef PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED
     282                 :   if (mCMSMode != eCMSMode_Off)
     283                 :     png_set_chunk_malloc_max(mPNG, 4000000L);
     284                 : #endif
     285                 : 
     286                 :   /* use this as libpng "progressive pointer" (retrieve in callbacks) */
     287                 :   png_set_progressive_read_fn(mPNG, static_cast<png_voidp>(this),
     288                 :                               nsPNGDecoder::info_callback,
     289                 :                               nsPNGDecoder::row_callback,
     290               4 :                               nsPNGDecoder::end_callback);
     291                 : 
     292                 : }
     293                 : 
     294                 : void
     295              22 : nsPNGDecoder::WriteInternal(const char *aBuffer, PRUint32 aCount)
     296                 : {
     297                 :   // We use gotos, so we need to declare variables here
     298              22 :   PRUint32 width = 0;
     299              22 :   PRUint32 height = 0;
     300                 : 
     301              22 :   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
     302                 : 
     303                 :   // If we only want width/height, we don't need to go through libpng
     304              22 :   if (IsSizeDecode()) {
     305                 : 
     306                 :     // Are we done?
     307               2 :     if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS)
     308               0 :       return;
     309                 : 
     310                 :     // Read data into our header buffer
     311                 :     PRUint32 bytesToRead = NS_MIN(aCount, BYTES_NEEDED_FOR_DIMENSIONS -
     312               2 :                                   mHeaderBytesRead);
     313               2 :     memcpy(mHeaderBuf + mHeaderBytesRead, aBuffer, bytesToRead);
     314               2 :     mHeaderBytesRead += bytesToRead;
     315                 : 
     316                 :     // If we're done now, verify the data and set up the container
     317               2 :     if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) {
     318                 : 
     319                 :       // Check that the signature bytes are right
     320               2 :       if (memcmp(mHeaderBuf, nsPNGDecoder::pngSignatureBytes, 
     321               2 :                  sizeof(pngSignatureBytes))) {
     322               0 :         PostDataError();
     323               0 :         return;
     324                 :       }
     325                 : 
     326                 :       // Grab the width and height, accounting for endianness (thanks libpng!)
     327               2 :       width = png_get_uint_32(mHeaderBuf + WIDTH_OFFSET);
     328               2 :       height = png_get_uint_32(mHeaderBuf + HEIGHT_OFFSET);
     329                 : 
     330                 :       // Too big?
     331               2 :       if ((width > MOZ_PNG_MAX_DIMENSION) || (height > MOZ_PNG_MAX_DIMENSION)) {
     332               0 :         PostDataError();
     333               0 :         return;
     334                 :       }
     335                 : 
     336                 :       // Post our size to the superclass
     337               2 :       PostSize(width, height);
     338                 :     }
     339                 :   }
     340                 : 
     341                 :   // Otherwise, we're doing a standard decode
     342                 :   else {
     343                 : 
     344                 :     // libpng uses setjmp/longjmp for error handling - set the buffer
     345              20 :     if (setjmp(png_jmpbuf(mPNG))) {
     346                 : 
     347                 :       // We might not really know what caused the error, but it makes more
     348                 :       // sense to blame the data.
     349               0 :       if (!HasError())
     350               0 :         PostDataError();
     351                 : 
     352               0 :       png_destroy_read_struct(&mPNG, &mInfo, NULL);
     353               0 :       return;
     354                 :     }
     355                 : 
     356                 :     // Pass the data off to libpng
     357              20 :     png_process_data(mPNG, mInfo, (unsigned char *)aBuffer, aCount);
     358                 : 
     359                 :   }
     360                 : }
     361                 : 
     362                 : // Sets up gamma pre-correction in libpng before our callback gets called.
     363                 : // We need to do this if we don't end up with a CMS profile.
     364                 : static void
     365               4 : PNGDoGammaCorrection(png_structp png_ptr, png_infop info_ptr)
     366                 : {
     367                 :   double aGamma;
     368                 : 
     369               4 :   if (png_get_gAMA(png_ptr, info_ptr, &aGamma)) {
     370               1 :     if ((aGamma <= 0.0) || (aGamma > 21474.83)) {
     371               0 :       aGamma = 0.45455;
     372               0 :       png_set_gAMA(png_ptr, info_ptr, aGamma);
     373                 :     }
     374               1 :     png_set_gamma(png_ptr, 2.2, aGamma);
     375                 :   }
     376                 :   else
     377               3 :     png_set_gamma(png_ptr, 2.2, 0.45455);
     378                 : 
     379               4 : }
     380                 : 
     381                 : // Adapted from http://www.littlecms.com/pngchrm.c example code
     382                 : static qcms_profile *
     383               4 : PNGGetColorProfile(png_structp png_ptr, png_infop info_ptr,
     384                 :                    int color_type, qcms_data_type *inType, PRUint32 *intent)
     385                 : {
     386               4 :   qcms_profile *profile = nsnull;
     387               4 :   *intent = QCMS_INTENT_PERCEPTUAL; // Our default
     388                 : 
     389                 :   // First try to see if iCCP chunk is present
     390               4 :   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) {
     391                 :     png_uint_32 profileLen;
     392                 : #if (PNG_LIBPNG_VER < 10500)
     393                 :     char *profileData, *profileName;
     394                 : #else
     395                 :     png_bytep profileData;
     396                 :     png_charp profileName;
     397                 : #endif
     398                 :     int compression;
     399                 : 
     400                 :     png_get_iCCP(png_ptr, info_ptr, &profileName, &compression,
     401               0 :                  &profileData, &profileLen);
     402                 : 
     403                 :     profile = qcms_profile_from_memory(
     404                 : #if (PNG_LIBPNG_VER < 10500)
     405                 :                                        profileData,
     406                 : #else
     407                 :                                        (char *)profileData,
     408                 : #endif
     409               0 :                                        profileLen);
     410               0 :     if (profile) {
     411               0 :       PRUint32 profileSpace = qcms_profile_get_color_space(profile);
     412                 : 
     413               0 :       bool mismatch = false;
     414               0 :       if (color_type & PNG_COLOR_MASK_COLOR) {
     415               0 :         if (profileSpace != icSigRgbData)
     416               0 :           mismatch = true;
     417                 :       } else {
     418               0 :         if (profileSpace == icSigRgbData)
     419               0 :           png_set_gray_to_rgb(png_ptr);
     420               0 :         else if (profileSpace != icSigGrayData)
     421               0 :           mismatch = true;
     422                 :       }
     423                 : 
     424               0 :       if (mismatch) {
     425               0 :         qcms_profile_release(profile);
     426               0 :         profile = nsnull;
     427                 :       } else {
     428               0 :         *intent = qcms_profile_get_rendering_intent(profile);
     429                 :       }
     430                 :     }
     431                 :   }
     432                 : 
     433                 :   // Check sRGB chunk
     434               4 :   if (!profile && png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) {
     435               0 :     profile = qcms_profile_sRGB();
     436                 : 
     437               0 :     if (profile) {
     438                 :       int fileIntent;
     439               0 :       png_set_gray_to_rgb(png_ptr);
     440               0 :       png_get_sRGB(png_ptr, info_ptr, &fileIntent);
     441                 :       PRUint32 map[] = { QCMS_INTENT_PERCEPTUAL,
     442                 :                          QCMS_INTENT_RELATIVE_COLORIMETRIC,
     443                 :                          QCMS_INTENT_SATURATION,
     444               0 :                          QCMS_INTENT_ABSOLUTE_COLORIMETRIC };
     445               0 :       *intent = map[fileIntent];
     446                 :     }
     447                 :   }
     448                 : 
     449                 :   // Check gAMA/cHRM chunks
     450               9 :   if (!profile &&
     451               4 :        png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) &&
     452               1 :        png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM)) {
     453                 :     qcms_CIE_xyYTRIPLE primaries;
     454                 :     qcms_CIE_xyY whitePoint;
     455                 : 
     456                 :     png_get_cHRM(png_ptr, info_ptr,
     457                 :                  &whitePoint.x, &whitePoint.y,
     458                 :                  &primaries.red.x,   &primaries.red.y,
     459                 :                  &primaries.green.x, &primaries.green.y,
     460               0 :                  &primaries.blue.x,  &primaries.blue.y);
     461                 :     whitePoint.Y =
     462               0 :       primaries.red.Y = primaries.green.Y = primaries.blue.Y = 1.0;
     463                 : 
     464                 :     double gammaOfFile;
     465                 : 
     466               0 :     png_get_gAMA(png_ptr, info_ptr, &gammaOfFile);
     467                 : 
     468                 :     profile = qcms_profile_create_rgb_with_gamma(whitePoint, primaries,
     469               0 :                                                  1.0/gammaOfFile);
     470                 : 
     471               0 :     if (profile)
     472               0 :       png_set_gray_to_rgb(png_ptr);
     473                 :   }
     474                 : 
     475               4 :   if (profile) {
     476               0 :     PRUint32 profileSpace = qcms_profile_get_color_space(profile);
     477               0 :     if (profileSpace == icSigGrayData) {
     478               0 :       if (color_type & PNG_COLOR_MASK_ALPHA)
     479               0 :         *inType = QCMS_DATA_GRAYA_8;
     480                 :       else
     481               0 :         *inType = QCMS_DATA_GRAY_8;
     482                 :     } else {
     483               0 :       if (color_type & PNG_COLOR_MASK_ALPHA ||
     484               0 :           png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
     485               0 :         *inType = QCMS_DATA_RGBA_8;
     486                 :       else
     487               0 :         *inType = QCMS_DATA_RGB_8;
     488                 :     }
     489                 :   }
     490                 : 
     491               4 :   return profile;
     492                 : }
     493                 : 
     494                 : void
     495               4 : nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr)
     496                 : {
     497                 : /*  int number_passes;   NOT USED  */
     498                 :   png_uint_32 width, height;
     499                 :   int bit_depth, color_type, interlace_type, compression_type, filter_type;
     500                 :   unsigned int channels;
     501                 : 
     502               4 :   png_bytep trans = NULL;
     503               4 :   int num_trans = 0;
     504                 : 
     505                 :   nsPNGDecoder *decoder =
     506               4 :                static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
     507                 : 
     508                 :   /* always decode to 24-bit RGB or 32-bit RGBA  */
     509                 :   png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
     510               4 :                &interlace_type, &compression_type, &filter_type);
     511                 : 
     512                 :   /* Are we too big? */
     513               4 :   if (width > MOZ_PNG_MAX_DIMENSION || height > MOZ_PNG_MAX_DIMENSION)
     514               0 :     longjmp(png_jmpbuf(decoder->mPNG), 1);
     515                 : 
     516                 :   // Post our size to the superclass
     517               4 :   decoder->PostSize(width, height);
     518               4 :   if (decoder->HasError()) {
     519                 :     // Setting the size lead to an error; this can happen when for example
     520                 :     // a multipart channel sends an image of a different size.
     521               0 :     longjmp(png_jmpbuf(decoder->mPNG), 1);
     522                 :   }
     523                 : 
     524               4 :   if (color_type == PNG_COLOR_TYPE_PALETTE)
     525               0 :     png_set_expand(png_ptr);
     526                 : 
     527               4 :   if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
     528               0 :     png_set_expand(png_ptr);
     529                 : 
     530               4 :   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
     531               0 :     int sample_max = (1 << bit_depth);
     532                 :     png_color_16p trans_values;
     533               0 :     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               0 :     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               0 :         png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0);
     547                 :       }
     548                 :     else
     549               0 :       png_set_expand(png_ptr);
     550                 :   }
     551                 : 
     552               4 :   if (bit_depth == 16)
     553               0 :     png_set_scale_16(png_ptr);
     554                 : 
     555                 :   qcms_data_type inType;
     556               4 :   PRUint32 intent = -1;
     557                 :   PRUint32 pIntent;
     558               4 :   if (decoder->mCMSMode != eCMSMode_Off) {
     559               4 :     intent = gfxPlatform::GetRenderingIntent();
     560                 :     decoder->mInProfile = PNGGetColorProfile(png_ptr, info_ptr,
     561               4 :                                              color_type, &inType, &pIntent);
     562                 :     /* If we're not mandating an intent, use the one from the image. */
     563               4 :     if (intent == PRUint32(-1))
     564               0 :       intent = pIntent;
     565                 :   }
     566               4 :   if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) {
     567                 :     qcms_data_type outType;
     568                 : 
     569               0 :     if (color_type & PNG_COLOR_MASK_ALPHA || num_trans)
     570               0 :       outType = QCMS_DATA_RGBA_8;
     571                 :     else
     572               0 :       outType = QCMS_DATA_RGB_8;
     573                 : 
     574                 :     decoder->mTransform = qcms_transform_create(decoder->mInProfile,
     575                 :                                            inType,
     576                 :                                            gfxPlatform::GetCMSOutputProfile(),
     577                 :                                            outType,
     578               0 :                                            (qcms_intent)intent);
     579                 :   } else {
     580               4 :     png_set_gray_to_rgb(png_ptr);
     581                 : 
     582                 :     // only do gamma correction if CMS isn't entirely disabled
     583               4 :     if (decoder->mCMSMode != eCMSMode_Off)
     584               4 :       PNGDoGammaCorrection(png_ptr, info_ptr);
     585                 : 
     586               4 :     if (decoder->mCMSMode == eCMSMode_All) {
     587               0 :       if (color_type & PNG_COLOR_MASK_ALPHA || num_trans)
     588               0 :         decoder->mTransform = gfxPlatform::GetCMSRGBATransform();
     589                 :       else
     590               0 :         decoder->mTransform = gfxPlatform::GetCMSRGBTransform();
     591                 :     }
     592                 :   }
     593                 : 
     594                 :   /* let libpng expand interlaced images */
     595               4 :   if (interlace_type == PNG_INTERLACE_ADAM7) {
     596                 :     /* number_passes = */
     597               0 :     png_set_interlace_handling(png_ptr);
     598                 :   }
     599                 : 
     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               4 :   png_read_update_info(png_ptr, info_ptr);
     603               4 :   decoder->mChannels = channels = png_get_channels(png_ptr, info_ptr);
     604                 : 
     605                 :   /*---------------------------------------------------------------*/
     606                 :   /* copy PNG info into imagelib structs (formerly png_set_dims()) */
     607                 :   /*---------------------------------------------------------------*/
     608                 : 
     609               4 :   PRInt32 alpha_bits = 1;
     610                 : 
     611               4 :   if (channels == 2 || channels == 4) {
     612                 :     /* check if alpha is coming from a tRNS chunk and is binary */
     613               3 :     if (num_trans) {
     614                 :       /* if it's not an indexed color image, tRNS means binary */
     615               0 :       if (color_type == PNG_COLOR_TYPE_PALETTE) {
     616               0 :         for (int i=0; i<num_trans; i++) {
     617               0 :           if ((trans[i] != 0) && (trans[i] != 255)) {
     618               0 :             alpha_bits = 8;
     619               0 :             break;
     620                 :           }
     621                 :         }
     622                 :       }
     623                 :     } else {
     624               3 :       alpha_bits = 8;
     625                 :     }
     626                 :   }
     627                 : 
     628               4 :   if (channels == 1 || channels == 3)
     629               1 :     decoder->format = gfxASurface::ImageFormatRGB24;
     630               3 :   else if (channels == 2 || channels == 4)
     631               3 :     decoder->format = gfxASurface::ImageFormatARGB32;
     632                 : 
     633                 : #ifdef PNG_APNG_SUPPORTED
     634               4 :   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL))
     635               0 :     png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback, NULL);
     636                 : 
     637               4 :   if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) {
     638               0 :     decoder->mFrameIsHidden = true;
     639                 :   } else {
     640                 : #endif
     641               4 :     decoder->CreateFrame(0, 0, width, height, decoder->format);
     642                 : #ifdef PNG_APNG_SUPPORTED
     643                 :   }
     644                 : #endif
     645                 : 
     646               4 :   if (decoder->mTransform &&
     647                 :       (channels <= 2 || interlace_type == PNG_INTERLACE_ADAM7)) {
     648               0 :     PRUint32 bpp[] = { 0, 3, 4, 3, 4 };
     649                 :     decoder->mCMSLine =
     650               0 :       (PRUint8 *)moz_malloc(bpp[channels] * width);
     651               0 :     if (!decoder->mCMSLine) {
     652               0 :       longjmp(png_jmpbuf(decoder->mPNG), 5); // NS_ERROR_OUT_OF_MEMORY
     653                 :     }
     654                 :   }
     655                 : 
     656               4 :   if (interlace_type == PNG_INTERLACE_ADAM7) {
     657               0 :     if (height < PR_INT32_MAX / (width * channels))
     658               0 :       decoder->interlacebuf = (PRUint8 *)moz_malloc(channels * width * height);
     659               0 :     if (!decoder->interlacebuf) {
     660               0 :       longjmp(png_jmpbuf(decoder->mPNG), 5); // NS_ERROR_OUT_OF_MEMORY
     661                 :     }
     662                 :   }
     663                 : 
     664                 :   /* Reject any ancillary chunk after IDAT with a bad CRC (bug #397593).
     665                 :    * It would be better to show the default frame (if one has already been
     666                 :    * successfully decoded) before bailing, but it's simpler to just bail
     667                 :    * out with an error message.
     668                 :    */
     669               4 :   png_set_crc_action(png_ptr, PNG_CRC_NO_CHANGE, PNG_CRC_ERROR_QUIT);
     670                 : 
     671                 :   return;
     672                 : }
     673                 : 
     674                 : void
     675             195 : nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row,
     676                 :                            png_uint_32 row_num, int pass)
     677                 : {
     678                 :   /* libpng comments:
     679                 :    *
     680                 :    * this function is called for every row in the image.  If the
     681                 :    * image is interlacing, and you turned on the interlace handler,
     682                 :    * this function will be called for every row in every pass.
     683                 :    * Some of these rows will not be changed from the previous pass.
     684                 :    * When the row is not changed, the new_row variable will be NULL.
     685                 :    * The rows and passes are called in order, so you don't really
     686                 :    * need the row_num and pass, but I'm supplying them because it
     687                 :    * may make your life easier.
     688                 :    *
     689                 :    * For the non-NULL rows of interlaced images, you must call
     690                 :    * png_progressive_combine_row() passing in the row and the
     691                 :    * old row.  You can call this function for NULL rows (it will
     692                 :    * just return) and for non-interlaced images (it just does the
     693                 :    * memcpy for you) if it will make the code easier.  Thus, you
     694                 :    * can just do this for all cases:
     695                 :    *
     696                 :    *    png_progressive_combine_row(png_ptr, old_row, new_row);
     697                 :    *
     698                 :    * where old_row is what was displayed for previous rows.  Note
     699                 :    * that the first pass (pass == 0 really) will completely cover
     700                 :    * the old row, so the rows do not have to be initialized.  After
     701                 :    * the first pass (and only for interlaced images), you will have
     702                 :    * to pass the current row, and the function will combine the
     703                 :    * old row and the new row.
     704                 :    */
     705                 :   nsPNGDecoder *decoder =
     706             195 :                static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
     707                 : 
     708                 :   // skip this frame
     709             195 :   if (decoder->mFrameIsHidden)
     710               0 :     return;
     711                 : 
     712             195 :   if (row_num >= (png_uint_32) decoder->mFrameRect.height)
     713               0 :     return;
     714                 : 
     715             195 :   if (new_row) {
     716             195 :     PRInt32 width = decoder->mFrameRect.width;
     717             195 :     PRUint32 iwidth = decoder->mFrameRect.width;
     718                 : 
     719             195 :     png_bytep line = new_row;
     720             195 :     if (decoder->interlacebuf) {
     721               0 :       line = decoder->interlacebuf + (row_num * decoder->mChannels * width);
     722               0 :       png_progressive_combine_row(png_ptr, line, new_row);
     723                 :     }
     724                 : 
     725             195 :     PRUint32 bpr = width * sizeof(PRUint32);
     726             195 :     PRUint32 *cptr32 = (PRUint32*)(decoder->mImageData + (row_num*bpr));
     727             195 :     bool rowHasNoAlpha = true;
     728                 : 
     729             195 :     if (decoder->mTransform) {
     730               0 :       if (decoder->mCMSLine) {
     731                 :         qcms_transform_data(decoder->mTransform, line, decoder->mCMSLine,
     732               0 :                             iwidth);
     733                 :         /* copy alpha over */
     734               0 :         PRUint32 channels = decoder->mChannels;
     735               0 :         if (channels == 2 || channels == 4) {
     736               0 :           for (PRUint32 i = 0; i < iwidth; i++)
     737               0 :             decoder->mCMSLine[4 * i + 3] = line[channels * i + channels - 1];
     738                 :         }
     739               0 :         line = decoder->mCMSLine;
     740                 :       } else {
     741               0 :         qcms_transform_data(decoder->mTransform, line, line, iwidth);
     742                 :        }
     743                 :      }
     744                 : 
     745             195 :     switch (decoder->format) {
     746                 :       case gfxASurface::ImageFormatRGB24:
     747                 :       {
     748                 :         // counter for while() loops below
     749               3 :         PRUint32 idx = iwidth;
     750                 : 
     751                 :         // copy as bytes until source pointer is 32-bit-aligned
     752               3 :         for (; (NS_PTR_TO_UINT32(line) & 0x3) && idx; --idx) {
     753               0 :           *cptr32++ = GFX_PACKED_PIXEL(0xFF, line[0], line[1], line[2]);
     754               0 :           line += 3;
     755                 :         }
     756                 : 
     757                 :         // copy pixels in blocks of 4
     758               6 :         while (idx >= 4) {
     759               0 :           GFX_BLOCK_RGB_TO_FRGB(line, cptr32);
     760               0 :           idx    -=  4;
     761               0 :           line   += 12;
     762               0 :           cptr32 +=  4;
     763                 :         }
     764                 : 
     765                 :         // copy remaining pixel(s)
     766              15 :         while (idx--) {
     767                 :           // 32-bit read of final pixel will exceed buffer, so read bytes
     768               9 :           *cptr32++ = GFX_PACKED_PIXEL(0xFF, line[0], line[1], line[2]);
     769               9 :           line += 3;
     770                 :         }
     771                 :       }
     772               3 :       break;
     773                 :       case gfxASurface::ImageFormatARGB32:
     774                 :       {
     775             192 :         if (!decoder->mDisablePremultipliedAlpha) {
     776           12480 :           for (PRUint32 x=width; x>0; --x) {
     777           12288 :             *cptr32++ = GFX_PACKED_PIXEL(line[3], line[0], line[1], line[2]);
     778           12288 :             if (line[3] != 0xff)
     779            3969 :               rowHasNoAlpha = false;
     780           12288 :             line += 4;
     781                 :           }
     782                 :         } else {
     783               0 :           for (PRUint32 x=width; x>0; --x) {
     784               0 :             *cptr32++ = GFX_PACKED_PIXEL_NO_PREMULTIPLY(line[3], line[0], line[1], line[2]);
     785               0 :             if (line[3] != 0xff)
     786               0 :               rowHasNoAlpha = false;
     787               0 :             line += 4;
     788                 :           }
     789                 :         }
     790                 :       }
     791             192 :       break;
     792                 :       default:
     793               0 :         longjmp(png_jmpbuf(decoder->mPNG), 1);
     794                 :     }
     795                 : 
     796             195 :     if (!rowHasNoAlpha)
     797             192 :       decoder->mFrameHasNoAlpha = false;
     798                 : 
     799             195 :     PRUint32 numFrames = decoder->mImage.GetNumFrames();
     800             195 :     if (numFrames <= 1) {
     801                 :       // Only do incremental image display for the first frame
     802                 :       // XXXbholley - this check should be handled in the superclass
     803             195 :       nsIntRect r(0, row_num, width, 1);
     804             195 :       decoder->PostInvalidation(r);
     805                 :     }
     806                 :   }
     807                 : }
     808                 : 
     809                 : // got the header of a new frame that's coming
     810                 : void
     811               0 : nsPNGDecoder::frame_info_callback(png_structp png_ptr, png_uint_32 frame_num)
     812                 : {
     813                 : #ifdef PNG_APNG_SUPPORTED
     814                 :   png_uint_32 x_offset, y_offset;
     815                 :   PRInt32 width, height;
     816                 : 
     817                 :   nsPNGDecoder *decoder =
     818               0 :                static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
     819                 : 
     820                 :   // old frame is done
     821               0 :   decoder->EndImageFrame();
     822                 : 
     823                 :   // Only the first frame can be hidden, so unhide unconditionally here.
     824               0 :   decoder->mFrameIsHidden = false;
     825                 : 
     826               0 :   x_offset = png_get_next_frame_x_offset(png_ptr, decoder->mInfo);
     827               0 :   y_offset = png_get_next_frame_y_offset(png_ptr, decoder->mInfo);
     828               0 :   width = png_get_next_frame_width(png_ptr, decoder->mInfo);
     829               0 :   height = png_get_next_frame_height(png_ptr, decoder->mInfo);
     830                 : 
     831               0 :   decoder->CreateFrame(x_offset, y_offset, width, height, decoder->format);
     832                 : #endif
     833               0 : }
     834                 : 
     835                 : void
     836               4 : nsPNGDecoder::end_callback(png_structp png_ptr, png_infop info_ptr)
     837                 : {
     838                 :   /* libpng comments:
     839                 :    *
     840                 :    * this function is called when the whole image has been read,
     841                 :    * including any chunks after the image (up to and including
     842                 :    * the IEND).  You will usually have the same info chunk as you
     843                 :    * had in the header, although some data may have been added
     844                 :    * to the comments and time fields.
     845                 :    *
     846                 :    * Most people won't do much here, perhaps setting a flag that
     847                 :    * marks the image as finished.
     848                 :    */
     849                 : 
     850                 :   nsPNGDecoder *decoder =
     851               4 :                static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
     852                 : 
     853                 :   // We shouldn't get here if we've hit an error
     854               4 :   NS_ABORT_IF_FALSE(!decoder->HasError(), "Finishing up PNG but hit error!");
     855                 : 
     856                 : #ifdef PNG_APNG_SUPPORTED
     857               4 :   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) {
     858               0 :     PRInt32 num_plays = png_get_num_plays(png_ptr, info_ptr);
     859               0 :     decoder->mImage.SetLoopCount(num_plays - 1);
     860                 :   }
     861                 : #endif
     862                 : 
     863                 :   // Send final notifications
     864               4 :   decoder->EndImageFrame();
     865               4 :   decoder->PostDecodeDone();
     866               4 : }
     867                 : 
     868                 : 
     869                 : void
     870               0 : nsPNGDecoder::error_callback(png_structp png_ptr, png_const_charp error_msg)
     871                 : {
     872               0 :   PR_LOG(gPNGLog, PR_LOG_ERROR, ("libpng error: %s\n", error_msg));
     873               0 :   longjmp(png_jmpbuf(png_ptr), 1);
     874                 : }
     875                 : 
     876                 : 
     877                 : void
     878               0 : nsPNGDecoder::warning_callback(png_structp png_ptr, png_const_charp warning_msg)
     879                 : {
     880               0 :   PR_LOG(gPNGLog, PR_LOG_WARNING, ("libpng warning: %s\n", warning_msg));
     881               0 : }
     882                 : 
     883                 : Telemetry::ID
     884               0 : nsPNGDecoder::SpeedHistogram()
     885                 : {
     886               0 :   return Telemetry::IMAGE_DECODE_SPEED_PNG;
     887                 : }
     888                 : 
     889                 : 
     890                 : } // namespace image
     891            4392 : } // namespace mozilla

Generated by: LCOV version 1.7