Bug 1255107 (Part 3) - Use SurfacePipe in the PNG decoder. r=njn
authorSeth Fowler <mark.seth.fowler@gmail.com>
Fri, 24 Jun 2016 15:20:32 -0700
changeset 344755 39ba4da73c6c040e291520ecbec2d61bdeb3d168
parent 344754 212067476b418138224e2a694965b5f3e0c4fe86
child 344756 b041861694f02202197c43325f30f9a93944fee5
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnjn
bugs1255107
milestone50.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 1255107 (Part 3) - Use SurfacePipe in the PNG decoder. r=njn
image/Decoder.cpp
image/Decoder.h
image/FrameAnimator.cpp
image/FrameAnimator.h
image/decoders/nsPNGDecoder.cpp
image/decoders/nsPNGDecoder.h
image/imgFrame.cpp
image/imgFrame.h
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -424,27 +424,29 @@ Decoder::PostIsAnimated(int32_t aFirstFr
 }
 
 void
 Decoder::PostFrameStop(Opacity aFrameOpacity
                          /* = Opacity::SOME_TRANSPARENCY */,
                        DisposalMethod aDisposalMethod
                          /* = DisposalMethod::KEEP */,
                        int32_t aTimeout         /* = 0 */,
-                       BlendMethod aBlendMethod /* = BlendMethod::OVER */)
+                       BlendMethod aBlendMethod /* = BlendMethod::OVER */,
+                       const Maybe<nsIntRect>& aBlendRect /* = Nothing() */)
 {
   // We should be mid-frame
   MOZ_ASSERT(!IsMetadataDecode(), "Stopping frame during metadata decode");
   MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one");
   MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");
 
   // Update our state
   mInFrame = false;
 
-  mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout, aBlendMethod);
+  mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout,
+                        aBlendMethod, aBlendRect);
 
   mProgress |= FLAG_FRAME_COMPLETE;
 
   // If we're not sending partial invalidations, then we send an invalidation
   // here when the first frame is complete.
   if (!ShouldSendPartialInvalidations() && mFrameCount == 1) {
     mInvalidRect.UnionRect(mInvalidRect,
                            gfx::IntRect(gfx::IntPoint(0, 0), GetSize()));
--- a/image/Decoder.h
+++ b/image/Decoder.h
@@ -327,17 +327,18 @@ protected:
   // Called by decoders when they end a frame. Informs the image, sends
   // notifications, and does internal book-keeping.
   // Specify whether this frame is opaque as an optimization.
   // For animated images, specify the disposal, blend method and timeout for
   // this frame.
   void PostFrameStop(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY,
                      DisposalMethod aDisposalMethod = DisposalMethod::KEEP,
                      int32_t aTimeout = 0,
-                     BlendMethod aBlendMethod = BlendMethod::OVER);
+                     BlendMethod aBlendMethod = BlendMethod::OVER,
+                     const Maybe<nsIntRect>& aBlendRect = Nothing());
 
   /**
    * Called by the decoders when they have a region to invalidate. We may not
    * actually pass these invalidations on right away.
    *
    * @param aRect The invalidation rect in the coordinate system of the unscaled
    *              image (that is, the image at its intrinsic size).
    * @param aRectAtTargetSize If not Nothing(), the invalidation rect in the
--- a/image/FrameAnimator.cpp
+++ b/image/FrameAnimator.cpp
@@ -620,17 +620,18 @@ FrameAnimator::DoBlend(nsIntRect* aDirty
                            compositingFrameData.mRect);
               }
             }
             DrawFrameTo(prevFrameData.mRawData, prevFrameData.mRect,
                         prevFrameData.mPaletteDataLength,
                         prevFrameData.mHasAlpha,
                         compositingFrameData.mRawData,
                         compositingFrameData.mRect,
-                        prevFrameData.mBlendMethod);
+                        prevFrameData.mBlendMethod,
+                        prevFrameData.mBlendRect);
           }
         }
     }
   } else if (needToBlankComposite) {
     // If we just created the composite, it could have anything in its
     // buffers. Clear them
     ClearFrame(compositingFrameData.mRawData,
                compositingFrameData.mRect);
@@ -668,17 +669,18 @@ FrameAnimator::DoBlend(nsIntRect* aDirty
   }
 
   // blit next frame into it's correct spot
   DrawFrameTo(nextFrameData.mRawData, nextFrameData.mRect,
               nextFrameData.mPaletteDataLength,
               nextFrameData.mHasAlpha,
               compositingFrameData.mRawData,
               compositingFrameData.mRect,
-              nextFrameData.mBlendMethod);
+              nextFrameData.mBlendMethod,
+              nextFrameData.mBlendRect);
 
   // Tell the image that it is fully 'downloaded'.
   mCompositingFrame->Finish();
 
   mLastCompositedFrameIndex = int32_t(aNextFrameIndex);
 
   return true;
 }
@@ -737,17 +739,17 @@ FrameAnimator::CopyFrameImage(const uint
 
   return true;
 }
 
 nsresult
 FrameAnimator::DrawFrameTo(const uint8_t* aSrcData, const nsIntRect& aSrcRect,
                            uint32_t aSrcPaletteLength, bool aSrcHasAlpha,
                            uint8_t* aDstPixels, const nsIntRect& aDstRect,
-                           BlendMethod aBlendMethod)
+                           BlendMethod aBlendMethod, const Maybe<nsIntRect>& aBlendRect)
 {
   NS_ENSURE_ARG_POINTER(aSrcData);
   NS_ENSURE_ARG_POINTER(aDstPixels);
 
   // According to both AGIF and APNG specs, offsets are unsigned
   if (aSrcRect.x < 0 || aSrcRect.y < 0) {
     NS_WARNING("FrameAnimator::DrawFrameTo: negative offsets not allowed");
     return NS_ERROR_FAILURE;
@@ -817,26 +819,63 @@ FrameAnimator::DrawFrameTo(const uint8_t
                                aDstRect.height,
                                reinterpret_cast<uint32_t*>(aDstPixels),
                                aDstRect.width * 4);
     if (!dst) {
       pixman_image_unref(src);
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
+    // XXX(seth): This is inefficient but we'll remove it quite soon when we
+    // move frame compositing into SurfacePipe. For now we need this because
+    // RemoveFrameRectFilter has transformed PNG frames with frame rects into
+    // imgFrame's with no frame rects, but with a region of 0 alpha where the
+    // frame rect should be. This works really nicely if we're using
+    // BlendMethod::OVER, but BlendMethod::SOURCE will result in that frame rect
+    // area overwriting the previous frame, which makes the animation look
+    // wrong. This quick hack fixes that by first compositing the whle new frame
+    // with BlendMethod::OVER, and then recopying the area that uses
+    // BlendMethod::SOURCE if needed. To make this work, the decoder has to
+    // provide a "blend rect" that tells us where to do this. This is just the
+    // frame rect, but hidden in a way that makes it invisible to most of the
+    // system, so we can keep eliminating dependencies on it.
     auto op = aBlendMethod == BlendMethod::SOURCE ? PIXMAN_OP_SRC
                                                   : PIXMAN_OP_OVER;
-    pixman_image_composite32(op,
-                             src,
-                             nullptr,
-                             dst,
-                             0, 0,
-                             0, 0,
-                             aSrcRect.x, aSrcRect.y,
-                             aSrcRect.width, aSrcRect.height);
+
+    if (aBlendMethod == BlendMethod::OVER || !aBlendRect ||
+        (aBlendMethod == BlendMethod::SOURCE && aSrcRect.IsEqualEdges(*aBlendRect))) {
+      // We don't need to do anything clever. (Or, in the case where no blend
+      // rect was specified, we can't.)
+      pixman_image_composite32(op,
+                               src,
+                               nullptr,
+                               dst,
+                               0, 0,
+                               0, 0,
+                               aSrcRect.x, aSrcRect.y,
+                               aSrcRect.width, aSrcRect.height);
+    } else {
+      // We need to do the OVER followed by SOURCE trick above.
+      pixman_image_composite32(PIXMAN_OP_OVER,
+                               src,
+                               nullptr,
+                               dst,
+                               0, 0,
+                               0, 0,
+                               aSrcRect.x, aSrcRect.y,
+                               aSrcRect.width, aSrcRect.height);
+      pixman_image_composite32(PIXMAN_OP_SRC,
+                               src,
+                               nullptr,
+                               dst,
+                               aBlendRect->x, aBlendRect->y,
+                               0, 0,
+                               aBlendRect->x, aBlendRect->y,
+                               aBlendRect->width, aBlendRect->height);
+    }
 
     pixman_image_unref(src);
     pixman_image_unref(dst);
   }
 
   return NS_OK;
 }
 
--- a/image/FrameAnimator.h
+++ b/image/FrameAnimator.h
@@ -2,16 +2,17 @@
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_FrameAnimator_h
 #define mozilla_image_FrameAnimator_h
 
+#include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/TimeStamp.h"
 #include "gfx2DGlue.h"
 #include "gfxTypes.h"
 #include "imgFrame.h"
 #include "nsCOMPtr.h"
 #include "nsRect.h"
 #include "SurfaceCache.h"
@@ -241,17 +242,18 @@ private: // methods
    * @aDstRect the size of the composition frame
    * @aBlendMethod the blend method for how to blend src on the composition
    * frame.
    */
   static nsresult DrawFrameTo(const uint8_t* aSrcData,
                               const nsIntRect& aSrcRect,
                               uint32_t aSrcPaletteLength, bool aSrcHasAlpha,
                               uint8_t* aDstPixels, const nsIntRect& aDstRect,
-                              BlendMethod aBlendMethod);
+                              BlendMethod aBlendMethod,
+                              const Maybe<nsIntRect>& aBlendRect);
 
 private: // data
   //! A weak pointer to our owning image.
   RasterImage* mImage;
 
   //! The intrinsic size of the image.
   gfx::IntSize mSize;
 
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -11,16 +11,18 @@
 #include "nsColor.h"
 #include "nsIInputStream.h"
 #include "nsMemory.h"
 #include "nsPNGDecoder.h"
 #include "nsRect.h"
 #include "nspr.h"
 #include "png.h"
 #include "RasterImage.h"
