1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/image/decoders/nsPNGDecoder.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,900 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "ImageLogging.h" 1.11 +#include "nsPNGDecoder.h" 1.12 + 1.13 +#include "nsMemory.h" 1.14 +#include "nsRect.h" 1.15 + 1.16 +#include "nsIInputStream.h" 1.17 + 1.18 +#include "RasterImage.h" 1.19 + 1.20 +#include "gfxColor.h" 1.21 +#include "nsColor.h" 1.22 + 1.23 +#include "nspr.h" 1.24 +#include "png.h" 1.25 + 1.26 +#include "gfxPlatform.h" 1.27 +#include <algorithm> 1.28 + 1.29 +namespace mozilla { 1.30 +namespace image { 1.31 + 1.32 +#ifdef PR_LOGGING 1.33 +static PRLogModuleInfo * 1.34 +GetPNGLog() 1.35 +{ 1.36 + static PRLogModuleInfo *sPNGLog; 1.37 + if (!sPNGLog) 1.38 + sPNGLog = PR_NewLogModule("PNGDecoder"); 1.39 + return sPNGLog; 1.40 +} 1.41 + 1.42 +static PRLogModuleInfo * 1.43 +GetPNGDecoderAccountingLog() 1.44 +{ 1.45 + static PRLogModuleInfo *sPNGDecoderAccountingLog; 1.46 + if (!sPNGDecoderAccountingLog) 1.47 + sPNGDecoderAccountingLog = PR_NewLogModule("PNGDecoderAccounting"); 1.48 + return sPNGDecoderAccountingLog; 1.49 +} 1.50 +#endif 1.51 + 1.52 +/* limit image dimensions (bug #251381, #591822, and #967656) */ 1.53 +#ifndef MOZ_PNG_MAX_DIMENSION 1.54 +# define MOZ_PNG_MAX_DIMENSION 32767 1.55 +#endif 1.56 + 1.57 +// For size decodes 1.58 +#define WIDTH_OFFSET 16 1.59 +#define HEIGHT_OFFSET (WIDTH_OFFSET + 4) 1.60 +#define BYTES_NEEDED_FOR_DIMENSIONS (HEIGHT_OFFSET + 4) 1.61 + 1.62 +nsPNGDecoder::AnimFrameInfo::AnimFrameInfo() 1.63 + : mDispose(FrameBlender::kDisposeKeep) 1.64 + , mBlend(FrameBlender::kBlendOver) 1.65 + , mTimeout(0) 1.66 +{} 1.67 + 1.68 +#ifdef PNG_APNG_SUPPORTED 1.69 +nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo) 1.70 + : mDispose(FrameBlender::kDisposeKeep) 1.71 + , mBlend(FrameBlender::kBlendOver) 1.72 + , mTimeout(0) 1.73 +{ 1.74 + png_uint_16 delay_num, delay_den; 1.75 + /* delay, in seconds is delay_num/delay_den */ 1.76 + png_byte dispose_op; 1.77 + png_byte blend_op; 1.78 + delay_num = png_get_next_frame_delay_num(aPNG, aInfo); 1.79 + delay_den = png_get_next_frame_delay_den(aPNG, aInfo); 1.80 + dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo); 1.81 + blend_op = png_get_next_frame_blend_op(aPNG, aInfo); 1.82 + 1.83 + if (delay_num == 0) { 1.84 + mTimeout = 0; // SetFrameTimeout() will set to a minimum 1.85 + } else { 1.86 + if (delay_den == 0) 1.87 + delay_den = 100; // so says the APNG spec 1.88 + 1.89 + // Need to cast delay_num to float to have a proper division and 1.90 + // the result to int to avoid compiler warning 1.91 + mTimeout = static_cast<int32_t>(static_cast<double>(delay_num) * 1000 / delay_den); 1.92 + } 1.93 + 1.94 + if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) { 1.95 + mDispose = FrameBlender::kDisposeRestorePrevious; 1.96 + } else if (dispose_op == PNG_DISPOSE_OP_BACKGROUND) { 1.97 + mDispose = FrameBlender::kDisposeClear; 1.98 + } else { 1.99 + mDispose = FrameBlender::kDisposeKeep; 1.100 + } 1.101 + 1.102 + if (blend_op == PNG_BLEND_OP_SOURCE) { 1.103 + mBlend = FrameBlender::kBlendSource; 1.104 + } else { 1.105 + mBlend = FrameBlender::kBlendOver; 1.106 + } 1.107 +} 1.108 +#endif 1.109 + 1.110 +// First 8 bytes of a PNG file 1.111 +const uint8_t 1.112 +nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 }; 1.113 + 1.114 +nsPNGDecoder::nsPNGDecoder(RasterImage &aImage) 1.115 + : Decoder(aImage), 1.116 + mPNG(nullptr), mInfo(nullptr), 1.117 + mCMSLine(nullptr), interlacebuf(nullptr), 1.118 + mInProfile(nullptr), mTransform(nullptr), 1.119 + mHeaderBytesRead(0), mCMSMode(0), 1.120 + mChannels(0), mFrameIsHidden(false), 1.121 + mDisablePremultipliedAlpha(false), 1.122 + mNumFrames(0) 1.123 +{ 1.124 +} 1.125 + 1.126 +nsPNGDecoder::~nsPNGDecoder() 1.127 +{ 1.128 + if (mPNG) 1.129 + png_destroy_read_struct(&mPNG, mInfo ? &mInfo : nullptr, nullptr); 1.130 + if (mCMSLine) 1.131 + nsMemory::Free(mCMSLine); 1.132 + if (interlacebuf) 1.133 + nsMemory::Free(interlacebuf); 1.134 + if (mInProfile) { 1.135 + qcms_profile_release(mInProfile); 1.136 + 1.137 + /* mTransform belongs to us only if mInProfile is non-null */ 1.138 + if (mTransform) 1.139 + qcms_transform_release(mTransform); 1.140 + } 1.141 +} 1.142 + 1.143 +// CreateFrame() is used for both simple and animated images 1.144 +void nsPNGDecoder::CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset, 1.145 + int32_t width, int32_t height, 1.146 + gfxImageFormat format) 1.147 +{ 1.148 + // Our first full frame is automatically created by the image decoding 1.149 + // infrastructure. Just use it as long as it matches up. 1.150 + MOZ_ASSERT(HasSize()); 1.151 + if (mNumFrames != 0 || 1.152 + !GetCurrentFrame()->GetRect().IsEqualEdges(nsIntRect(x_offset, y_offset, width, height))) { 1.153 + NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format); 1.154 + } else if (mNumFrames == 0) { 1.155 + // Our preallocated frame matches up, with the possible exception of alpha. 1.156 + if (format == gfxImageFormat::RGB24) { 1.157 + GetCurrentFrame()->SetHasNoAlpha(); 1.158 + } 1.159 + } 1.160 + 1.161 + mFrameRect.x = x_offset; 1.162 + mFrameRect.y = y_offset; 1.163 + mFrameRect.width = width; 1.164 + mFrameRect.height = height; 1.165 + 1.166 + PR_LOG(GetPNGDecoderAccountingLog(), PR_LOG_DEBUG, 1.167 + ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created " 1.168 + "image frame with %dx%d pixels in container %p", 1.169 + width, height, 1.170 + &mImage)); 1.171 + 1.172 + mFrameHasNoAlpha = true; 1.173 + 1.174 +#ifdef PNG_APNG_SUPPORTED 1.175 + if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) { 1.176 + mAnimInfo = AnimFrameInfo(mPNG, mInfo); 1.177 + } 1.178 +#endif 1.179 +} 1.180 + 1.181 +// set timeout and frame disposal method for the current frame 1.182 +void nsPNGDecoder::EndImageFrame() 1.183 +{ 1.184 + if (mFrameIsHidden) 1.185 + return; 1.186 + 1.187 + mNumFrames++; 1.188 + 1.189 + FrameBlender::FrameAlpha alpha; 1.190 + if (mFrameHasNoAlpha) 1.191 + alpha = FrameBlender::kFrameOpaque; 1.192 + else 1.193 + alpha = FrameBlender::kFrameHasAlpha; 1.194 + 1.195 +#ifdef PNG_APNG_SUPPORTED 1.196 + uint32_t numFrames = GetFrameCount(); 1.197 + 1.198 + // We can't use mPNG->num_frames_read as it may be one ahead. 1.199 + if (numFrames > 1) { 1.200 + PostInvalidation(mFrameRect); 1.201 + } 1.202 +#endif 1.203 + 1.204 + PostFrameStop(alpha, mAnimInfo.mDispose, mAnimInfo.mTimeout, mAnimInfo.mBlend); 1.205 +} 1.206 + 1.207 +void 1.208 +nsPNGDecoder::InitInternal() 1.209 +{ 1.210 + // For size decodes, we don't need to initialize the png decoder 1.211 + if (IsSizeDecode()) { 1.212 + return; 1.213 + } 1.214 + 1.215 + mCMSMode = gfxPlatform::GetCMSMode(); 1.216 + if ((mDecodeFlags & DECODER_NO_COLORSPACE_CONVERSION) != 0) 1.217 + mCMSMode = eCMSMode_Off; 1.218 + mDisablePremultipliedAlpha = (mDecodeFlags & DECODER_NO_PREMULTIPLY_ALPHA) != 0; 1.219 + 1.220 +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED 1.221 + static png_byte color_chunks[]= 1.222 + { 99, 72, 82, 77, '\0', /* cHRM */ 1.223 + 105, 67, 67, 80, '\0'}; /* iCCP */ 1.224 + static png_byte unused_chunks[]= 1.225 + { 98, 75, 71, 68, '\0', /* bKGD */ 1.226 + 104, 73, 83, 84, '\0', /* hIST */ 1.227 + 105, 84, 88, 116, '\0', /* iTXt */ 1.228 + 111, 70, 70, 115, '\0', /* oFFs */ 1.229 + 112, 67, 65, 76, '\0', /* pCAL */ 1.230 + 115, 67, 65, 76, '\0', /* sCAL */ 1.231 + 112, 72, 89, 115, '\0', /* pHYs */ 1.232 + 115, 66, 73, 84, '\0', /* sBIT */ 1.233 + 115, 80, 76, 84, '\0', /* sPLT */ 1.234 + 116, 69, 88, 116, '\0', /* tEXt */ 1.235 + 116, 73, 77, 69, '\0', /* tIME */ 1.236 + 122, 84, 88, 116, '\0'}; /* zTXt */ 1.237 +#endif 1.238 + 1.239 + /* For full decodes, do png init stuff */ 1.240 + 1.241 + /* Initialize the container's source image header. */ 1.242 + /* Always decode to 24 bit pixdepth */ 1.243 + 1.244 + mPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING, 1.245 + nullptr, nsPNGDecoder::error_callback, 1.246 + nsPNGDecoder::warning_callback); 1.247 + if (!mPNG) { 1.248 + PostDecoderError(NS_ERROR_OUT_OF_MEMORY); 1.249 + return; 1.250 + } 1.251 + 1.252 + mInfo = png_create_info_struct(mPNG); 1.253 + if (!mInfo) { 1.254 + PostDecoderError(NS_ERROR_OUT_OF_MEMORY); 1.255 + png_destroy_read_struct(&mPNG, nullptr, nullptr); 1.256 + return; 1.257 + } 1.258 + 1.259 +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED 1.260 + /* Ignore unused chunks */ 1.261 + if (mCMSMode == eCMSMode_Off) 1.262 + png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2); 1.263 + 1.264 + png_set_keep_unknown_chunks(mPNG, 1, unused_chunks, 1.265 + (int)sizeof(unused_chunks)/5); 1.266 +#endif 1.267 + 1.268 +#ifdef PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED 1.269 + if (mCMSMode != eCMSMode_Off) 1.270 + png_set_chunk_malloc_max(mPNG, 4000000L); 1.271 +#endif 1.272 + 1.273 +#ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED 1.274 +#ifndef PR_LOGGING 1.275 + /* Disallow palette-index checking, for speed; we would ignore the warning 1.276 + * anyhow unless we have defined PR_LOGGING. This feature was added at 1.277 + * libpng version 1.5.10 and is disabled in the embedded libpng but enabled 1.278 + * by default in the system libpng. This call also disables it in the 1.279 + * system libpng, for decoding speed. Bug #745202. 1.280 + */ 1.281 + png_set_check_for_invalid_index(mPNG, 0); 1.282 +#endif 1.283 +#endif 1.284 + 1.285 + /* use this as libpng "progressive pointer" (retrieve in callbacks) */ 1.286 + png_set_progressive_read_fn(mPNG, static_cast<png_voidp>(this), 1.287 + nsPNGDecoder::info_callback, 1.288 + nsPNGDecoder::row_callback, 1.289 + nsPNGDecoder::end_callback); 1.290 + 1.291 +} 1.292 + 1.293 +void 1.294 +nsPNGDecoder::WriteInternal(const char *aBuffer, uint32_t aCount, DecodeStrategy) 1.295 +{ 1.296 + NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!"); 1.297 + 1.298 + // If we only want width/height, we don't need to go through libpng 1.299 + if (IsSizeDecode()) { 1.300 + 1.301 + // Are we done? 1.302 + if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) 1.303 + return; 1.304 + 1.305 + // Scan the header for the width and height bytes 1.306 + uint32_t pos = 0; 1.307 + const uint8_t *bptr = (uint8_t *)aBuffer; 1.308 + 1.309 + while (pos < aCount && mHeaderBytesRead < BYTES_NEEDED_FOR_DIMENSIONS) { 1.310 + // Verify the signature bytes 1.311 + if (mHeaderBytesRead < sizeof(pngSignatureBytes)) { 1.312 + if (bptr[pos] != nsPNGDecoder::pngSignatureBytes[mHeaderBytesRead]) { 1.313 + PostDataError(); 1.314 + return; 1.315 + } 1.316 + } 1.317 + 1.318 + // Get width and height bytes into the buffer 1.319 + if ((mHeaderBytesRead >= WIDTH_OFFSET) && 1.320 + (mHeaderBytesRead < BYTES_NEEDED_FOR_DIMENSIONS)) { 1.321 + mSizeBytes[mHeaderBytesRead - WIDTH_OFFSET] = bptr[pos]; 1.322 + } 1.323 + pos ++; 1.324 + mHeaderBytesRead ++; 1.325 + } 1.326 + 1.327 + // If we're done now, verify the data and set up the container 1.328 + if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) { 1.329 + 1.330 + // Grab the width and height, accounting for endianness (thanks libpng!) 1.331 + uint32_t width = png_get_uint_32(mSizeBytes); 1.332 + uint32_t height = png_get_uint_32(mSizeBytes + 4); 1.333 + 1.334 + // Too big? 1.335 + if ((width > MOZ_PNG_MAX_DIMENSION) || (height > MOZ_PNG_MAX_DIMENSION)) { 1.336 + PostDataError(); 1.337 + return; 1.338 + } 1.339 + 1.340 + // Post our size to the superclass 1.341 + PostSize(width, height); 1.342 + } 1.343 + } 1.344 + 1.345 + // Otherwise, we're doing a standard decode 1.346 + else { 1.347 + 1.348 + // libpng uses setjmp/longjmp for error handling - set the buffer 1.349 + if (setjmp(png_jmpbuf(mPNG))) { 1.350 + 1.351 + // We might not really know what caused the error, but it makes more 1.352 + // sense to blame the data. 1.353 + if (!HasError()) 1.354 + PostDataError(); 1.355 + 1.356 + png_destroy_read_struct(&mPNG, &mInfo, nullptr); 1.357 + return; 1.358 + } 1.359 + 1.360 + // Pass the data off to libpng 1.361 + png_process_data(mPNG, mInfo, (unsigned char *)aBuffer, aCount); 1.362 + 1.363 + } 1.364 +} 1.365 + 1.366 +// Sets up gamma pre-correction in libpng before our callback gets called. 1.367 +// We need to do this if we don't end up with a CMS profile. 1.368 +static void 1.369 +PNGDoGammaCorrection(png_structp png_ptr, png_infop info_ptr) 1.370 +{ 1.371 + double aGamma; 1.372 + 1.373 + if (png_get_gAMA(png_ptr, info_ptr, &aGamma)) { 1.374 + if ((aGamma <= 0.0) || (aGamma > 21474.83)) { 1.375 + aGamma = 0.45455; 1.376 + png_set_gAMA(png_ptr, info_ptr, aGamma); 1.377 + } 1.378 + png_set_gamma(png_ptr, 2.2, aGamma); 1.379 + } 1.380 + else 1.381 + png_set_gamma(png_ptr, 2.2, 0.45455); 1.382 + 1.383 +} 1.384 + 1.385 +// Adapted from http://www.littlecms.com/pngchrm.c example code 1.386 +static qcms_profile * 1.387 +PNGGetColorProfile(png_structp png_ptr, png_infop info_ptr, 1.388 + int color_type, qcms_data_type *inType, uint32_t *intent) 1.389 +{ 1.390 + qcms_profile *profile = nullptr; 1.391 + *intent = QCMS_INTENT_PERCEPTUAL; // Our default 1.392 + 1.393 + // First try to see if iCCP chunk is present 1.394 + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) { 1.395 + png_uint_32 profileLen; 1.396 +#if (PNG_LIBPNG_VER < 10500) 1.397 + char *profileData, *profileName; 1.398 +#else 1.399 + png_bytep profileData; 1.400 + png_charp profileName; 1.401 +#endif 1.402 + int compression; 1.403 + 1.404 + png_get_iCCP(png_ptr, info_ptr, &profileName, &compression, 1.405 + &profileData, &profileLen); 1.406 + 1.407 + profile = qcms_profile_from_memory( 1.408 +#if (PNG_LIBPNG_VER < 10500) 1.409 + profileData, 1.410 +#else 1.411 + (char *)profileData, 1.412 +#endif 1.413 + profileLen); 1.414 + if (profile) { 1.415 + uint32_t profileSpace = qcms_profile_get_color_space(profile); 1.416 + 1.417 + bool mismatch = false; 1.418 + if (color_type & PNG_COLOR_MASK_COLOR) { 1.419 + if (profileSpace != icSigRgbData) 1.420 + mismatch = true; 1.421 + } else { 1.422 + if (profileSpace == icSigRgbData) 1.423 + png_set_gray_to_rgb(png_ptr); 1.424 + else if (profileSpace != icSigGrayData) 1.425 + mismatch = true; 1.426 + } 1.427 + 1.428 + if (mismatch) { 1.429 + qcms_profile_release(profile); 1.430 + profile = nullptr; 1.431 + } else { 1.432 + *intent = qcms_profile_get_rendering_intent(profile); 1.433 + } 1.434 + } 1.435 + } 1.436 + 1.437 + // Check sRGB chunk 1.438 + if (!profile && png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { 1.439 + profile = qcms_profile_sRGB(); 1.440 + 1.441 + if (profile) { 1.442 + int fileIntent; 1.443 + png_set_gray_to_rgb(png_ptr); 1.444 + png_get_sRGB(png_ptr, info_ptr, &fileIntent); 1.445 + uint32_t map[] = { QCMS_INTENT_PERCEPTUAL, 1.446 + QCMS_INTENT_RELATIVE_COLORIMETRIC, 1.447 + QCMS_INTENT_SATURATION, 1.448 + QCMS_INTENT_ABSOLUTE_COLORIMETRIC }; 1.449 + *intent = map[fileIntent]; 1.450 + } 1.451 + } 1.452 + 1.453 + // Check gAMA/cHRM chunks 1.454 + if (!profile && 1.455 + png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) && 1.456 + png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM)) { 1.457 + qcms_CIE_xyYTRIPLE primaries; 1.458 + qcms_CIE_xyY whitePoint; 1.459 + 1.460 + png_get_cHRM(png_ptr, info_ptr, 1.461 + &whitePoint.x, &whitePoint.y, 1.462 + &primaries.red.x, &primaries.red.y, 1.463 + &primaries.green.x, &primaries.green.y, 1.464 + &primaries.blue.x, &primaries.blue.y); 1.465 + whitePoint.Y = 1.466 + primaries.red.Y = primaries.green.Y = primaries.blue.Y = 1.0; 1.467 + 1.468 + double gammaOfFile; 1.469 + 1.470 + png_get_gAMA(png_ptr, info_ptr, &gammaOfFile); 1.471 + 1.472 + profile = qcms_profile_create_rgb_with_gamma(whitePoint, primaries, 1.473 + 1.0/gammaOfFile); 1.474 + 1.475 + if (profile) 1.476 + png_set_gray_to_rgb(png_ptr); 1.477 + } 1.478 + 1.479 + if (profile) { 1.480 + uint32_t profileSpace = qcms_profile_get_color_space(profile); 1.481 + if (profileSpace == icSigGrayData) { 1.482 + if (color_type & PNG_COLOR_MASK_ALPHA) 1.483 + *inType = QCMS_DATA_GRAYA_8; 1.484 + else 1.485 + *inType = QCMS_DATA_GRAY_8; 1.486 + } else { 1.487 + if (color_type & PNG_COLOR_MASK_ALPHA || 1.488 + png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) 1.489 + *inType = QCMS_DATA_RGBA_8; 1.490 + else 1.491 + *inType = QCMS_DATA_RGB_8; 1.492 + } 1.493 + } 1.494 + 1.495 + return profile; 1.496 +} 1.497 + 1.498 +void 1.499 +nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) 1.500 +{ 1.501 +/* int number_passes; NOT USED */ 1.502 + png_uint_32 width, height; 1.503 + int bit_depth, color_type, interlace_type, compression_type, filter_type; 1.504 + unsigned int channels; 1.505 + 1.506 + png_bytep trans = nullptr; 1.507 + int num_trans = 0; 1.508 + 1.509 + nsPNGDecoder *decoder = 1.510 + static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr)); 1.511 + 1.512 + /* always decode to 24-bit RGB or 32-bit RGBA */ 1.513 + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, 1.514 + &interlace_type, &compression_type, &filter_type); 1.515 + 1.516 + /* Are we too big? */ 1.517 + if (width > MOZ_PNG_MAX_DIMENSION || height > MOZ_PNG_MAX_DIMENSION) 1.518 + longjmp(png_jmpbuf(decoder->mPNG), 1); 1.519 + 1.520 + // Post our size to the superclass 1.521 + decoder->PostSize(width, height); 1.522 + if (decoder->HasError()) { 1.523 + // Setting the size led to an error. 1.524 + longjmp(png_jmpbuf(decoder->mPNG), 1); 1.525 + } 1.526 + 1.527 + if (color_type == PNG_COLOR_TYPE_PALETTE) 1.528 + png_set_expand(png_ptr); 1.529 + 1.530 + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) 1.531 + png_set_expand(png_ptr); 1.532 + 1.533 + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { 1.534 + int sample_max = (1 << bit_depth); 1.535 + png_color_16p trans_values; 1.536 + png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values); 1.537 + /* libpng doesn't reject a tRNS chunk with out-of-range samples 1.538 + so we check it here to avoid setting up a useless opacity 1.539 + channel or producing unexpected transparent pixels when using 1.540 + libpng-1.2.19 through 1.2.26 (bug #428045) */ 1.541 + if ((color_type == PNG_COLOR_TYPE_GRAY && 1.542 + (int)trans_values->gray > sample_max) || 1.543 + (color_type == PNG_COLOR_TYPE_RGB && 1.544 + ((int)trans_values->red > sample_max || 1.545 + (int)trans_values->green > sample_max || 1.546 + (int)trans_values->blue > sample_max))) 1.547 + { 1.548 + /* clear the tRNS valid flag and release tRNS memory */ 1.549 + png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0); 1.550 + } 1.551 + else 1.552 + png_set_expand(png_ptr); 1.553 + } 1.554 + 1.555 + if (bit_depth == 16) 1.556 + png_set_scale_16(png_ptr); 1.557 + 1.558 + qcms_data_type inType = QCMS_DATA_RGBA_8; 1.559 + uint32_t intent = -1; 1.560 + uint32_t pIntent; 1.561 + if (decoder->mCMSMode != eCMSMode_Off) { 1.562 + intent = gfxPlatform::GetRenderingIntent(); 1.563 + decoder->mInProfile = PNGGetColorProfile(png_ptr, info_ptr, 1.564 + color_type, &inType, &pIntent); 1.565 + /* If we're not mandating an intent, use the one from the image. */ 1.566 + if (intent == uint32_t(-1)) 1.567 + intent = pIntent; 1.568 + } 1.569 + if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) { 1.570 + qcms_data_type outType; 1.571 + 1.572 + if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) 1.573 + outType = QCMS_DATA_RGBA_8; 1.574 + else 1.575 + outType = QCMS_DATA_RGB_8; 1.576 + 1.577 + decoder->mTransform = qcms_transform_create(decoder->mInProfile, 1.578 + inType, 1.579 + gfxPlatform::GetCMSOutputProfile(), 1.580 + outType, 1.581 + (qcms_intent)intent); 1.582 + } else { 1.583 + png_set_gray_to_rgb(png_ptr); 1.584 + 1.585 + // only do gamma correction if CMS isn't entirely disabled 1.586 + if (decoder->mCMSMode != eCMSMode_Off) 1.587 + PNGDoGammaCorrection(png_ptr, info_ptr); 1.588 + 1.589 + if (decoder->mCMSMode == eCMSMode_All) { 1.590 + if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) 1.591 + decoder->mTransform = gfxPlatform::GetCMSRGBATransform(); 1.592 + else 1.593 + decoder->mTransform = gfxPlatform::GetCMSRGBTransform(); 1.594 + } 1.595 + } 1.596 + 1.597 + /* let libpng expand interlaced images */ 1.598 + if (interlace_type == PNG_INTERLACE_ADAM7) { 1.599 + /* number_passes = */ 1.600 + png_set_interlace_handling(png_ptr); 1.601 + } 1.602 + 1.603 + /* now all of those things we set above are used to update various struct 1.604 + * members and whatnot, after which we can get channels, rowbytes, etc. */ 1.605 + png_read_update_info(png_ptr, info_ptr); 1.606 + decoder->mChannels = channels = png_get_channels(png_ptr, info_ptr); 1.607 + 1.608 + /*---------------------------------------------------------------*/ 1.609 + /* copy PNG info into imagelib structs (formerly png_set_dims()) */ 1.610 + /*---------------------------------------------------------------*/ 1.611 + 1.612 + // This code is currently unused, but it will be needed for bug 517713. 1.613 +#if 0 1.614 + int32_t alpha_bits = 1; 1.615 + 1.616 + if (channels == 2 || channels == 4) { 1.617 + /* check if alpha is coming from a tRNS chunk and is binary */ 1.618 + if (num_trans) { 1.619 + /* if it's not an indexed color image, tRNS means binary */ 1.620 + if (color_type == PNG_COLOR_TYPE_PALETTE) { 1.621 + for (int i=0; i<num_trans; i++) { 1.622 + if ((trans[i] != 0) && (trans[i] != 255)) { 1.623 + alpha_bits = 8; 1.624 + break; 1.625 + } 1.626 + } 1.627 + } 1.628 + } else { 1.629 + alpha_bits = 8; 1.630 + } 1.631 + } 1.632 +#endif 1.633 + 1.634 + if (channels == 1 || channels == 3) 1.635 + decoder->format = gfxImageFormat::RGB24; 1.636 + else if (channels == 2 || channels == 4) 1.637 + decoder->format = gfxImageFormat::ARGB32; 1.638 + 1.639 +#ifdef PNG_APNG_SUPPORTED 1.640 + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) 1.641 + png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback, 1.642 + nullptr); 1.643 + 1.644 + if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) { 1.645 + decoder->mFrameIsHidden = true; 1.646 + } else { 1.647 +#endif 1.648 + decoder->CreateFrame(0, 0, width, height, decoder->format); 1.649 +#ifdef PNG_APNG_SUPPORTED 1.650 + } 1.651 +#endif 1.652 + 1.653 + if (decoder->mTransform && 1.654 + (channels <= 2 || interlace_type == PNG_INTERLACE_ADAM7)) { 1.655 + uint32_t bpp[] = { 0, 3, 4, 3, 4 }; 1.656 + decoder->mCMSLine = 1.657 + (uint8_t *)moz_malloc(bpp[channels] * width); 1.658 + if (!decoder->mCMSLine) { 1.659 + longjmp(png_jmpbuf(decoder->mPNG), 5); // NS_ERROR_OUT_OF_MEMORY 1.660 + } 1.661 + } 1.662 + 1.663 + if (interlace_type == PNG_INTERLACE_ADAM7) { 1.664 + if (height < INT32_MAX / (width * channels)) 1.665 + decoder->interlacebuf = (uint8_t *)moz_malloc(channels * width * height); 1.666 + if (!decoder->interlacebuf) { 1.667 + longjmp(png_jmpbuf(decoder->mPNG), 5); // NS_ERROR_OUT_OF_MEMORY 1.668 + } 1.669 + } 1.670 + 1.671 + if (decoder->NeedsNewFrame()) { 1.672 + /* We know that we need a new frame, so pause input so the decoder 1.673 + * infrastructure can give it to us. 1.674 + */ 1.675 + png_process_data_pause(png_ptr, /* save = */ 1); 1.676 + } 1.677 +} 1.678 + 1.679 +void 1.680 +nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row, 1.681 + png_uint_32 row_num, int pass) 1.682 +{ 1.683 + /* libpng comments: 1.684 + * 1.685 + * this function is called for every row in the image. If the 1.686 + * image is interlacing, and you turned on the interlace handler, 1.687 + * this function will be called for every row in every pass. 1.688 + * Some of these rows will not be changed from the previous pass. 1.689 + * When the row is not changed, the new_row variable will be 1.690 + * nullptr. The rows and passes are called in order, so you don't 1.691 + * really need the row_num and pass, but I'm supplying them 1.692 + * because it may make your life easier. 1.693 + * 1.694 + * For the non-nullptr rows of interlaced images, you must call 1.695 + * png_progressive_combine_row() passing in the row and the 1.696 + * old row. You can call this function for nullptr rows (it will 1.697 + * just return) and for non-interlaced images (it just does the 1.698 + * memcpy for you) if it will make the code easier. Thus, you 1.699 + * can just do this for all cases: 1.700 + * 1.701 + * png_progressive_combine_row(png_ptr, old_row, new_row); 1.702 + * 1.703 + * where old_row is what was displayed for previous rows. Note 1.704 + * that the first pass (pass == 0 really) will completely cover 1.705 + * the old row, so the rows do not have to be initialized. After 1.706 + * the first pass (and only for interlaced images), you will have 1.707 + * to pass the current row, and the function will combine the 1.708 + * old row and the new row. 1.709 + */ 1.710 + nsPNGDecoder *decoder = 1.711 + static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr)); 1.712 + 1.713 + // skip this frame 1.714 + if (decoder->mFrameIsHidden) 1.715 + return; 1.716 + 1.717 + if (row_num >= (png_uint_32) decoder->mFrameRect.height) 1.718 + return; 1.719 + 1.720 + if (new_row) { 1.721 + int32_t width = decoder->mFrameRect.width; 1.722 + uint32_t iwidth = decoder->mFrameRect.width; 1.723 + 1.724 + png_bytep line = new_row; 1.725 + if (decoder->interlacebuf) { 1.726 + line = decoder->interlacebuf + (row_num * decoder->mChannels * width); 1.727 + png_progressive_combine_row(png_ptr, line, new_row); 1.728 + } 1.729 + 1.730 + uint32_t bpr = width * sizeof(uint32_t); 1.731 + uint32_t *cptr32 = (uint32_t*)(decoder->mImageData + (row_num*bpr)); 1.732 + bool rowHasNoAlpha = true; 1.733 + 1.734 + if (decoder->mTransform) { 1.735 + if (decoder->mCMSLine) { 1.736 + qcms_transform_data(decoder->mTransform, line, decoder->mCMSLine, 1.737 + iwidth); 1.738 + /* copy alpha over */ 1.739 + uint32_t channels = decoder->mChannels; 1.740 + if (channels == 2 || channels == 4) { 1.741 + for (uint32_t i = 0; i < iwidth; i++) 1.742 + decoder->mCMSLine[4 * i + 3] = line[channels * i + channels - 1]; 1.743 + } 1.744 + line = decoder->mCMSLine; 1.745 + } else { 1.746 + qcms_transform_data(decoder->mTransform, line, line, iwidth); 1.747 + } 1.748 + } 1.749 + 1.750 + switch (decoder->format) { 1.751 + case gfxImageFormat::RGB24: 1.752 + { 1.753 + // counter for while() loops below 1.754 + uint32_t idx = iwidth; 1.755 + 1.756 + // copy as bytes until source pointer is 32-bit-aligned 1.757 + for (; (NS_PTR_TO_UINT32(line) & 0x3) && idx; --idx) { 1.758 + *cptr32++ = gfxPackedPixel(0xFF, line[0], line[1], line[2]); 1.759 + line += 3; 1.760 + } 1.761 + 1.762 + // copy pixels in blocks of 4 1.763 + while (idx >= 4) { 1.764 + GFX_BLOCK_RGB_TO_FRGB(line, cptr32); 1.765 + idx -= 4; 1.766 + line += 12; 1.767 + cptr32 += 4; 1.768 + } 1.769 + 1.770 + // copy remaining pixel(s) 1.771 + while (idx--) { 1.772 + // 32-bit read of final pixel will exceed buffer, so read bytes 1.773 + *cptr32++ = gfxPackedPixel(0xFF, line[0], line[1], line[2]); 1.774 + line += 3; 1.775 + } 1.776 + } 1.777 + break; 1.778 + case gfxImageFormat::ARGB32: 1.779 + { 1.780 + if (!decoder->mDisablePremultipliedAlpha) { 1.781 + for (uint32_t x=width; x>0; --x) { 1.782 + *cptr32++ = gfxPackedPixel(line[3], line[0], line[1], line[2]); 1.783 + if (line[3] != 0xff) 1.784 + rowHasNoAlpha = false; 1.785 + line += 4; 1.786 + } 1.787 + } else { 1.788 + for (uint32_t x=width; x>0; --x) { 1.789 + *cptr32++ = gfxPackedPixelNoPreMultiply(line[3], line[0], line[1], line[2]); 1.790 + if (line[3] != 0xff) 1.791 + rowHasNoAlpha = false; 1.792 + line += 4; 1.793 + } 1.794 + } 1.795 + } 1.796 + break; 1.797 + default: 1.798 + longjmp(png_jmpbuf(decoder->mPNG), 1); 1.799 + } 1.800 + 1.801 + if (!rowHasNoAlpha) 1.802 + decoder->mFrameHasNoAlpha = false; 1.803 + 1.804 + if (decoder->mNumFrames <= 1) { 1.805 + // Only do incremental image display for the first frame 1.806 + // XXXbholley - this check should be handled in the superclass 1.807 + nsIntRect r(0, row_num, width, 1); 1.808 + decoder->PostInvalidation(r); 1.809 + } 1.810 + } 1.811 +} 1.812 + 1.813 +#ifdef PNG_APNG_SUPPORTED 1.814 +// got the header of a new frame that's coming 1.815 +void 1.816 +nsPNGDecoder::frame_info_callback(png_structp png_ptr, png_uint_32 frame_num) 1.817 +{ 1.818 + png_uint_32 x_offset, y_offset; 1.819 + int32_t width, height; 1.820 + 1.821 + nsPNGDecoder *decoder = 1.822 + static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr)); 1.823 + 1.824 + // old frame is done 1.825 + decoder->EndImageFrame(); 1.826 + 1.827 + // Only the first frame can be hidden, so unhide unconditionally here. 1.828 + decoder->mFrameIsHidden = false; 1.829 + 1.830 + x_offset = png_get_next_frame_x_offset(png_ptr, decoder->mInfo); 1.831 + y_offset = png_get_next_frame_y_offset(png_ptr, decoder->mInfo); 1.832 + width = png_get_next_frame_width(png_ptr, decoder->mInfo); 1.833 + height = png_get_next_frame_height(png_ptr, decoder->mInfo); 1.834 + 1.835 + decoder->CreateFrame(x_offset, y_offset, width, height, decoder->format); 1.836 + 1.837 + if (decoder->NeedsNewFrame()) { 1.838 + /* We know that we need a new frame, so pause input so the decoder 1.839 + * infrastructure can give it to us. 1.840 + */ 1.841 + png_process_data_pause(png_ptr, /* save = */ 1); 1.842 + } 1.843 +} 1.844 +#endif 1.845 + 1.846 +void 1.847 +nsPNGDecoder::end_callback(png_structp png_ptr, png_infop info_ptr) 1.848 +{ 1.849 + /* libpng comments: 1.850 + * 1.851 + * this function is called when the whole image has been read, 1.852 + * including any chunks after the image (up to and including 1.853 + * the IEND). You will usually have the same info chunk as you 1.854 + * had in the header, although some data may have been added 1.855 + * to the comments and time fields. 1.856 + * 1.857 + * Most people won't do much here, perhaps setting a flag that 1.858 + * marks the image as finished. 1.859 + */ 1.860 + 1.861 + nsPNGDecoder *decoder = 1.862 + static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr)); 1.863 + 1.864 + // We shouldn't get here if we've hit an error 1.865 + NS_ABORT_IF_FALSE(!decoder->HasError(), "Finishing up PNG but hit error!"); 1.866 + 1.867 + int32_t loop_count = 0; 1.868 +#ifdef PNG_APNG_SUPPORTED 1.869 + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) { 1.870 + int32_t num_plays = png_get_num_plays(png_ptr, info_ptr); 1.871 + loop_count = num_plays - 1; 1.872 + } 1.873 +#endif 1.874 + 1.875 + // Send final notifications 1.876 + decoder->EndImageFrame(); 1.877 + decoder->PostDecodeDone(loop_count); 1.878 +} 1.879 + 1.880 + 1.881 +void 1.882 +nsPNGDecoder::error_callback(png_structp png_ptr, png_const_charp error_msg) 1.883 +{ 1.884 + PR_LOG(GetPNGLog(), PR_LOG_ERROR, ("libpng error: %s\n", error_msg)); 1.885 + longjmp(png_jmpbuf(png_ptr), 1); 1.886 +} 1.887 + 1.888 + 1.889 +void 1.890 +nsPNGDecoder::warning_callback(png_structp png_ptr, png_const_charp warning_msg) 1.891 +{ 1.892 + PR_LOG(GetPNGLog(), PR_LOG_WARNING, ("libpng warning: %s\n", warning_msg)); 1.893 +} 1.894 + 1.895 +Telemetry::ID 1.896 +nsPNGDecoder::SpeedHistogram() 1.897 +{ 1.898 + return Telemetry::IMAGE_DECODE_SPEED_PNG; 1.899 +} 1.900 + 1.901 + 1.902 +} // namespace image 1.903 +} // namespace mozilla