LCOV - code coverage report
Current view: top level - image/decoders - nsPNGDecoder.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 273 415 65.8 %
Date: 2017-07-14 16:53:18 Functions: 28 36 77.8 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
       2             :  *
       3             :  * This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "ImageLogging.h" // Must appear first
       8             : #include "nsPNGDecoder.h"
       9             : 
      10             : #include <algorithm>
      11             : #include <cstdint>
      12             : 
      13             : #include "gfxColor.h"
      14             : #include "gfxPlatform.h"
      15             : #include "imgFrame.h"
      16             : #include "nsColor.h"
      17             : #include "nsIInputStream.h"
      18             : #include "nsMemory.h"
      19             : #include "nsRect.h"
      20             : #include "nspr.h"
      21             : #include "png.h"
      22             : 
      23             : #include "RasterImage.h"
      24             : #include "SurfaceCache.h"
      25             : #include "SurfacePipeFactory.h"
      26             : #include "mozilla/DebugOnly.h"
      27             : #include "mozilla/Telemetry.h"
      28             : 
      29             : using namespace mozilla::gfx;
      30             : 
      31             : using std::min;
      32             : 
      33             : namespace mozilla {
      34             : namespace image {
      35             : 
      36             : static LazyLogModule sPNGLog("PNGDecoder");
      37             : static LazyLogModule sPNGDecoderAccountingLog("PNGDecoderAccounting");
      38             : 
      39             : // limit image dimensions (bug #251381, #591822, #967656, and #1283961)
      40             : #ifndef MOZ_PNG_MAX_WIDTH
      41             : #  define MOZ_PNG_MAX_WIDTH 0x7fffffff // Unlimited
      42             : #endif
      43             : #ifndef MOZ_PNG_MAX_HEIGHT
      44             : #  define MOZ_PNG_MAX_HEIGHT 0x7fffffff // Unlimited
      45             : #endif
      46             : 
      47          31 : nsPNGDecoder::AnimFrameInfo::AnimFrameInfo()
      48             :  : mDispose(DisposalMethod::KEEP)
      49             :  , mBlend(BlendMethod::OVER)
      50          31 :  , mTimeout(0)
      51          31 : { }
      52             : 
      53             : #ifdef PNG_APNG_SUPPORTED
      54             : 
      55          40 : int32_t GetNextFrameDelay(png_structp aPNG, png_infop aInfo)
      56             : {
      57             :   // Delay, in seconds, is delayNum / delayDen.
      58          40 :   png_uint_16 delayNum = png_get_next_frame_delay_num(aPNG, aInfo);
      59          40 :   png_uint_16 delayDen = png_get_next_frame_delay_den(aPNG, aInfo);
      60             : 
      61          40 :   if (delayNum == 0) {
      62           0 :     return 0; // SetFrameTimeout() will set to a minimum.
      63             :   }
      64             : 
      65          40 :   if (delayDen == 0) {
      66           0 :     delayDen = 100; // So says the APNG spec.
      67             :   }
      68             : 
      69             :   // Need to cast delay_num to float to have a proper division and
      70             :   // the result to int to avoid a compiler warning.
      71          40 :   return static_cast<int32_t>(static_cast<double>(delayNum) * 1000 / delayDen);
      72             : }
      73             : 
      74          36 : nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo)
      75             :  : mDispose(DisposalMethod::KEEP)
      76             :  , mBlend(BlendMethod::OVER)
      77          36 :  , mTimeout(0)
      78             : {
      79          36 :   png_byte dispose_op = png_get_next_frame_dispose_op(aPNG, aInfo);
      80          36 :   png_byte blend_op = png_get_next_frame_blend_op(aPNG, aInfo);
      81             : 
      82          36 :   if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) {
      83           0 :     mDispose = DisposalMethod::RESTORE_PREVIOUS;
      84          36 :   } else if (dispose_op == PNG_DISPOSE_OP_BACKGROUND) {
      85           0 :     mDispose = DisposalMethod::CLEAR;
      86             :   } else {
      87          36 :     mDispose = DisposalMethod::KEEP;
      88             :   }
      89             : 
      90          36 :   if (blend_op == PNG_BLEND_OP_SOURCE) {
      91          36 :     mBlend = BlendMethod::SOURCE;
      92             :   } else {
      93           0 :     mBlend = BlendMethod::OVER;
      94             :   }
      95             : 
      96          36 :   mTimeout = GetNextFrameDelay(aPNG, aInfo);
      97          36 : }
      98             : #endif
      99             : 
     100             : // First 8 bytes of a PNG file
     101             : const uint8_t
     102             : nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
     103             : 
     104          31 : nsPNGDecoder::nsPNGDecoder(RasterImage* aImage)
     105             :  : Decoder(aImage)
     106          62 :  , mLexer(Transition::ToUnbuffered(State::FINISHED_PNG_DATA,
     107             :                                    State::PNG_DATA,
     108             :                                    SIZE_MAX),
     109             :           Transition::TerminateSuccess())
     110             :  , mNextTransition(Transition::ContinueUnbuffered(State::PNG_DATA))
     111             :  , mLastChunkLength(0)
     112             :  , mPNG(nullptr)
     113             :  , mInfo(nullptr)
     114             :  , mCMSLine(nullptr)
     115             :  , interlacebuf(nullptr)
     116             :  , mInProfile(nullptr)
     117             :  , mTransform(nullptr)
     118             :  , mFormat(SurfaceFormat::UNKNOWN)
     119             :  , mCMSMode(0)
     120             :  , mChannels(0)
     121             :  , mPass(0)
     122             :  , mFrameIsHidden(false)
     123             :  , mDisablePremultipliedAlpha(false)
     124          93 :  , mNumFrames(0)
     125          31 : { }
     126             : 
     127          93 : nsPNGDecoder::~nsPNGDecoder()
     128             : {
     129          31 :   if (mPNG) {
     130          31 :     png_destroy_read_struct(&mPNG, mInfo ? &mInfo : nullptr, nullptr);
     131             :   }
     132          31 :   if (mCMSLine) {
     133           0 :     free(mCMSLine);
     134             :   }
     135          31 :   if (interlacebuf) {
     136           0 :     free(interlacebuf);
     137             :   }
     138          31 :   if (mInProfile) {
     139           8 :     qcms_profile_release(mInProfile);
     140             : 
     141             :     // mTransform belongs to us only if mInProfile is non-null
     142           8 :     if (mTransform) {
     143           8 :       qcms_transform_release(mTransform);
     144             :     }
     145             :   }
     146          93 : }
     147             : 
     148             : nsPNGDecoder::TransparencyType
     149          65 : nsPNGDecoder::GetTransparencyType(const IntRect& aFrameRect)
     150             : {
     151             :   // Check if the image has a transparent color in its palette.
     152          65 :   if (HasAlphaChannel()) {
     153          65 :     return TransparencyType::eAlpha;
     154             :   }
     155           0 :   if (!aFrameRect.IsEqualEdges(FullFrame())) {
     156           0 :     MOZ_ASSERT(HasAnimation());
     157           0 :     return TransparencyType::eFrameRect;
     158             :   }
     159             : 
     160           0 :   return TransparencyType::eNone;
     161             : }
     162             : 
     163             : void
     164          65 : nsPNGDecoder::PostHasTransparencyIfNeeded(TransparencyType aTransparencyType)
     165             : {
     166          65 :   switch (aTransparencyType) {
     167             :     case TransparencyType::eNone:
     168           0 :       return;
     169             : 
     170             :     case TransparencyType::eAlpha:
     171          65 :       PostHasTransparency();
     172          65 :       return;
     173             : 
     174             :     case TransparencyType::eFrameRect:
     175             :       // If the first frame of animated image doesn't draw into the whole image,
     176             :       // then record that it is transparent. For subsequent frames, this doesn't
     177             :       // affect transparency, because they're composited on top of all previous
     178             :       // frames.
     179           0 :       if (mNumFrames == 0) {
     180           0 :         PostHasTransparency();
     181             :       }
     182           0 :       return;
     183             :   }
     184             : }
     185             : 
     186             : // CreateFrame() is used for both simple and animated images.
     187             : nsresult
     188          47 : nsPNGDecoder::CreateFrame(const FrameInfo& aFrameInfo)
     189             : {
     190          47 :   MOZ_ASSERT(HasSize());
     191          47 :   MOZ_ASSERT(!IsMetadataDecode());
     192             : 
     193             :   // Check if we have transparency, and send notifications if needed.
     194          47 :   auto transparency = GetTransparencyType(aFrameInfo.mFrameRect);
     195          47 :   PostHasTransparencyIfNeeded(transparency);
     196          47 :   mFormat = transparency == TransparencyType::eNone
     197          47 :           ? SurfaceFormat::B8G8R8X8
     198             :           : SurfaceFormat::B8G8R8A8;
     199             : 
     200             :   // Make sure there's no animation or padding if we're downscaling.
     201          47 :   MOZ_ASSERT_IF(Size() != OutputSize(), mNumFrames == 0);
     202          47 :   MOZ_ASSERT_IF(Size() != OutputSize(), !GetImageMetadata().HasAnimation());
     203          47 :   MOZ_ASSERT_IF(Size() != OutputSize(),
     204             :                 transparency != TransparencyType::eFrameRect);
     205             : 
     206             :   // If this image is interlaced, we can display better quality intermediate
     207             :   // results to the user by post processing them with ADAM7InterpolatingFilter.
     208          47 :   SurfacePipeFlags pipeFlags = aFrameInfo.mIsInterlaced
     209          47 :                              ? SurfacePipeFlags::ADAM7_INTERPOLATE
     210          47 :                              : SurfacePipeFlags();
     211             : 
     212          47 :   if (mNumFrames == 0) {
     213             :     // The first frame may be displayed progressively.
     214          13 :     pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY;
     215             :   }
     216             : 
     217             :   Maybe<SurfacePipe> pipe =
     218          94 :     SurfacePipeFactory::CreateSurfacePipe(this, mNumFrames, Size(),
     219          94 :                                           OutputSize(), aFrameInfo.mFrameRect,
     220         141 :                                           mFormat, pipeFlags);
     221             : 
     222          47 :   if (!pipe) {
     223           0 :     mPipe = SurfacePipe();
     224           0 :     return NS_ERROR_FAILURE;
     225             :   }
     226             : 
     227          47 :   mPipe = Move(*pipe);
     228             : 
     229          47 :   mFrameRect = aFrameInfo.mFrameRect;
     230          47 :   mPass = 0;
     231             : 
     232          47 :   MOZ_LOG(sPNGDecoderAccountingLog, LogLevel::Debug,
     233             :          ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created "
     234             :           "image frame with %dx%d pixels for decoder %p",
     235             :           mFrameRect.width, mFrameRect.height, this));
     236             : 
     237             : #ifdef PNG_APNG_SUPPORTED
     238          47 :   if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) {
     239          36 :     mAnimInfo = AnimFrameInfo(mPNG, mInfo);
     240             : 
     241          36 :     if (mAnimInfo.mDispose == DisposalMethod::CLEAR) {
     242             :       // We may have to display the background under this image during
     243             :       // animation playback, so we regard it as transparent.
     244           0 :       PostHasTransparency();
     245             :     }
     246             :   }
     247             : #endif
     248             : 
     249          47 :   return NS_OK;
     250             : }
     251             : 
     252             : // set timeout and frame disposal method for the current frame
     253             : void
     254          47 : nsPNGDecoder::EndImageFrame()
     255             : {
     256          47 :   if (mFrameIsHidden) {
     257           0 :     return;
     258             :   }
     259             : 
     260          47 :   mNumFrames++;
     261             : 
     262          47 :   Opacity opacity = mFormat == SurfaceFormat::B8G8R8X8
     263          47 :                   ? Opacity::FULLY_OPAQUE
     264          47 :                   : Opacity::SOME_TRANSPARENCY;
     265             : 
     266          47 :   PostFrameStop(opacity, mAnimInfo.mDispose,
     267             :                 FrameTimeout::FromRawMilliseconds(mAnimInfo.mTimeout),
     268          94 :                 mAnimInfo.mBlend, Some(mFrameRect));
     269             : }
     270             : 
     271             : nsresult
     272          31 : nsPNGDecoder::InitInternal()
     273             : {
     274          31 :   mCMSMode = gfxPlatform::GetCMSMode();
     275          31 :   if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
     276           0 :     mCMSMode = eCMSMode_Off;
     277             :   }
     278          31 :   mDisablePremultipliedAlpha =
     279          62 :     bool(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
     280             : 
     281             : #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
     282             :   static png_byte color_chunks[]=
     283             :        { 99,  72,  82,  77, '\0',   // cHRM
     284             :         105,  67,  67,  80, '\0'};  // iCCP
     285             :   static png_byte unused_chunks[]=
     286             :        { 98,  75,  71,  68, '\0',   // bKGD
     287             :         104,  73,  83,  84, '\0',   // hIST
     288             :         105,  84,  88, 116, '\0',   // iTXt
     289             :         111,  70,  70, 115, '\0',   // oFFs
     290             :         112,  67,  65,  76, '\0',   // pCAL
     291             :         115,  67,  65,  76, '\0',   // sCAL
     292             :         112,  72,  89, 115, '\0',   // pHYs
     293             :         115,  66,  73,  84, '\0',   // sBIT
     294             :         115,  80,  76,  84, '\0',   // sPLT
     295             :         116,  69,  88, 116, '\0',   // tEXt
     296             :         116,  73,  77,  69, '\0',   // tIME
     297             :         122,  84,  88, 116, '\0'};  // zTXt
     298             : #endif
     299             : 
     300             :   // Initialize the container's source image header
     301             :   // Always decode to 24 bit pixdepth
     302             : 
     303          31 :   mPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING,
     304             :                                 nullptr, nsPNGDecoder::error_callback,
     305             :                                 nsPNGDecoder::warning_callback);
     306          31 :   if (!mPNG) {
     307           0 :     return NS_ERROR_OUT_OF_MEMORY;
     308             :   }
     309             : 
     310          31 :   mInfo = png_create_info_struct(mPNG);
     311          31 :   if (!mInfo) {
     312           0 :     png_destroy_read_struct(&mPNG, nullptr, nullptr);
     313           0 :     return NS_ERROR_OUT_OF_MEMORY;
     314             :   }
     315             : 
     316             : #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
     317             :   // Ignore unused chunks
     318             :   if (mCMSMode == eCMSMode_Off || IsMetadataDecode()) {
     319             :     png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2);
     320             :   }
     321             : 
     322             :   png_set_keep_unknown_chunks(mPNG, 1, unused_chunks,
     323             :                               (int)sizeof(unused_chunks)/5);
     324             : #endif
     325             : 
     326             : #ifdef PNG_SET_USER_LIMITS_SUPPORTED
     327          31 :   png_set_user_limits(mPNG, MOZ_PNG_MAX_WIDTH, MOZ_PNG_MAX_HEIGHT);
     328          31 :   if (mCMSMode != eCMSMode_Off) {
     329          31 :     png_set_chunk_malloc_max(mPNG, 4000000L);
     330             :   }
     331             : #endif
     332             : 
     333             : #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED
     334             :   // Disallow palette-index checking, for speed; we would ignore the warning
     335             :   // anyhow.  This feature was added at libpng version 1.5.10 and is disabled
     336             :   // in the embedded libpng but enabled by default in the system libpng.  This
     337             :   // call also disables it in the system libpng, for decoding speed.
     338             :   // Bug #745202.
     339             :   png_set_check_for_invalid_index(mPNG, 0);
     340             : #endif
     341             : 
     342             : #ifdef PNG_SET_OPTION_SUPPORTED
     343             : #if defined(PNG_sRGB_PROFILE_CHECKS) && PNG_sRGB_PROFILE_CHECKS >= 0
     344             :   // Skip checking of sRGB ICC profiles
     345             :   png_set_option(mPNG, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
     346             : #endif
     347             : 
     348             : #ifdef PNG_MAXIMUM_INFLATE_WINDOW
     349             :   // Force a larger zlib inflate window as some images in the wild have
     350             :   // incorrectly set metadata (specifically CMF bits) which prevent us from
     351             :   // decoding them otherwise.
     352          31 :   png_set_option(mPNG, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON);
     353             : #endif
     354             : #endif
     355             : 
     356             :   // use this as libpng "progressive pointer" (retrieve in callbacks)
     357          31 :   png_set_progressive_read_fn(mPNG, static_cast<png_voidp>(this),
     358             :                               nsPNGDecoder::info_callback,
     359             :                               nsPNGDecoder::row_callback,
     360          31 :                               nsPNGDecoder::end_callback);
     361             : 
     362          31 :   return NS_OK;
     363             : }
     364             : 
     365             : LexerResult
     366          65 : nsPNGDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
     367             : {
     368          65 :   MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
     369             : 
     370             :   return mLexer.Lex(aIterator, aOnResume,
     371          65 :                     [=](State aState, const char* aData, size_t aLength) {
     372          65 :     switch (aState) {
     373             :       case State::PNG_DATA:
     374          65 :         return ReadPNGData(aData, aLength);
     375             :       case State::FINISHED_PNG_DATA:
     376           0 :         return FinishedPNGData();
     377             :     }
     378           0 :     MOZ_CRASH("Unknown State");
     379          65 :   });
     380             : }
     381             : 
     382             : LexerTransition<nsPNGDecoder::State>
     383          65 : nsPNGDecoder::ReadPNGData(const char* aData, size_t aLength)
     384             : {
     385             :   // If we were waiting until after returning from a yield to call
     386             :   // CreateFrame(), call it now.
     387          65 :   if (mNextFrameInfo) {
     388          34 :     if (NS_FAILED(CreateFrame(*mNextFrameInfo))) {
     389           0 :       return Transition::TerminateFailure();
     390             :     }
     391             : 
     392          34 :     MOZ_ASSERT(mImageData, "Should have a buffer now");
     393          34 :     mNextFrameInfo = Nothing();
     394             :   }
     395             : 
     396             :   // libpng uses setjmp/longjmp for error handling.
     397          65 :   if (setjmp(png_jmpbuf(mPNG))) {
     398           0 :     return Transition::TerminateFailure();
     399             :   }
     400             : 
     401             :   // Pass the data off to libpng.
     402          65 :   mLastChunkLength = aLength;
     403          65 :   mNextTransition = Transition::ContinueUnbuffered(State::PNG_DATA);
     404          65 :   png_process_data(mPNG, mInfo,
     405             :                    reinterpret_cast<unsigned char*>(const_cast<char*>((aData))),
     406          65 :                    aLength);
     407             : 
     408             :   // Make sure that we've reached a terminal state if decoding is done.
     409          65 :   MOZ_ASSERT_IF(GetDecodeDone(), mNextTransition.NextStateIsTerminal());
     410          65 :   MOZ_ASSERT_IF(HasError(), mNextTransition.NextStateIsTerminal());
     411             : 
     412             :   // Continue with whatever transition the callback code requested. We
     413             :   // initialized this to Transition::ContinueUnbuffered(State::PNG_DATA) above,
     414             :   // so by default we just continue the unbuffered read.
     415          65 :   return mNextTransition;
     416             : }
     417             : 
     418             : LexerTransition<nsPNGDecoder::State>
     419           0 : nsPNGDecoder::FinishedPNGData()
     420             : {
     421             :   // Since we set up an unbuffered read for SIZE_MAX bytes, if we actually read
     422             :   // all that data something is really wrong.
     423           0 :   MOZ_ASSERT_UNREACHABLE("Read the entire address space?");
     424             :   return Transition::TerminateFailure();
     425             : }
     426             : 
     427             : // Sets up gamma pre-correction in libpng before our callback gets called.
     428             : // We need to do this if we don't end up with a CMS profile.
     429             : static void
     430          23 : PNGDoGammaCorrection(png_structp png_ptr, png_infop info_ptr)
     431             : {
     432             :   double aGamma;
     433             : 
     434          23 :   if (png_get_gAMA(png_ptr, info_ptr, &aGamma)) {
     435           0 :     if ((aGamma <= 0.0) || (aGamma > 21474.83)) {
     436           0 :       aGamma = 0.45455;
     437           0 :       png_set_gAMA(png_ptr, info_ptr, aGamma);
     438             :     }
     439           0 :     png_set_gamma(png_ptr, 2.2, aGamma);
     440             :   } else {
     441          23 :     png_set_gamma(png_ptr, 2.2, 0.45455);
     442             :   }
     443          23 : }
     444             : 
     445             : // Adapted from http://www.littlecms.com/pngchrm.c example code
     446             : static qcms_profile*
     447          31 : PNGGetColorProfile(png_structp png_ptr, png_infop info_ptr,
     448             :                    int color_type, qcms_data_type* inType, uint32_t* intent)
     449             : {
     450          31 :   qcms_profile* profile = nullptr;
     451          31 :   *intent = QCMS_INTENT_PERCEPTUAL; // Our default
     452             : 
     453             :   // First try to see if iCCP chunk is present
     454          31 :   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) {
     455             :     png_uint_32 profileLen;
     456             :     png_bytep profileData;
     457             :     png_charp profileName;
     458             :     int compression;
     459             : 
     460             :     png_get_iCCP(png_ptr, info_ptr, &profileName, &compression,
     461           0 :                  &profileData, &profileLen);
     462             : 
     463           0 :     profile = qcms_profile_from_memory((char*)profileData, profileLen);
     464           0 :     if (profile) {
     465           0 :       uint32_t profileSpace = qcms_profile_get_color_space(profile);
     466             : 
     467           0 :       bool mismatch = false;
     468           0 :       if (color_type & PNG_COLOR_MASK_COLOR) {
     469           0 :         if (profileSpace != icSigRgbData) {
     470           0 :           mismatch = true;
     471             :         }
     472             :       } else {
     473           0 :         if (profileSpace == icSigRgbData) {
     474           0 :           png_set_gray_to_rgb(png_ptr);
     475           0 :         } else if (profileSpace != icSigGrayData) {
     476           0 :           mismatch = true;
     477             :         }
     478             :       }
     479             : 
     480           0 :       if (mismatch) {
     481           0 :         qcms_profile_release(profile);
     482           0 :         profile = nullptr;
     483             :       } else {
     484           0 :         *intent = qcms_profile_get_rendering_intent(profile);
     485             :       }
     486             :     }
     487             :   }
     488             : 
     489             :   // Check sRGB chunk
     490          31 :   if (!profile && png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) {
     491           8 :     profile = qcms_profile_sRGB();
     492             : 
     493           8 :     if (profile) {
     494             :       int fileIntent;
     495           8 :       png_set_gray_to_rgb(png_ptr);
     496           8 :       png_get_sRGB(png_ptr, info_ptr, &fileIntent);
     497             :       uint32_t map[] = { QCMS_INTENT_PERCEPTUAL,
     498             :                          QCMS_INTENT_RELATIVE_COLORIMETRIC,
     499             :                          QCMS_INTENT_SATURATION,
     500           8 :                          QCMS_INTENT_ABSOLUTE_COLORIMETRIC };
     501           8 :       *intent = map[fileIntent];
     502             :     }
     503             :   }
     504             : 
     505             :   // Check gAMA/cHRM chunks
     506          54 :   if (!profile &&
     507          31 :        png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA) &&
     508           0 :        png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM)) {
     509             :     qcms_CIE_xyYTRIPLE primaries;
     510             :     qcms_CIE_xyY whitePoint;
     511             : 
     512             :     png_get_cHRM(png_ptr, info_ptr,
     513             :                  &whitePoint.x, &whitePoint.y,
     514             :                  &primaries.red.x,   &primaries.red.y,
     515             :                  &primaries.green.x, &primaries.green.y,
     516           0 :                  &primaries.blue.x,  &primaries.blue.y);
     517           0 :     whitePoint.Y =
     518           0 :       primaries.red.Y = primaries.green.Y = primaries.blue.Y = 1.0;
     519             : 
     520             :     double gammaOfFile;
     521             : 
     522           0 :     png_get_gAMA(png_ptr, info_ptr, &gammaOfFile);
     523             : 
     524           0 :     profile = qcms_profile_create_rgb_with_gamma(whitePoint, primaries,
     525           0 :                                                  1.0/gammaOfFile);
     526             : 
     527           0 :     if (profile) {
     528           0 :       png_set_gray_to_rgb(png_ptr);
     529             :     }
     530             :   }
     531             : 
     532          31 :   if (profile) {
     533           8 :     uint32_t profileSpace = qcms_profile_get_color_space(profile);
     534           8 :     if (profileSpace == icSigGrayData) {
     535           0 :       if (color_type & PNG_COLOR_MASK_ALPHA) {
     536           0 :         *inType = QCMS_DATA_GRAYA_8;
     537             :       } else {
     538           0 :         *inType = QCMS_DATA_GRAY_8;
     539             :       }
     540             :     } else {
     541          10 :       if (color_type & PNG_COLOR_MASK_ALPHA ||
     542           2 :           png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
     543           8 :         *inType = QCMS_DATA_RGBA_8;
     544             :       } else {
     545           0 :         *inType = QCMS_DATA_RGB_8;
     546             :       }
     547             :     }
     548             :   }
     549             : 
     550          31 :   return profile;
     551             : }
     552             : 
     553             : void
     554          31 : nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr)
     555             : {
     556             :   png_uint_32 width, height;
     557             :   int bit_depth, color_type, interlace_type, compression_type, filter_type;
     558             :   unsigned int channels;
     559             : 
     560          31 :   png_bytep trans = nullptr;
     561          31 :   int num_trans = 0;
     562             : 
     563             :   nsPNGDecoder* decoder =
     564          31 :                static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
     565             : 
     566             :   // Always decode to 24-bit RGB or 32-bit RGBA
     567             :   png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
     568          31 :                &interlace_type, &compression_type, &filter_type);
     569             : 
     570          31 :   const IntRect frameRect(0, 0, width, height);
     571             : 
     572             :   // Post our size to the superclass
     573          31 :   decoder->PostSize(frameRect.width, frameRect.height);
     574             : 
     575          62 :   if (width >
     576          31 :     SurfaceCache::MaximumCapacity()/(bit_depth > 8 ? 16:8)) {
     577             :     // libpng needs space to allocate two row buffers
     578           0 :     png_error(decoder->mPNG, "Image is too wide");
     579             :   }
     580             : 
     581          31 :   if (decoder->HasError()) {
     582             :     // Setting the size led to an error.
     583           0 :     png_error(decoder->mPNG, "Sizing error");
     584             :   }
     585             : 
     586          31 :   if (color_type == PNG_COLOR_TYPE_PALETTE) {
     587           2 :     png_set_expand(png_ptr);
     588             :   }
     589             : 
     590          31 :   if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
     591           0 :     png_set_expand(png_ptr);
     592             :   }
     593             : 
     594          31 :   if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
     595             :     png_color_16p trans_values;
     596           2 :     png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values);
     597             :     // libpng doesn't reject a tRNS chunk with out-of-range samples
     598             :     // so we check it here to avoid setting up a useless opacity
     599             :     // channel or producing unexpected transparent pixels (bug #428045)
     600           2 :     if (bit_depth < 16) {
     601           2 :       png_uint_16 sample_max = (1 << bit_depth) - 1;
     602           2 :       if ((color_type == PNG_COLOR_TYPE_GRAY &&
     603           2 :            trans_values->gray > sample_max) ||
     604           2 :            (color_type == PNG_COLOR_TYPE_RGB &&
     605           0 :            (trans_values->red > sample_max ||
     606           0 :            trans_values->green > sample_max ||
     607           0 :            trans_values->blue > sample_max))) {
     608             :         // clear the tRNS valid flag and release tRNS memory
     609           0 :         png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0);
     610           0 :         num_trans = 0;
     611             :       }
     612             :     }
     613           2 :     if (num_trans != 0) {
     614           2 :       png_set_expand(png_ptr);
     615             :     }
     616             :   }
     617             : 
     618          31 :   if (bit_depth == 16) {
     619           0 :     png_set_scale_16(png_ptr);
     620             :   }
     621             : 
     622          31 :   qcms_data_type inType = QCMS_DATA_RGBA_8;
     623          31 :   uint32_t intent = -1;
     624             :   uint32_t pIntent;
     625          31 :   if (decoder->mCMSMode != eCMSMode_Off) {
     626          31 :     intent = gfxPlatform::GetRenderingIntent();
     627          31 :     decoder->mInProfile = PNGGetColorProfile(png_ptr, info_ptr,
     628             :                                              color_type, &inType, &pIntent);
     629             :     // If we're not mandating an intent, use the one from the image.
     630          31 :     if (intent == uint32_t(-1)) {
     631           0 :       intent = pIntent;
     632             :     }
     633             :   }
     634          31 :   if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) {
     635             :     qcms_data_type outType;
     636             : 
     637           8 :     if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) {
     638           8 :       outType = QCMS_DATA_RGBA_8;
     639             :     } else {
     640           0 :       outType = QCMS_DATA_RGB_8;
     641             :     }
     642             : 
     643           8 :     decoder->mTransform = qcms_transform_create(decoder->mInProfile,
     644             :                                            inType,
     645             :                                            gfxPlatform::GetCMSOutputProfile(),
     646             :                                            outType,
     647             :                                            (qcms_intent)intent);
     648             :   } else {
     649          23 :     png_set_gray_to_rgb(png_ptr);
     650             : 
     651             :     // only do gamma correction if CMS isn't entirely disabled
     652          23 :     if (decoder->mCMSMode != eCMSMode_Off) {
     653          23 :       PNGDoGammaCorrection(png_ptr, info_ptr);
     654             :     }
     655             : 
     656          23 :     if (decoder->mCMSMode == eCMSMode_All) {
     657           0 :       if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) {
     658           0 :         decoder->mTransform = gfxPlatform::GetCMSRGBATransform();
     659             :       } else {
     660           0 :         decoder->mTransform = gfxPlatform::GetCMSRGBTransform();
     661             :       }
     662             :     }
     663             :   }
     664             : 
     665             :   // Let libpng expand interlaced images.
     666          31 :   const bool isInterlaced = interlace_type == PNG_INTERLACE_ADAM7;
     667          31 :   if (isInterlaced) {
     668           0 :     png_set_interlace_handling(png_ptr);
     669             :   }
     670             : 
     671             :   // now all of those things we set above are used to update various struct
     672             :   // members and whatnot, after which we can get channels, rowbytes, etc.
     673          31 :   png_read_update_info(png_ptr, info_ptr);
     674          31 :   decoder->mChannels = channels = png_get_channels(png_ptr, info_ptr);
     675             : 
     676             :   //---------------------------------------------------------------//
     677             :   // copy PNG info into imagelib structs (formerly png_set_dims()) //
     678             :   //---------------------------------------------------------------//
     679             : 
     680          31 :   if (channels < 1 || channels > 4) {
     681           0 :     png_error(decoder->mPNG, "Invalid number of channels");
     682             :   }
     683             : 
     684             : #ifdef PNG_APNG_SUPPORTED
     685          31 :   bool isAnimated = png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL);
     686          31 :   if (isAnimated) {
     687           4 :     int32_t rawTimeout = GetNextFrameDelay(png_ptr, info_ptr);
     688           4 :     decoder->PostIsAnimated(FrameTimeout::FromRawMilliseconds(rawTimeout));
     689             : 
     690           4 :     if (decoder->Size() != decoder->OutputSize() &&
     691           0 :         !decoder->IsFirstFrameDecode()) {
     692           0 :       MOZ_ASSERT_UNREACHABLE("Doing downscale-during-decode "
     693             :                              "for an animated image?");
     694             :       png_error(decoder->mPNG, "Invalid downscale attempt"); // Abort decode.
     695             :     }
     696             :   }
     697             : #endif
     698             : 
     699          31 :   if (decoder->IsMetadataDecode()) {
     700             :     // If we are animated then the first frame rect is either:
     701             :     // 1) the whole image if the IDAT chunk is part of the animation
     702             :     // 2) the frame rect of the first fDAT chunk otherwise.
     703             :     // If we are not animated then we want to make sure to call
     704             :     // PostHasTransparency in the metadata decode if we need to. So it's
     705             :     // okay to pass IntRect(0, 0, width, height) here for animated images;
     706             :     // they will call with the proper first frame rect in the full decode.
     707          18 :     auto transparency = decoder->GetTransparencyType(frameRect);
     708          18 :     decoder->PostHasTransparencyIfNeeded(transparency);
     709             : 
     710             :     // We have the metadata we're looking for, so stop here, before we allocate
     711             :     // buffers below.
     712          18 :     return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS);
     713             :   }
     714             : 
     715             : #ifdef PNG_APNG_SUPPORTED
     716          13 :   if (isAnimated) {
     717             :     png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback,
     718           2 :                                  nullptr);
     719             :   }
     720             : 
     721          13 :   if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) {
     722           0 :     decoder->mFrameIsHidden = true;
     723             :   } else {
     724             : #endif
     725          26 :     nsresult rv = decoder->CreateFrame(FrameInfo{ frameRect,
     726          13 :                                                   isInterlaced });
     727          13 :     if (NS_FAILED(rv)) {
     728           0 :       png_error(decoder->mPNG, "CreateFrame failed");
     729             :     }
     730          13 :     MOZ_ASSERT(decoder->mImageData, "Should have a buffer now");
     731             : #ifdef PNG_APNG_SUPPORTED
     732             :   }
     733             : #endif
     734             : 
     735          13 :   if (decoder->mTransform && (channels <= 2 || isInterlaced)) {
     736           0 :     uint32_t bpp[] = { 0, 3, 4, 3, 4 };
     737           0 :     decoder->mCMSLine =
     738           0 :       static_cast<uint8_t*>(malloc(bpp[channels] * frameRect.width));
     739           0 :     if (!decoder->mCMSLine) {
     740           0 :       png_error(decoder->mPNG, "malloc of mCMSLine failed");
     741             :     }
     742             :   }
     743             : 
     744          13 :   if (interlace_type == PNG_INTERLACE_ADAM7) {
     745           0 :     if (frameRect.height < INT32_MAX / (frameRect.width * int32_t(channels))) {
     746           0 :       const size_t bufferSize = channels * frameRect.width * frameRect.height;
     747             : 
     748           0 :       if (bufferSize > SurfaceCache::MaximumCapacity()) {
     749           0 :         png_error(decoder->mPNG, "Insufficient memory to deinterlace image");
     750             :       }
     751             : 
     752           0 :       decoder->interlacebuf = static_cast<uint8_t*>(malloc(bufferSize));
     753             :     }
     754           0 :     if (!decoder->interlacebuf) {
     755           0 :       png_error(decoder->mPNG, "malloc of interlacebuf failed");
     756             :     }
     757             :   }
     758             : }
     759             : 
     760             : void
     761        1537 : nsPNGDecoder::PostInvalidationIfNeeded()
     762             : {
     763        3076 :   Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect();
     764        1539 :   if (!invalidRect) {
     765           0 :     return;
     766             :   }
     767             : 
     768        1539 :   PostInvalidation(invalidRect->mInputSpaceRect,
     769        3078 :                    Some(invalidRect->mOutputSpaceRect));
     770             : }
     771             : 
     772             : static NextPixel<uint32_t>
     773           0 : PackRGBPixelAndAdvance(uint8_t*& aRawPixelInOut)
     774             : {
     775             :   const uint32_t pixel =
     776           0 :     gfxPackedPixel(0xFF, aRawPixelInOut[0], aRawPixelInOut[1],
     777           0 :                    aRawPixelInOut[2]);
     778           0 :   aRawPixelInOut += 3;
     779           0 :   return AsVariant(pixel);
     780             : }
     781             : 
     782             : static NextPixel<uint32_t>
     783       57171 : PackRGBAPixelAndAdvance(uint8_t*& aRawPixelInOut)
     784             : {
     785             :   const uint32_t pixel =
     786      114342 :     gfxPackedPixel(aRawPixelInOut[3], aRawPixelInOut[0],
     787      171531 :                    aRawPixelInOut[1], aRawPixelInOut[2]);
     788       57189 :   aRawPixelInOut += 4;
     789       57189 :   return AsVariant(pixel);
     790             : }
     791             : 
     792             : static NextPixel<uint32_t>
     793           0 : PackUnpremultipliedRGBAPixelAndAdvance(uint8_t*& aRawPixelInOut)
     794             : {
     795             :   const uint32_t pixel =
     796           0 :     gfxPackedPixelNoPreMultiply(aRawPixelInOut[3], aRawPixelInOut[0],
     797           0 :                                 aRawPixelInOut[1], aRawPixelInOut[2]);
     798           0 :   aRawPixelInOut += 4;
     799           0 :   return AsVariant(pixel);
     800             : }
     801             : 
     802             : void
     803        1539 : nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row,
     804             :                            png_uint_32 row_num, int pass)
     805             : {
     806             :   /* libpng comments:
     807             :    *
     808             :    * This function is called for every row in the image.  If the
     809             :    * image is interlacing, and you turned on the interlace handler,
     810             :    * this function will be called for every row in every pass.
     811             :    * Some of these rows will not be changed from the previous pass.
     812             :    * When the row is not changed, the new_row variable will be
     813             :    * nullptr. The rows and passes are called in order, so you don't
     814             :    * really need the row_num and pass, but I'm supplying them
     815             :    * because it may make your life easier.
     816             :    *
     817             :    * For the non-nullptr rows of interlaced images, you must call
     818             :    * png_progressive_combine_row() passing in the row and the
     819             :    * old row.  You can call this function for nullptr rows (it will
     820             :    * just return) and for non-interlaced images (it just does the
     821             :    * memcpy for you) if it will make the code easier.  Thus, you
     822             :    * can just do this for all cases:
     823             :    *
     824             :    *    png_progressive_combine_row(png_ptr, old_row, new_row);
     825             :    *
     826             :    * where old_row is what was displayed for previous rows.  Note
     827             :    * that the first pass (pass == 0 really) will completely cover
     828             :    * the old row, so the rows do not have to be initialized.  After
     829             :    * the first pass (and only for interlaced images), you will have
     830             :    * to pass the current row, and the function will combine the
     831             :    * old row and the new row.
     832             :    */
     833             :   nsPNGDecoder* decoder =
     834        1539 :     static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
     835             : 
     836        1539 :   if (decoder->mFrameIsHidden) {
     837           0 :     return;  // Skip this frame.
     838             :   }
     839             : 
     840        1539 :   MOZ_ASSERT_IF(decoder->IsFirstFrameDecode(), decoder->mNumFrames == 0);
     841             : 
     842        1539 :   while (pass > decoder->mPass) {
     843             :     // Advance to the next pass. We may have to do this multiple times because
     844             :     // libpng will skip passes if the image is so small that no pixels have
     845             :     // changed on a given pass, but ADAM7InterpolatingFilter needs to be reset
     846             :     // once for every pass to perform interpolation properly.
     847           0 :     decoder->mPipe.ResetToFirstRow();
     848           0 :     decoder->mPass++;
     849             :   }
     850             : 
     851             :   const png_uint_32 height =
     852        1539 :     static_cast<png_uint_32>(decoder->mFrameRect.height);
     853             : 
     854        1539 :   if (row_num >= height) {
     855             :     // Bail if we receive extra rows. This is especially important because if we
     856             :     // didn't, we might overflow the deinterlacing buffer.
     857           0 :     MOZ_ASSERT_UNREACHABLE("libpng producing extra rows?");
     858             :     return;
     859             :   }
     860             : 
     861             :   // Note that |new_row| may be null here, indicating that this is an interlaced
     862             :   // image and |row_callback| is being called for a row that hasn't changed.
     863        1539 :   MOZ_ASSERT_IF(!new_row, decoder->interlacebuf);
     864        1539 :   uint8_t* rowToWrite = new_row;
     865             : 
     866        1539 :   if (decoder->interlacebuf) {
     867           0 :     uint32_t width = uint32_t(decoder->mFrameRect.width);
     868             : 
     869             :     // We'll output the deinterlaced version of the row.
     870           0 :     rowToWrite = decoder->interlacebuf + (row_num * decoder->mChannels * width);
     871             : 
     872             :     // Update the deinterlaced version of this row with the new data.
     873           0 :     png_progressive_combine_row(png_ptr, rowToWrite, new_row);
     874             :   }
     875             : 
     876        1539 :   decoder->WriteRow(rowToWrite);
     877             : }
     878             : 
     879             : void
     880        1539 : nsPNGDecoder::WriteRow(uint8_t* aRow)
     881             : {
     882        1539 :   MOZ_ASSERT(aRow);
     883             : 
     884        1539 :   uint8_t* rowToWrite = aRow;
     885        1539 :   uint32_t width = uint32_t(mFrameRect.width);
     886             : 
     887             :   // Apply color management to the row, if necessary, before writing it out.
     888        1539 :   if (mTransform) {
     889          42 :     if (mCMSLine) {
     890           0 :       qcms_transform_data(mTransform, rowToWrite, mCMSLine, width);
     891             : 
     892             :       // Copy alpha over.
     893           0 :       if (HasAlphaChannel()) {
     894           0 :         for (uint32_t i = 0; i < width; ++i) {
     895           0 :           mCMSLine[4 * i + 3] = rowToWrite[mChannels * i + mChannels - 1];
     896             :         }
     897             :       }
     898             : 
     899           0 :       rowToWrite = mCMSLine;
     900             :     } else {
     901          42 :       qcms_transform_data(mTransform, rowToWrite, rowToWrite, width);
     902             :     }
     903             :   }
     904             : 
     905             :   // Write this row to the SurfacePipe.
     906        3078 :   DebugOnly<WriteState> result;
     907        1539 :   if (HasAlphaChannel()) {
     908        1539 :     if (mDisablePremultipliedAlpha) {
     909           0 :       result = mPipe.WritePixelsToRow<uint32_t>([&]{
     910           0 :         return PackUnpremultipliedRGBAPixelAndAdvance(rowToWrite);
     911           0 :       });
     912             :     } else {
     913       60254 :       result = mPipe.WritePixelsToRow<uint32_t>([&]{
     914       57178 :         return PackRGBAPixelAndAdvance(rowToWrite);
     915       58715 :       });
     916             :     }
     917             :   } else {
     918           0 :     result = mPipe.WritePixelsToRow<uint32_t>([&]{
     919           0 :       return PackRGBPixelAndAdvance(rowToWrite);
     920           0 :     });
     921             :   }
     922             : 
     923        1537 :   MOZ_ASSERT(WriteState(result) != WriteState::FAILURE);
     924             : 
     925        1537 :   PostInvalidationIfNeeded();
     926        1539 : }
     927             : 
     928             : void
     929          31 : nsPNGDecoder::DoTerminate(png_structp aPNGStruct, TerminalState aState)
     930             : {
     931             :   // Stop processing data. Note that we intentionally ignore the return value of
     932             :   // png_process_data_pause(), which tells us how many bytes of the data that
     933             :   // was passed to png_process_data() have not been consumed yet, because now
     934             :   // that we've reached a terminal state, we won't do any more decoding or call
     935             :   // back into libpng anymore.
     936          31 :   png_process_data_pause(aPNGStruct, /* save = */ false);
     937             : 
     938             :   mNextTransition = aState == TerminalState::SUCCESS
     939             :                   ? Transition::TerminateSuccess()
     940          31 :                   : Transition::TerminateFailure();
     941          31 : }
     942             : 
     943             : void
     944          34 : nsPNGDecoder::DoYield(png_structp aPNGStruct)
     945             : {
     946             :   // Pause data processing. png_process_data_pause() returns how many bytes of
     947             :   // the data that was passed to png_process_data() have not been consumed yet.
     948             :   // We use this information to tell StreamingLexer where to place us in the
     949             :   // input stream when we come back from the yield.
     950          34 :   png_size_t pendingBytes = png_process_data_pause(aPNGStruct,
     951          34 :                                                    /* save = */ false);
     952             : 
     953          34 :   MOZ_ASSERT(pendingBytes < mLastChunkLength);
     954          34 :   size_t consumedBytes = mLastChunkLength - min(pendingBytes, mLastChunkLength);
     955             : 
     956             :   mNextTransition =
     957          34 :     Transition::ContinueUnbufferedAfterYield(State::PNG_DATA, consumedBytes);
     958          34 : }
     959             : 
     960             : nsresult
     961          31 : nsPNGDecoder::FinishInternal()
     962             : {
     963             :   // We shouldn't be called in error cases.
     964          31 :   MOZ_ASSERT(!HasError(), "Can't call FinishInternal on error!");
     965             : 
     966          31 :   if (IsMetadataDecode()) {
     967          18 :     return NS_OK;
     968             :   }
     969             : 
     970          13 :   int32_t loop_count = 0;
     971             : #ifdef PNG_APNG_SUPPORTED
     972          13 :   if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) {
     973           2 :     int32_t num_plays = png_get_num_plays(mPNG, mInfo);
     974           2 :     loop_count = num_plays - 1;
     975             :   }
     976             : #endif
     977             : 
     978          13 :   if (InFrame()) {
     979          13 :     EndImageFrame();
     980             :   }
     981          13 :   PostDecodeDone(loop_count);
     982             : 
     983          13 :   return NS_OK;
     984             : }
     985             : 
     986             : 
     987             : #ifdef PNG_APNG_SUPPORTED
     988             : // got the header of a new frame that's coming
     989             : void
     990          34 : nsPNGDecoder::frame_info_callback(png_structp png_ptr, png_uint_32 frame_num)
     991             : {
     992             :   nsPNGDecoder* decoder =
     993          34 :                static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
     994             : 
     995             :   // old frame is done
     996          34 :   decoder->EndImageFrame();
     997             : 
     998          34 :   const bool previousFrameWasHidden = decoder->mFrameIsHidden;
     999             : 
    1000          34 :   if (!previousFrameWasHidden && decoder->IsFirstFrameDecode()) {
    1001             :     // We're about to get a second non-hidden frame, but we only want the first.
    1002             :     // Stop decoding now. (And avoid allocating the unnecessary buffers below.)
    1003           0 :     return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS);
    1004             :   }
    1005             : 
    1006             :   // Only the first frame can be hidden, so unhide unconditionally here.
    1007          34 :   decoder->mFrameIsHidden = false;
    1008             : 
    1009             :   // Save the information necessary to create the frame; we'll actually create
    1010             :   // it when we return from the yield.
    1011             :   const IntRect frameRect(png_get_next_frame_x_offset(png_ptr, decoder->mInfo),
    1012             :                           png_get_next_frame_y_offset(png_ptr, decoder->mInfo),
    1013             :                           png_get_next_frame_width(png_ptr, decoder->mInfo),
    1014          34 :                           png_get_next_frame_height(png_ptr, decoder->mInfo));
    1015          34 :   const bool isInterlaced = bool(decoder->interlacebuf);
    1016             : 
    1017             : #ifndef MOZ_EMBEDDED_LIBPNG
    1018             :   // if using system library, check frame_width and height against 0
    1019             :   if (frameRect.width == 0) {
    1020             :     png_error(png_ptr, "Frame width must not be 0");
    1021             :   }
    1022             :   if (frameRect.height == 0) {
    1023             :     png_error(png_ptr, "Frame height must not be 0");
    1024             :   }
    1025             : #endif
    1026             : 
    1027          34 :   const FrameInfo info { frameRect, isInterlaced };
    1028             : 
    1029             :   // If the previous frame was hidden, skip the yield (which will mislead the
    1030             :   // caller, who will think the previous frame was real) and just allocate the
    1031             :   // new frame here.
    1032          34 :   if (previousFrameWasHidden) {
    1033           0 :     if (NS_FAILED(decoder->CreateFrame(info))) {
    1034           0 :       return decoder->DoTerminate(png_ptr, TerminalState::FAILURE);
    1035             :     }
    1036             : 
    1037           0 :     MOZ_ASSERT(decoder->mImageData, "Should have a buffer now");
    1038           0 :     return;  // No yield, so we'll just keep decoding.
    1039             :   }
    1040             : 
    1041             :   // Yield to the caller to notify them that the previous frame is now complete.
    1042          34 :   decoder->mNextFrameInfo = Some(info);
    1043          34 :   return decoder->DoYield(png_ptr);
    1044             : }
    1045             : #endif
    1046             : 
    1047             : void
    1048          13 : nsPNGDecoder::end_callback(png_structp png_ptr, png_infop info_ptr)
    1049             : {
    1050             :   /* libpng comments:
    1051             :    *
    1052             :    * this function is called when the whole image has been read,
    1053             :    * including any chunks after the image (up to and including
    1054             :    * the IEND).  You will usually have the same info chunk as you
    1055             :    * had in the header, although some data may have been added
    1056             :    * to the comments and time fields.
    1057             :    *
    1058             :    * Most people won't do much here, perhaps setting a flag that
    1059             :    * marks the image as finished.
    1060             :    */
    1061             : 
    1062             :   nsPNGDecoder* decoder =
    1063          13 :                static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
    1064             : 
    1065             :   // We shouldn't get here if we've hit an error
    1066          13 :   MOZ_ASSERT(!decoder->HasError(), "Finishing up PNG but hit error!");
    1067             : 
    1068          13 :   return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS);
    1069             : }
    1070             : 
    1071             : 
    1072             : void
    1073           0 : nsPNGDecoder::error_callback(png_structp png_ptr, png_const_charp error_msg)
    1074             : {
    1075           0 :   MOZ_LOG(sPNGLog, LogLevel::Error, ("libpng error: %s\n", error_msg));
    1076           0 :   png_longjmp(png_ptr, 1);
    1077             : }
    1078             : 
    1079             : 
    1080             : void
    1081           0 : nsPNGDecoder::warning_callback(png_structp png_ptr, png_const_charp warning_msg)
    1082             : {
    1083           0 :   MOZ_LOG(sPNGLog, LogLevel::Warning, ("libpng warning: %s\n", warning_msg));
    1084           0 : }
    1085             : 
    1086             : Maybe<Telemetry::HistogramID>
    1087          31 : nsPNGDecoder::SpeedHistogram() const
    1088             : {
    1089          31 :   return Some(Telemetry::IMAGE_DECODE_SPEED_PNG);
    1090             : }
    1091             : 
    1092             : bool
    1093           0 : nsPNGDecoder::IsValidICO() const
    1094             : {
    1095             :   // Only 32-bit RGBA PNGs are valid ICO resources; see here:
    1096             :   //   http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
    1097             : 
    1098             :   // If there are errors in the call to png_get_IHDR, the error_callback in
    1099             :   // nsPNGDecoder.cpp is called.  In this error callback we do a longjmp, so
    1100             :   // we need to save the jump buffer here. Otherwise we'll end up without a
    1101             :   // proper callstack.
    1102           0 :   if (setjmp(png_jmpbuf(mPNG))) {
    1103             :     // We got here from a longjmp call indirectly from png_get_IHDR
    1104           0 :     return false;
    1105             :   }
    1106             : 
    1107             :   png_uint_32
    1108             :       png_width,  // Unused
    1109             :       png_height; // Unused
    1110             : 
    1111             :   int png_bit_depth,
    1112             :       png_color_type;
    1113             : 
    1114           0 :   if (png_get_IHDR(mPNG, mInfo, &png_width, &png_height, &png_bit_depth,
    1115             :                    &png_color_type, nullptr, nullptr, nullptr)) {
    1116             : 
    1117           0 :     return ((png_color_type == PNG_COLOR_TYPE_RGB_ALPHA ||
    1118           0 :              png_color_type == PNG_COLOR_TYPE_RGB) &&
    1119           0 :             png_bit_depth == 8);
    1120             :   } else {
    1121           0 :     return false;
    1122             :   }
    1123             : }
    1124             : 
    1125             : } // namespace image
    1126             : } // namespace mozilla

Generated by: LCOV version 1.13