+#include "SurfacePipeFactory.h"
+#include "mozilla/DebugOnly.h"
 #include "mozilla/Telemetry.h"
 
 #include <algorithm>
 
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace image {
@@ -86,28 +88,33 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameIn
 }
 #endif
 
 // First 8 bytes of a PNG file
 const uint8_t
 nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
 
 nsPNGDecoder::nsPNGDecoder(RasterImage* aImage)
- : 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)
-{
-}
+ : Decoder(aImage)
+ , mPNG(nullptr)
+ , mInfo(nullptr)
+ , mCMSLine(nullptr)
+ , interlacebuf(nullptr)
+ , mInProfile(nullptr)
+ , mTransform(nullptr)
+ , format(gfx::SurfaceFormat::UNKNOWN)
+ , mHeaderBytesRead(0)
+ , mCMSMode(0)
+ , mChannels(0)
+ , mPass(0)
+ , mFrameIsHidden(false)
+ , mDisablePremultipliedAlpha(false)
+ , mSuccessfulEarlyFinish(false)
+ , mNumFrames(0)
+{ }
 
 nsPNGDecoder::~nsPNGDecoder()
 {
   if (mPNG) {
     png_destroy_read_struct(&mPNG, mInfo ? &mInfo : nullptr, nullptr);
   }
   if (mCMSLine) {
     free(mCMSLine);
@@ -120,87 +127,121 @@ nsPNGDecoder::~nsPNGDecoder()
 
     // mTransform belongs to us only if mInProfile is non-null
     if (mTransform) {
       qcms_transform_release(mTransform);
     }
   }
 }
 
