PageRenderTime 78ms CodeModel.GetById 13ms app.highlight 58ms RepoModel.GetById 1ms app.codeStats 0ms

/image/decoders/nsPNGDecoder.cpp

http://github.com/zpao/v8monkey
C++ | 891 lines | 590 code | 139 blank | 162 comment | 163 complexity | 2bfe382a8670c9403e1cf545275fbc1c MD5 | raw file
  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
 63namespace mozilla {
 64namespace image {
 65
 66#ifdef PR_LOGGING
 67static PRLogModuleInfo *gPNGLog = PR_NewLogModule("PNGDecoder");
 68static PRLogModuleInfo *gPNGDecoderAccountingLog =
 69                        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
 81const PRUint8 
 82nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
 83
 84nsPNGDecoder::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   mCMSMode(0), mDisablePremultipliedAlpha(false)
 92{
 93}
 94
 95nsPNGDecoder::~nsPNGDecoder()
 96{
 97  if (mPNG)
 98    png_destroy_read_struct(&mPNG, mInfo ? &mInfo : NULL, NULL);
 99  if (mCMSLine)
100    nsMemory::Free(mCMSLine);
101  if (interlacebuf)
102    nsMemory::Free(interlacebuf);
103  if (mInProfile) {
104    qcms_profile_release(mInProfile);
105
106    /* mTransform belongs to us only if mInProfile is non-null */
107    if (mTransform)
108      qcms_transform_release(mTransform);
109  }
110  if (mHeaderBuf)
111    nsMemory::Free(mHeaderBuf);
112}
113
114// CreateFrame() is used for both simple and animated images
115void 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                                   &mImageData, &imageDataLength);
123  if (NS_FAILED(rv))
124    longjmp(png_jmpbuf(mPNG), 5); // NS_ERROR_OUT_OF_MEMORY
125
126  mFrameRect.x = x_offset;
127  mFrameRect.y = y_offset;
128  mFrameRect.width = width;
129  mFrameRect.height = height;
130
131#ifdef PNG_APNG_SUPPORTED
132  if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL))
133    SetAnimFrameInfo();
134#endif
135
136  // Tell the superclass we're starting a frame
137  PostFrameStart();
138
139  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  mFrameHasNoAlpha = true;
146}
147
148#ifdef PNG_APNG_SUPPORTED
149// set timeout and frame disposal method for the current frame
150void 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  delay_num = png_get_next_frame_delay_num(mPNG, mInfo);
159  delay_den = png_get_next_frame_delay_den(mPNG, mInfo);
160  dispose_op = png_get_next_frame_dispose_op(mPNG, mInfo);
161  blend_op = png_get_next_frame_blend_op(mPNG, mInfo);
162
163  if (delay_num == 0) {
164    timeout = 0; // SetFrameTimeout() will set to a minimum
165  } else {
166    if (delay_den == 0)
167      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              (static_cast<PRFloat64>(delay_num) * 1000 / delay_den);
173  }
174
175  PRUint32 numFrames = mImage.GetNumFrames();
176
177  mImage.SetFrameTimeout(numFrames - 1, timeout);
178
179  if (dispose_op == PNG_DISPOSE_OP_PREVIOUS)
180      mImage.SetFrameDisposalMethod(numFrames - 1,
181                                    RasterImage::kDisposeRestorePrevious);
182  else if (dispose_op == PNG_DISPOSE_OP_BACKGROUND)
183      mImage.SetFrameDisposalMethod(numFrames - 1,
184                                    RasterImage::kDisposeClear);
185  else
186      mImage.SetFrameDisposalMethod(numFrames - 1,
187                                    RasterImage::kDisposeKeep);
188
189  if (blend_op == PNG_BLEND_OP_SOURCE)
190      mImage.SetFrameBlendMethod(numFrames - 1, RasterImage::kBlendSource);
191  /*else // 'over' is the default
192      mImage.SetFrameBlendMethod(numFrames - 1, RasterImage::kBlendOver); */
193}
194#endif
195
196// set timeout and frame disposal method for the current frame
197void nsPNGDecoder::EndImageFrame()
198{
199  if (mFrameIsHidden)
200    return;
201
202  PRUint32 numFrames = 1;
203#ifdef PNG_APNG_SUPPORTED
204  numFrames = mImage.GetNumFrames();
205
206  // We can't use mPNG->num_frames_read as it may be one ahead.
207  if (numFrames > 1) {
208    // Tell the image renderer that the frame is complete
209    if (mFrameHasNoAlpha)
210      mImage.SetFrameHasNoAlpha(numFrames - 1);
211
212    PostInvalidation(mFrameRect);
213  }
214#endif
215
216  PostFrameStop();
217}
218
219void
220nsPNGDecoder::InitInternal()
221{
222  mCMSMode = gfxPlatform::GetCMSMode();
223  if ((mDecodeFlags & DECODER_NO_COLORSPACE_CONVERSION) != 0)
224    mCMSMode = eCMSMode_Off;
225  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  if (IsSizeDecode()) {
248    mHeaderBuf = (PRUint8 *)moz_xmalloc(BYTES_NEEDED_FOR_DIMENSIONS);
249    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                                nsPNGDecoder::warning_callback);
260  if (!mPNG) {
261    PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
262    return;
263  }
264
265  mInfo = png_create_info_struct(mPNG);
266  if (!mInfo) {
267    PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
268    png_destroy_read_struct(&mPNG, NULL, NULL);
269    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                              nsPNGDecoder::end_callback);
291
292}
293
294void
295nsPNGDecoder::WriteInternal(const char *aBuffer, PRUint32 aCount)
296{
297  // We use gotos, so we need to declare variables here
298  PRUint32 width = 0;
299  PRUint32 height = 0;
300
301  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  if (IsSizeDecode()) {
305
306    // Are we done?
307    if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS)
308      return;
309
310    // Read data into our header buffer
311    PRUint32 bytesToRead = NS_MIN(aCount, BYTES_NEEDED_FOR_DIMENSIONS -
312                                  mHeaderBytesRead);
313    memcpy(mHeaderBuf + mHeaderBytesRead, aBuffer, bytesToRead);
314    mHeaderBytesRead += bytesToRead;
315
316    // If we're done now, verify the data and set up the container
317    if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) {
318
319      // Check that the signature bytes are right
320      if (memcmp(mHeaderBuf, nsPNGDecoder::pngSignatureBytes, 
321                 sizeof(pngSignatureBytes))) {
322        PostDataError();
323        return;
324      }
325
326      // Grab the width and height, accounting for endianness (thanks libpng!)
327      width = png_get_uint_32(mHeaderBuf + WIDTH_OFFSET);
328      height = png_get_uint_32(mHeaderBuf + HEIGHT_OFFSET);
329
330      // Too big?
331      if ((width > MOZ_PNG_MAX_DIMENSION) || (height > MOZ_PNG_MAX_DIMENSION)) {
332        PostDataError();
333        return;
334      }
335
336      // Post our size to the superclass
337      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    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      if (!HasError())
350        PostDataError();
351
352      png_destroy_read_struct(&mPNG, &mInfo, NULL);
353      return;
354    }
355
356    // Pass the data off to libpng
357    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.
364static void
365PNGDoGammaCorrection(png_structp png_ptr, png_infop info_ptr)
366{
367  double aGamma;
368
369  if (png_get_gAMA(png_ptr, info_ptr, &aGamma)) {
370    if ((aGamma <= 0.0) || (aGamma > 21474.83)) {
371      aGamma = 0.45455;
372      png_set_gAMA(png_ptr, info_ptr, aGamma);
373    }
374    png_set_gamma(png_ptr, 2.2, aGamma);
375  }
376  else
377    png_set_gamma(png_ptr, 2.2, 0.45455);
378
379}
380
381// Adapted from http://www.littlecms.com/pngchrm.c example code
382static qcms_profile *
383PNGGetColorProfile(png_structp png_ptr, png_infop info_ptr,
384                   int color_type, qcms_data_type *inType, PRUint32 *intent)
385{
386  qcms_profile *profile = nsnull;
387  *intent = QCMS_INTENT_PERCEPTUAL; // Our default
388
389  // First try to see if iCCP chunk is present
390  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                 &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                                       profileLen);
410    if (profile) {
411      PRUint32 profileSpace = qcms_profile_get_color_space(profile);
412
413      bool mismatch = false;
414      if (color_type & PNG_COLOR_MASK_COLOR) {
415        if (profileSpace != icSigRgbData)
416          mismatch = true;
417      } else {
418        if (profileSpace == icSigRgbData)
419          png_set_gray_to_rgb(png_ptr);
420        else if (profileSpace != icSigGrayData)
421          mismatch = true;
422      }
423
424      if (mismatch) {
425        qcms_profile_release(profile);
426        profile = nsnull;
427      } else {
428        *intent = qcms_profile_get_rendering_intent(profile);
429      }
430    }
431  }
432
433  // Check sRGB chunk
434  if (!profile && png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) {
435    profile = qcms_profile_sRGB();
436
437    if (profile) {
438      int fileIntent;
439      png_set_gray_to_rgb(png_ptr);
440      png_get_sRGB(png_ptr, info_ptr, &fileIntent);
441      PRUint32 map[] = { QCMS_INTENT_PERCEPTUAL,
442                         QCMS_INTENT_RELATIVE_COLORIMETRIC,
443                         QCMS_INTENT_SATURATION,
444                         QCMS_INTENT_ABSOLUTE_COLORIMETRIC };
445      *intent = map[fileIntent];
446    }
447  }
448
449  // Check gAMA/cHRM chunks
450  if (!profile &&
451       png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) &&
452       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                 &primaries.blue.x,  &primaries.blue.y);
461    whitePoint.Y =
462      primaries.red.Y = primaries.green.Y = primaries.blue.Y = 1.0;
463
464    double gammaOfFile;
465
466    png_get_gAMA(png_ptr, info_ptr, &gammaOfFile);
467
468    profile = qcms_profile_create_rgb_with_gamma(whitePoint, primaries,
469                                                 1.0/gammaOfFile);
470
471    if (profile)
472      png_set_gray_to_rgb(png_ptr);
473  }
474
475  if (profile) {
476    PRUint32 profileSpace = qcms_profile_get_color_space(profile);
477    if (profileSpace == icSigGrayData) {
478      if (color_type & PNG_COLOR_MASK_ALPHA)
479        *inType = QCMS_DATA_GRAYA_8;
480      else
481        *inType = QCMS_DATA_GRAY_8;
482    } else {
483      if (color_type & PNG_COLOR_MASK_ALPHA ||
484          png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
485        *inType = QCMS_DATA_RGBA_8;
486      else
487        *inType = QCMS_DATA_RGB_8;
488    }
489  }
490
491  return profile;
492}
493
494void
495nsPNGDecoder::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  png_bytep trans = NULL;
503  int num_trans = 0;
504
505  nsPNGDecoder *decoder =
506               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               &interlace_type, &compression_type, &filter_type);
511
512  /* Are we too big? */
513  if (width > MOZ_PNG_MAX_DIMENSION || height > MOZ_PNG_MAX_DIMENSION)
514    longjmp(png_jmpbuf(decoder->mPNG), 1);
515
516  // Post our size to the superclass
517  decoder->PostSize(width, height);
518  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    longjmp(png_jmpbuf(decoder->mPNG), 1);
522  }
523
524  if (color_type == PNG_COLOR_TYPE_PALETTE)
525    png_set_expand(png_ptr);
526
527  if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
528    png_set_expand(png_ptr);
529
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  }
551
552  if (bit_depth == 16)
553    png_set_strip_16(png_ptr);
554
555  qcms_data_type inType;
556  PRUint32 intent = -1;
557  PRUint32 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 == PRUint32(-1))
564      intent = pIntent;
565  }
566  if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) {
567    qcms_data_type outType;
568
569    if (color_type & PNG_COLOR_MASK_ALPHA || num_trans)
570      outType = QCMS_DATA_RGBA_8;
571    else
572      outType = QCMS_DATA_RGB_8;
573
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);
581
582    // only do gamma correction if CMS isn't entirely disabled
583    if (decoder->mCMSMode != eCMSMode_Off)
584      PNGDoGammaCorrection(png_ptr, info_ptr);
585
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  }
593
594  /* let libpng expand interlaced images */
595  if (interlace_type == PNG_INTERLACE_ADAM7) {
596    /* number_passes = */
597    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  png_read_update_info(png_ptr, info_ptr);
603  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  PRInt32 alpha_bits = 1;
610
611  if (channels == 2 || channels == 4) {
612    /* check if alpha is coming from a tRNS chunk and is binary */
613    if (num_trans) {
614      /* if it's not an indexed color image, tRNS means binary */
615      if (color_type == PNG_COLOR_TYPE_PALETTE) {
616        for (int i=0; i<num_trans; i++) {
617          if ((trans[i] != 0) && (trans[i] != 255)) {
618            alpha_bits = 8;
619            break;
620          }
621        }
622      }
623    } else {
624      alpha_bits = 8;
625    }
626  }
627
628  if (channels == 1 || channels == 3)
629    decoder->format = gfxASurface::ImageFormatRGB24;
630  else if (channels == 2 || channels == 4)
631    decoder->format = gfxASurface::ImageFormatARGB32;
632
633#ifdef PNG_APNG_SUPPORTED
634  if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL))
635    png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback, NULL);
636
637  if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) {
638    decoder->mFrameIsHidden = true;
639  } else {
640#endif
641    decoder->CreateFrame(0, 0, width, height, decoder->format);
642#ifdef PNG_APNG_SUPPORTED
643  }
644#endif
645
646  if (decoder->mTransform &&
647      (channels <= 2 || interlace_type == PNG_INTERLACE_ADAM7)) {
648    PRUint32 bpp[] = { 0, 3, 4, 3, 4 };
649    decoder->mCMSLine =
650      (PRUint8 *)moz_malloc(bpp[channels] * width);
651    if (!decoder->mCMSLine) {
652      longjmp(png_jmpbuf(decoder->mPNG), 5); // NS_ERROR_OUT_OF_MEMORY
653    }
654  }
655
656  if (interlace_type == PNG_INTERLACE_ADAM7) {
657    if (height < PR_INT32_MAX / (width * channels))
658      decoder->interlacebuf = (PRUint8 *)moz_malloc(channels * width * height);
659    if (!decoder->interlacebuf) {
660      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  png_set_crc_action(png_ptr, PNG_CRC_NO_CHANGE, PNG_CRC_ERROR_QUIT);
670
671  return;
672}
673
674void
675nsPNGDecoder::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               static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
707
708  // skip this frame
709  if (decoder->mFrameIsHidden)
710    return;
711
712  if (row_num >= (png_uint_32) decoder->mFrameRect.height)
713    return;
714
715  if (new_row) {
716    PRInt32 width = decoder->mFrameRect.width;
717    PRUint32 iwidth = decoder->mFrameRect.width;
718
719    png_bytep line = new_row;
720    if (decoder->interlacebuf) {
721      line = decoder->interlacebuf + (row_num * decoder->mChannels * width);
722      png_progressive_combine_row(png_ptr, line, new_row);
723    }
724
725    PRUint32 bpr = width * sizeof(PRUint32);
726    PRUint32 *cptr32 = (PRUint32*)(decoder->mImageData + (row_num*bpr));
727    bool rowHasNoAlpha = true;
728
729    if (decoder->mTransform) {
730      if (decoder->mCMSLine) {
731        qcms_transform_data(decoder->mTransform, line, decoder->mCMSLine,
732                            iwidth);
733        /* copy alpha over */
734        PRUint32 channels = decoder->mChannels;
735        if (channels == 2 || channels == 4) {
736          for (PRUint32 i = 0; i < iwidth; i++)
737            decoder->mCMSLine[4 * i + 3] = line[channels * i + channels - 1];
738        }
739        line = decoder->mCMSLine;
740      } else {
741        qcms_transform_data(decoder->mTransform, line, line, iwidth);
742       }
743     }
744
745    switch (decoder->format) {
746      case gfxASurface::ImageFormatRGB24:
747      {
748        // counter for while() loops below
749        PRUint32 idx = iwidth;
750
751        // copy as bytes until source pointer is 32-bit-aligned
752        for (; (NS_PTR_TO_UINT32(line) & 0x3) && idx; --idx) {
753          *cptr32++ = GFX_PACKED_PIXEL(0xFF, line[0], line[1], line[2]);
754          line += 3;
755        }
756
757        // copy pixels in blocks of 4
758        while (idx >= 4) {
759          GFX_BLOCK_RGB_TO_FRGB(line, cptr32);
760          idx    -=  4;
761          line   += 12;
762          cptr32 +=  4;
763        }
764
765        // copy remaining pixel(s)
766        while (idx--) {
767          // 32-bit read of final pixel will exceed buffer, so read bytes
768          *cptr32++ = GFX_PACKED_PIXEL(0xFF, line[0], line[1], line[2]);
769          line += 3;
770        }
771      }
772      break;
773      case gfxASurface::ImageFormatARGB32:
774      {
775        if (!decoder->mDisablePremultipliedAlpha) {
776          for (PRUint32 x=width; x>0; --x) {
777            *cptr32++ = GFX_PACKED_PIXEL(line[3], line[0], line[1], line[2]);
778            if (line[3] != 0xff)
779              rowHasNoAlpha = false;
780            line += 4;
781          }
782        } else {
783          for (PRUint32 x=width; x>0; --x) {
784            *cptr32++ = GFX_PACKED_PIXEL_NO_PREMULTIPLY(line[3], line[0], line[1], line[2]);
785            if (line[3] != 0xff)
786              rowHasNoAlpha = false;
787            line += 4;
788          }
789        }
790      }
791      break;
792      default:
793        longjmp(png_jmpbuf(decoder->mPNG), 1);
794    }
795
796    if (!rowHasNoAlpha)
797      decoder->mFrameHasNoAlpha = false;
798
799    PRUint32 numFrames = decoder->mImage.GetNumFrames();
800    if (numFrames <= 1) {
801      // Only do incremental image display for the first frame
802      // XXXbholley - this check should be handled in the superclass
803      nsIntRect r(0, row_num, width, 1);
804      decoder->PostInvalidation(r);
805    }
806  }
807}
808
809// got the header of a new frame that's coming
810void
811nsPNGDecoder::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               static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
819
820  // old frame is done
821  decoder->EndImageFrame();
822
823  // Only the first frame can be hidden, so unhide unconditionally here.
824  decoder->mFrameIsHidden = false;
825
826  x_offset = png_get_next_frame_x_offset(png_ptr, decoder->mInfo);
827  y_offset = png_get_next_frame_y_offset(png_ptr, decoder->mInfo);
828  width = png_get_next_frame_width(png_ptr, decoder->mInfo);
829  height = png_get_next_frame_height(png_ptr, decoder->mInfo);
830
831  decoder->CreateFrame(x_offset, y_offset, width, height, decoder->format);
832#endif
833}
834
835void
836nsPNGDecoder::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               static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
852
853  // We shouldn't get here if we've hit an error
854  NS_ABORT_IF_FALSE(!decoder->HasError(), "Finishing up PNG but hit error!");
855
856#ifdef PNG_APNG_SUPPORTED
857  if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) {
858    PRInt32 num_plays = png_get_num_plays(png_ptr, info_ptr);
859    decoder->mImage.SetLoopCount(num_plays - 1);
860  }
861#endif
862
863  // Send final notifications
864  decoder->EndImageFrame();
865  decoder->PostDecodeDone();
866}
867
868
869void
870nsPNGDecoder::error_callback(png_structp png_ptr, png_const_charp error_msg)
871{
872  PR_LOG(gPNGLog, PR_LOG_ERROR, ("libpng error: %s\n", error_msg));
873  longjmp(png_jmpbuf(png_ptr), 1);
874}
875
876
877void
878nsPNGDecoder::warning_callback(png_structp png_ptr, png_const_charp warning_msg)
879{
880  PR_LOG(gPNGLog, PR_LOG_WARNING, ("libpng warning: %s\n", warning_msg));
881}
882
883Telemetry::ID
884nsPNGDecoder::SpeedHistogram()
885{
886  return Telemetry::IMAGE_DECODE_SPEED_PNG;
887}
888
889
890} // namespace image
891} // namespace mozilla