image/decoders/nsPNGDecoder.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
michael@0 2 *
michael@0 3 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "ImageLogging.h"
michael@0 8 #include "nsPNGDecoder.h"
michael@0 9
michael@0 10 #include "nsMemory.h"
michael@0 11 #include "nsRect.h"
michael@0 12
michael@0 13 #include "nsIInputStream.h"
michael@0 14
michael@0 15 #include "RasterImage.h"
michael@0 16
michael@0 17 #include "gfxColor.h"
michael@0 18 #include "nsColor.h"
michael@0 19
michael@0 20 #include "nspr.h"
michael@0 21 #include "png.h"
michael@0 22
michael@0 23 #include "gfxPlatform.h"
michael@0 24 #include <algorithm>
michael@0 25
michael@0 26 namespace mozilla {
michael@0 27 namespace image {
michael@0 28
michael@0 29 #ifdef PR_LOGGING
michael@0 30 static PRLogModuleInfo *
michael@0 31 GetPNGLog()
michael@0 32 {
michael@0 33 static PRLogModuleInfo *sPNGLog;
michael@0 34 if (!sPNGLog)
michael@0 35 sPNGLog = PR_NewLogModule("PNGDecoder");
michael@0 36 return sPNGLog;
michael@0 37 }
michael@0 38
michael@0 39 static PRLogModuleInfo *
michael@0 40 GetPNGDecoderAccountingLog()
michael@0 41 {
michael@0 42 static PRLogModuleInfo *sPNGDecoderAccountingLog;
michael@0 43 if (!sPNGDecoderAccountingLog)
michael@0 44 sPNGDecoderAccountingLog = PR_NewLogModule("PNGDecoderAccounting");
michael@0 45 return sPNGDecoderAccountingLog;
michael@0 46 }
michael@0 47 #endif
michael@0 48
michael@0 49 /* limit image dimensions (bug #251381, #591822, and #967656) */
michael@0 50 #ifndef MOZ_PNG_MAX_DIMENSION
michael@0 51 # define MOZ_PNG_MAX_DIMENSION 32767
michael@0 52 #endif
michael@0 53
michael@0 54 // For size decodes
michael@0 55 #define WIDTH_OFFSET 16
michael@0 56 #define HEIGHT_OFFSET (WIDTH_OFFSET + 4)
michael@0 57 #define BYTES_NEEDED_FOR_DIMENSIONS (HEIGHT_OFFSET + 4)
michael@0 58
michael@0 59 nsPNGDecoder::AnimFrameInfo::AnimFrameInfo()
michael@0 60 : mDispose(FrameBlender::kDisposeKeep)
michael@0 61 , mBlend(FrameBlender::kBlendOver)
michael@0 62 , mTimeout(0)
michael@0 63 {}
michael@0 64
michael@0 65 #ifdef PNG_APNG_SUPPORTED
michael@0 66 nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo)
michael@0 67 : mDispose(FrameBlender::kDisposeKeep)
michael@0 68 , mBlend(FrameBlender::kBlendOver)
michael@0 69 , mTimeout(0)
michael@0 70 {
michael@0 71 png_uint_16 delay_num, delay_den;
michael@0 72 /* delay, in seconds is delay_num/delay_den */
michael@0 73 png_byte dispose_op;
michael@0 74 png_byte blend_op;
michael@0 75 delay_num = png_get_next_frame_delay_num(aPNG, aInfo);
michael@0 76 delay_den = png_get_next_frame_delay_den(aPNG, aInfo);
michael@0 77 dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo);
michael@0 78 blend_op = png_get_next_frame_blend_op(aPNG, aInfo);
michael@0 79
michael@0 80 if (delay_num == 0) {
michael@0 81 mTimeout = 0; // SetFrameTimeout() will set to a minimum
michael@0 82 } else {
michael@0 83 if (delay_den == 0)
michael@0 84 delay_den = 100; // so says the APNG spec
michael@0 85
michael@0 86 // Need to cast delay_num to float to have a proper division and
michael@0 87 // the result to int to avoid compiler warning
michael@0 88 mTimeout = static_cast<int32_t>(static_cast<double>(delay_num) * 1000 / delay_den);
michael@0 89 }
michael@0 90
michael@0 91 if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) {
michael@0 92 mDispose = FrameBlender::kDisposeRestorePrevious;
michael@0 93 } else if (dispose_op == PNG_DISPOSE_OP_BACKGROUND) {
michael@0 94 mDispose = FrameBlender::kDisposeClear;
michael@0 95 } else {
michael@0 96 mDispose = FrameBlender::kDisposeKeep;
michael@0 97 }
michael@0 98
michael@0 99 if (blend_op == PNG_BLEND_OP_SOURCE) {
michael@0 100 mBlend = FrameBlender::kBlendSource;
michael@0 101 } else {
michael@0 102 mBlend = FrameBlender::kBlendOver;
michael@0 103 }
michael@0 104 }
michael@0 105 #endif
michael@0 106
michael@0 107 // First 8 bytes of a PNG file
michael@0 108 const uint8_t
michael@0 109 nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
michael@0 110
michael@0 111 nsPNGDecoder::nsPNGDecoder(RasterImage &aImage)
michael@0 112 : Decoder(aImage),
michael@0 113 mPNG(nullptr), mInfo(nullptr),
michael@0 114 mCMSLine(nullptr), interlacebuf(nullptr),
michael@0 115 mInProfile(nullptr), mTransform(nullptr),
michael@0 116 mHeaderBytesRead(0), mCMSMode(0),
michael@0 117 mChannels(0), mFrameIsHidden(false),
michael@0 118 mDisablePremultipliedAlpha(false),
michael@0 119 mNumFrames(0)
michael@0 120 {
michael@0 121 }
michael@0 122
michael@0 123 nsPNGDecoder::~nsPNGDecoder()
michael@0 124 {
michael@0 125 if (mPNG)
michael@0 126 png_destroy_read_struct(&mPNG, mInfo ? &mInfo : nullptr, nullptr);
michael@0 127 if (mCMSLine)
michael@0 128 nsMemory::Free(mCMSLine);
michael@0 129 if (interlacebuf)
michael@0 130 nsMemory::Free(interlacebuf);
michael@0 131 if (mInProfile) {
michael@0 132 qcms_profile_release(mInProfile);
michael@0 133
michael@0 134 /* mTransform belongs to us only if mInProfile is non-null */
michael@0 135 if (mTransform)
michael@0 136 qcms_transform_release(mTransform);
michael@0 137 }
michael@0 138 }
michael@0 139
michael@0 140 // CreateFrame() is used for both simple and animated images
michael@0 141 void nsPNGDecoder::CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
michael@0 142 int32_t width, int32_t height,
michael@0 143 gfxImageFormat format)
michael@0 144 {
michael@0 145 // Our first full frame is automatically created by the image decoding
michael@0 146 // infrastructure. Just use it as long as it matches up.
michael@0 147 MOZ_ASSERT(HasSize());
michael@0 148 if (mNumFrames != 0 ||
michael@0 149 !GetCurrentFrame()->GetRect().IsEqualEdges(nsIntRect(x_offset, y_offset, width, height))) {
michael@0 150 NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format);
michael@0 151 } else if (mNumFrames == 0) {
michael@0 152 // Our preallocated frame matches up, with the possible exception of alpha.
michael@0 153 if (format == gfxImageFormat::RGB24) {
michael@0 154 GetCurrentFrame()->SetHasNoAlpha();
michael@0 155 }
michael@0 156 }
michael@0 157
michael@0 158 mFrameRect.x = x_offset;
michael@0 159 mFrameRect.y = y_offset;
michael@0 160 mFrameRect.width = width;
michael@0 161 mFrameRect.height = height;
michael@0 162
michael@0 163 PR_LOG(GetPNGDecoderAccountingLog(), PR_LOG_DEBUG,
michael@0 164 ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created "
michael@0 165 "image frame with %dx%d pixels in container %p",
michael@0 166 width, height,
michael@0 167 &mImage));
michael@0 168
michael@0 169 mFrameHasNoAlpha = true;
michael@0 170
michael@0 171 #ifdef PNG_APNG_SUPPORTED
michael@0 172 if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) {
michael@0 173 mAnimInfo = AnimFrameInfo(mPNG, mInfo);
michael@0 174 }
michael@0 175 #endif
michael@0 176 }
michael@0 177
michael@0 178 // set timeout and frame disposal method for the current frame
michael@0 179 void nsPNGDecoder::EndImageFrame()
michael@0 180 {
michael@0 181 if (mFrameIsHidden)
michael@0 182 return;
michael@0 183
michael@0 184 mNumFrames++;
michael@0 185
michael@0 186 FrameBlender::FrameAlpha alpha;
michael@0 187 if (mFrameHasNoAlpha)
michael@0 188 alpha = FrameBlender::kFrameOpaque;
michael@0 189 else
michael@0 190 alpha = FrameBlender::kFrameHasAlpha;
michael@0 191
michael@0 192 #ifdef PNG_APNG_SUPPORTED
michael@0 193 uint32_t numFrames = GetFrameCount();
michael@0 194
michael@0 195 // We can't use mPNG->num_frames_read as it may be one ahead.
michael@0 196 if (numFrames > 1) {
michael@0 197 PostInvalidation(mFrameRect);
michael@0 198 }
michael@0 199 #endif
michael@0 200
michael@0 201 PostFrameStop(alpha, mAnimInfo.mDispose, mAnimInfo.mTimeout, mAnimInfo.mBlend);
michael@0 202 }
michael@0 203
michael@0 204 void
michael@0 205 nsPNGDecoder::InitInternal()
michael@0 206 {
michael@0 207 // For size decodes, we don't need to initialize the png decoder
michael@0 208 if (IsSizeDecode()) {
michael@0 209 return;
michael@0 210 }
michael@0 211
michael@0 212 mCMSMode = gfxPlatform::GetCMSMode();
michael@0 213 if ((mDecodeFlags & DECODER_NO_COLORSPACE_CONVERSION) != 0)
michael@0 214 mCMSMode = eCMSMode_Off;
michael@0 215 mDisablePremultipliedAlpha = (mDecodeFlags & DECODER_NO_PREMULTIPLY_ALPHA) != 0;
michael@0 216
michael@0 217 #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
michael@0 218 static png_byte color_chunks[]=
michael@0 219 { 99, 72, 82, 77, '\0', /* cHRM */
michael@0 220 105, 67, 67, 80, '\0'}; /* iCCP */
michael@0 221 static png_byte unused_chunks[]=
michael@0 222 { 98, 75, 71, 68, '\0', /* bKGD */
michael@0 223 104, 73, 83, 84, '\0', /* hIST */
michael@0 224 105, 84, 88, 116, '\0', /* iTXt */
michael@0 225 111, 70, 70, 115, '\0', /* oFFs */
michael@0 226 112, 67, 65, 76, '\0', /* pCAL */
michael@0 227 115, 67, 65, 76, '\0', /* sCAL */
michael@0 228 112, 72, 89, 115, '\0', /* pHYs */
michael@0 229 115, 66, 73, 84, '\0', /* sBIT */
michael@0 230 115, 80, 76, 84, '\0', /* sPLT */
michael@0 231 116, 69, 88, 116, '\0', /* tEXt */
michael@0 232 116, 73, 77, 69, '\0', /* tIME */
michael@0 233 122, 84, 88, 116, '\0'}; /* zTXt */
michael@0 234 #endif
michael@0 235
michael@0 236 /* For full decodes, do png init stuff */
michael@0 237
michael@0 238 /* Initialize the container's source image header. */
michael@0 239 /* Always decode to 24 bit pixdepth */
michael@0 240
michael@0 241 mPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING,
michael@0 242 nullptr, nsPNGDecoder::error_callback,
michael@0 243 nsPNGDecoder::warning_callback);
michael@0 244 if (!mPNG) {
michael@0 245 PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
michael@0 246 return;
michael@0 247 }
michael@0 248
michael@0 249 mInfo = png_create_info_struct(mPNG);
michael@0 250 if (!mInfo) {
michael@0 251 PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
michael@0 252 png_destroy_read_struct(&mPNG, nullptr, nullptr);
michael@0 253 return;
michael@0 254 }
michael@0 255
michael@0 256 #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
michael@0 257 /* Ignore unused chunks */
michael@0 258 if (mCMSMode == eCMSMode_Off)
michael@0 259 png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2);
michael@0 260
michael@0 261 png_set_keep_unknown_chunks(mPNG, 1, unused_chunks,
michael@0 262 (int)sizeof(unused_chunks)/5);
michael@0 263 #endif
michael@0 264
michael@0 265 #ifdef PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED
michael@0 266 if (mCMSMode != eCMSMode_Off)
michael@0 267 png_set_chunk_malloc_max(mPNG, 4000000L);
michael@0 268 #endif
michael@0 269
michael@0 270 #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED
michael@0 271 #ifndef PR_LOGGING
michael@0 272 /* Disallow palette-index checking, for speed; we would ignore the warning
michael@0 273 * anyhow unless we have defined PR_LOGGING. This feature was added at
michael@0 274 * libpng version 1.5.10 and is disabled in the embedded libpng but enabled
michael@0 275 * by default in the system libpng. This call also disables it in the
michael@0 276 * system libpng, for decoding speed. Bug #745202.
michael@0 277 */
michael@0 278 png_set_check_for_invalid_index(mPNG, 0);
michael@0 279 #endif
michael@0 280 #endif
michael@0 281
michael@0 282 /* use this as libpng "progressive pointer" (retrieve in callbacks) */
michael@0 283 png_set_progressive_read_fn(mPNG, static_cast<png_voidp>(this),
michael@0 284 nsPNGDecoder::info_callback,
michael@0 285 nsPNGDecoder::row_callback,
michael@0 286 nsPNGDecoder::end_callback);
michael@0 287
michael@0 288 }
michael@0 289
michael@0 290 void
michael@0 291 nsPNGDecoder::WriteInternal(const char *aBuffer, uint32_t aCount, DecodeStrategy)
michael@0 292 {
michael@0 293 NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
michael@0 294
michael@0 295 // If we only want width/height, we don't need to go through libpng
michael@0 296 if (IsSizeDecode()) {
michael@0 297
michael@0 298 // Are we done?
michael@0 299 if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS)
michael@0 300 return;
michael@0 301
michael@0 302 // Scan the header for the width and height bytes
michael@0 303 uint32_t pos = 0;
michael@0 304 const uint8_t *bptr = (uint8_t *)aBuffer;
michael@0 305
michael@0 306 while (pos < aCount && mHeaderBytesRead < BYTES_NEEDED_FOR_DIMENSIONS) {
michael@0 307 // Verify the signature bytes
michael@0 308 if (mHeaderBytesRead < sizeof(pngSignatureBytes)) {
michael@0 309 if (bptr[pos] != nsPNGDecoder::pngSignatureBytes[mHeaderBytesRead]) {
michael@0 310 PostDataError();
michael@0 311 return;
michael@0 312 }
michael@0 313 }
michael@0 314
michael@0 315 // Get width and height bytes into the buffer
michael@0 316 if ((mHeaderBytesRead >= WIDTH_OFFSET) &&
michael@0 317 (mHeaderBytesRead < BYTES_NEEDED_FOR_DIMENSIONS)) {
michael@0 318 mSizeBytes[mHeaderBytesRead - WIDTH_OFFSET] = bptr[pos];
michael@0 319 }
michael@0 320 pos ++;
michael@0 321 mHeaderBytesRead ++;
michael@0 322 }
michael@0 323
michael@0 324 // If we're done now, verify the data and set up the container
michael@0 325 if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS) {
michael@0 326
michael@0 327 // Grab the width and height, accounting for endianness (thanks libpng!)
michael@0 328 uint32_t width = png_get_uint_32(mSizeBytes);
michael@0 329 uint32_t height = png_get_uint_32(mSizeBytes + 4);
michael@0 330
michael@0 331 // Too big?
michael@0 332 if ((width > MOZ_PNG_MAX_DIMENSION) || (height > MOZ_PNG_MAX_DIMENSION)) {
michael@0 333 PostDataError();
michael@0 334 return;
michael@0 335 }
michael@0 336
michael@0 337 // Post our size to the superclass
michael@0 338 PostSize(width, height);
michael@0 339 }
michael@0 340 }
michael@0 341
michael@0 342 // Otherwise, we're doing a standard decode
michael@0 343 else {
michael@0 344
michael@0 345 // libpng uses setjmp/longjmp for error handling - set the buffer
michael@0 346 if (setjmp(png_jmpbuf(mPNG))) {
michael@0 347
michael@0 348 // We might not really know what caused the error, but it makes more
michael@0 349 // sense to blame the data.
michael@0 350 if (!HasError())
michael@0 351 PostDataError();
michael@0 352
michael@0 353 png_destroy_read_struct(&mPNG, &mInfo, nullptr);
michael@0 354 return;
michael@0 355 }
michael@0 356
michael@0 357 // Pass the data off to libpng
michael@0 358 png_process_data(mPNG, mInfo, (unsigned char *)aBuffer, aCount);
michael@0 359
michael@0 360 }
michael@0 361 }
michael@0 362
michael@0 363 // Sets up gamma pre-correction in libpng before our callback gets called.
michael@0 364 // We need to do this if we don't end up with a CMS profile.
michael@0 365 static void
michael@0 366 PNGDoGammaCorrection(png_structp png_ptr, png_infop info_ptr)
michael@0 367 {
michael@0 368 double aGamma;
michael@0 369
michael@0 370 if (png_get_gAMA(png_ptr, info_ptr, &aGamma)) {
michael@0 371 if ((aGamma <= 0.0) || (aGamma > 21474.83)) {
michael@0 372 aGamma = 0.45455;
michael@0 373 png_set_gAMA(png_ptr, info_ptr, aGamma);
michael@0 374 }
michael@0 375 png_set_gamma(png_ptr, 2.2, aGamma);
michael@0 376 }
michael@0 377 else
michael@0 378 png_set_gamma(png_ptr, 2.2, 0.45455);
michael@0 379
michael@0 380 }
michael@0 381
michael@0 382 // Adapted from http://www.littlecms.com/pngchrm.c example code
michael@0 383 static qcms_profile *
michael@0 384 PNGGetColorProfile(png_structp png_ptr, png_infop info_ptr,
michael@0 385 int color_type, qcms_data_type *inType, uint32_t *intent)
michael@0 386 {
michael@0 387 qcms_profile *profile = nullptr;
michael@0 388 *intent = QCMS_INTENT_PERCEPTUAL; // Our default
michael@0 389
michael@0 390 // First try to see if iCCP chunk is present
michael@0 391 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) {
michael@0 392 png_uint_32 profileLen;
michael@0 393 #if (PNG_LIBPNG_VER < 10500)
michael@0 394 char *profileData, *profileName;
michael@0 395 #else
michael@0 396 png_bytep profileData;
michael@0 397 png_charp profileName;
michael@0 398 #endif
michael@0 399 int compression;
michael@0 400
michael@0 401 png_get_iCCP(png_ptr, info_ptr, &profileName, &compression,
michael@0 402 &profileData, &profileLen);
michael@0 403
michael@0 404 profile = qcms_profile_from_memory(
michael@0 405 #if (PNG_LIBPNG_VER < 10500)
michael@0 406 profileData,
michael@0 407 #else
michael@0 408 (char *)profileData,
michael@0 409 #endif
michael@0 410 profileLen);
michael@0 411 if (profile) {
michael@0 412 uint32_t profileSpace = qcms_profile_get_color_space(profile);
michael@0 413
michael@0 414 bool mismatch = false;
michael@0 415 if (color_type & PNG_COLOR_MASK_COLOR) {
michael@0 416 if (profileSpace != icSigRgbData)
michael@0 417 mismatch = true;
michael@0 418 } else {
michael@0 419 if (profileSpace == icSigRgbData)
michael@0 420 png_set_gray_to_rgb(png_ptr);
michael@0 421 else if (profileSpace != icSigGrayData)
michael@0 422 mismatch = true;
michael@0 423 }
michael@0 424
michael@0 425 if (mismatch) {
michael@0 426 qcms_profile_release(profile);
michael@0 427 profile = nullptr;
michael@0 428 } else {
michael@0 429 *intent = qcms_profile_get_rendering_intent(profile);
michael@0 430 }
michael@0 431 }
michael@0 432 }
michael@0 433
michael@0 434 // Check sRGB chunk
michael@0 435 if (!profile && png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) {
michael@0 436 profile = qcms_profile_sRGB();
michael@0 437
michael@0 438 if (profile) {
michael@0 439 int fileIntent;
michael@0 440 png_set_gray_to_rgb(png_ptr);
michael@0 441 png_get_sRGB(png_ptr, info_ptr, &fileIntent);
michael@0 442 uint32_t map[] = { QCMS_INTENT_PERCEPTUAL,
michael@0 443 QCMS_INTENT_RELATIVE_COLORIMETRIC,
michael@0 444 QCMS_INTENT_SATURATION,
michael@0 445 QCMS_INTENT_ABSOLUTE_COLORIMETRIC };
michael@0 446 *intent = map[fileIntent];
michael@0 447 }
michael@0 448 }
michael@0 449
michael@0 450 // Check gAMA/cHRM chunks
michael@0 451 if (!profile &&
michael@0 452 png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) &&
michael@0 453 png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM)) {
michael@0 454 qcms_CIE_xyYTRIPLE primaries;
michael@0 455 qcms_CIE_xyY whitePoint;
michael@0 456
michael@0 457 png_get_cHRM(png_ptr, info_ptr,
michael@0 458 &whitePoint.x, &whitePoint.y,
michael@0 459 &primaries.red.x, &primaries.red.y,
michael@0 460 &primaries.green.x, &primaries.green.y,
michael@0 461 &primaries.blue.x, &primaries.blue.y);
michael@0 462 whitePoint.Y =
michael@0 463 primaries.red.Y = primaries.green.Y = primaries.blue.Y = 1.0;
michael@0 464
michael@0 465 double gammaOfFile;
michael@0 466
michael@0 467 png_get_gAMA(png_ptr, info_ptr, &gammaOfFile);
michael@0 468
michael@0 469 profile = qcms_profile_create_rgb_with_gamma(whitePoint, primaries,
michael@0 470 1.0/gammaOfFile);
michael@0 471
michael@0 472 if (profile)
michael@0 473 png_set_gray_to_rgb(png_ptr);
michael@0 474 }
michael@0 475
michael@0 476 if (profile) {
michael@0 477 uint32_t profileSpace = qcms_profile_get_color_space(profile);
michael@0 478 if (profileSpace == icSigGrayData) {
michael@0 479 if (color_type & PNG_COLOR_MASK_ALPHA)
michael@0 480 *inType = QCMS_DATA_GRAYA_8;
michael@0 481 else
michael@0 482 *inType = QCMS_DATA_GRAY_8;
michael@0 483 } else {
michael@0 484 if (color_type & PNG_COLOR_MASK_ALPHA ||
michael@0 485 png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
michael@0 486 *inType = QCMS_DATA_RGBA_8;
michael@0 487 else
michael@0 488 *inType = QCMS_DATA_RGB_8;
michael@0 489 }
michael@0 490 }
michael@0 491
michael@0 492 return profile;
michael@0 493 }
michael@0 494
michael@0 495 void
michael@0 496 nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr)
michael@0 497 {
michael@0 498 /* int number_passes; NOT USED */
michael@0 499 png_uint_32 width, height;
michael@0 500 int bit_depth, color_type, interlace_type, compression_type, filter_type;
michael@0 501 unsigned int channels;
michael@0 502
michael@0 503 png_bytep trans = nullptr;
michael@0 504 int num_trans = 0;
michael@0 505
michael@0 506 nsPNGDecoder *decoder =
michael@0 507 static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
michael@0 508
michael@0 509 /* always decode to 24-bit RGB or 32-bit RGBA */
michael@0 510 png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
michael@0 511 &interlace_type, &compression_type, &filter_type);
michael@0 512
michael@0 513 /* Are we too big? */
michael@0 514 if (width > MOZ_PNG_MAX_DIMENSION || height > MOZ_PNG_MAX_DIMENSION)
michael@0 515 longjmp(png_jmpbuf(decoder->mPNG), 1);
michael@0 516
michael@0 517 // Post our size to the superclass
michael@0 518 decoder->PostSize(width, height);
michael@0 519 if (decoder->HasError()) {
michael@0 520 // Setting the size led to an error.
michael@0 521 longjmp(png_jmpbuf(decoder->mPNG), 1);
michael@0 522 }
michael@0 523
michael@0 524 if (color_type == PNG_COLOR_TYPE_PALETTE)
michael@0 525 png_set_expand(png_ptr);
michael@0 526
michael@0 527 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
michael@0 528 png_set_expand(png_ptr);
michael@0 529
michael@0 530 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
michael@0 531 int sample_max = (1 << bit_depth);
michael@0 532 png_color_16p trans_values;
michael@0 533 png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values);
michael@0 534 /* libpng doesn't reject a tRNS chunk with out-of-range samples
michael@0 535 so we check it here to avoid setting up a useless opacity
michael@0 536 channel or producing unexpected transparent pixels when using
michael@0 537 libpng-1.2.19 through 1.2.26 (bug #428045) */
michael@0 538 if ((color_type == PNG_COLOR_TYPE_GRAY &&
michael@0 539 (int)trans_values->gray > sample_max) ||
michael@0 540 (color_type == PNG_COLOR_TYPE_RGB &&
michael@0 541 ((int)trans_values->red > sample_max ||
michael@0 542 (int)trans_values->green > sample_max ||
michael@0 543 (int)trans_values->blue > sample_max)))
michael@0 544 {
michael@0 545 /* clear the tRNS valid flag and release tRNS memory */
michael@0 546 png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0);
michael@0 547 }
michael@0 548 else
michael@0 549 png_set_expand(png_ptr);
michael@0 550 }
michael@0 551
michael@0 552 if (bit_depth == 16)
michael@0 553 png_set_scale_16(png_ptr);
michael@0 554
michael@0 555 qcms_data_type inType = QCMS_DATA_RGBA_8;
michael@0 556 uint32_t intent = -1;
michael@0 557 uint32_t pIntent;
michael@0 558 if (decoder->mCMSMode != eCMSMode_Off) {
michael@0 559 intent = gfxPlatform::GetRenderingIntent();
michael@0 560 decoder->mInProfile = PNGGetColorProfile(png_ptr, info_ptr,
michael@0 561 color_type, &inType, &pIntent);
michael@0 562 /* If we're not mandating an intent, use the one from the image. */
michael@0 563 if (intent == uint32_t(-1))
michael@0 564 intent = pIntent;
michael@0 565 }
michael@0 566 if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) {
michael@0 567 qcms_data_type outType;
michael@0 568
michael@0 569 if (color_type & PNG_COLOR_MASK_ALPHA || num_trans)
michael@0 570 outType = QCMS_DATA_RGBA_8;
michael@0 571 else
michael@0 572 outType = QCMS_DATA_RGB_8;
michael@0 573
michael@0 574 decoder->mTransform = qcms_transform_create(decoder->mInProfile,
michael@0 575 inType,
michael@0 576 gfxPlatform::GetCMSOutputProfile(),
michael@0 577 outType,
michael@0 578 (qcms_intent)intent);
michael@0 579 } else {
michael@0 580 png_set_gray_to_rgb(png_ptr);
michael@0 581
michael@0 582 // only do gamma correction if CMS isn't entirely disabled
michael@0 583 if (decoder->mCMSMode != eCMSMode_Off)
michael@0 584 PNGDoGammaCorrection(png_ptr, info_ptr);
michael@0 585
michael@0 586 if (decoder->mCMSMode == eCMSMode_All) {
michael@0 587 if (color_type & PNG_COLOR_MASK_ALPHA || num_trans)
michael@0 588 decoder->mTransform = gfxPlatform::GetCMSRGBATransform();
michael@0 589 else
michael@0 590 decoder->mTransform = gfxPlatform::GetCMSRGBTransform();
michael@0 591 }
michael@0 592 }
michael@0 593
michael@0 594 /* let libpng expand interlaced images */
michael@0 595 if (interlace_type == PNG_INTERLACE_ADAM7) {
michael@0 596 /* number_passes = */
michael@0 597 png_set_interlace_handling(png_ptr);
michael@0 598 }
michael@0 599
michael@0 600 /* now all of those things we set above are used to update various struct
michael@0 601 * members and whatnot, after which we can get channels, rowbytes, etc. */
michael@0 602 png_read_update_info(png_ptr, info_ptr);
michael@0 603 decoder->mChannels = channels = png_get_channels(png_ptr, info_ptr);
michael@0 604
michael@0 605 /*---------------------------------------------------------------*/
michael@0 606 /* copy PNG info into imagelib structs (formerly png_set_dims()) */
michael@0 607 /*---------------------------------------------------------------*/
michael@0 608
michael@0 609 // This code is currently unused, but it will be needed for bug 517713.
michael@0 610 #if 0
michael@0 611 int32_t alpha_bits = 1;
michael@0 612
michael@0 613 if (channels == 2 || channels == 4) {
michael@0 614 /* check if alpha is coming from a tRNS chunk and is binary */
michael@0 615 if (num_trans) {
michael@0 616 /* if it's not an indexed color image, tRNS means binary */
michael@0 617 if (color_type == PNG_COLOR_TYPE_PALETTE) {
michael@0 618 for (int i=0; i<num_trans; i++) {
michael@0 619 if ((trans[i] != 0) && (trans[i] != 255)) {
michael@0 620 alpha_bits = 8;
michael@0 621 break;
michael@0 622 }
michael@0 623 }
michael@0 624 }
michael@0 625 } else {
michael@0 626 alpha_bits = 8;
michael@0 627 }
michael@0 628 }
michael@0 629 #endif
michael@0 630
michael@0 631 if (channels == 1 || channels == 3)
michael@0 632 decoder->format = gfxImageFormat::RGB24;
michael@0 633 else if (channels == 2 || channels == 4)
michael@0 634 decoder->format = gfxImageFormat::ARGB32;
michael@0 635
michael@0 636 #ifdef PNG_APNG_SUPPORTED
michael@0 637 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL))
michael@0 638 png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback,
michael@0 639 nullptr);
michael@0 640
michael@0 641 if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) {
michael@0 642 decoder->mFrameIsHidden = true;
michael@0 643 } else {
michael@0 644 #endif
michael@0 645 decoder->CreateFrame(0, 0, width, height, decoder->format);
michael@0 646 #ifdef PNG_APNG_SUPPORTED
michael@0 647 }
michael@0 648 #endif
michael@0 649
michael@0 650 if (decoder->mTransform &&
michael@0 651 (channels <= 2 || interlace_type == PNG_INTERLACE_ADAM7)) {
michael@0 652 uint32_t bpp[] = { 0, 3, 4, 3, 4 };
michael@0 653 decoder->mCMSLine =
michael@0 654 (uint8_t *)moz_malloc(bpp[channels] * width);
michael@0 655 if (!decoder->mCMSLine) {
michael@0 656 longjmp(png_jmpbuf(decoder->mPNG), 5); // NS_ERROR_OUT_OF_MEMORY
michael@0 657 }
michael@0 658 }
michael@0 659
michael@0 660 if (interlace_type == PNG_INTERLACE_ADAM7) {
michael@0 661 if (height < INT32_MAX / (width * channels))
michael@0 662 decoder->interlacebuf = (uint8_t *)moz_malloc(channels * width * height);
michael@0 663 if (!decoder->interlacebuf) {
michael@0 664 longjmp(png_jmpbuf(decoder->mPNG), 5); // NS_ERROR_OUT_OF_MEMORY
michael@0 665 }
michael@0 666 }
michael@0 667
michael@0 668 if (decoder->NeedsNewFrame()) {
michael@0 669 /* We know that we need a new frame, so pause input so the decoder
michael@0 670 * infrastructure can give it to us.
michael@0 671 */
michael@0 672 png_process_data_pause(png_ptr, /* save = */ 1);
michael@0 673 }
michael@0 674 }
michael@0 675
michael@0 676 void
michael@0 677 nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row,
michael@0 678 png_uint_32 row_num, int pass)
michael@0 679 {
michael@0 680 /* libpng comments:
michael@0 681 *
michael@0 682 * this function is called for every row in the image. If the
michael@0 683 * image is interlacing, and you turned on the interlace handler,
michael@0 684 * this function will be called for every row in every pass.
michael@0 685 * Some of these rows will not be changed from the previous pass.
michael@0 686 * When the row is not changed, the new_row variable will be
michael@0 687 * nullptr. The rows and passes are called in order, so you don't
michael@0 688 * really need the row_num and pass, but I'm supplying them
michael@0 689 * because it may make your life easier.
michael@0 690 *
michael@0 691 * For the non-nullptr rows of interlaced images, you must call
michael@0 692 * png_progressive_combine_row() passing in the row and the
michael@0 693 * old row. You can call this function for nullptr rows (it will
michael@0 694 * just return) and for non-interlaced images (it just does the
michael@0 695 * memcpy for you) if it will make the code easier. Thus, you
michael@0 696 * can just do this for all cases:
michael@0 697 *
michael@0 698 * png_progressive_combine_row(png_ptr, old_row, new_row);
michael@0 699 *
michael@0 700 * where old_row is what was displayed for previous rows. Note
michael@0 701 * that the first pass (pass == 0 really) will completely cover
michael@0 702 * the old row, so the rows do not have to be initialized. After
michael@0 703 * the first pass (and only for interlaced images), you will have
michael@0 704 * to pass the current row, and the function will combine the
michael@0 705 * old row and the new row.
michael@0 706 */
michael@0 707 nsPNGDecoder *decoder =
michael@0 708 static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
michael@0 709
michael@0 710 // skip this frame
michael@0 711 if (decoder->mFrameIsHidden)
michael@0 712 return;
michael@0 713
michael@0 714 if (row_num >= (png_uint_32) decoder->mFrameRect.height)
michael@0 715 return;
michael@0 716
michael@0 717 if (new_row) {
michael@0 718 int32_t width = decoder->mFrameRect.width;
michael@0 719 uint32_t iwidth = decoder->mFrameRect.width;
michael@0 720
michael@0 721 png_bytep line = new_row;
michael@0 722 if (decoder->interlacebuf) {
michael@0 723 line = decoder->interlacebuf + (row_num * decoder->mChannels * width);
michael@0 724 png_progressive_combine_row(png_ptr, line, new_row);
michael@0 725 }
michael@0 726
michael@0 727 uint32_t bpr = width * sizeof(uint32_t);
michael@0 728 uint32_t *cptr32 = (uint32_t*)(decoder->mImageData + (row_num*bpr));
michael@0 729 bool rowHasNoAlpha = true;
michael@0 730
michael@0 731 if (decoder->mTransform) {
michael@0 732 if (decoder->mCMSLine) {
michael@0 733 qcms_transform_data(decoder->mTransform, line, decoder->mCMSLine,
michael@0 734 iwidth);
michael@0 735 /* copy alpha over */
michael@0 736 uint32_t channels = decoder->mChannels;
michael@0 737 if (channels == 2 || channels == 4) {
michael@0 738 for (uint32_t i = 0; i < iwidth; i++)
michael@0 739 decoder->mCMSLine[4 * i + 3] = line[channels * i + channels - 1];
michael@0 740 }
michael@0 741 line = decoder->mCMSLine;
michael@0 742 } else {
michael@0 743 qcms_transform_data(decoder->mTransform, line, line, iwidth);
michael@0 744 }
michael@0 745 }
michael@0 746
michael@0 747 switch (decoder->format) {
michael@0 748 case gfxImageFormat::RGB24:
michael@0 749 {
michael@0 750 // counter for while() loops below
michael@0 751 uint32_t idx = iwidth;
michael@0 752
michael@0 753 // copy as bytes until source pointer is 32-bit-aligned
michael@0 754 for (; (NS_PTR_TO_UINT32(line) & 0x3) && idx; --idx) {
michael@0 755 *cptr32++ = gfxPackedPixel(0xFF, line[0], line[1], line[2]);
michael@0 756 line += 3;
michael@0 757 }
michael@0 758
michael@0 759 // copy pixels in blocks of 4
michael@0 760 while (idx >= 4) {
michael@0 761 GFX_BLOCK_RGB_TO_FRGB(line, cptr32);
michael@0 762 idx -= 4;
michael@0 763 line += 12;
michael@0 764 cptr32 += 4;
michael@0 765 }
michael@0 766
michael@0 767 // copy remaining pixel(s)
michael@0 768 while (idx--) {
michael@0 769 // 32-bit read of final pixel will exceed buffer, so read bytes
michael@0 770 *cptr32++ = gfxPackedPixel(0xFF, line[0], line[1], line[2]);
michael@0 771 line += 3;
michael@0 772 }
michael@0 773 }
michael@0 774 break;
michael@0 775 case gfxImageFormat::ARGB32:
michael@0 776 {
michael@0 777 if (!decoder->mDisablePremultipliedAlpha) {
michael@0 778 for (uint32_t x=width; x>0; --x) {
michael@0 779 *cptr32++ = gfxPackedPixel(line[3], line[0], line[1], line[2]);
michael@0 780 if (line[3] != 0xff)
michael@0 781 rowHasNoAlpha = false;
michael@0 782 line += 4;
michael@0 783 }
michael@0 784 } else {
michael@0 785 for (uint32_t x=width; x>0; --x) {
michael@0 786 *cptr32++ = gfxPackedPixelNoPreMultiply(line[3], line[0], line[1], line[2]);
michael@0 787 if (line[3] != 0xff)
michael@0 788 rowHasNoAlpha = false;
michael@0 789 line += 4;
michael@0 790 }
michael@0 791 }
michael@0 792 }
michael@0 793 break;
michael@0 794 default:
michael@0 795 longjmp(png_jmpbuf(decoder->mPNG), 1);
michael@0 796 }
michael@0 797
michael@0 798 if (!rowHasNoAlpha)
michael@0 799 decoder->mFrameHasNoAlpha = false;
michael@0 800
michael@0 801 if (decoder->mNumFrames <= 1) {
michael@0 802 // Only do incremental image display for the first frame
michael@0 803 // XXXbholley - this check should be handled in the superclass
michael@0 804 nsIntRect r(0, row_num, width, 1);
michael@0 805 decoder->PostInvalidation(r);
michael@0 806 }
michael@0 807 }
michael@0 808 }
michael@0 809
michael@0 810 #ifdef PNG_APNG_SUPPORTED
michael@0 811 // got the header of a new frame that's coming
michael@0 812 void
michael@0 813 nsPNGDecoder::frame_info_callback(png_structp png_ptr, png_uint_32 frame_num)
michael@0 814 {
michael@0 815 png_uint_32 x_offset, y_offset;
michael@0 816 int32_t width, height;
michael@0 817
michael@0 818 nsPNGDecoder *decoder =
michael@0 819 static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
michael@0 820
michael@0 821 // old frame is done
michael@0 822 decoder->EndImageFrame();
michael@0 823
michael@0 824 // Only the first frame can be hidden, so unhide unconditionally here.
michael@0 825 decoder->mFrameIsHidden = false;
michael@0 826
michael@0 827 x_offset = png_get_next_frame_x_offset(png_ptr, decoder->mInfo);
michael@0 828 y_offset = png_get_next_frame_y_offset(png_ptr, decoder->mInfo);
michael@0 829 width = png_get_next_frame_width(png_ptr, decoder->mInfo);
michael@0 830 height = png_get_next_frame_height(png_ptr, decoder->mInfo);
michael@0 831
michael@0 832 decoder->CreateFrame(x_offset, y_offset, width, height, decoder->format);
michael@0 833
michael@0 834 if (decoder->NeedsNewFrame()) {
michael@0 835 /* We know that we need a new frame, so pause input so the decoder
michael@0 836 * infrastructure can give it to us.
michael@0 837 */
michael@0 838 png_process_data_pause(png_ptr, /* save = */ 1);
michael@0 839 }
michael@0 840 }
michael@0 841 #endif
michael@0 842
michael@0 843 void
michael@0 844 nsPNGDecoder::end_callback(png_structp png_ptr, png_infop info_ptr)
michael@0 845 {
michael@0 846 /* libpng comments:
michael@0 847 *
michael@0 848 * this function is called when the whole image has been read,
michael@0 849 * including any chunks after the image (up to and including
michael@0 850 * the IEND). You will usually have the same info chunk as you
michael@0 851 * had in the header, although some data may have been added
michael@0 852 * to the comments and time fields.
michael@0 853 *
michael@0 854 * Most people won't do much here, perhaps setting a flag that
michael@0 855 * marks the image as finished.
michael@0 856 */
michael@0 857
michael@0 858 nsPNGDecoder *decoder =
michael@0 859 static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
michael@0 860
michael@0 861 // We shouldn't get here if we've hit an error
michael@0 862 NS_ABORT_IF_FALSE(!decoder->HasError(), "Finishing up PNG but hit error!");
michael@0 863
michael@0 864 int32_t loop_count = 0;
michael@0 865 #ifdef PNG_APNG_SUPPORTED
michael@0 866 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL)) {
michael@0 867 int32_t num_plays = png_get_num_plays(png_ptr, info_ptr);
michael@0 868 loop_count = num_plays - 1;
michael@0 869 }
michael@0 870 #endif
michael@0 871
michael@0 872 // Send final notifications
michael@0 873 decoder->EndImageFrame();
michael@0 874 decoder->PostDecodeDone(loop_count);
michael@0 875 }
michael@0 876
michael@0 877
michael@0 878 void
michael@0 879 nsPNGDecoder::error_callback(png_structp png_ptr, png_const_charp error_msg)
michael@0 880 {
michael@0 881 PR_LOG(GetPNGLog(), PR_LOG_ERROR, ("libpng error: %s\n", error_msg));
michael@0 882 longjmp(png_jmpbuf(png_ptr), 1);
michael@0 883 }
michael@0 884
michael@0 885
michael@0 886 void
michael@0 887 nsPNGDecoder::warning_callback(png_structp png_ptr, png_const_charp warning_msg)
michael@0 888 {
michael@0 889 PR_LOG(GetPNGLog(), PR_LOG_WARNING, ("libpng warning: %s\n", warning_msg));
michael@0 890 }
michael@0 891
michael@0 892 Telemetry::ID
michael@0 893 nsPNGDecoder::SpeedHistogram()
michael@0 894 {
michael@0 895 return Telemetry::IMAGE_DECODE_SPEED_PNG;
michael@0 896 }
michael@0 897
michael@0 898
michael@0 899 } // namespace image
michael@0 900 } // namespace mozilla

mercurial