-void
-nsPNGDecoder::CheckForTransparency(SurfaceFormat aFormat,
-                                   const IntRect& aFrameRect)
+nsPNGDecoder::TransparencyType
+nsPNGDecoder::GetTransparencyType(SurfaceFormat aFormat,
+                                  const IntRect& aFrameRect)
 {
   // Check if the image has a transparent color in its palette.
   if (aFormat == SurfaceFormat::B8G8R8A8) {
-    PostHasTransparency();
+    return TransparencyType::eAlpha;
+  }
+  if (!IntRect(IntPoint(), GetSize()).IsEqualEdges(aFrameRect)) {
+    MOZ_ASSERT(HasAnimation());
+    return TransparencyType::eFrameRect;
   }
 
-  // If the first frame of animated image doesn't draw into the whole image,
-  // then record that it is transparent.
-  if (mNumFrames == 0 && !IntRect(IntPoint(), GetSize()).IsEqualEdges(aFrameRect)) {
-    MOZ_ASSERT(HasAnimation());
-    PostHasTransparency();
+  return TransparencyType::eNone;
+}
+
+void
+nsPNGDecoder::PostHasTransparencyIfNeeded(TransparencyType aTransparencyType)
+{
+  switch (aTransparencyType) {
+    case TransparencyType::eNone:
+      return;
+
+    case TransparencyType::eAlpha:
+      PostHasTransparency();
+      return;
+
+    case TransparencyType::eFrameRect:
+      // If the first frame of animated image doesn't draw into the whole image,
+      // then record that it is transparent. For subsequent frames, this doesn't
+      // affect transparency, because they're composited on top of all previous
+      // frames.
+      if (mNumFrames == 0) {
+        PostHasTransparency();
+      }
+      return;
   }
 }
 
-// CreateFrame() is used for both simple and animated images
+// CreateFrame() is used for both simple and animated images.
 nsresult
-nsPNGDecoder::CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset,
-                          int32_t aWidth, int32_t aHeight,
-                          gfx::SurfaceFormat aFormat)
+nsPNGDecoder::CreateFrame(SurfaceFormat aFormat,
+                          const IntRect& aFrameRect,
+                          bool aIsInterlaced)
 {
   MOZ_ASSERT(HasSize());
   MOZ_ASSERT(!IsMetadataDecode());
 
-  IntRect frameRect(aXOffset, aYOffset, aWidth, aHeight);
-  CheckForTransparency(aFormat, frameRect);
+  // Check if we have transparency, and send notifications if needed.
+  auto transparency = GetTransparencyType(aFormat, aFrameRect);
+  PostHasTransparencyIfNeeded(transparency);
+  SurfaceFormat format = transparency == TransparencyType::eNone
+                       ? SurfaceFormat::B8G8R8X8
+                       : SurfaceFormat::B8G8R8A8;
 
   // Make sure there's no animation or padding if we're downscaling.
+  MOZ_ASSERT_IF(mDownscaler, mNumFrames == 0);
   MOZ_ASSERT_IF(mDownscaler, !GetImageMetadata().HasAnimation());
-  MOZ_ASSERT_IF(mDownscaler,
-                IntRect(IntPoint(), GetSize()).IsEqualEdges(frameRect));
+  MOZ_ASSERT_IF(mDownscaler, transparency != TransparencyType::eFrameRect);
 
   IntSize targetSize = mDownscaler ? mDownscaler->TargetSize()
                                    : GetSize();
-  IntRect targetFrameRect = mDownscaler ? IntRect(IntPoint(), targetSize)
-                                        : frameRect;
-  nsresult rv = AllocateFrame(mNumFrames, targetSize, targetFrameRect, aFormat);
-  if (NS_FAILED(rv)) {
-    return rv;
+
+  // If this image is interlaced, we can display better quality intermediate
+  // results to the user by post processing them with ADAM7InterpolatingFilter.
+  SurfacePipeFlags pipeFlags = aIsInterlaced
+                             ? SurfacePipeFlags::ADAM7_INTERPOLATE
+                             : SurfacePipeFlags();
+
+  if (mNumFrames == 0) {
+    // The first frame may be displayed progressively.
+    pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY;
   }
 
-  mFrameRect = frameRect;
+  Maybe<SurfacePipe> pipe =
+    SurfacePipeFactory::CreateSurfacePipe(this, mNumFrames, GetSize(), targetSize,
+                                          aFrameRect, format, pipeFlags);
+
+  if (!pipe) {
+    mPipe = SurfacePipe();
+    return NS_ERROR_FAILURE;
+  }
+
+  mPipe = Move(*pipe);
+
+  mFrameRect = aFrameRect;
+  mPass = 0;
 
   MOZ_LOG(sPNGDecoderAccountingLog, LogLevel::Debug,
          ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created "
           "image frame with %dx%d pixels for decoder %p",
-          aWidth, aHeight, this));
+          aFrameRect.width, aFrameRect.height, this));
 
 #ifdef PNG_APNG_SUPPORTED
   if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) {
     mAnimInfo = AnimFrameInfo(mPNG, mInfo);
 
     if (mAnimInfo.mDispose == DisposalMethod::CLEAR) {
       // We may have to display the background under this image during
       // animation playback, so we regard it as transparent.
       PostHasTransparency();
     }
   }
 #endif
 
-  if (mDownscaler) {
-    bool hasAlpha = aFormat != SurfaceFormat::B8G8R8X8;
-    rv = mDownscaler->BeginFrame(frameRect.Size(), Nothing(),
-                                 mImageData, hasAlpha);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-  }
-
   return NS_OK;
 }
 
 // set timeout and frame disposal method for the current frame
 void
 nsPNGDecoder::EndImageFrame()
 {
   if (mFrameIsHidden) {
@@ -210,17 +251,17 @@ nsPNGDecoder::EndImageFrame()
   mNumFrames++;
 
   Opacity opacity = Opacity::SOME_TRANSPARENCY;
   if (format == gfx::SurfaceFormat::B8G8R8X8) {
     opacity = Opacity::FULLY_OPAQUE;
   }
 
   PostFrameStop(opacity, mAnimInfo.mDispose, mAnimInfo.mTimeout,
-                mAnimInfo.mBlend);
+                mAnimInfo.mBlend, Some(mFrameRect));
 }
 
 void
 nsPNGDecoder::InitInternal()
 {
   mCMSMode = gfxPlatform::GetCMSMode();
   if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
     mCMSMode = eCMSMode_Off;
@@ -453,17 +494,16 @@ PNGGetColorProfile(png_structp png_ptr, 
   }
 
   return profile;
 }
 
 void
 nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr)
 {
-//  int number_passes;   NOT USED
   png_uint_32 width, height;
   int bit_depth, color_type, interlace_type, compression_type, filter_type;
   unsigned int channels;
 
   png_bytep trans = nullptr;
   int num_trans = 0;
 
   nsPNGDecoder* decoder =
@@ -473,18 +513,20 @@ nsPNGDecoder::info_callback(png_structp 
   png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
                &interlace_type, &compression_type, &filter_type);
 
   // Are we too big?
   if (width > MOZ_PNG_MAX_DIMENSION || height > MOZ_PNG_MAX_DIMENSION) {
     png_longjmp(decoder->mPNG, 1);
   }
 
+  const IntRect frameRect(0, 0, width, height);
+
   // Post our size to the superclass
-  decoder->PostSize(width, height);
+  decoder->PostSize(frameRect.width, frameRect.height);
   if (decoder->HasError()) {
     // Setting the size led to an error.
     png_longjmp(decoder->mPNG, 1);
   }
 
   if (color_type == PNG_COLOR_TYPE_PALETTE) {
     png_set_expand(png_ptr);
   }
@@ -559,19 +601,19 @@ nsPNGDecoder::info_callback(png_structp 
       if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) {
         decoder->mTransform = gfxPlatform::GetCMSRGBATransform();
       } else {
         decoder->mTransform = gfxPlatform::GetCMSRGBTransform();
       }
     }
   }
 
