image/decoders/nsPNGDecoder.cpp

changeset 0
6474c204b198
     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

mercurial