Bug 1187546 - Make it possible to ask image decoders to only decode the first frame. r=tn
authorSeth Fowler <mark.seth.fowler@gmail.com>
Fri, 31 Jul 2015 18:10:23 -0700
changeset 287426 070dc7eabe5fa2df17e152fd54a2dc8ce719fc60
parent 287425 ffac74e3366ce4d0af763ebf4cc8235e3e06879b
child 287427 05f618ac3d24b37b7582d92122e9e52441112a46
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn
bugs1187546
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1187546 - Make it possible to ask image decoders to only decode the first frame. r=tn
image/Decoder.cpp
image/Decoder.h
image/decoders/nsGIFDecoder2.cpp
image/decoders/nsICODecoder.cpp
image/decoders/nsPNGDecoder.cpp
image/decoders/nsPNGDecoder.h
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -33,16 +33,17 @@ Decoder::Decoder(RasterImage* aImage)
   , mChunkCount(0)
   , mFlags(0)
   , mBytesDecoded(0)
   , mInitialized(false)
   , mMetadataDecode(false)
   , mSendPartialInvalidations(false)
   , mImageIsTransient(false)
   , mImageIsLocked(false)
+  , mFirstFrameDecode(false)
   , mInFrame(false)
   , mIsAnimated(false)
   , mDataDone(false)
   , mDecodeDone(false)
   , mDataError(false)
   , mDecodeAborted(false)
   , mShouldReportError(false)
 { }
--- a/image/Decoder.h
+++ b/image/Decoder.h
@@ -189,16 +189,28 @@ public:
   void SetImageIsLocked()
   {
     MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
     mImageIsLocked = true;
   }
 
   bool ImageIsLocked() const { return mImageIsLocked; }
 
+
+  /**
+   * Set whether we should stop decoding after the first frame.
+   */
+  void SetIsFirstFrameDecode()
+  {
+    MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
+    mFirstFrameDecode = true;
+  }
+
+  bool IsFirstFrameDecode() const { return mFirstFrameDecode; }
+
   size_t BytesDecoded() const { return mBytesDecoded; }
 
   // The amount of time we've spent inside Write() so far for this decoder.
   TimeDuration DecodeTime() const { return mDecodeTime; }
 
   // The number of times Write() has been called so far for this decoder.
   uint32_t ChunkCount() const { return mChunkCount; }
 
@@ -438,16 +450,17 @@ private:
   uint32_t mFlags;
   size_t mBytesDecoded;
 
   bool mInitialized : 1;
   bool mMetadataDecode : 1;
   bool mSendPartialInvalidations : 1;
   bool mImageIsTransient : 1;
   bool mImageIsLocked : 1;
+  bool mFirstFrameDecode : 1;
   bool mInFrame : 1;
   bool mIsAnimated : 1;
   bool mDataDone : 1;
   bool mDecodeDone : 1;
   bool mDataError : 1;
   bool mDecodeAborted : 1;
   bool mShouldReportError : 1;
 };
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -901,16 +901,23 @@ nsGIFDecoder2::WriteInternal(const char*
 
         default:
           // 0,3-7 are yet to be defined netscape extension codes
           mGIFStruct.state = gif_error;
       }
       break;
 
     case gif_image_header: {
+      if (mGIFStruct.images_decoded > 0 && IsFirstFrameDecode()) {
+        // We're about to get a second frame, but we only want the first. Stop
+        // decoding now.
+        mGIFStruct.state = gif_done;
+        break;
+      }
+
       // Get image offsets, with respect to the screen origin
       mGIFStruct.x_offset = GETINT16(q);
       mGIFStruct.y_offset = GETINT16(q + 2);
 
       // Get image width and height.
       mGIFStruct.width  = GETINT16(q + 4);
       mGIFStruct.height = GETINT16(q + 6);
 
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -356,16 +356,19 @@ nsICODecoder::WriteInternal(const char* 
     aBuffer += toCopy;
 
     mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes,
                      PNGSIGNATURESIZE);
     if (mIsPNG) {
       mContainedDecoder = new nsPNGDecoder(mImage);
       mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
       mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations);
+      if (mFirstFrameDecode) {
+        mContainedDecoder->SetIsFirstFrameDecode();
+      }
       mContainedDecoder->Init();
       if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE)) {
         return;
       }
     }
   }
 
   // If we have a PNG, let the PNG decoder do all of the rest of the work