-  // let libpng expand interlaced images
-  if (interlace_type == PNG_INTERLACE_ADAM7) {
-    // number_passes =
+  // Let libpng expand interlaced images.
+  const bool isInterlaced = interlace_type == PNG_INTERLACE_ADAM7;
+  if (isInterlaced) {
     png_set_interlace_handling(png_ptr);
   }
 
   // now all of those things we set above are used to update various struct
   // members and whatnot, after which we can get channels, rowbytes, etc.
   png_read_update_info(png_ptr, info_ptr);
   decoder->mChannels = channels = png_get_channels(png_ptr, info_ptr);
 
@@ -590,30 +632,30 @@ nsPNGDecoder::info_callback(png_structp 
 #ifdef PNG_APNG_SUPPORTED
   bool isAnimated = png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL);
   if (isAnimated) {
     decoder->PostIsAnimated(GetNextFrameDelay(png_ptr, info_ptr));
 
     if (decoder->mDownscaler && !decoder->IsFirstFrameDecode()) {
       MOZ_ASSERT_UNREACHABLE("Doing downscale-during-decode "
                              "for an animated image?");
-      decoder->mDownscaler.reset();
+      png_longjmp(decoder->mPNG, 1);  // Abort the decode.
     }
   }
 #endif
 
   if (decoder->IsMetadataDecode()) {
     // If we are animated then the first frame rect is either: 1) the whole image
     // if the IDAT chunk is part of the animation 2) the frame rect of the first
     // fDAT chunk otherwise. If we are not animated then we want to make sure to
     // call PostHasTransparency in the metadata decode if we need to. So it's okay
     // to pass IntRect(0, 0, width, height) here for animated images; they will
     // call with the proper first frame rect in the full decode.
-    decoder->CheckForTransparency(decoder->format,
-                                  IntRect(0, 0, width, height));
+    auto transparency = decoder->GetTransparencyType(decoder->format, frameRect);
+    decoder->PostHasTransparencyIfNeeded(transparency);
 
     // We have the metadata we're looking for, so we don't need to decode any
     // further.
     decoder->mSuccessfulEarlyFinish = true;
     png_longjmp(decoder->mPNG, 1);
   }
 
 #ifdef PNG_APNG_SUPPORTED
@@ -621,146 +663,84 @@ nsPNGDecoder::info_callback(png_structp 
     png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback,
                                  nullptr);
   }
 
   if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) {
     decoder->mFrameIsHidden = true;
   } else {
 #endif
-    nsresult rv = decoder->CreateFrame(0, 0, width, height, decoder->format);
+    nsresult rv = decoder->CreateFrame(decoder->format, frameRect, isInterlaced);
     if (NS_FAILED(rv)) {
       png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY
     }
     MOZ_ASSERT(decoder->mImageData, "Should have a buffer now");
 #ifdef PNG_APNG_SUPPORTED
   }
 #endif
 
