Bug 1116737 - Merge FrameBlender into FrameAnimator. r=tn
authorSeth Fowler <seth@mozilla.com>
Wed, 07 Jan 2015 13:07:23 -0800
changeset 248376 689797cc26c4d6a947b3e5e6526a62734b5e9372
parent 248375 3ca05c4232abd5690a6a7637ae55454a949ce3b4
child 248377 6fde8c97e7a82fc91d9950295f0da2a301b93350
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn
bugs1116737
milestone37.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 1116737 - Merge FrameBlender into FrameAnimator. r=tn
image/decoders/nsBMPDecoder.cpp
image/decoders/nsGIFDecoder2.cpp
image/decoders/nsJPEGDecoder.cpp
image/decoders/nsPNGDecoder.cpp
image/decoders/nsPNGDecoder.h
image/src/Decoder.cpp
image/src/Decoder.h
image/src/FrameAnimator.cpp
image/src/FrameAnimator.h
image/src/FrameBlender.cpp
image/src/FrameBlender.h
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/imgFrame.cpp
image/src/imgFrame.h
image/src/moz.build
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -139,19 +139,19 @@ nsBMPDecoder::FinishInternal()
     // Send notifications if appropriate
     if (!IsSizeDecode() && HasSize()) {
 
         // Invalidate
         nsIntRect r(0, 0, mBIH.width, GetHeight());
         PostInvalidation(r);
 
         if (mUseAlphaData) {
-          PostFrameStop(FrameBlender::kFrameHasAlpha);
+          PostFrameStop(Opacity::SOME_TRANSPARENCY);
         } else {
-          PostFrameStop(FrameBlender::kFrameOpaque);
+          PostFrameStop(Opacity::OPAQUE);
         }
         PostDecodeDone();
     }
 }
 
 // ----------------------------------------
 // Actual Data Processing
 // ----------------------------------------
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -211,17 +211,17 @@ nsGIFDecoder2::BeginImageFrame(uint16_t 
   mCurrentFrameIndex = mGIFStruct.images_decoded;
 }
 
 
 //******************************************************************************
 void
 nsGIFDecoder2::EndImageFrame()
 {
-  FrameBlender::FrameAlpha alpha = FrameBlender::kFrameHasAlpha;
+  Opacity opacity = Opacity::SOME_TRANSPARENCY;
 
   // First flush all pending image data
   if (!mGIFStruct.images_decoded) {
     // Only need to flush first frame
     FlushImageData();
 
     // If the first frame is smaller in height than the entire image, send an
     // invalidation for the area it does not have data for.
@@ -230,17 +230,17 @@ nsGIFDecoder2::EndImageFrame()
     if (realFrameHeight < mGIFStruct.screen_height) {
       nsIntRect r(0, realFrameHeight,
                   mGIFStruct.screen_width,
                   mGIFStruct.screen_height - realFrameHeight);
       PostInvalidation(r);
     }
     // This transparency check is only valid for first frame
     if (mGIFStruct.is_transparent && !mSawTransparency) {
-      alpha = FrameBlender::kFrameOpaque;
+      opacity = Opacity::OPAQUE;
     }
   }
   mCurrentRow = mLastFlushedRow = -1;
   mCurrentPass = mLastFlushedPass = 0;
 
   // Only add frame if we have any rows at all
   if (mGIFStruct.rows_remaining != mGIFStruct.height) {
     if (mGIFStruct.rows_remaining && mGIFStruct.images_decoded) {
@@ -254,18 +254,18 @@ nsGIFDecoder2::EndImageFrame()
 
   // Unconditionally increment images_decoded, because we unconditionally
   // append frames in BeginImageFrame(). This ensures that images_decoded
   // always refers to the frame in mImage we're currently decoding,
   // even if some of them weren't decoded properly and thus are blank.
   mGIFStruct.images_decoded++;
 
   // Tell the superclass we finished a frame
-  PostFrameStop(alpha,
-                FrameBlender::FrameDisposalMethod(mGIFStruct.disposal_method),
+  PostFrameStop(opacity,
+                DisposalMethod(mGIFStruct.disposal_method),
                 mGIFStruct.delay_time);
 
   // Reset the transparent pixel
   if (mOldColor) {
     mColormap[mGIFStruct.tpixel] = mOldColor;
     mOldColor = 0;
   }
 
@@ -825,20 +825,19 @@ nsGIFDecoder2::WriteInternal(const char*
       mGIFStruct.disposal_method = ((*q) >> 2) & 0x7;
       // Some specs say 3rd bit (value 4), other specs say value 3
       // Let's choose 3 (the more popular)
       if (mGIFStruct.disposal_method == 4) {
         mGIFStruct.disposal_method = 3;
       }
 
       {
-        int32_t method =
-          FrameBlender::FrameDisposalMethod(mGIFStruct.disposal_method);
-        if (method == FrameBlender::kDisposeClearAll ||
-            method == FrameBlender::kDisposeClear) {
+        DisposalMethod method = DisposalMethod(mGIFStruct.disposal_method);
+        if (method == DisposalMethod::CLEAR_ALL ||
+            method == DisposalMethod::CLEAR) {
           // We may have to display the background under this image during
           // animation playback, so we regard it as transparent.
           PostHasTransparency();
         }
       }
 
       mGIFStruct.delay_time = GETINT16(q + 1) * 10;
       GETN(1, gif_consume_block);
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -575,17 +575,17 @@ nsJPEGDecoder::ReadOrientationFromEXIF()
   EXIFData exif = EXIFParser::Parse(marker->data,
                                     static_cast<uint32_t>(marker->data_length));
   return exif.orientation;
 }
 
 void
 nsJPEGDecoder::NotifyDone()
 {
-  PostFrameStop(FrameBlender::kFrameOpaque);
+  PostFrameStop(Opacity::OPAQUE);
   PostDecodeDone();
 }
 
 void
 nsJPEGDecoder::OutputScanlines(bool* suspend)
 {
   *suspend = false;
 
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -49,25 +49,25 @@ GetPNGDecoderAccountingLog()
 #endif
 
 // For size decodes
 #define WIDTH_OFFSET 16
 #define HEIGHT_OFFSET (WIDTH_OFFSET + 4)
 #define BYTES_NEEDED_FOR_DIMENSIONS (HEIGHT_OFFSET + 4)
 
 nsPNGDecoder::AnimFrameInfo::AnimFrameInfo()
- : mDispose(FrameBlender::kDisposeKeep)
- , mBlend(FrameBlender::kBlendOver)
+ : mDispose(DisposalMethod::KEEP)
+ , mBlend(BlendMethod::OVER)
  , mTimeout(0)
 { }
 
 #ifdef PNG_APNG_SUPPORTED
 nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo)
- : mDispose(FrameBlender::kDisposeKeep)
- , mBlend(FrameBlender::kBlendOver)
+ : mDispose(DisposalMethod::KEEP)
+ , mBlend(BlendMethod::OVER)
  , mTimeout(0)
 {
   png_uint_16 delay_num, delay_den;
   // delay, in seconds is delay_num/delay_den
   png_byte dispose_op;
   png_byte blend_op;
   delay_num = png_get_next_frame_delay_num(aPNG, aInfo);
   delay_den = png_get_next_frame_delay_den(aPNG, aInfo);
@@ -83,27 +83,27 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameIn
 
     // Need to cast delay_num to float to have a proper division and
     // the result to int to avoid compiler warning
     mTimeout = static_cast<int32_t>(static_cast<double>(delay_num) *
                                     1000 / delay_den);
   }
 
   if (dispose_op == PNG_DISPOSE_OP_PREVIOUS) {
-    mDispose = FrameBlender::kDisposeRestorePrevious;
+    mDispose = DisposalMethod::RESTORE_PREVIOUS;
   } else if (dispose_op == PNG_DISPOSE_OP_BACKGROUND) {
-    mDispose = FrameBlender::kDisposeClear;
+    mDispose = DisposalMethod::CLEAR;
   } else {
-    mDispose = FrameBlender::kDisposeKeep;
+    mDispose = DisposalMethod::KEEP;
   }
 
   if (blend_op == PNG_BLEND_OP_SOURCE) {
-    mBlend = FrameBlender::kBlendSource;
+    mBlend = BlendMethod::SOURCE;
   } else {
-    mBlend = FrameBlender::kBlendOver;
+    mBlend = BlendMethod::OVER;
   }
 }
 #endif
 
 // First 8 bytes of a PNG file
 const uint8_t
 nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
 
@@ -180,17 +180,17 @@ void nsPNGDecoder::CreateFrame(png_uint_
           "image frame with %dx%d pixels in container %p",
           width, height,
           &mImage));
 
 #ifdef PNG_APNG_SUPPORTED
   if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) {
     mAnimInfo = AnimFrameInfo(mPNG, mInfo);
 
-    if (mAnimInfo.mDispose == FrameBlender::kDisposeClear) {
+    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
 }
 
@@ -199,33 +199,33 @@ void
 nsPNGDecoder::EndImageFrame()
 {
   if (mFrameIsHidden) {
     return;
   }
 
   mNumFrames++;
 
-  FrameBlender::FrameAlpha alpha;
+  Opacity opacity;
   if (mFrameHasNoAlpha) {
-    alpha = FrameBlender::kFrameOpaque;
+    opacity = Opacity::OPAQUE;
   } else {
-    alpha = FrameBlender::kFrameHasAlpha;
+    opacity = Opacity::SOME_TRANSPARENCY;
   }
 
 #ifdef PNG_APNG_SUPPORTED
   uint32_t numFrames = GetFrameCount();
 
   // We can't use mPNG->num_frames_read as it may be one ahead.
   if (numFrames > 1) {
     PostInvalidation(mFrameRect);
   }
 #endif
 
-  PostFrameStop(alpha, mAnimInfo.mDispose, mAnimInfo.mTimeout,
+  PostFrameStop(opacity, mAnimInfo.mDispose, mAnimInfo.mTimeout,
                 mAnimInfo.mBlend);
 }
 
 void
 nsPNGDecoder::InitInternal()
 {
   // For size decodes, we don't need to initialize the png decoder
   if (IsSizeDecode()) {
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -93,18 +93,18 @@ public:
 
   struct AnimFrameInfo
   {
     AnimFrameInfo();
 #ifdef PNG_APNG_SUPPORTED
     AnimFrameInfo(png_structp aPNG, png_infop aInfo);
 #endif
 
-    FrameBlender::FrameDisposalMethod mDispose;
-    FrameBlender::FrameBlendMethod mBlend;
+    DisposalMethod mDispose;
+    BlendMethod mBlend;
     int32_t mTimeout;
   };
 
   AnimFrameInfo mAnimInfo;
 
   // The number of frames we've finished.
   uint32_t mNumFrames;
 
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -328,34 +328,34 @@ Decoder::PostFrameStart()
   // Decoder implementations should only call this method if they successfully
   // appended the frame to the image. So mFrameCount should always match that
   // reported by the Image.
   MOZ_ASSERT(mFrameCount == mImage.GetNumFrames(),
              "Decoder frame count doesn't match image's!");
 }
 
 void
-Decoder::PostFrameStop(FrameBlender::FrameAlpha aFrameAlpha /* = FrameBlender::kFrameHasAlpha */,
-                       FrameBlender::FrameDisposalMethod aDisposalMethod /* = FrameBlender::kDisposeKeep */,
+Decoder::PostFrameStop(Opacity aFrameOpacity /* = Opacity::TRANSPARENT */,
+                       DisposalMethod aDisposalMethod /* = DisposalMethod::KEEP */,
                        int32_t aTimeout /* = 0 */,
-                       FrameBlender::FrameBlendMethod aBlendMethod /* = FrameBlender::kBlendOver */)
+                       BlendMethod aBlendMethod /* = BlendMethod::OVER */)
 {
   // We should be mid-frame
   MOZ_ASSERT(!IsSizeDecode(), "Stopping frame during a size 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;
 
-  if (aFrameAlpha == FrameBlender::kFrameOpaque) {
+  if (aFrameOpacity == Opacity::OPAQUE) {
     mCurrentFrame->SetHasNoAlpha();
   }
 
-  mCurrentFrame->SetFrameDisposalMethod(aDisposalMethod);
+  mCurrentFrame->SetDisposalMethod(aDisposalMethod);
   mCurrentFrame->SetRawTimeout(aTimeout);
   mCurrentFrame->SetBlendMethod(aBlendMethod);
   mCurrentFrame->ImageUpdated(mCurrentFrame->GetRect());
 
   mProgress |= FLAG_FRAME_COMPLETE | FLAG_ONLOAD_UNBLOCKED;
 }
 
 void
--- a/image/src/Decoder.h
+++ b/image/src/Decoder.h
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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_IMAGELIB_DECODER_H_
 #define MOZILLA_IMAGELIB_DECODER_H_
 
+#include "FrameAnimator.h"
 #include "RasterImage.h"
 #include "mozilla/RefPtr.h"
 #include "DecodePool.h"
 #include "ImageMetadata.h"
 #include "Orientation.h"
 #include "mozilla/Telemetry.h"
 
 namespace mozilla {
@@ -221,35 +222,35 @@ protected:
                 Orientation aOrientation = Orientation());
 
   // Called by decoders if they determine that the image has transparency.
   //
   // This should be fired as early as possible to allow observers to do things
   // that affect content, so it's necessarily pessimistic - if there's a
   // possibility that the image has transparency, for example because its header
   // specifies that it has an alpha channel, we fire PostHasTransparency
-  // immediately. PostFrameStop's aFrameAlpha argument, on the other hand, is
+  // immediately. PostFrameStop's aFrameOpacity argument, on the other hand, is
   // only used internally to ImageLib. Because PostFrameStop isn't delivered
   // until the entire frame has been decoded, decoders may take into account the
   // actual contents of the frame and give a more accurate result.
   void PostHasTransparency();
 
   // Called by decoders when they begin a frame. Informs the image, sends
   // notifications, and does internal book-keeping.
   void PostFrameStart();
 
   // 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(FrameBlender::FrameAlpha aFrameAlpha = FrameBlender::kFrameHasAlpha,
-                     FrameBlender::FrameDisposalMethod aDisposalMethod = FrameBlender::kDisposeKeep,
+  void PostFrameStop(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY,
+                     DisposalMethod aDisposalMethod = DisposalMethod::KEEP,
                      int32_t aTimeout = 0,
-                     FrameBlender::FrameBlendMethod aBlendMethod = FrameBlender::kBlendOver);
+                     BlendMethod aBlendMethod = BlendMethod::OVER);
 
   // Called by the decoders when they have a region to invalidate. We may not
   // actually pass these invalidations on right away.
   void PostInvalidation(nsIntRect& aRect);
 
   // Called by the decoders when they have successfully decoded the image. This
   // may occur as the result of the decoder getting to the appropriate point in
   // the stream, or by us calling FinishInternal().
--- a/image/src/FrameAnimator.cpp
+++ b/image/src/FrameAnimator.cpp
@@ -1,20 +1,27 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * 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/. */
 
 #include "FrameAnimator.h"
-#include "FrameBlender.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Move.h"
+#include "imgIContainer.h"
+#include "MainThreadUtils.h"
 #include "RasterImage.h"
 
-#include "imgIContainer.h"
+#include "pixman.h"
 
 namespace mozilla {
+
+using namespace gfx;
+
 namespace image {
 
 int32_t
 FrameAnimator::GetSingleLoopTime() const
 {
   // If we aren't done decoding, we don't know the image's full play time.
   if (!mDoneDecoding) {
     return -1;
@@ -22,17 +29,17 @@ FrameAnimator::GetSingleLoopTime() const
 
   // If we're not looping, a single loop time has no meaning
   if (mAnimationMode != imgIContainer::kNormalAnimMode) {
     return -1;
   }
 
   uint32_t looptime = 0;
   for (uint32_t i = 0; i < mImage->GetNumFrames(); ++i) {
-    int32_t timeout = mFrameBlender.GetTimeoutForFrame(i);
+    int32_t timeout = GetTimeoutForFrame(i);
     if (timeout >= 0) {
       looptime += static_cast<uint32_t>(timeout);
     } else {
       // If we have a frame that never times out, we're probably in an error
       // case, but let's handle it more gracefully.
       NS_WARNING("Negative frame timeout - how did this happen?");
       return -1;
     }
@@ -41,17 +48,17 @@ FrameAnimator::GetSingleLoopTime() const
   return looptime;
 }
 
 TimeStamp
 FrameAnimator::GetCurrentImgFrameEndTime() const
 {
   TimeStamp currentFrameTime = mCurrentAnimationFrameTime;
   int32_t timeout =
-    mFrameBlender.GetTimeoutForFrame(mCurrentAnimationFrameIndex);
+    GetTimeoutForFrame(mCurrentAnimationFrameIndex);
 
   if (timeout < 0) {
     // We need to return a sentinel value in this case, because our logic
     // doesn't work correctly if we have a negative timeout value. We use
     // one year in the future as the sentinel because it works with the loop
     // in RequestRefresh() below.
     // XXX(seth): It'd be preferable to make our logic work correctly with
     // negative timeouts.
@@ -72,17 +79,17 @@ FrameAnimator::AdvanceFrame(TimeStamp aT
   NS_ASSERTION(aTime <= TimeStamp::Now(),
                "Given time appears to be in the future");
 
   uint32_t currentFrameIndex = mCurrentAnimationFrameIndex;
   uint32_t nextFrameIndex = currentFrameIndex + 1;
   int32_t timeout = 0;
 
   RefreshResult ret;
-  RawAccessFrameRef nextFrame = mFrameBlender.GetRawFrame(nextFrameIndex);
+  RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex);
 
   // If we're done decoding, we know we've got everything we're going to get.
   // If we aren't, we only display fully-downloaded frames; everything else
   // gets delayed.
   bool canDisplay = mDoneDecoding || (nextFrame && nextFrame->ImageComplete());
 
   if (!canDisplay) {
     // Uh oh, the frame we want to show is currently being decoded (partial)
@@ -91,56 +98,56 @@ FrameAnimator::AdvanceFrame(TimeStamp aT
   }
 
   // If we're done decoding the next frame, go ahead and display it now and
   // reinit with the next frame's delay time.
   if (mImage->GetNumFrames() == nextFrameIndex) {
     // End of an animation loop...
 
     // If we are not looping forever, initialize the loop counter
-    if (mLoopCounter < 0 && mFrameBlender.LoopCount() >= 0) {
-      mLoopCounter = mFrameBlender.LoopCount();
+    if (mLoopRemainingCount < 0 && LoopCount() >= 0) {
+      mLoopRemainingCount = LoopCount();
     }
 
     // If animation mode is "loop once", or we're at end of loop counter,
-    // it's time to stop animating
+    // it's time to stop animating.
     if (mAnimationMode == imgIContainer::kLoopOnceAnimMode ||
-        mLoopCounter == 0) {
+        mLoopRemainingCount == 0) {
       ret.animationFinished = true;
     }
 
     nextFrameIndex = 0;
 
-    if (mLoopCounter > 0) {
-      mLoopCounter--;
+    if (mLoopRemainingCount > 0) {
+      mLoopRemainingCount--;
     }
 
     // If we're done, exit early.
     if (ret.animationFinished) {
       return ret;
     }
   }
 
-  timeout = mFrameBlender.GetTimeoutForFrame(nextFrameIndex);
+  timeout = GetTimeoutForFrame(nextFrameIndex);
 
   // Bad data
   if (timeout < 0) {
     ret.animationFinished = true;
     ret.error = true;
   }
 
   if (nextFrameIndex == 0) {
     ret.dirtyRect = mFirstFrameRefreshArea;
   } else {
     // Change frame
     if (nextFrameIndex != currentFrameIndex + 1) {
-      nextFrame = mFrameBlender.GetRawFrame(nextFrameIndex);
+      nextFrame = GetRawFrame(nextFrameIndex);
     }
 
-    if (!mFrameBlender.DoBlend(&ret.dirtyRect, currentFrameIndex,
+    if (!DoBlend(&ret.dirtyRect, currentFrameIndex,
                                nextFrameIndex)) {
       // something went wrong, move on to next
       NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
       nextFrame->SetCompositingFailed(true);
       mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime();
       mCurrentAnimationFrameIndex = nextFrameIndex;
 
       ret.error = true;
@@ -205,16 +212,17 @@ FrameAnimator::RequestRefresh(const Time
 
   return ret;
 }
 
 void
 FrameAnimator::ResetAnimation()
 {
   mCurrentAnimationFrameIndex = 0;
+  mLastCompositedFrameIndex = -1;
 }
 
 void
 FrameAnimator::SetDoneDecoding(bool aDone)
 {
   mDoneDecoding = aDone;
 }
 
@@ -257,10 +265,508 @@ FrameAnimator::GetCurrentAnimationFrameI
 }
 
 nsIntRect
 FrameAnimator::GetFirstFrameRefreshArea() const
 {
   return mFirstFrameRefreshArea;
 }
 
+DrawableFrameRef
+FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
+{
+  MOZ_ASSERT(aFrameNum != 0, "First frame is never composited");
+
+  // If we have a composited version of this frame, return that.
+  if (mLastCompositedFrameIndex == int32_t(aFrameNum)) {
+    return mCompositingFrame->DrawableRef();
+  }
+
+  // Otherwise return the raw frame. DoBlend is required to ensure that we only
+  // hit this case if the frame is not paletted and doesn't require compositing.
+  DrawableFrameRef ref =
+    SurfaceCache::Lookup(ImageKey(mImage),
+                         RasterSurfaceKey(mSize,
+                                          0,  // Default decode flags.
+                                          aFrameNum));
+  MOZ_ASSERT(!ref || !ref->GetIsPaletted(), "About to return a paletted frame");
+  return ref;
+}
+
+int32_t
+FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
+{
+  RawAccessFrameRef frame = GetRawFrame(aFrameNum);
+  const int32_t timeout = frame->GetRawTimeout();
+
+  // Ensure a minimal time between updates so we don't throttle the UI thread.
+  // consider 0 == unspecified and make it fast but not too fast.  Unless we
+  // have a single loop GIF. See bug 890743, bug 125137, bug 139677, and bug
+  // 207059. The behavior of recent IE and Opera versions seems to be:
+  // IE 6/Win:
+  //   10 - 50ms go 100ms
+  //   >50ms go correct speed
+  // Opera 7 final/Win:
+  //   10ms goes 100ms
+  //   >10ms go correct speed
+  // It seems that there are broken tools out there that set a 0ms or 10ms
+  // timeout when they really want a "default" one.  So munge values in that
+  // range.
+  if (timeout >= 0 && timeout <= 10 && mLoopCount != 0) {
+    return 100;
+  }
+
+  return timeout;
+}
+
+size_t
+FrameAnimator::SizeOfCompositingFrames(gfxMemoryLocation aLocation,
+                                       MallocSizeOf aMallocSizeOf) const
+{
+  size_t n = 0;
+
+  if (mCompositingFrame) {
+    n += mCompositingFrame->SizeOfExcludingThis(aLocation, aMallocSizeOf);
+  }
+  if (mCompositingPrevFrame) {
+    n += mCompositingPrevFrame->SizeOfExcludingThis(aLocation, aMallocSizeOf);
+  }
+
+  return n;
+}
+
+RawAccessFrameRef
+FrameAnimator::GetRawFrame(uint32_t aFrameNum) const
+{
+  DrawableFrameRef ref =
+    SurfaceCache::Lookup(ImageKey(mImage),
+                         RasterSurfaceKey(mSize,
+                                          0,  // Default decode flags.
+                                          aFrameNum));
+  return ref ? ref->RawAccessRef()
+             : RawAccessFrameRef();
+}
+
+//******************************************************************************
+// DoBlend gets called when the timer for animation get fired and we have to
+// update the composited frame of the animation.
+bool
+FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
+                       uint32_t aPrevFrameIndex,
+                       uint32_t aNextFrameIndex)
+{
+  RawAccessFrameRef prevFrame = GetRawFrame(aPrevFrameIndex);
+  RawAccessFrameRef nextFrame = GetRawFrame(aNextFrameIndex);
+
+  MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");
+
+  DisposalMethod prevFrameDisposalMethod = prevFrame->GetDisposalMethod();
+  if (prevFrameDisposalMethod == DisposalMethod::RESTORE_PREVIOUS &&
+      !mCompositingPrevFrame) {
+    prevFrameDisposalMethod = DisposalMethod::CLEAR;
+  }
+
+  nsIntRect prevFrameRect = prevFrame->GetRect();
+  bool isFullPrevFrame = (prevFrameRect.x == 0 && prevFrameRect.y == 0 &&
+                          prevFrameRect.width == mSize.width &&
+                          prevFrameRect.height == mSize.height);
+
+  // Optimization: DisposeClearAll if the previous frame is the same size as
+  //               container and it's clearing itself
+  if (isFullPrevFrame &&
+      (prevFrameDisposalMethod == DisposalMethod::CLEAR)) {
+    prevFrameDisposalMethod = DisposalMethod::CLEAR_ALL;
+  }
+
+  DisposalMethod nextFrameDisposalMethod = nextFrame->GetDisposalMethod();
+  nsIntRect nextFrameRect = nextFrame->GetRect();
+  bool isFullNextFrame = (nextFrameRect.x == 0 && nextFrameRect.y == 0 &&
+                          nextFrameRect.width == mSize.width &&
+                          nextFrameRect.height == mSize.height);
+
+  if (!nextFrame->GetIsPaletted()) {
+    // Optimization: Skip compositing if the previous frame wants to clear the
+    //               whole image
+    if (prevFrameDisposalMethod == DisposalMethod::CLEAR_ALL) {
+      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
+      return true;
+    }
+
+    // Optimization: Skip compositing if this frame is the same size as the
+    //               container and it's fully drawing over prev frame (no alpha)
+    if (isFullNextFrame &&
+        (nextFrameDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) &&
+        !nextFrame->GetHasAlpha()) {
+      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
+      return true;
+    }
+  }
+
+  // Calculate area that needs updating
+  switch (prevFrameDisposalMethod) {
+    default:
+    case DisposalMethod::NOT_SPECIFIED:
+    case DisposalMethod::KEEP:
+      *aDirtyRect = nextFrameRect;
+      break;
+
+    case DisposalMethod::CLEAR_ALL:
+      // Whole image container is cleared
+      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
+      break;
+
+    case DisposalMethod::CLEAR:
+      // Calc area that needs to be redrawn (the combination of previous and
+      // this frame)
+      // XXX - This could be done with multiple framechanged calls
+      //       Having prevFrame way at the top of the image, and nextFrame
+      //       way at the bottom, and both frames being small, we'd be
+      //       telling framechanged to refresh the whole image when only two
+      //       small areas are needed.
+      aDirtyRect->UnionRect(nextFrameRect, prevFrameRect);
+      break;
+
+    case DisposalMethod::RESTORE_PREVIOUS:
+      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
+      break;
+  }
+
+  // Optimization:
+  //   Skip compositing if the last composited frame is this frame
+  //   (Only one composited frame was made for this animation.  Example:
+  //    Only Frame 3 of a 10 frame image required us to build a composite frame
+  //    On the second loop, we do not need to rebuild the frame
+  //    since it's still sitting in compositingFrame)
+  if (mLastCompositedFrameIndex == int32_t(aNextFrameIndex)) {
+    return true;
+  }
+
+  bool needToBlankComposite = false;
+
+  // Create the Compositing Frame
+  if (!mCompositingFrame) {
+    nsRefPtr<imgFrame> newFrame = new imgFrame;
+    nsresult rv = newFrame->InitForDecoder(ThebesIntSize(mSize),
+                                           SurfaceFormat::B8G8R8A8);
+    if (NS_FAILED(rv)) {
+      mCompositingFrame.reset();
+      return false;
+    }
+    mCompositingFrame = newFrame->RawAccessRef();
+    needToBlankComposite = true;
+  } else if (int32_t(aNextFrameIndex) != mLastCompositedFrameIndex+1) {
+
+    // If we are not drawing on top of last composited frame,
+    // then we are building a new composite frame, so let's clear it first.
+    needToBlankComposite = true;
+  }
+
+  // More optimizations possible when next frame is not transparent
+  // But if the next frame has DisposalMethod::RESTORE_PREVIOUS,
+  // this "no disposal" optimization is not possible,
+  // because the frame in "after disposal operation" state
+  // needs to be stored in compositingFrame, so it can be
+  // copied into compositingPrevFrame later.
+  bool doDisposal = true;
+  if (!nextFrame->GetHasAlpha() &&
+      nextFrameDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
+    if (isFullNextFrame) {
+      // Optimization: No need to dispose prev.frame when
+      // next frame is full frame and not transparent.
+      doDisposal = false;
+      // No need to blank the composite frame
+      needToBlankComposite = false;
+    } else {
+      if ((prevFrameRect.x >= nextFrameRect.x) &&
+          (prevFrameRect.y >= nextFrameRect.y) &&
+          (prevFrameRect.x + prevFrameRect.width <=
+              nextFrameRect.x + nextFrameRect.width) &&
+          (prevFrameRect.y + prevFrameRect.height <=
+              nextFrameRect.y + nextFrameRect.height)) {
+        // Optimization: No need to dispose prev.frame when
+        // next frame fully overlaps previous frame.
+        doDisposal = false;
+      }
+    }
+  }
+
+  if (doDisposal) {
+    // Dispose of previous: clear, restore, or keep (copy)
+    switch (prevFrameDisposalMethod) {
+      case DisposalMethod::CLEAR:
+        if (needToBlankComposite) {
+          // If we just created the composite, it could have anything in its
+          // buffer. Clear whole frame
+          ClearFrame(mCompositingFrame->GetRawData(),
+                     mCompositingFrame->GetRect());
+        } else {
+          // Only blank out previous frame area (both color & Mask/Alpha)
+          ClearFrame(mCompositingFrame->GetRawData(),
+                     mCompositingFrame->GetRect(),
+                     prevFrameRect);
+        }
+        break;
+
+      case DisposalMethod::CLEAR_ALL:
+        ClearFrame(mCompositingFrame->GetRawData(),
+                   mCompositingFrame->GetRect());
+        break;
+
+      case DisposalMethod::RESTORE_PREVIOUS:
+        // It would be better to copy only the area changed back to
+        // compositingFrame.
+        if (mCompositingPrevFrame) {
+          CopyFrameImage(mCompositingPrevFrame->GetRawData(),
+                         mCompositingPrevFrame->GetRect(),
+                         mCompositingFrame->GetRawData(),
+                         mCompositingFrame->GetRect());
+
+          // destroy only if we don't need it for this frame's disposal
+          if (nextFrameDisposalMethod !=
+              DisposalMethod::RESTORE_PREVIOUS) {
+            mCompositingPrevFrame.reset();
+          }
+        } else {
+          ClearFrame(mCompositingFrame->GetRawData(),
+                     mCompositingFrame->GetRect());
+        }
+        break;
+
+      default:
+        // Copy previous frame into compositingFrame before we put the new
+        // frame on top
+        // Assumes that the previous frame represents a full frame (it could be
+        // smaller in size than the container, as long as the frame before it
+        // erased itself)
+        // Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1)
+        // will always be a valid frame number.
+        if (mLastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
+          if (isFullPrevFrame && !prevFrame->GetIsPaletted()) {
+            // Just copy the bits
+            CopyFrameImage(prevFrame->GetRawData(),
+                           prevFrame->GetRect(),
+                           mCompositingFrame->GetRawData(),
+                           mCompositingFrame->GetRect());
+          } else {
+            if (needToBlankComposite) {
+              // Only blank composite when prev is transparent or not full.
+              if (prevFrame->GetHasAlpha() || !isFullPrevFrame) {
+                ClearFrame(mCompositingFrame->GetRawData(),
+                           mCompositingFrame->GetRect());
+              }
+            }
+            DrawFrameTo(prevFrame->GetRawData(), prevFrameRect,
+                        prevFrame->PaletteDataLength(),
+                        prevFrame->GetHasAlpha(),
+                        mCompositingFrame->GetRawData(),
+                        mCompositingFrame->GetRect(),
+                        prevFrame->GetBlendMethod());
+          }
+        }
+    }
+  } else if (needToBlankComposite) {
+    // If we just created the composite, it could have anything in its
+    // buffers. Clear them
+    ClearFrame(mCompositingFrame->GetRawData(),
+               mCompositingFrame->GetRect());
+  }
+
+  // Check if the frame we are composing wants the previous image restored after
+  // it is done. Don't store it (again) if last frame wanted its image restored
+  // too
+  if ((nextFrameDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) &&
+      (prevFrameDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) {
+    // We are storing the whole image.
+    // It would be better if we just stored the area that nextFrame is going to
+    // overwrite.
+    if (!mCompositingPrevFrame) {
+      nsRefPtr<imgFrame> newFrame = new imgFrame;
+      nsresult rv = newFrame->InitForDecoder(ThebesIntSize(mSize),
+                                             SurfaceFormat::B8G8R8A8);
+      if (NS_FAILED(rv)) {
+        mCompositingPrevFrame.reset();
+        return false;
+      }
+
+      mCompositingPrevFrame = newFrame->RawAccessRef();
+    }
+
+    CopyFrameImage(mCompositingFrame->GetRawData(),
+                   mCompositingFrame->GetRect(),
+                   mCompositingPrevFrame->GetRawData(),
+                   mCompositingPrevFrame->GetRect());
+  }
+
+  // blit next frame into it's correct spot
+  DrawFrameTo(nextFrame->GetRawData(), nextFrameRect,
+              nextFrame->PaletteDataLength(),
+              nextFrame->GetHasAlpha(),
+              mCompositingFrame->GetRawData(),
+              mCompositingFrame->GetRect(),
+              nextFrame->GetBlendMethod());
+
+  // Set timeout of CompositeFrame to timeout of frame we just composed
+  // Bug 177948
+  int32_t timeout = nextFrame->GetRawTimeout();
+  mCompositingFrame->SetRawTimeout(timeout);
+
+  // Tell the image that it is fully 'downloaded'.
+  nsresult rv =
+    mCompositingFrame->ImageUpdated(mCompositingFrame->GetRect());
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  mLastCompositedFrameIndex = int32_t(aNextFrameIndex);
+
+  return true;
+}
+
+//******************************************************************************
+// Fill aFrame with black. Does also clears the mask.
+void
+FrameAnimator::ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect)
+{
+  if (!aFrameData) {
+    return;
+  }
+
+  memset(aFrameData, 0, aFrameRect.width * aFrameRect.height * 4);
+}
+
+//******************************************************************************
+void
+FrameAnimator::ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect,
+                          const nsIntRect& aRectToClear)
+{
+  if (!aFrameData || aFrameRect.width <= 0 || aFrameRect.height <= 0 ||
+      aRectToClear.width <= 0 || aRectToClear.height <= 0) {
+    return;
+  }
+
+  nsIntRect toClear = aFrameRect.Intersect(aRectToClear);
+  if (toClear.IsEmpty()) {
+    return;
+  }
+
+  uint32_t bytesPerRow = aFrameRect.width * 4;
+  for (int row = toClear.y; row < toClear.y + toClear.height; ++row) {
+    memset(aFrameData + toClear.x * 4 + row * bytesPerRow, 0,
+           toClear.width * 4);
+  }
+}
+
+//******************************************************************************
+// Whether we succeed or fail will not cause a crash, and there's not much
+// we can do about a failure, so there we don't return a nsresult
+bool
+FrameAnimator::CopyFrameImage(const uint8_t* aDataSrc, const nsIntRect& aRectSrc,
+                              uint8_t* aDataDest, const nsIntRect& aRectDest)
+{
+  uint32_t dataLengthSrc = aRectSrc.width * aRectSrc.height * 4;
+  uint32_t dataLengthDest = aRectDest.width * aRectDest.height * 4;
+
+  if (!aDataDest || !aDataSrc || dataLengthSrc != dataLengthDest) {
+    return false;
+  }
+
+  memcpy(aDataDest, aDataSrc, dataLengthDest);
+
+  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)
+{
+  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;
+  }
+  // Outside the destination frame, skip it
+  if ((aSrcRect.x > aDstRect.width) || (aSrcRect.y > aDstRect.height)) {
+    return NS_OK;
+  }
+
+  if (aSrcPaletteLength) {
+    // Larger than the destination frame, clip it
+    int32_t width = std::min(aSrcRect.width, aDstRect.width - aSrcRect.x);
+    int32_t height = std::min(aSrcRect.height, aDstRect.height - aSrcRect.y);
+
+    // The clipped image must now fully fit within destination image frame
+    NS_ASSERTION((aSrcRect.x >= 0) && (aSrcRect.y >= 0) &&
+                 (aSrcRect.x + width <= aDstRect.width) &&
+                 (aSrcRect.y + height <= aDstRect.height),
+                "FrameAnimator::DrawFrameTo: Invalid aSrcRect");
+
+    // clipped image size may be smaller than source, but not larger
+    NS_ASSERTION((width <= aSrcRect.width) && (height <= aSrcRect.height),
+                 "FrameAnimator::DrawFrameTo: source must be smaller than dest");
+
+    // Get pointers to image data
+    const uint8_t* srcPixels = aSrcData + aSrcPaletteLength;
+    uint32_t* dstPixels = reinterpret_cast<uint32_t*>(aDstPixels);
+    const uint32_t* colormap = reinterpret_cast<const uint32_t*>(aSrcData);
+
+    // Skip to the right offset
+    dstPixels += aSrcRect.x + (aSrcRect.y * aDstRect.width);
+    if (!aSrcHasAlpha) {
+      for (int32_t r = height; r > 0; --r) {
+        for (int32_t c = 0; c < width; c++) {
+          dstPixels[c] = colormap[srcPixels[c]];
+        }
+        // Go to the next row in the source resp. destination image
+        srcPixels += aSrcRect.width;
+        dstPixels += aDstRect.width;
+      }
+    } else {
+      for (int32_t r = height; r > 0; --r) {
+        for (int32_t c = 0; c < width; c++) {
+          const uint32_t color = colormap[srcPixels[c]];
+          if (color) {
+            dstPixels[c] = color;
+          }
+        }
+        // Go to the next row in the source resp. destination image
+        srcPixels += aSrcRect.width;
+        dstPixels += aDstRect.width;
+      }
+    }
+  } else {
+    pixman_image_t* src =
+      pixman_image_create_bits(
+          aSrcHasAlpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
+          aSrcRect.width, aSrcRect.height,
+          reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(aSrcData)),
+          aSrcRect.width * 4);
+    pixman_image_t* dst =
+      pixman_image_create_bits(PIXMAN_a8r8g8b8,
+                               aDstRect.width,
+                               aDstRect.height,
+                               reinterpret_cast<uint32_t*>(aDstPixels),
+                               aDstRect.width * 4);
+
+    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);
+
+    pixman_image_unref(src);
+    pixman_image_unref(dst);
+  }
+
+  return NS_OK;
+}
+
 } // namespace image
 } // namespace mozilla
--- a/image/src/FrameAnimator.h
+++ b/image/src/FrameAnimator.h
@@ -2,35 +2,42 @@
  *
  * 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_src_FrameAnimator_h
 #define mozilla_image_src_FrameAnimator_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"
 
 namespace mozilla {
 namespace image {
 
-class FrameBlender;
 class RasterImage;
 
 class FrameAnimator
 {
 public:
   FrameAnimator(RasterImage* aImage,
-                FrameBlender& aFrameBlender,
+                gfx::IntSize aSize,
                 uint16_t aAnimationMode)
-    : mCurrentAnimationFrameIndex(0)
-    , mLoopCounter(-1)
-    , mImage(aImage)
-    , mFrameBlender(aFrameBlender)
+    : mImage(aImage)
+    , mSize(aSize)
+    , mCurrentAnimationFrameIndex(0)
+    , mLoopRemainingCount(-1)
+    , mLastCompositedFrameIndex(-1)
+    , mLoopCount(-1)
     , mAnimationMode(aAnimationMode)
     , mDoneDecoding(false)
   { }
 
   /**
    * Return value from RequestRefresh. Tells callers what happened in that call
    * to RequestRefresh.
    */
@@ -119,16 +126,40 @@ public:
    */
   uint32_t GetCurrentAnimationFrameIndex() const;
 
   /**
    * Get the area we refresh when we loop around to the first frame.
    */
   nsIntRect GetFirstFrameRefreshArea() const;
 
+  /**
+   * If we have a composited frame for @aFrameNum, returns it. Otherwise, returns
+   * an empty DrawableFrameRef. It is an error to call this method with
+   * aFrameNum == 0, because the first frame is never composited.
+   */
+  DrawableFrameRef GetCompositedFrame(uint32_t aFrameNum);
+
+  /*
+   * Returns the frame's adjusted timeout. If the animation loops and the
+   * timeout falls in between a certain range then the timeout is adjusted so
+   * that it's never 0. If the animation does not loop then no adjustments are
+   * made.
+   */
+  int32_t GetTimeoutForFrame(uint32_t aFrameNum) const;
+
+  /*
+   * Set number of times to loop the image.
+   * @note -1 means loop forever.
+   */
+  void SetLoopCount(int32_t aLoopCount) { mLoopCount = aLoopCount; }
+  int32_t LoopCount() const { return mLoopCount; }
+
+  size_t SizeOfCompositingFrames(gfxMemoryLocation aLocation,
+                                 MallocSizeOf aMallocSizeOf) const;
 private: // methods
   /**
    * Gets the length of a single loop of this image, in milliseconds.
    *
    * If this image is not finished decoding, is not animated, or it is animated
    * but does not loop, returns -1. Can return 0 in the case of an animated
    * image that has a 0ms delay between its frames and does not loop.
    */
@@ -150,34 +181,105 @@ private: // methods
 
   /**
    * Get the time the frame we're currently displaying is supposed to end.
    *
    * In the error case, returns an "infinity" timestamp.
    */
   TimeStamp GetCurrentImgFrameEndTime() const;
 
+  bool DoBlend(nsIntRect* aDirtyRect, uint32_t aPrevFrameIndex,
+               uint32_t aNextFrameIndex);
+
+  /**
+   * Get the @aIndex-th frame in the frame index, ignoring results of blending.
+   */
+  RawAccessFrameRef GetRawFrame(uint32_t aFrameNum) const;
+
+  /** Clears an area of <aFrame> with transparent black.
+   *
+   * @param aFrameData Target Frame data
+   * @param aFrameRect The rectangle of the data pointed ot by aFrameData
+   *
+   * @note Does also clears the transparency mask
+   */
+  static void ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect);
+
+  //! @overload
+  static void ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect,
+                         const nsIntRect& aRectToClear);
+
+  //! Copy one frame's image and mask into another
+  static bool CopyFrameImage(const uint8_t* aDataSrc, const nsIntRect& aRectSrc,
+                             uint8_t* aDataDest, const nsIntRect& aRectDest);
+
+  /**
+   * Draws one frame's image to into another, at the position specified by
+   * aSrcRect.
+   *
+   * @aSrcData the raw data of the current frame being drawn
+   * @aSrcRect the size of the source frame, and the position of that frame in
+   *           the composition frame
+   * @aSrcPaletteLength the length (in bytes) of the palette at the beginning
+   *                    of the source data (0 if image is not paletted)
+   * @aSrcHasAlpha whether the source data represents an image with alpha
+   * @aDstPixels the raw data of the composition frame where the current frame
+   *             is drawn into (32-bit ARGB)
+   * @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);
+
 private: // data
+  //! A weak pointer to our owning image.
+  RasterImage* mImage;
+
+  //! The intrinsic size of the image.
+  gfx::IntSize mSize;
+
+  /** For managing blending of frames
+   *
+   * Some animations will use the compositingFrame to composite images
+   * and just hand this back to the caller when it is time to draw the frame.
+   * NOTE: When clearing compositingFrame, remember to set
+   *       lastCompositedFrameIndex to -1.  Code assume that if
+   *       lastCompositedFrameIndex >= 0 then compositingFrame exists.
+   */
+  RawAccessFrameRef mCompositingFrame;
+
+  /** the previous composited frame, for DISPOSE_RESTORE_PREVIOUS
+   *
+   * The Previous Frame (all frames composited up to the current) needs to be
+   * stored in cases where the image specifies it wants the last frame back
+   * when it's done with the current frame.
+   */
+  RawAccessFrameRef mCompositingPrevFrame;
+
   //! Area of the first frame that needs to be redrawn on subsequent loops.
   nsIntRect mFirstFrameRefreshArea;
 
   //! the time that the animation advanced to the current frame
   TimeStamp mCurrentAnimationFrameTime;
 
   //! The current frame index we're on. 0 to (numFrames - 1).
   uint32_t mCurrentAnimationFrameIndex;
 
   //! number of loops remaining before animation stops (-1 no stop)
-  int32_t mLoopCounter;
+  int32_t mLoopRemainingCount;
 
-  //! A weak pointer to our owner.
-  RasterImage* mImage;
+  //! Track the last composited frame for Optimizations (See DoComposite code)
+  int32_t mLastCompositedFrameIndex;
 
-  //! All the frames of the image, shared with our owner
-  FrameBlender& mFrameBlender;
+  //! The total number of loops for the image.
+  int32_t mLoopCount;
 
   //! The animation mode of this image. Constants defined in imgIContainer.
   uint16_t mAnimationMode;
 
   //! Whether this image is done being decoded.
   bool mDoneDecoding;
 };
 
deleted file mode 100644
--- a/image/src/FrameBlender.cpp
+++ /dev/null
@@ -1,525 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- * 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/. */
-
-#include "FrameBlender.h"
-
-#include "mozilla/MemoryReporting.h"
-#include "mozilla/Move.h"
-#include "MainThreadUtils.h"
-
-#include "pixman.h"
-
-namespace mozilla {
-
-using namespace gfx;
-
-namespace image {
-
-DrawableFrameRef
-FrameBlender::GetCompositedFrame(uint32_t aFrameNum)
-{
-  MOZ_ASSERT(aFrameNum != 0, "First frame is never composited");
-
-  // If we have a composited version of this frame, return that.
-  if (mLastCompositedFrameIndex == int32_t(aFrameNum)) {
-    return mCompositingFrame->DrawableRef();
-  }
-
-  // Otherwise return the raw frame. DoBlend is required to ensure that we only
-  // hit this case if the frame is not paletted and doesn't require compositing.
-  DrawableFrameRef ref =
-    SurfaceCache::Lookup(mImageKey,
-                         RasterSurfaceKey(mSize,
-                                          0,  // Default decode flags.
-                                          aFrameNum));
-  MOZ_ASSERT(!ref || !ref->GetIsPaletted(), "About to return a paletted frame");
-  return ref;
-}
-
-RawAccessFrameRef
-FrameBlender::GetRawFrame(uint32_t aFrameNum)
-{
-  DrawableFrameRef ref =
-    SurfaceCache::Lookup(mImageKey,
-                         RasterSurfaceKey(mSize,
-                                          0,  // Default decode flags.
-                                          aFrameNum));
-  return ref ? ref->RawAccessRef()
-             : RawAccessFrameRef();
-}
-
-int32_t
-FrameBlender::GetTimeoutForFrame(uint32_t aFrameNum)
-{
-  RawAccessFrameRef frame = GetRawFrame(aFrameNum);
-  const int32_t timeout = frame->GetRawTimeout();
-
-  // Ensure a minimal time between updates so we don't throttle the UI thread.
-  // consider 0 == unspecified and make it fast but not too fast.  Unless we
-  // have a single loop GIF. See bug 890743, bug 125137, bug 139677, and bug
-  // 207059. The behavior of recent IE and Opera versions seems to be:
-  // IE 6/Win:
-  //   10 - 50ms go 100ms
-  //   >50ms go correct speed
-  // Opera 7 final/Win:
-  //   10ms goes 100ms
-  //   >10ms go correct speed
-  // It seems that there are broken tools out there that set a 0ms or 10ms
-  // timeout when they really want a "default" one.  So munge values in that
-  // range.
-  if (timeout >= 0 && timeout <= 10 && mLoopCount != 0) {
-    return 100;
-  }
-
-  return timeout;
-}
-
-//******************************************************************************
-// DoBlend gets called when the timer for animation get fired and we have to
-// update the composited frame of the animation.
-bool
-FrameBlender::DoBlend(nsIntRect* aDirtyRect,
-                      uint32_t aPrevFrameIndex,
-                      uint32_t aNextFrameIndex)
-{
-  RawAccessFrameRef prevFrame = GetRawFrame(aPrevFrameIndex);
-  RawAccessFrameRef nextFrame = GetRawFrame(aNextFrameIndex);
-
-  MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");
-
-  int32_t prevFrameDisposalMethod = prevFrame->GetFrameDisposalMethod();
-  if (prevFrameDisposalMethod == FrameBlender::kDisposeRestorePrevious &&
-      !mCompositingPrevFrame) {
-    prevFrameDisposalMethod = FrameBlender::kDisposeClear;
-  }
-
-  nsIntRect prevFrameRect = prevFrame->GetRect();
-  bool isFullPrevFrame = (prevFrameRect.x == 0 && prevFrameRect.y == 0 &&
-                          prevFrameRect.width == mSize.width &&
-                          prevFrameRect.height == mSize.height);
-
-  // Optimization: DisposeClearAll if the previous frame is the same size as
-  //               container and it's clearing itself
-  if (isFullPrevFrame &&
-      (prevFrameDisposalMethod == FrameBlender::kDisposeClear)) {
-    prevFrameDisposalMethod = FrameBlender::kDisposeClearAll;
-  }
-
-  int32_t nextFrameDisposalMethod = nextFrame->GetFrameDisposalMethod();
-  nsIntRect nextFrameRect = nextFrame->GetRect();
-  bool isFullNextFrame = (nextFrameRect.x == 0 && nextFrameRect.y == 0 &&
-                          nextFrameRect.width == mSize.width &&
-                          nextFrameRect.height == mSize.height);
-
-  if (!nextFrame->GetIsPaletted()) {
-    // Optimization: Skip compositing if the previous frame wants to clear the
-    //               whole image
-    if (prevFrameDisposalMethod == FrameBlender::kDisposeClearAll) {
-      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
-      return true;
-    }
-
-    // Optimization: Skip compositing if this frame is the same size as the
-    //               container and it's fully drawing over prev frame (no alpha)
-    if (isFullNextFrame &&
-        (nextFrameDisposalMethod != FrameBlender::kDisposeRestorePrevious) &&
-        !nextFrame->GetHasAlpha()) {
-      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
-      return true;
-    }
-  }
-
-  // Calculate area that needs updating
-  switch (prevFrameDisposalMethod) {
-    default:
-    case FrameBlender::kDisposeNotSpecified:
-    case FrameBlender::kDisposeKeep:
-      *aDirtyRect = nextFrameRect;
-      break;
-
-    case FrameBlender::kDisposeClearAll:
-      // Whole image container is cleared
-      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
-      break;
-
-    case FrameBlender::kDisposeClear:
-      // Calc area that needs to be redrawn (the combination of previous and
-      // this frame)
-      // XXX - This could be done with multiple framechanged calls
-      //       Having prevFrame way at the top of the image, and nextFrame
-      //       way at the bottom, and both frames being small, we'd be
-      //       telling framechanged to refresh the whole image when only two
-      //       small areas are needed.
-      aDirtyRect->UnionRect(nextFrameRect, prevFrameRect);
-      break;
-
-    case FrameBlender::kDisposeRestorePrevious:
-      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
-      break;
-  }
-
-  // Optimization:
-  //   Skip compositing if the last composited frame is this frame
-  //   (Only one composited frame was made for this animation.  Example:
-  //    Only Frame 3 of a 10 frame image required us to build a composite frame
-  //    On the second loop, we do not need to rebuild the frame
-  //    since it's still sitting in compositingFrame)
-  if (mLastCompositedFrameIndex == int32_t(aNextFrameIndex)) {
-    return true;
-  }
-
-  bool needToBlankComposite = false;
-
-  // Create the Compositing Frame
-  if (!mCompositingFrame) {
-    nsRefPtr<imgFrame> newFrame = new imgFrame;
-    nsresult rv = newFrame->InitForDecoder(ThebesIntSize(mSize),
-                                           SurfaceFormat::B8G8R8A8);
-    if (NS_FAILED(rv)) {
-      mCompositingFrame.reset();
-      return false;
-    }
-    mCompositingFrame = newFrame->RawAccessRef();
-    needToBlankComposite = true;
-  } else if (int32_t(aNextFrameIndex) != mLastCompositedFrameIndex+1) {
-
-    // If we are not drawing on top of last composited frame,
-    // then we are building a new composite frame, so let's clear it first.
-    needToBlankComposite = true;
-  }
-
-  // More optimizations possible when next frame is not transparent
-  // But if the next frame has FrameBlender::kDisposeRestorePrevious,
-  // this "no disposal" optimization is not possible,
-  // because the frame in "after disposal operation" state
-  // needs to be stored in compositingFrame, so it can be
-  // copied into compositingPrevFrame later.
-  bool doDisposal = true;
-  if (!nextFrame->GetHasAlpha() &&
-      nextFrameDisposalMethod != FrameBlender::kDisposeRestorePrevious) {
-    if (isFullNextFrame) {
-      // Optimization: No need to dispose prev.frame when
-      // next frame is full frame and not transparent.
-      doDisposal = false;
-      // No need to blank the composite frame
-      needToBlankComposite = false;
-    } else {
-      if ((prevFrameRect.x >= nextFrameRect.x) &&
-          (prevFrameRect.y >= nextFrameRect.y) &&
-          (prevFrameRect.x + prevFrameRect.width <=
-              nextFrameRect.x + nextFrameRect.width) &&
-          (prevFrameRect.y + prevFrameRect.height <=
-              nextFrameRect.y + nextFrameRect.height)) {
-        // Optimization: No need to dispose prev.frame when
-        // next frame fully overlaps previous frame.
-        doDisposal = false;
-      }
-    }
-  }
-
-  if (doDisposal) {
-    // Dispose of previous: clear, restore, or keep (copy)
-    switch (prevFrameDisposalMethod) {
-      case FrameBlender::kDisposeClear:
-        if (needToBlankComposite) {
-          // If we just created the composite, it could have anything in its
-          // buffer. Clear whole frame
-          ClearFrame(mCompositingFrame->GetRawData(),
-                     mCompositingFrame->GetRect());
-        } else {
-          // Only blank out previous frame area (both color & Mask/Alpha)
-          ClearFrame(mCompositingFrame->GetRawData(),
-                     mCompositingFrame->GetRect(),
-                     prevFrameRect);
-        }
-        break;
-
-      case FrameBlender::kDisposeClearAll:
-        ClearFrame(mCompositingFrame->GetRawData(),
-                   mCompositingFrame->GetRect());
-        break;
-
-      case FrameBlender::kDisposeRestorePrevious:
-        // It would be better to copy only the area changed back to
-        // compositingFrame.
-        if (mCompositingPrevFrame) {
-          CopyFrameImage(mCompositingPrevFrame->GetRawData(),
-                         mCompositingPrevFrame->GetRect(),
-                         mCompositingFrame->GetRawData(),
-                         mCompositingFrame->GetRect());
-
-          // destroy only if we don't need it for this frame's disposal
-          if (nextFrameDisposalMethod !=
-              FrameBlender::kDisposeRestorePrevious) {
-            mCompositingPrevFrame.reset();
-          }
-        } else {
-          ClearFrame(mCompositingFrame->GetRawData(),
-                     mCompositingFrame->GetRect());
-        }
-        break;
-
-      default:
-        // Copy previous frame into compositingFrame before we put the new
-        // frame on top
-        // Assumes that the previous frame represents a full frame (it could be
-        // smaller in size than the container, as long as the frame before it
-        // erased itself)
-        // Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1)
-        // will always be a valid frame number.
-        if (mLastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
-          if (isFullPrevFrame && !prevFrame->GetIsPaletted()) {
-            // Just copy the bits
-            CopyFrameImage(prevFrame->GetRawData(),
-                           prevFrame->GetRect(),
-                           mCompositingFrame->GetRawData(),
-                           mCompositingFrame->GetRect());
-          } else {
-            if (needToBlankComposite) {
-              // Only blank composite when prev is transparent or not full.
-              if (prevFrame->GetHasAlpha() || !isFullPrevFrame) {
-                ClearFrame(mCompositingFrame->GetRawData(),
-                           mCompositingFrame->GetRect());
-              }
-            }
-            DrawFrameTo(prevFrame->GetRawData(), prevFrameRect,
-                        prevFrame->PaletteDataLength(),
-                        prevFrame->GetHasAlpha(),
-                        mCompositingFrame->GetRawData(),
-                        mCompositingFrame->GetRect(),
-                        FrameBlendMethod(prevFrame->GetBlendMethod()));
-          }
-        }
-    }
-  } else if (needToBlankComposite) {
-    // If we just created the composite, it could have anything in its
-    // buffers. Clear them
-    ClearFrame(mCompositingFrame->GetRawData(),
-               mCompositingFrame->GetRect());
-  }
-
-  // Check if the frame we are composing wants the previous image restored after
-  // it is done. Don't store it (again) if last frame wanted its image restored
-  // too
-  if ((nextFrameDisposalMethod == FrameBlender::kDisposeRestorePrevious) &&
-      (prevFrameDisposalMethod != FrameBlender::kDisposeRestorePrevious)) {
-    // We are storing the whole image.
-    // It would be better if we just stored the area that nextFrame is going to
-    // overwrite.
-    if (!mCompositingPrevFrame) {
-      nsRefPtr<imgFrame> newFrame = new imgFrame;
-      nsresult rv = newFrame->InitForDecoder(ThebesIntSize(mSize),
-                                             SurfaceFormat::B8G8R8A8);
-      if (NS_FAILED(rv)) {
-        mCompositingPrevFrame.reset();
-        return false;
-      }
-
-      mCompositingPrevFrame = newFrame->RawAccessRef();
-    }
-
-    CopyFrameImage(mCompositingFrame->GetRawData(),
-                   mCompositingFrame->GetRect(),
-                   mCompositingPrevFrame->GetRawData(),
-                   mCompositingPrevFrame->GetRect());
-  }
-
-  // blit next frame into it's correct spot
-  DrawFrameTo(nextFrame->GetRawData(), nextFrameRect,
-              nextFrame->PaletteDataLength(),
-              nextFrame->GetHasAlpha(),
-              mCompositingFrame->GetRawData(),
-              mCompositingFrame->GetRect(),
-              FrameBlendMethod(nextFrame->GetBlendMethod()));
-
-  // Set timeout of CompositeFrame to timeout of frame we just composed
-  // Bug 177948
-  int32_t timeout = nextFrame->GetRawTimeout();
-  mCompositingFrame->SetRawTimeout(timeout);
-
-  // Tell the image that it is fully 'downloaded'.
-  nsresult rv =
-    mCompositingFrame->ImageUpdated(mCompositingFrame->GetRect());
-  if (NS_FAILED(rv)) {
-    return false;
-  }
-
-  mLastCompositedFrameIndex = int32_t(aNextFrameIndex);
-
-  return true;
-}
-
-//******************************************************************************
-// Fill aFrame with black. Does also clears the mask.
-void
-FrameBlender::ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect)
-{
-  if (!aFrameData) {
-    return;
-  }
-
-  memset(aFrameData, 0, aFrameRect.width * aFrameRect.height * 4);
-}
-
-//******************************************************************************
-void
-FrameBlender::ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect,
-                         const nsIntRect& aRectToClear)
-{
-  if (!aFrameData || aFrameRect.width <= 0 || aFrameRect.height <= 0 ||
-      aRectToClear.width <= 0 || aRectToClear.height <= 0) {
-    return;
-  }
-
-  nsIntRect toClear = aFrameRect.Intersect(aRectToClear);
-  if (toClear.IsEmpty()) {
-    return;
-  }
-
-  uint32_t bytesPerRow = aFrameRect.width * 4;
-  for (int row = toClear.y; row < toClear.y + toClear.height; ++row) {
-    memset(aFrameData + toClear.x * 4 + row * bytesPerRow, 0,
-           toClear.width * 4);
-  }
-}
-
-//******************************************************************************
-// Whether we succeed or fail will not cause a crash, and there's not much
-// we can do about a failure, so there we don't return a nsresult
-bool
-FrameBlender::CopyFrameImage(const uint8_t* aDataSrc, const nsIntRect& aRectSrc,
-                             uint8_t* aDataDest, const nsIntRect& aRectDest)
-{
-  uint32_t dataLengthSrc = aRectSrc.width * aRectSrc.height * 4;
-  uint32_t dataLengthDest = aRectDest.width * aRectDest.height * 4;
-
-  if (!aDataDest || !aDataSrc || dataLengthSrc != dataLengthDest) {
-    return false;
-  }
-
-  memcpy(aDataDest, aDataSrc, dataLengthDest);
-
-  return true;
-}
-
-nsresult
-FrameBlender::DrawFrameTo(const uint8_t* aSrcData, const nsIntRect& aSrcRect,
-                          uint32_t aSrcPaletteLength, bool aSrcHasAlpha,
-                          uint8_t* aDstPixels, const nsIntRect& aDstRect,
-                          FrameBlender::FrameBlendMethod aBlendMethod)
-{
-  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("FrameBlender::DrawFrameTo: negative offsets not allowed");
-    return NS_ERROR_FAILURE;
-  }
-  // Outside the destination frame, skip it
-  if ((aSrcRect.x > aDstRect.width) || (aSrcRect.y > aDstRect.height)) {
-    return NS_OK;
-  }
-
-  if (aSrcPaletteLength) {
-    // Larger than the destination frame, clip it
-    int32_t width = std::min(aSrcRect.width, aDstRect.width - aSrcRect.x);
-    int32_t height = std::min(aSrcRect.height, aDstRect.height - aSrcRect.y);
-
-    // The clipped image must now fully fit within destination image frame
-    NS_ASSERTION((aSrcRect.x >= 0) && (aSrcRect.y >= 0) &&
-                 (aSrcRect.x + width <= aDstRect.width) &&
-                 (aSrcRect.y + height <= aDstRect.height),
-                "FrameBlender::DrawFrameTo: Invalid aSrcRect");
-
-    // clipped image size may be smaller than source, but not larger
-    NS_ASSERTION((width <= aSrcRect.width) && (height <= aSrcRect.height),
-                 "FrameBlender::DrawFrameTo: source must be smaller than dest");
-
-    // Get pointers to image data
-    const uint8_t* srcPixels = aSrcData + aSrcPaletteLength;
-    uint32_t* dstPixels = reinterpret_cast<uint32_t*>(aDstPixels);
-    const uint32_t* colormap = reinterpret_cast<const uint32_t*>(aSrcData);
-
-    // Skip to the right offset
-    dstPixels += aSrcRect.x + (aSrcRect.y * aDstRect.width);
-    if (!aSrcHasAlpha) {
-      for (int32_t r = height; r > 0; --r) {
-        for (int32_t c = 0; c < width; c++) {
-          dstPixels[c] = colormap[srcPixels[c]];
-        }
-        // Go to the next row in the source resp. destination image
-        srcPixels += aSrcRect.width;
-        dstPixels += aDstRect.width;
-      }
-    } else {
-      for (int32_t r = height; r > 0; --r) {
-        for (int32_t c = 0; c < width; c++) {
-          const uint32_t color = colormap[srcPixels[c]];
-          if (color) {
-            dstPixels[c] = color;
-          }
-        }
-        // Go to the next row in the source resp. destination image
-        srcPixels += aSrcRect.width;
-        dstPixels += aDstRect.width;
-      }
-    }
-  } else {
-    pixman_image_t* src =
-      pixman_image_create_bits(
-          aSrcHasAlpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
-          aSrcRect.width, aSrcRect.height,
-          reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(aSrcData)),
-          aSrcRect.width * 4);
-    pixman_image_t* dst =
-      pixman_image_create_bits(PIXMAN_a8r8g8b8,
-                               aDstRect.width,
-                               aDstRect.height,
-                               reinterpret_cast<uint32_t*>(aDstPixels),
-                               aDstRect.width * 4);
-
-    auto op = aBlendMethod == FrameBlender::kBlendSource ? 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);
-
-    pixman_image_unref(src);
-    pixman_image_unref(dst);
-  }
-
-  return NS_OK;
-}
-
-size_t
-FrameBlender::SizeOfDecoded(gfxMemoryLocation aLocation,
-                            MallocSizeOf aMallocSizeOf) const
-{
-  size_t n = 0;
-
-  if (mCompositingFrame) {
-    n += mCompositingFrame->SizeOfExcludingThis(aLocation, aMallocSizeOf);
-  }
-  if (mCompositingPrevFrame) {
-    n += mCompositingPrevFrame->SizeOfExcludingThis(aLocation, aMallocSizeOf);
-  }
-
-  return n;
-}
-
-void
-FrameBlender::ResetAnimation()
-{
-  mLastCompositedFrameIndex = -1;
-}
-
-} // namespace image
-} // namespace mozilla
deleted file mode 100644
--- a/image/src/FrameBlender.h
+++ /dev/null
@@ -1,170 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * 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_imagelib_FrameBlender_h_
-#define mozilla_imagelib_FrameBlender_h_
-
-#include "mozilla/MemoryReporting.h"
-#include "gfx2DGlue.h"
-#include "gfxTypes.h"
-#include "imgFrame.h"
-#include "nsCOMPtr.h"
-#include "SurfaceCache.h"
-
-namespace mozilla {
-namespace image {
-
-/**
- * FrameBlender stores and gives access to imgFrames. It also knows how to
- * blend frames from previous to next, looping if necessary.
- *
- * All logic about when and whether to blend are external to FrameBlender.
- */
-class FrameBlender
-{
-public:
-  FrameBlender(ImageKey aImageKey, gfx::IntSize aSize)
-   : mImageKey(aImageKey)
-   , mSize(aSize)
-   , mLastCompositedFrameIndex(-1)
-   , mLoopCount(-1)
-  { }
-
-  bool DoBlend(nsIntRect* aDirtyRect, uint32_t aPrevFrameIndex,
-               uint32_t aNextFrameIndex);
-
-  /**
-   * If we have a composited frame for @aFrameNum, returns it. Otherwise, returns
-   * an empty DrawableFrameRef. It is an error to call this method with
-   * aFrameNum == 0, because the first frame is never composited.
-   */
-  DrawableFrameRef GetCompositedFrame(uint32_t aFrameNum);
-
-  /**
-   * Get the @aIndex-th frame in the frame index, ignoring results of blending.
-   */
-  RawAccessFrameRef GetRawFrame(uint32_t aFrameNum);
-
-  /*
-   * Returns the frame's adjusted timeout. If the animation loops and the
-   * timeout falls in between a certain range then the timeout is adjusted so
-   * that it's never 0. If the animation does not loop then no adjustments are
-   * made.
-   */
-  int32_t GetTimeoutForFrame(uint32_t aFrameNum);
-
-  /*
-   * Set number of times to loop the image.
-   * @note -1 means loop forever.
-   */
-  void SetLoopCount(int32_t aLoopCount) { mLoopCount = aLoopCount; }
-  int32_t LoopCount() const { return mLoopCount; }
-
-  size_t SizeOfDecoded(gfxMemoryLocation aLocation,
-                       MallocSizeOf aMallocSizeOf) const;
-
-  void ResetAnimation();
-
-  // "Blend" method indicates how the current image is combined with the
-  // previous image.
-  enum FrameBlendMethod {
-    // All color components of the frame, including alpha, overwrite the current
-    // contents of the frame's output buffer region
-    kBlendSource =  0,
-
-    // The frame should be composited onto the output buffer based on its alpha,
-    // using a simple OVER operation
-    kBlendOver
-  };
-
-  enum FrameDisposalMethod {
-    kDisposeClearAll         = -1, // Clear the whole image, revealing
-                                   // what was there before the gif displayed
-    kDisposeNotSpecified,   // Leave frame, let new frame draw on top
-    kDisposeKeep,           // Leave frame, let new frame draw on top
-    kDisposeClear,          // Clear the frame's area, revealing bg
-    kDisposeRestorePrevious // Restore the previous (composited) frame
-  };
-
-  // A hint as to whether an individual frame is entirely opaque, or requires
-  // alpha blending.
-  enum FrameAlpha {
-    kFrameHasAlpha,
-    kFrameOpaque
-  };
-
-private:
-  /** Clears an area of <aFrame> with transparent black.
-   *
-   * @param aFrameData Target Frame data
-   * @param aFrameRect The rectangle of the data pointed ot by aFrameData
-   *
-   * @note Does also clears the transparency mask
-   */
-  static void ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect);
-
-  //! @overload
-  static void ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect,
-                         const nsIntRect& aRectToClear);
-
-  //! Copy one frame's image and mask into another
-  static bool CopyFrameImage(const uint8_t* aDataSrc, const nsIntRect& aRectSrc,
-                             uint8_t* aDataDest, const nsIntRect& aRectDest);
-
-  /**
-   * Draws one frame's image to into another, at the position specified by
-   * aSrcRect.
-   *
-   * @aSrcData the raw data of the current frame being drawn
-   * @aSrcRect the size of the source frame, and the position of that frame in
-   *           the composition frame
-   * @aSrcPaletteLength the length (in bytes) of the palette at the beginning
-   *                    of the source data (0 if image is not paletted)
-   * @aSrcHasAlpha whether the source data represents an image with alpha
-   * @aDstPixels the raw data of the composition frame where the current frame
-   *             is drawn into (32-bit ARGB)
-   * @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,
-                              FrameBlendMethod aBlendMethod);
-
-private: // data
-  ImageKey mImageKey;
-  gfx::IntSize mSize;
-
-  //! Track the last composited frame for Optimizations (See DoComposite code)
-  int32_t mLastCompositedFrameIndex;
-
-  /** For managing blending of frames
-   *
-   * Some animations will use the compositingFrame to composite images
-   * and just hand this back to the caller when it is time to draw the frame.
-   * NOTE: When clearing compositingFrame, remember to set
-   *       lastCompositedFrameIndex to -1.  Code assume that if
-   *       lastCompositedFrameIndex >= 0 then compositingFrame exists.
-   */
-  RawAccessFrameRef mCompositingFrame;
-
-  /** the previous composited frame, for DISPOSE_RESTORE_PREVIOUS
-   *
-   * The Previous Frame (all frames composited up to the current) needs to be
-   * stored in cases where the image specifies it wants the last frame back
-   * when it's done with the current frame.
-   */
-  RawAccessFrameRef mCompositingPrevFrame;
-
-  int32_t mLoopCount;
-};
-
-} // namespace image
-} // namespace mozilla
-
-#endif /* mozilla_imagelib_FrameBlender_h_ */
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -547,20 +547,19 @@ RasterImage::LookupFrameInternal(uint32_
 {
   if (!mAnim) {
     NS_ASSERTION(aFrameNum == 0,
                  "Don't ask for a frame > 0 if we're not animated!");
     aFrameNum = 0;
   }
 
   if (mAnim && aFrameNum > 0) {
-    MOZ_ASSERT(mFrameBlender, "mAnim but no mFrameBlender?");
     MOZ_ASSERT(DecodeFlags(aFlags) == DECODE_FLAGS_DEFAULT,
                "Can't composite frames with non-default decode flags");
-    return mFrameBlender->GetCompositedFrame(aFrameNum);
+    return mAnim->GetCompositedFrame(aFrameNum);
   }
 
   return SurfaceCache::Lookup(ImageKey(this),
                               RasterSurfaceKey(aSize.ToIntSize(),
                                                DecodeFlags(aFlags),
                                                aFrameNum));
 }
 
@@ -697,18 +696,18 @@ RasterImage::GetFirstFrameDelay()
 {
   if (mError)
     return -1;
 
   bool animated = false;
   if (NS_FAILED(GetAnimated(&animated)) || !animated)
     return -1;
 
-  MOZ_ASSERT(mFrameBlender, "Animated images should have a FrameBlender");
-  return mFrameBlender->GetTimeoutForFrame(0);
+  MOZ_ASSERT(mAnim, "Animated images should have a FrameAnimator");
+  return mAnim->GetTimeoutForFrame(0);
 }
 
 TemporaryRef<SourceSurface>
 RasterImage::CopyFrame(uint32_t aWhichFrame,
                        uint32_t aFlags,
                        bool aShouldSyncNotify /* = true */)
 {
   if (aWhichFrame > FRAME_MAX_VALUE)
@@ -920,18 +919,18 @@ RasterImage::SizeOfSourceWithComputedFal
 }
 
 size_t
 RasterImage::SizeOfDecoded(gfxMemoryLocation aLocation,
                            MallocSizeOf aMallocSizeOf) const
 {
   size_t n = 0;
   n += SurfaceCache::SizeOfSurfaces(ImageKey(this), aLocation, aMallocSizeOf);
-  if (mFrameBlender) {
-    n += mFrameBlender->SizeOfDecoded(aLocation, aMallocSizeOf);
+  if (mAnim) {
+    n += mAnim->SizeOfCompositingFrames(aLocation, aMallocSizeOf);
   }
   return n;
 }
 
 RawAccessFrameRef
 RasterImage::InternalAddFrame(uint32_t aFrameNum,
                               const nsIntRect& aFrameRect,
                               uint32_t aDecodeFlags,
@@ -979,20 +978,18 @@ RasterImage::InternalAddFrame(uint32_t a
                                           aFrameNum),
                          Lifetime::Persistent);
   if (!succeeded) {
     return RawAccessFrameRef();
   }
 
   if (aFrameNum == 1) {
     // We're becoming animated, so initialize animation stuff.
-    MOZ_ASSERT(!mFrameBlender, "Already have a FrameBlender?");
     MOZ_ASSERT(!mAnim, "Already have animation state?");
-    mFrameBlender.emplace(ImageKey(this), mSize.ToIntSize());
-    mAnim = MakeUnique<FrameAnimator>(this, *mFrameBlender, mAnimationMode);
+    mAnim = MakeUnique<FrameAnimator>(this, mSize.ToIntSize(), mAnimationMode);
 
     // We don't support discarding animated images (See bug 414259).
     // Lock the image and throw away the key.
     //
     // Note that this is inefficient, since we could get rid of the source data
     // too. However, doing this is actually hard, because we're probably
     // mid-decode, and thus we're decoding out of the source buffer. Since we're
     // going to fix this anyway later, and since we didn't kill the source data
@@ -1000,19 +997,20 @@ RasterImage::InternalAddFrame(uint32_t a
     LockImage();
 
     MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
     aPreviousFrame->SetRawAccessOnly();
 
     // If we dispose of the first frame by clearing it, then the first frame's
     // refresh area is all of itself.
     // RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR).
-    int32_t frameDisposalMethod = aPreviousFrame->GetFrameDisposalMethod();
-    if (frameDisposalMethod == FrameBlender::kDisposeClear ||
-        frameDisposalMethod == FrameBlender::kDisposeRestorePrevious) {
+    DisposalMethod disposalMethod = aPreviousFrame->GetDisposalMethod();
+    if (disposalMethod == DisposalMethod::CLEAR ||
+        disposalMethod == DisposalMethod::CLEAR_ALL ||
+        disposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
       mAnim->SetFirstFrameRefreshArea(aPreviousFrame->GetRect());
     }
 
     if (mPendingAnimation && ShouldAnimate()) {
       StartAnimation();
     }
   }
 
@@ -1092,17 +1090,17 @@ RasterImage::EnsureFrame(uint32_t aFrame
   // We're replacing a frame. It must be the first frame; there's no reason to
   // ever replace any other frame, since the first frame is the only one we
   // speculatively allocate without knowing what the decoder really needs.
   // XXX(seth): I'm not convinced there's any reason to support this at all. We
   // should figure out how to avoid triggering this and rip it out.
   MOZ_ASSERT(aFrameNum == 0, "Replacing a frame other than the first?");
   MOZ_ASSERT(GetNumFrames() == 1, "Should have only one frame");
   MOZ_ASSERT(aPreviousFrame, "Need the previous frame to replace");
-  MOZ_ASSERT(!mFrameBlender && !mAnim, "Shouldn't be animated");
+  MOZ_ASSERT(!mAnim, "Shouldn't be animated");
   if (aFrameNum != 0 || !aPreviousFrame || GetNumFrames() != 1) {
     return RawAccessFrameRef();
   }
 
   MOZ_ASSERT(!aPreviousFrame->GetRect().IsEqualEdges(aFrameRect) ||
              aPreviousFrame->GetFormat() != aFormat ||
              aPreviousFrame->GetPaletteDepth() != aPaletteDepth,
              "Replacing first frame with the same kind of frame?");
@@ -1167,27 +1165,24 @@ RasterImage::StartAnimation()
   // mPendingAnimation will cause us to start animating as soon as we have a
   // second frame, which causes mAnim to be constructed.
   mPendingAnimation = !mAnim;
   if (mPendingAnimation) {
     return NS_OK;
   }
 
   // A timeout of -1 means we should display this frame forever.
-  MOZ_ASSERT(mFrameBlender, "Have an animation but no FrameBlender?");
-  if (mFrameBlender->GetTimeoutForFrame(GetCurrentFrameIndex()) < 0) {
+  if (mAnim->GetTimeoutForFrame(GetCurrentFrameIndex()) < 0) {
     mAnimationFinished = true;
     return NS_ERROR_ABORT;
   }
 
-  if (mAnim) {
-    // We need to set the time that this initial frame was first displayed, as
-    // this is used in AdvanceFrame().
-    mAnim->InitAnimationFrameTimeIfNecessary();
-  }
+  // We need to set the time that this initial frame was first displayed, as
+  // this is used in AdvanceFrame().
+  mAnim->InitAnimationFrameTimeIfNecessary();
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* void stopAnimation (); */
 nsresult
 RasterImage::StopAnimation()
@@ -1220,18 +1215,17 @@ RasterImage::ResetAnimation()
     return NS_OK;
   }
 
   mAnimationFinished = false;
 
   if (mAnimating)
     StopAnimation();
 
-  MOZ_ASSERT(mFrameBlender, "Should have a FrameBlender");
-  mFrameBlender->ResetAnimation();
+  MOZ_ASSERT(mAnim, "Should have a FrameAnimator");
   mAnim->ResetAnimation();
 
   UpdateImageContainer();
 
   // Note - We probably want to kick off a redecode somewhere around here when
   // we fix bug 500402.
 
   // Update display
@@ -1268,20 +1262,19 @@ RasterImage::GetFrameIndex(uint32_t aWhi
 }
 
 void
 RasterImage::SetLoopCount(int32_t aLoopCount)
 {
   if (mError)
     return;
 
+  // No need to set this if we're not an animation.
   if (mAnim) {
-    // No need to set this if we're not an animation
-    MOZ_ASSERT(mFrameBlender, "Should have a FrameBlender");
-    mFrameBlender->SetLoopCount(aLoopCount);
+    mAnim->SetLoopCount(aLoopCount);
   }
 }
 
 NS_IMETHODIMP_(nsIntRect)
 RasterImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
 {
   return aRect;
 }
@@ -1533,18 +1526,17 @@ RasterImage::Discard()
 
   // As soon as an image becomes animated, it becomes non-discardable and any
   // timers are cancelled.
   NS_ABORT_IF_FALSE(!mAnim, "Asked to discard for animated image!");
 
   // For post-operation logging
   int old_frame_count = GetNumFrames();
 
-  // Delete all the decoded frames
-  mFrameBlender.reset();
+  // Delete all the decoded frames.
   SurfaceCache::RemoveImage(ImageKey(this));
 
   // Flag that we no longer have decoded frames for this image
   mDecoded = false;
   mFrameCount = 0;
 
   // Notify that we discarded
   if (mProgressTracker) {
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -13,17 +13,16 @@
  * @author  Arron Mogge <paper@animecity.nu>
  * @author  Andrew Smith <asmith15@learn.senecac.on.ca>
  */
 
 #ifndef mozilla_imagelib_RasterImage_h_
 #define mozilla_imagelib_RasterImage_h_
 
 #include "Image.h"
-#include "FrameBlender.h"
 #include "nsCOMPtr.h"
 #include "imgIContainer.h"
 #include "nsIProperties.h"
 #include "nsTArray.h"
 #include "imgFrame.h"
 #include "nsThreadUtils.h"
 #include "DecodePool.h"
 #include "Orientation.h"
@@ -343,21 +342,19 @@ private: // data
   // with the browser's needs for displaying the image to the user.
   // As such, we may need to redecode if we're being asked for
   // a frame with different flags.  0 indicates default flags.
   //
   // Valid flag bits are imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA
   // and imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION.
   uint32_t                   mFrameDecodeFlags;
 
-  //! All the frames of the image.
-  Maybe<FrameBlender>       mFrameBlender;
-
   nsCOMPtr<nsIProperties>   mProperties;
 
+  //! All the frames of the image.
   // IMPORTANT: if you use mAnim in a method, call EnsureImageIsDecoded() first to ensure
   // that the frames actually exist (they may have been discarded to save memory, or
   // we maybe decoding on draw).
   UniquePtr<FrameAnimator> mAnim;
 
   // Image locking.
   uint32_t                   mLockCount;
 
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -128,19 +128,19 @@ static bool AllowedImageAndFrameDimensio
 }
 
 
 imgFrame::imgFrame() :
   mDecoded(0, 0, 0, 0),
   mDecodedMutex("imgFrame::mDecoded"),
   mPalettedImageData(nullptr),
   mTimeout(100),
-  mDisposalMethod(0), /* imgIContainer::kDisposeNotSpecified */
   mLockCount(0),
-  mBlendMethod(1), /* imgIContainer::kBlendOver */
+  mDisposalMethod(DisposalMethod::NOT_SPECIFIED),
+  mBlendMethod(BlendMethod::OVER),
   mSinglePixel(false),
   mCompositingFailed(false),
   mHasNoAlpha(false),
   mNonPremult(false),
   mOptimizable(false)
 {
   static bool hasCheckedOptimize = false;
   if (!hasCheckedOptimize) {
@@ -830,36 +830,16 @@ int32_t imgFrame::GetRawTimeout() const
   return mTimeout;
 }
 
 void imgFrame::SetRawTimeout(int32_t aTimeout)
 {
   mTimeout = aTimeout;
 }
 
-int32_t imgFrame::GetFrameDisposalMethod() const
-{
-  return mDisposalMethod;
-}
-
-void imgFrame::SetFrameDisposalMethod(int32_t aFrameDisposalMethod)
-{
-  mDisposalMethod = aFrameDisposalMethod;
-}
-
-int32_t imgFrame::GetBlendMethod() const
-{
-  return mBlendMethod;
-}
-
-void imgFrame::SetBlendMethod(int32_t aBlendMethod)
-{
-  mBlendMethod = (int8_t)aBlendMethod;
-}
-
 // This can be called from any thread.
 bool imgFrame::ImageComplete() const
 {
   MutexAutoLock lock(mDecodedMutex);
 
   return mDecoded.IsEqualInterior(nsIntRect(mOffset.x, mOffset.y,
                                             mSize.width, mSize.height));
 }
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -5,27 +5,51 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef imgFrame_h
 #define imgFrame_h
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Move.h"
 #include "mozilla/Mutex.h"
+#include "mozilla/TypedEnum.h"
 #include "mozilla/VolatileBuffer.h"
 #include "gfxDrawable.h"
 #include "imgIContainer.h"
 
 namespace mozilla {
 namespace image {
 
 class ImageRegion;
 class DrawableFrameRef;
 class RawAccessFrameRef;
 
+MOZ_BEGIN_ENUM_CLASS(BlendMethod, int8_t)
+  // All color components of the frame, including alpha, overwrite the current
+  // contents of the frame's output buffer region.
+  SOURCE,
+
+  // The frame should be composited onto the output buffer based on its alpha,
+  // using a simple OVER operation.
+  OVER
+MOZ_END_ENUM_CLASS(BlendMethod)
+
+MOZ_BEGIN_ENUM_CLASS(DisposalMethod, int8_t)
+  CLEAR_ALL = -1,  // Clear the whole image, revealing what's underneath.
+  NOT_SPECIFIED,   // Leave the frame and let the new frame draw on top.
+  KEEP,            // Leave the frame and let the new frame draw on top.
+  CLEAR,           // Clear the frame's area, revealing what's underneath.
+  RESTORE_PREVIOUS // Restore the previous (composited) frame.
+MOZ_END_ENUM_CLASS(DisposalMethod)
+
+MOZ_BEGIN_ENUM_CLASS(Opacity, uint8_t)
+  OPAQUE,
+  SOME_TRANSPARENCY
+MOZ_END_ENUM_CLASS(Opacity)
+
 class imgFrame
 {
   typedef gfx::Color Color;
   typedef gfx::DataSourceSurface DataSourceSurface;
   typedef gfx::DrawTarget DrawTarget;
   typedef gfx::IntSize IntSize;
   typedef gfx::SourceSurface SourceSurface;
   typedef gfx::SurfaceFormat SurfaceFormat;
@@ -108,20 +132,25 @@ public:
   uint8_t* GetImageData() const;
   void GetPaletteData(uint32_t **aPalette, uint32_t *length) const;
   uint32_t* GetPaletteData() const;
   uint8_t* GetRawData() const;
 
   int32_t GetRawTimeout() const;
   void SetRawTimeout(int32_t aTimeout);
 
-  int32_t GetFrameDisposalMethod() const;
-  void SetFrameDisposalMethod(int32_t aFrameDisposalMethod);
-  int32_t GetBlendMethod() const;
-  void SetBlendMethod(int32_t aBlendMethod);
+  DisposalMethod GetDisposalMethod() const { return mDisposalMethod; }
+  void SetDisposalMethod(DisposalMethod aDisposalMethod)
+  {
+    mDisposalMethod = aDisposalMethod;
+  }
+
+  BlendMethod GetBlendMethod() const { return mBlendMethod; }
+  void SetBlendMethod(BlendMethod aBlendMethod) { mBlendMethod = aBlendMethod; }
+
   bool ImageComplete() const;
 
   void SetHasNoAlpha();
   void SetAsNonPremult(bool aIsNonPremult);
 
   bool GetCompositingFailed() const;
   void SetCompositingFailed(bool val);
 
@@ -197,27 +226,27 @@ private: // data
   // The paletted data comes first, then the image data itself.
   // Total length is PaletteDataLength() + GetImageDataLength().
   uint8_t*     mPalettedImageData;
 
   // Note that the data stored in gfx::Color is *non-alpha-premultiplied*.
   Color        mSinglePixelColor;
 
   int32_t      mTimeout; // -1 means display forever
-  int32_t      mDisposalMethod;
 
   /** Indicates how many readers currently have locked this frame */
   int32_t mLockCount;
 
   RefPtr<VolatileBuffer> mVBuf;
   VolatileBufferPtr<uint8_t> mVBufPtr;
 
-  SurfaceFormat mFormat;
-  uint8_t      mPaletteDepth;
-  int8_t       mBlendMethod;
+  SurfaceFormat  mFormat;
+  uint8_t        mPaletteDepth;
+  DisposalMethod mDisposalMethod;
+  BlendMethod    mBlendMethod;
   bool mSinglePixel;
   bool mCompositingFailed;
   bool mHasNoAlpha;
   bool mNonPremult;
   bool mOptimizable;
 
   friend class DrawableFrameRef;
   friend class RawAccessFrameRef;
--- a/image/src/moz.build
+++ b/image/src/moz.build
@@ -16,17 +16,16 @@ EXPORTS += [
 ]
 
 UNIFIED_SOURCES += [
     'ClippedImage.cpp',
     'DecodePool.cpp',
     'Decoder.cpp',
     'DynamicImage.cpp',
     'FrameAnimator.cpp',
-    'FrameBlender.cpp',
     'FrozenImage.cpp',
     'Image.cpp',
     'ImageFactory.cpp',
     'ImageMetadata.cpp',
     'ImageOps.cpp',
     'ImageWrapper.cpp',
     'imgFrame.cpp',
     'imgTools.cpp',