|
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/. */ |
|
6 |
|
7 #include "ImageLogging.h" |
|
8 #include "nsPNGDecoder.h" |
|
9 |
|
10 #include "nsMemory.h" |
|
11 #include "nsRect.h" |
|
12 |
|
13 #include "nsIInputStream.h" |
|
14 |
|
15 #include "RasterImage.h" |
|
16 |
|
17 #include "gfxColor.h" |
|
18 #include "nsColor.h" |
|
19 |
|
20 #include "nspr.h" |
|
21 #include "png.h" |
|
22 |
|
23 #include "gfxPlatform.h" |
|
24 #include <algorithm> |
|
25 |
|
26 namespace mozilla { |
|
27 namespace image { |
|
28 |
|
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 } |
|
38 |
|
39 static PRLogModuleInfo * |
|
40 GetPNGDecoderAccountingLog() |
|
41 { |
|
42 static PRLogModuleInfo *sPNGDecoderAccountingLog; |
|
43 if (!sPNGDecoderAccountingLog) |
|
44 sPNGDecoderAccountingLog = PR_NewLogModule("PNGDecoderAccounting"); |
|
45 return sPNGDecoderAccountingLog; |
|
46 } |
|
47 #endif |
|
48 |
|
49 /* limit image dimensions (bug #251381, #591822, and #967656) */ |
|
50 #ifndef MOZ_PNG_MAX_DIMENSION |
|
51 # define MOZ_PNG_MAX_DIMENSION 32767 |
|
52 #endif |
|
53 |
|
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) |
|
58 |
|
59 nsPNGDecoder::AnimFrameInfo::AnimFrameInfo() |
|
60 : mDispose(FrameBlender::kDisposeKeep) |
|
61 , mBlend(FrameBlender::kBlendOver) |
|
62 , mTimeout(0) |
|
63 {} |
|
64 |
|
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); |
|
79 |
|
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 |
|
85 |
|
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 } |
|
90 |
|
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 } |
|
98 |
|
99 if (blend_op == PNG_BLEND_OP_SOURCE) { |
|
100 mBlend = FrameBlender::kBlendSource; |
|
101 } else { |
|
102 mBlend = FrameBlender::kBlendOver; |
|
103 } |
|
104 } |
|
105 #endif |
|
106 |
|
107 // First 8 bytes of a PNG file |
|
108 const uint8_t |
|
109 nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 }; |
|
110 |
|
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 } |
|
122 |
|
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); |
|
133 |
|
134 /* mTransform belongs to us only if mInProfile is non-null */ |
|
135 if (mTransform) |
|
136 qcms_transform_release(mTransform); |
|
137 } |
|
138 } |
|
139 |
|
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 } |
|
157 |
|
158 mFrameRect.x = x_offset; |
|
159 mFrameRect.y = y_offset; |
|
160 mFrameRect.width = width; |
|
161 mFrameRect.height = height; |
|
162 |
|
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)); |
|
168 |
|
169 mFrameHasNoAlpha = true; |
|
170 |
|
171 #ifdef PNG_APNG_SUPPORTED |
|
172 if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) { |
|
173 mAnimInfo = AnimFrameInfo(mPNG, mInfo); |
|
174 } |
|
175 #endif |
|
176 } |
|
177 |
|
178 // set timeout and frame disposal method for the current frame |
|
179 void nsPNGDecoder::EndImageFrame() |
|
180 { |
|
181 if (mFrameIsHidden) |
|
182 return; |
|
183 |
|
184 mNumFrames++; |
|
185 |
|
186 FrameBlender::FrameAlpha alpha; |
|
187 if (mFrameHasNoAlpha) |
|
188 alpha = FrameBlender::kFrameOpaque; |
|
189 else |
|
190 alpha = FrameBlender::kFrameHasAlpha; |
|
191 |
|
192 #ifdef PNG_APNG_SUPPORTED |
|
193 uint32_t numFrames = GetFrameCount(); |
|
194 |
|
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 |
|
200 |
|
201 PostFrameStop(alpha, mAnimInfo.mDispose, mAnimInfo.mTimeout, mAnimInfo.mBlend); |
|
202 } |
|
203 |
|
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 } |
|
211 |
|
212 mCMSMode = gfxPlatform::GetCMSMode(); |
|
213 if ((mDecodeFlags & DECODER_NO_COLORSPACE_CONVERSION) != 0) |
|
214 mCMSMode = eCMSMode_Off; |
|
215 mDisablePremultipliedAlpha = (mDecodeFlags & DECODER_NO_PREMULTIPLY_ALPHA) != 0; |
|
216 |
|
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 |
|
235 |
|
236 /* For full decodes, do png init stuff */ |
|
237 |
|
238 /* Initialize the container's source image header. */ |
|
239 /* Always decode to 24 bit pixdepth */ |
|
240 |
|
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 } |
|
248 |
|
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 } |
|
255 |
|
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); |
|
260 |
|
261 png_set_keep_unknown_chunks(mPNG, 1, unused_chunks, |
|
262 (int)sizeof(unused_chunks)/5); |
|
263 #endif |
|
264 |
|
265 #ifdef PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED |
|
266 if (mCMSMode != eCMSMode_Off) |
|
267 png_set_chunk_malloc_max(mPNG, 4000000L); |
|
268 #endif |
|
269 |
|
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 |
|
281 |
|
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); |
|
287 |
|
288 } |
|
289 |
|
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!"); |
|
294 |
|
295 // If we only want width/height, we don't need to go through libpng |
|
296 if (IsSizeDecode()) { |
|
297 |
|
298 // Are we done? |
|
299 if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) |
|
300 return; |
|
301 |
|
302 // Scan the header for the width and height bytes |
|
303 uint32_t pos = 0; |
|
304 const uint8_t *bptr = (uint8_t *)aBuffer; |
|
305 |
|
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 } |
|
314 |
|
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 } |
|
323 |
|
324 // If we're done now, verify the data and set up the container |
|
325 if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) { |
|
326 |
|
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); |
|
330 |
|
331 // Too big? |
|
332 if ((width > MOZ_PNG_MAX_DIMENSION) || (height > MOZ_PNG_MAX_DIMENSION)) { |
|
333 PostDataError(); |
|
334 return; |
|
335 } |
|
336 |
|
337 // Post our size to the superclass |
|
338 PostSize(width, height); |
|
339 } |
|
340 } |
|
341 |
|
342 // Otherwise, we're doing a standard decode |
|
343 else { |
|
344 |
|
345 // libpng uses setjmp/longjmp for error handling - set the buffer |
|
346 if (setjmp(png_jmpbuf(mPNG))) { |
|
347 |
|
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(); |
|
352 |
|
353 png_destroy_read_struct(&mPNG, &mInfo, nullptr); |
|
354 return; |
|
355 } |
|
356 |
|
357 // Pass the data off to libpng |
|
358 png_process_data(mPNG, mInfo, (unsigned char *)aBuffer, aCount); |
|
359 |
|
360 } |
|
361 } |
|
362 |
|
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; |
|
369 |
|
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); |
|
379 |
|
380 } |
|
381 |
|
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 |
|
389 |
|
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; |
|
400 |
|
401 png_get_iCCP(png_ptr, info_ptr, &profileName, &compression, |
|
402 &profileData, &profileLen); |
|
403 |
|
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); |
|
413 |
|
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 } |
|
424 |
|
425 if (mismatch) { |
|
426 qcms_profile_release(profile); |
|
427 profile = nullptr; |
|
428 } else { |
|
429 *intent = qcms_profile_get_rendering_intent(profile); |
|
430 } |
|
431 } |
|
432 } |
|
433 |
|
434 // Check sRGB chunk |
|
435 if (!profile && png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { |
|
436 profile = qcms_profile_sRGB(); |
|
437 |
|
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 } |
|
449 |
|
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; |
|
456 |
|
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; |
|
464 |
|
465 double gammaOfFile; |
|
466 |
|
467 png_get_gAMA(png_ptr, info_ptr, &gammaOfFile); |
|
468 |
|
469 profile = qcms_profile_create_rgb_with_gamma(whitePoint, primaries, |
|
470 1.0/gammaOfFile); |
|
471 |
|
472 if (profile) |
|
473 png_set_gray_to_rgb(png_ptr); |
|
474 } |
|
475 |
|
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 } |
|
491 |
|
492 return profile; |
|
493 } |
|
494 |
|
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; |
|
502 |
|
503 png_bytep trans = nullptr; |
|
504 int num_trans = 0; |
|
505 |
|
506 nsPNGDecoder *decoder = |
|
507 static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr)); |
|
508 |
|
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); |
|
512 |
|
513 /* Are we too big? */ |
|
514 if (width > MOZ_PNG_MAX_DIMENSION || height > MOZ_PNG_MAX_DIMENSION) |
|
515 longjmp(png_jmpbuf(decoder->mPNG), 1); |
|
516 |
|
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 } |
|
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_scale_16(png_ptr); |
|
554 |
|
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; |
|
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 // This code is currently unused, but it will be needed for bug 517713. |
|
610 #if 0 |
|
611 int32_t alpha_bits = 1; |
|
612 |
|
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 |
|
630 |
|
631 if (channels == 1 || channels == 3) |
|
632 decoder->format = gfxImageFormat::RGB24; |
|
633 else if (channels == 2 || channels == 4) |
|
634 decoder->format = gfxImageFormat::ARGB32; |
|
635 |
|
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); |
|
640 |
|
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 |
|
649 |
|
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 } |
|
659 |
|
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 } |
|
667 |
|
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 } |
|
675 |
|
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)); |
|
709 |
|
710 // skip this frame |
|
711 if (decoder->mFrameIsHidden) |
|
712 return; |
|
713 |
|
714 if (row_num >= (png_uint_32) decoder->mFrameRect.height) |
|
715 return; |
|
716 |
|
717 if (new_row) { |
|
718 int32_t width = decoder->mFrameRect.width; |
|
719 uint32_t iwidth = decoder->mFrameRect.width; |
|
720 |
|
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 } |
|
726 |
|
727 uint32_t bpr = width * sizeof(uint32_t); |
|
728 uint32_t *cptr32 = (uint32_t*)(decoder->mImageData + (row_num*bpr)); |
|
729 bool rowHasNoAlpha = true; |
|
730 |
|
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 } |
|
746 |
|
747 switch (decoder->format) { |
|
748 case gfxImageFormat::RGB24: |
|
749 { |
|
750 // counter for while() loops below |
|
751 uint32_t idx = iwidth; |
|
752 |
|
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 } |
|
758 |
|
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 } |
|
766 |
|
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 } |
|
797 |
|
798 if (!rowHasNoAlpha) |
|
799 decoder->mFrameHasNoAlpha = false; |
|
800 |
|
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 } |
|
809 |
|
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; |
|
817 |
|
818 nsPNGDecoder *decoder = |
|
819 static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr)); |
|
820 |
|
821 // old frame is done |
|
822 decoder->EndImageFrame(); |
|
823 |
|
824 // Only the first frame can be hidden, so unhide unconditionally here. |
|
825 decoder->mFrameIsHidden = false; |
|
826 |
|
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); |
|
831 |
|
832 decoder->CreateFrame(x_offset, y_offset, width, height, decoder->format); |
|
833 |
|
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 |
|
842 |
|
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 */ |
|
857 |
|
858 nsPNGDecoder *decoder = |
|
859 static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr)); |
|
860 |
|
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!"); |
|
863 |
|
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 |
|
871 |
|
872 // Send final notifications |
|
873 decoder->EndImageFrame(); |
|
874 decoder->PostDecodeDone(loop_count); |
|
875 } |
|
876 |
|
877 |
|
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 } |
|
884 |
|
885 |
|
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 } |
|
891 |
|
892 Telemetry::ID |
|
893 nsPNGDecoder::SpeedHistogram() |
|
894 { |
|
895 return Telemetry::IMAGE_DECODE_SPEED_PNG; |
|
896 } |
|
897 |
|
898 |
|
899 } // namespace image |
|
900 } // namespace mozilla |