-  if (decoder->mTransform &&
-      (channels <= 2 || interlace_type == PNG_INTERLACE_ADAM7)) {
+  if (decoder->mTransform && (channels <= 2 || isInterlaced)) {
     uint32_t bpp[] = { 0, 3, 4, 3, 4 };
     decoder->mCMSLine =
-      (uint8_t*)malloc(bpp[channels] * width);
+      static_cast<uint8_t*>(malloc(bpp[channels] * frameRect.width));
     if (!decoder->mCMSLine) {
       png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY
     }
   }
 
   if (interlace_type == PNG_INTERLACE_ADAM7) {
-    if (height < INT32_MAX / (width * channels)) {
-      decoder->interlacebuf = (uint8_t*)malloc(channels * width * height);
+    if (frameRect.height < INT32_MAX / (frameRect.width * int32_t(channels))) {
+      const size_t bufferSize = channels * frameRect.width * frameRect.height;
+      decoder->interlacebuf = static_cast<uint8_t*>(malloc(bufferSize));
     }
     if (!decoder->interlacebuf) {
       png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY
     }
   }
 }
 
 void
-nsPNGDecoder::PostPartialInvalidation(const IntRect& aInvalidRegion)
-{
-  if (!mDownscaler) {
-    PostInvalidation(aInvalidRegion);
-    return;
-  }
-
-  if (!mDownscaler->HasInvalidation()) {
-    return;
-  }
-
-  DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect();
-  PostInvalidation(invalidRect.mOriginalSizeRect,
-                   Some(invalidRect.mTargetSizeRect));
-}
-
-void
-nsPNGDecoder::PostFullInvalidation()
+nsPNGDecoder::PostInvalidationIfNeeded()
 {
-  PostInvalidation(mFrameRect);
-
-  if (mDownscaler) {
-    mDownscaler->ResetForNextProgressivePass();
-  }
-}
-
-static void
-InterpolateInterlacedPNG(const int aPass, const bool aHasAlpha,
-                         const uint32_t aWidth, const uint32_t aHeight,
-                         uint8_t* aImageData)
-{
-  // At this point we have a completed pass of an interlaced image in
-  // imageData as an array of uint8_t ARGB or XRGB pixels, optionally
-  // premultiplied, 4 bytes per pixel. If there are leftover partial
-  // blocks at the right edge or bottom of the image, we just use the
-  // uninterpolated pixels that libpng gave us.
-  //
-  // See Bug #75077, Interpolation of interlaced PNG
-  // See https://en.wikipedia.org/wiki/Bilinear_interpolation
-  //
-  // Note: this doesn't work when downscaling so we simply show
-  // the uninterpolated blocks that libpng gives us.
-  //
-  // Don't try to interpolate images that are less than 8 columns wide
-  // or 8 rows high; do only square passes (0, 2, 4)
-  if ((aPass != 0 && aPass != 2 && aPass != 4) || aWidth < 8 || aHeight < 8) {
+  Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect();
+  if (!invalidRect) {
     return;
   }
 
-  /* Block dimensions are defined by the PNG specification */
-  uint32_t block_width[]  = { 8, 4, 4, 2, 2 };
-  uint32_t bw = block_width[aPass];
-  uint32_t bh = bw;
-
-  bool first_component = aHasAlpha ? 0: 1;
+  PostInvalidation(invalidRect->mInputSpaceRect,
+                   Some(invalidRect->mOutputSpaceRect));
+}
 
-  // Reduced version of the PNG_PASS_ROW_SHIFT(pass) macro in libpng/png.h
-  // Only works with square passes 0, 2, and 4
-  uint32_t divisor_shift = 3 - (aPass >> 1);
-
-  // Loop over blocks
-  for (uint32_t y = 0; y < aHeight - bh; y += bh) {
-    for (uint32_t x = 0; x < aWidth - bw; x += bw) {
-      // (x,y) is the top left corner of the block
-      // topleft is the first component of the top left pixel of the block
-      uint8_t* topleft = aImageData + 4 * (x + aWidth * y);
+static NextPixel<uint32_t>
+PackRGBPixelAndAdvance(uint8_t*& aRawPixelInOut)
+{
+  const uint32_t pixel =
+    gfxPackedPixel(0xFF, aRawPixelInOut[0], aRawPixelInOut[1], aRawPixelInOut[2]);
+  aRawPixelInOut += 3;
+  return AsVariant(pixel);
+}
 
-      // Loop over component=[A,]R,G,B
-      for (uint32_t component = first_component; component < 4; component++) {
-        if (x == 0) {
-          // Interpolate ARGB along the left side of the block
-          uint32_t top = *(topleft + component);
-          uint32_t bottom = *(topleft + component + (bh * 4 * aWidth));
-          for (uint32_t j = 1; j < bh; j++) {
-            *(topleft + component + j * 4 * aWidth) =
-              ((top * (bh - j) + bottom * j) >> divisor_shift) & 0xff;
-          }
-        }
+static NextPixel<uint32_t>
+PackRGBAPixelAndAdvance(uint8_t*& aRawPixelInOut)
+{
+  const uint32_t pixel =
+    gfxPackedPixel(aRawPixelInOut[3], aRawPixelInOut[0],
+                   aRawPixelInOut[1], aRawPixelInOut[2]);
+  aRawPixelInOut += 4;
+  return AsVariant(pixel);
+}
 
-        // Interpolate ARGB along the right side of the block
-        uint32_t top = *(topleft + component + 4 * bw);
-        uint32_t bottom = *(topleft + component + 4 * (bw + (bh * aWidth)));
-        for (uint32_t j = 1; j < bh; j++) {
-          *(topleft + component + 4 * (bw + j * aWidth)) =
-          ((top * (bh - j) + bottom * j) >> divisor_shift) & 0xff;
-        }
-
-        // Interpolate ARGB in the X-direction along the top edge
-        // and within the block
-        for (uint32_t j = 0; j < bh; j++) {
-          uint32_t left = *(topleft + component + 4 * j * aWidth);
-          uint32_t right = *(topleft + component + 4 * (bw + j * aWidth));
-          for (uint32_t i = 1; i < bw; i++) {
-            *(topleft + component + 4 * (i + j * aWidth)) =
-            ((left * (bw - i) + right * i) >> divisor_shift) & 0xff;
-          } // i
-        } // j
-      } // component
-    } // x
-  } // y
+static NextPixel<uint32_t>
+PackUnpremultipliedRGBAPixelAndAdvance(uint8_t*& aRawPixelInOut)
+{
+  const uint32_t pixel =
+    gfxPackedPixelNoPreMultiply(aRawPixelInOut[3], aRawPixelInOut[0],
+                                aRawPixelInOut[1], aRawPixelInOut[2]);
+  aRawPixelInOut += 4;
+  return AsVariant(pixel);
 }
 
 void
 nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row,
                            png_uint_32 row_num, int pass)
 {
   /* libpng comments:
    *
@@ -785,147 +765,119 @@ nsPNGDecoder::row_callback(png_structp p
    * where old_row is what was displayed for previous rows.  Note
    * that the first pass (pass == 0 really) will completely cover
    * the old row, so the rows do not have to be initialized.  After
    * the first pass (and only for interlaced images), you will have
    * to pass the current row, and the function will combine the
    * old row and the new row.
    */
   nsPNGDecoder* decoder =
-               static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
+    static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
 
-  // skip this frame
   if (decoder->mFrameIsHidden) {
-    return;
-  }
-
-  if (row_num >= static_cast<png_uint_32>(decoder->mFrameRect.height)) {
-    return;
+    return;  // Skip this frame.
   }
 
-  bool lastRow =
-    row_num == static_cast<png_uint_32>(decoder->mFrameRect.height) - 1;
+  while (pass > decoder->mPass) {
+    // Advance to the next pass. We may have to do this multiple times because
+    // libpng will skip passes if the image is so small that no pixels have
+    // changed on a given pass, but ADAM7InterpolatingFilter needs to be reset
+    // once for every pass to perform interpolation properly.
+    decoder->mPipe.ResetToFirstRow();
+    decoder->mPass++;
+  }
 
-  if (!new_row && !decoder->mDownscaler && !lastRow) {
-    // If |new_row| is null, that indicates that this is an interlaced image
-    // and |row_callback| is being called for a row that hasn't changed.
-    // Ordinarily we don't need to do anything in this case, but if we're
-    // downscaling, the downscaler doesn't store the rows from previous passes,
-    // so we still need to process the row.  If |lastRow| is true we need
-    // to finish the interlace pass.
+  const png_uint_32 height = static_cast<png_uint_32>(decoder->mFrameRect.height);
+
+  if (row_num >= height) {
+    // Bail if we receive extra rows. This is especially important because if we
+    // didn't, we might overflow the deinterlacing buffer.
+    MOZ_ASSERT_UNREACHABLE("libpng producing extra rows?");
     return;
   }
 
-  int32_t width = decoder->mFrameRect.width;
-  uint32_t iwidth = decoder->mFrameRect.width;
+  // Note that |new_row| may be null here, indicating that this is an interlaced
+  // image and |row_callback| is being called for a row that hasn't changed.
+  MOZ_ASSERT_IF(!new_row, decoder->interlacebuf);
+  uint8_t* rowToWrite = new_row;
 
-  png_bytep line = new_row;
   if (decoder->interlacebuf) {
-    line = decoder->interlacebuf + (row_num * decoder->mChannels * width);
-    png_progressive_combine_row(png_ptr, line, new_row);
+    uint32_t width = uint32_t(decoder->mFrameRect.width);
+
+    // We'll output the deinterlaced version of the row.
+    rowToWrite = decoder->interlacebuf + (row_num * decoder->mChannels * width);
+
+    // Update the deinterlaced version of this row with the new data.
+    png_progressive_combine_row(png_ptr, rowToWrite, new_row);
   }
 
-  uint32_t bpr = width * sizeof(uint32_t);
-  uint32_t* cptr32 = decoder->mDownscaler
-    ? reinterpret_cast<uint32_t*>(decoder->mDownscaler->RowBuffer())
-    : reinterpret_cast<uint32_t*>(decoder->mImageData + (row_num*bpr));
+  decoder->WriteRow(rowToWrite);
+}
+
+void
+nsPNGDecoder::WriteRow(uint8_t* aRow)
+{
+  MOZ_ASSERT(aRow);
+
+  uint8_t* rowToWrite = aRow;
+  uint32_t width = uint32_t(mFrameRect.width);
 
-  if (decoder->mTransform) {
-    if (decoder->mCMSLine) {
-      qcms_transform_data(decoder->mTransform, line, decoder->mCMSLine,
-                          iwidth);
-      // copy alpha over
-      uint32_t channels = decoder->mChannels;
-      if (channels == 2 || channels == 4) {
-        for (uint32_t i = 0; i < iwidth; i++)
-          decoder->mCMSLine[4 * i + 3] = line[channels * i + channels - 1];
+  // Apply color management to the row, if necessary, before writing it out.
+  if (mTransform) {
+    if (mCMSLine) {
+      qcms_transform_data(mTransform, rowToWrite, mCMSLine, width);
+
+      // Copy alpha over.
+      if (mChannels == 2 || mChannels == 4) {
+        for (uint32_t i = 0; i < width; ++i) {
+          mCMSLine[4 * i + 3] = rowToWrite[mChannels * i + mChannels - 1];
+        }
       }
-      line = decoder->mCMSLine;
+
+      rowToWrite = mCMSLine;
     } else {
-      qcms_transform_data(decoder->mTransform, line, line, iwidth);
+      qcms_transform_data(mTransform, rowToWrite, rowToWrite, width);
     }
   }
 
-  switch (decoder->format) {
-    case gfx::SurfaceFormat::B8G8R8X8: {
-      // counter for while() loops below
-      uint32_t idx = iwidth;
-
-      // copy as bytes until source pointer is 32-bit-aligned
-      for (; (NS_PTR_TO_UINT32(line) & 0x3) && idx; --idx) {
-        *cptr32++ = gfxPackedPixel(0xFF, line[0], line[1], line[2]);
-        line += 3;
-      }
-
-      // copy pixels in blocks of 4
-      while (idx >= 4) {
-        GFX_BLOCK_RGB_TO_FRGB(line, cptr32);
-        idx    -=  4;
-        line   += 12;
-        cptr32 +=  4;
-      }
+  // Write this row to the SurfacePipe.
+  DebugOnly<WriteState> result = WriteState::FAILURE;
+  switch (format) {
+    case SurfaceFormat::B8G8R8X8:
+      result = mPipe.WritePixelsToRow<uint32_t>([&]{
+        return PackRGBPixelAndAdvance(rowToWrite);
+      });
+      break;
 
-      // copy remaining pixel(s)
-      while (idx--) {
-        // 32-bit read of final pixel will exceed buffer, so read bytes
-        *cptr32++ = gfxPackedPixel(0xFF, line[0], line[1], line[2]);
-        line += 3;
+    case SurfaceFormat::B8G8R8A8:
+      if (mDisablePremultipliedAlpha) {
+        result = mPipe.WritePixelsToRow<uint32_t>([&]{
+          return PackUnpremultipliedRGBAPixelAndAdvance(rowToWrite);
+        });
+      } else {
+        result = mPipe.WritePixelsToRow<uint32_t>([&]{
+          return PackRGBAPixelAndAdvance(rowToWrite);
+        });
       }
-    }
-    break;
-    case gfx::SurfaceFormat::B8G8R8A8: {
-      if (!decoder->mDisablePremultipliedAlpha) {
-        for (uint32_t x=width; x>0; --x) {
-          *cptr32++ = gfxPackedPixel(line[3], line[0], line[1], line[2]);
-          line += 4;
-        }
-      } else {
-        for (uint32_t x=width; x>0; --x) {
-          *cptr32++ = gfxPackedPixelNoPreMultiply(line[3], line[0], line[1],
-                                                  line[2]);
-          line += 4;
-        }
-      }
-    }
-    break;
+      break;
+
     default:
-      png_longjmp(decoder->mPNG, 1);
+      png_longjmp(mPNG, 1);  // Abort the decode.
   }
 
-  if (decoder->mDownscaler) {
-    decoder->mDownscaler->CommitRow();
-  }
+  MOZ_ASSERT(result != WriteState::FAILURE);
 
-  if (!decoder->interlacebuf) {
-    // Do line-by-line partial invalidations for non-interlaced images.
-    decoder->PostPartialInvalidation(IntRect(0, row_num, width, 1));
-  } else if (lastRow) {
-    // Do only one full image invalidation for each even pass. (Bug 1187569)
-    if (decoder->mDownscaler) {
-      decoder->PostFullInvalidation();
-    } else if (pass % 2 == 0) {
-
-      const bool hasAlpha = decoder->format != SurfaceFormat::B8G8R8X8;
-      InterpolateInterlacedPNG(pass, hasAlpha,
-                               static_cast<uint32_t>(width),
-                               decoder->mFrameRect.height,
-                               decoder->mImageData);
-      decoder->PostFullInvalidation();
-    }
-  }
+  PostInvalidationIfNeeded();
 }
 
 #ifdef PNG_APNG_SUPPORTED
 // got the header of a new frame that's coming
 void
 nsPNGDecoder::frame_info_callback(png_structp png_ptr, png_uint_32 frame_num)
 {
-  png_uint_32 x_offset, y_offset;
-  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.
@@ -933,23 +885,24 @@ nsPNGDecoder::frame_info_callback(png_st
     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);
+  const IntRect frameRect(png_get_next_frame_x_offset(png_ptr, decoder->mInfo),
+                          png_get_next_frame_y_offset(png_ptr, decoder->mInfo),
+                          png_get_next_frame_width(png_ptr, decoder->mInfo),
+                          png_get_next_frame_height(png_ptr, decoder->mInfo));
 
-  nsresult rv =
-    decoder->CreateFrame(x_offset, y_offset, width, height, decoder->format);
+  const bool isInterlaced = bool(decoder->interlacebuf);
+
+  nsresult rv = decoder->CreateFrame(decoder->format, frameRect, isInterlaced);
   if (NS_FAILED(rv)) {
     png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY
   }
   MOZ_ASSERT(decoder->mImageData, "Should have a buffer now");
 }
 #endif
 
 void
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -3,45 +3,60 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_decoders_nsPNGDecoder_h
 #define mozilla_image_decoders_nsPNGDecoder_h
 
 #include "Decoder.h"
-
-#include "gfxTypes.h"
-
-#include "nsCOMPtr.h"
-
 #include "png.h"
-
 #include "qcms.h"
+#include "SurfacePipe.h"
 
 namespace mozilla {
 namespace image {
 class RasterImage;
 
 class nsPNGDecoder : public Decoder
 {
 public:
   virtual ~nsPNGDecoder();
 
   virtual void InitInternal() override;
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override;
   virtual Telemetry::ID SpeedHistogram() override;
 
-  nsresult CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset,
-                       int32_t aWidth, int32_t aHeight,
-                       gfx::SurfaceFormat aFormat);
+private:
+  friend class DecoderFactory;
+  friend class nsICODecoder;
+
+  // Decoders should only be instantiated via DecoderFactory.
+  // XXX(seth): nsICODecoder is temporarily an exception to this rule.
+  explicit nsPNGDecoder(RasterImage* aImage);
+
+  nsresult CreateFrame(gfx::SurfaceFormat aFormat,
+                       const gfx::IntRect& aFrameRect,
+                       bool aIsInterlaced);
   void EndImageFrame();
 
-  void CheckForTransparency(gfx::SurfaceFormat aFormat,
-                            const gfx::IntRect& aFrameRect);
+  enum class TransparencyType
+  {
+    eNone,
+    eAlpha,
+    eFrameRect
+  };
+
+  TransparencyType GetTransparencyType(gfx::SurfaceFormat aFormat,
+                                       const gfx::IntRect& aFrameRect);
+  void PostHasTransparencyIfNeeded(TransparencyType aTransparencyType);
+
+  void PostInvalidationIfNeeded();
+
+  void WriteRow(uint8_t* aRow);
 
   // Check if PNG is valid ICO (32bpp RGBA)
   // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
   bool IsValidICO() const
   {
     // If there are errors in the call to png_get_IHDR, the error_callback in
     // nsPNGDecoder.cpp is called.  In this error callback we do a longjmp, so
     // we need to save the jump buffer here. Oterwise we'll end up without a
@@ -64,27 +79,16 @@ public:
       return ((png_color_type == PNG_COLOR_TYPE_RGB_ALPHA ||
                png_color_type == PNG_COLOR_TYPE_RGB) &&
               png_bit_depth == 8);
     } else {
       return false;
     }
   }
 
-private:
-  friend class DecoderFactory;
-  friend class nsICODecoder;
-
-  // Decoders should only be instantiated via DecoderFactory.
-  // XXX(seth): nsICODecoder is temporarily an exception to this rule.
-  explicit nsPNGDecoder(RasterImage* aImage);
-
-  void PostPartialInvalidation(const IntRect& aInvalidRegion);
-  void PostFullInvalidation();
-
 public:
   png_structp mPNG;
   png_infop mInfo;
   nsIntRect mFrameRect;
   uint8_t* mCMSLine;
   uint8_t* interlacebuf;
   qcms_profile* mInProfile;
   qcms_transform* mTransform;
@@ -94,16 +98,17 @@ public:
   // For metadata decodes.
   uint8_t mSizeBytes[8]; // Space for width and height, both 4 bytes
   uint32_t mHeaderBytesRead;
 
   // whether CMS or premultiplied alpha are forced off
   uint32_t mCMSMode;
 
   uint8_t mChannels;
+  uint8_t mPass;
   bool mFrameIsHidden;
   bool mDisablePremultipliedAlpha;
   bool mSuccessfulEarlyFinish;
 
   struct AnimFrameInfo
   {
     AnimFrameInfo();
 #ifdef PNG_APNG_SUPPORTED
@@ -112,16 +117,18 @@ public:
 
     DisposalMethod mDispose;
     BlendMethod mBlend;
     int32_t mTimeout;
   };
 
   AnimFrameInfo mAnimInfo;
 
+  SurfacePipe mPipe;  /// The SurfacePipe used to write to the output surface.
+
   // The number of frames we've finished.
   uint32_t mNumFrames;
 
   // libpng callbacks
   // We put these in the class so that they can access protected members.
   static void PNGAPI info_callback(png_structp png_ptr, png_infop info_ptr);
   static void PNGAPI row_callback(png_structp png_ptr, png_bytep new_row,
                                   png_uint_32 row_num, int pass);
--- a/image/imgFrame.cpp
+++ b/image/imgFrame.cpp
@@ -633,28 +633,30 @@ imgFrame::ImageUpdatedInternal(const nsI
 
   return NS_OK;
 }
 
 void
 imgFrame::Finish(Opacity aFrameOpacity /* = Opacity::SOME_TRANSPARENCY */,
                  DisposalMethod aDisposalMethod /* = DisposalMethod::KEEP */,
                  int32_t aRawTimeout /* = 0 */,
-                 BlendMethod aBlendMethod /* = BlendMethod::OVER */)
+                 BlendMethod aBlendMethod /* = BlendMethod::OVER */,
+                 const Maybe<IntRect>& aBlendRect /* = Nothing() */)
 {
   MonitorAutoLock lock(mMonitor);
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
 
   if (aFrameOpacity == Opacity::FULLY_OPAQUE) {
     mHasNoAlpha = true;
   }
 
   mDisposalMethod = aDisposalMethod;
   mTimeout = aRawTimeout;
   mBlendMethod = aBlendMethod;
+  mBlendRect = aBlendRect;
   ImageUpdatedInternal(GetRect());
   mFinished = true;
 
   // The image is now complete, wake up anyone who's waiting.
   mMonitor.NotifyAll();
 }
 
 uint32_t
@@ -920,17 +922,17 @@ imgFrame::GetAnimationData() const
   } else {
     uint32_t length;
     GetImageDataInternal(&data, &length);
   }
 
   bool hasAlpha = mFormat == SurfaceFormat::B8G8R8A8;
 
   return AnimationData(data, PaletteDataLength(), mTimeout, GetRect(),
-                       mBlendMethod, mDisposalMethod, hasAlpha);
+                       mBlendMethod, mBlendRect, mDisposalMethod, hasAlpha);
 }
 
 void
 imgFrame::Abort()
 {
   MonitorAutoLock lock(mMonitor);
 
   mAborted = true;
--- a/image/imgFrame.h
+++ b/image/imgFrame.h
@@ -2,16 +2,17 @@
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_imgFrame_h
 #define mozilla_image_imgFrame_h
 
+#include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Move.h"
 #include "mozilla/VolatileBuffer.h"
 #include "gfxDrawable.h"
 #include "imgIContainer.h"
 #include "MainThreadUtils.h"
 
@@ -52,32 +53,34 @@ enum class Opacity : uint8_t {
  * It includes pointers to the raw image data of the underlying imgFrame, but
  * does not own that data. A RawAccessFrameRef for the underlying imgFrame must
  * outlive the AnimationData for it to remain valid.
  */
 struct AnimationData
 {
   AnimationData(uint8_t* aRawData, uint32_t aPaletteDataLength,
                 int32_t aRawTimeout, const nsIntRect& aRect,
-                BlendMethod aBlendMethod, DisposalMethod aDisposalMethod,
-                bool aHasAlpha)
+                BlendMethod aBlendMethod, const Maybe<gfx::IntRect>& aBlendRect,
+                DisposalMethod aDisposalMethod, bool aHasAlpha)
     : mRawData(aRawData)
     , mPaletteDataLength(aPaletteDataLength)
     , mRawTimeout(aRawTimeout)
     , mRect(aRect)
     , mBlendMethod(aBlendMethod)
+    , mBlendRect(aBlendRect)
     , mDisposalMethod(aDisposalMethod)
     , mHasAlpha(aHasAlpha)
   { }
 
   uint8_t* mRawData;
   uint32_t mPaletteDataLength;
   int32_t mRawTimeout;
   nsIntRect mRect;
   BlendMethod mBlendMethod;
+  Maybe<gfx::IntRect> mBlendRect;
   DisposalMethod mDisposalMethod;
   bool mHasAlpha;
 };
 
 class imgFrame
 {
   typedef gfx::Color Color;
   typedef gfx::DataSourceSurface DataSourceSurface;
@@ -165,21 +168,25 @@ public:
    *                         from the compositing frame before the next frame is
    *                         displayed.
    * @param aRawTimeout      For animation frames, the timeout in milliseconds
    *                         before the next frame is displayed. This timeout is
    *                         not necessarily the timeout that will actually be
    *                         used; see FrameAnimator::GetTimeoutForFrame.
    * @param aBlendMethod     For animation frames, a blending method to be used
    *                         when compositing this frame.
+   * @param aBlendRect       For animation frames, if present, the subrect in
+   *                         which @aBlendMethod applies. Outside of this
+   *                         subrect, BlendMethod::OVER is always used.
    */
   void Finish(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY,
               DisposalMethod aDisposalMethod = DisposalMethod::KEEP,
               int32_t aRawTimeout = 0,
-              BlendMethod aBlendMethod = BlendMethod::OVER);
+              BlendMethod aBlendMethod = BlendMethod::OVER,
+              const Maybe<IntRect>& aBlendRect = Nothing());
 
   /**
    * Mark this imgFrame as aborted. This informs the imgFrame that if it isn't
    * completely decoded now, it never will be.
    *
    * You must always call either Finish() or Abort() before releasing the last
    * RawAccessFrameRef pointing to an imgFrame.
    */
@@ -304,16 +311,17 @@ private: // data
   //! Number of RawAccessFrameRefs currently alive for this imgFrame.
   int32_t mLockCount;
 
   //! Raw timeout for this frame. (See FrameAnimator::GetTimeoutForFrame.)
   int32_t mTimeout; // -1 means display forever.
 
   DisposalMethod mDisposalMethod;
   BlendMethod    mBlendMethod;
+  Maybe<IntRect> mBlendRect;
   SurfaceFormat  mFormat;
 
   bool mHasNoAlpha;
   bool mAborted;
   bool mFinished;
   bool mOptimizable;