@@ -433,16 +436,19 @@ nsICODecoder::WriteInternal(const char* 
     // Init the bitmap decoder which will do most of the work for us
     // It will do everything except the AND mask which isn't present in bitmaps
     // bmpDecoder is for local scope ease, it will be freed by mContainedDecoder
     nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage);
     mContainedDecoder = bmpDecoder;
     bmpDecoder->SetUseAlphaData(true);
     mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
     mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations);
+    if (mFirstFrameDecode) {
+      mContainedDecoder->SetIsFirstFrameDecode();
+    }
     mContainedDecoder->Init();
 
     // The ICO format when containing a BMP does not include the 14 byte
     // bitmap file header. To use the code of the BMP decoder we need to
     // generate this header ourselves and feed it to the BMP decoder.
     int8_t bfhBuffer[BMPFILEHEADERSIZE];
     if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
       PostDataError();
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -110,16 +110,17 @@ nsPNGDecoder::nsPNGDecoder(RasterImage* 
  : Decoder(aImage),
    mPNG(nullptr), mInfo(nullptr),
    mCMSLine(nullptr), interlacebuf(nullptr),
    mInProfile(nullptr), mTransform(nullptr),
    format(gfx::SurfaceFormat::UNKNOWN),
    mHeaderBytesRead(0), mCMSMode(0),
    mChannels(0), mFrameIsHidden(false),
    mDisablePremultipliedAlpha(false),
+   mSuccessfulEarlyFinish(false),
    mNumFrames(0)
 {
 }
 
 nsPNGDecoder::~nsPNGDecoder()
 {
   if (mPNG) {
     png_destroy_read_struct(&mPNG, mInfo ? &mInfo : nullptr, nullptr);
@@ -370,17 +371,17 @@ nsPNGDecoder::WriteInternal(const char* 
   // Otherwise, we're doing a standard decode
   } else {
 
     // libpng uses setjmp/longjmp for error handling - set the buffer
     if (setjmp(png_jmpbuf(mPNG))) {
 
       // We might not really know what caused the error, but it makes more
       // sense to blame the data.
-      if (!HasError()) {
+      if (!mSuccessfulEarlyFinish && !HasError()) {
         PostDataError();
       }
 
       png_destroy_read_struct(&mPNG, &mInfo, nullptr);
       return;
     }
 
     // Pass the data off to libpng
@@ -822,16 +823,24 @@ nsPNGDecoder::frame_info_callback(png_st
   int32_t width, height;
 
   nsPNGDecoder* decoder =
                static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
 
   // old frame is done
   decoder->EndImageFrame();
 
+  if (!decoder->mFrameIsHidden && decoder->IsFirstFrameDecode()) {
+    // We're about to get a second non-hidden frame, but we only want the first.
+    // Stop decoding now.
+    decoder->PostDecodeDone();
+    decoder->mSuccessfulEarlyFinish = true;
+    png_longjmp(decoder->mPNG, 1);
+  }
+
   // Only the first frame can be hidden, so unhide unconditionally here.
   decoder->mFrameIsHidden = false;
 
   x_offset = png_get_next_frame_x_offset(png_ptr, decoder->mInfo);
   y_offset = png_get_next_frame_y_offset(png_ptr, decoder->mInfo);
   width = png_get_next_frame_width(png_ptr, decoder->mInfo);
   height = png_get_next_frame_height(png_ptr, decoder->mInfo);
 
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -90,16 +90,17 @@ public:
   uint32_t mHeaderBytesRead;
 
   // whether CMS or premultiplied alpha are forced off
   uint32_t mCMSMode;
 
   uint8_t mChannels;
   bool mFrameIsHidden;
   bool mDisablePremultipliedAlpha;
+  bool mSuccessfulEarlyFinish;
 
   struct AnimFrameInfo
   {
     AnimFrameInfo();
 #ifdef PNG_APNG_SUPPORTED
     AnimFrameInfo(png_structp aPNG, png_infop aInfo);
 #endif