Wed, 31 Dec 2014 06:09:35 +0100
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