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
|