Bug 867774 - Create a FrameBlender class that holds on to an image's frames and can blend frames together on demand (while leaving the decision as to which frames to external users). r=seth
authorJoe Drew <joe@drew.ca>
Mon, 17 Jun 2013 16:49:04 -0400
changeset 135693 7158e859e28c
parent 135692 f0380ec657f8
child 135694 f5f8e2d6d991
push id29779
push userjdrew@mozilla.com
push date2013-06-19 21:47 +0000
treeherdermozilla-inbound@f5f8e2d6d991 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersseth
bugs867774
milestone24.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 867774 - Create a FrameBlender class that holds on to an image's frames and can blend frames together on demand (while leaving the decision as to which frames to external users). r=seth FrameBlender steals RasterImage::mFrames, RasterImage::DoComposite, and the RasterImage blending helper functions CopyFrameImage, DrawFrameTo, and ClearFrame. Now RasterImage doesn't hold on to its frames directly, and defers all blending to FrameBlender::DoComposite.
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/FrameBlender.cpp
image/src/FrameBlender.h
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/moz.build
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -135,19 +135,19 @@ nsBMPDecoder::FinishInternal()
     // Send notifications if appropriate
     if (!IsSizeDecode() && HasSize()) {
 
         // Invalidate
         nsIntRect r(0, 0, mBIH.width, GetHeight());
         PostInvalidation(r);
 
         if (mUseAlphaData) {
-          PostFrameStop(RasterImage::kFrameHasAlpha);
+          PostFrameStop(FrameBlender::kFrameHasAlpha);
         } else {
-          PostFrameStop(RasterImage::kFrameOpaque);
+          PostFrameStop(FrameBlender::kFrameOpaque);
         }
         PostDecodeDone();
     }
 }
 
 // ----------------------------------------
 // Actual Data Processing
 // ----------------------------------------
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -194,17 +194,17 @@ void nsGIFDecoder2::BeginImageFrame(uint
 
   mCurrentFrame = mGIFStruct.images_decoded;
 }
 
 
 //******************************************************************************
 void nsGIFDecoder2::EndImageFrame()
 {
-  RasterImage::FrameAlpha alpha = RasterImage::kFrameHasAlpha;
+  FrameBlender::FrameAlpha alpha = FrameBlender::kFrameHasAlpha;
 
   // 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.
@@ -213,17 +213,17 @@ void 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 = RasterImage::kFrameOpaque;
+      alpha = FrameBlender::kFrameOpaque;
     }
   }
   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) {
@@ -236,17 +236,17 @@ void 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,
-                RasterImage::FrameDisposalMethod(mGIFStruct.disposal_method),
+                FrameBlender::FrameDisposalMethod(mGIFStruct.disposal_method),
                 mGIFStruct.delay_time);
 
   // Reset the transparent pixel
   if (mOldColor) {
     mColormap[mGIFStruct.tpixel] = mOldColor;
     mOldColor = 0;
   }
 
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -531,17 +531,17 @@ nsJPEGDecoder::WriteInternal(const char 
   PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG,
          ("} (end of function)"));
   return;
 }
 
 void
 nsJPEGDecoder::NotifyDone()
 {
-  PostFrameStop(RasterImage::kFrameOpaque);
+  PostFrameStop(FrameBlender::kFrameOpaque);
   PostDecodeDone();
 }
 
 void
 nsJPEGDecoder::OutputScanlines(bool* suspend)
 {
   *suspend = false;
 
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -50,25 +50,25 @@ GetPNGDecoderAccountingLog()
 #define MOZ_PNG_MAX_DIMENSION 1000000L
 
 // 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(RasterImage::kDisposeKeep)
- , mBlend(RasterImage::kBlendOver)
+ : mDispose(FrameBlender::kDisposeKeep)
+ , mBlend(FrameBlender::kBlendOver)
  , mTimeout(0)
 {}
 
 #ifdef PNG_APNG_SUPPORTED
 nsPNGDecoder::AnimFrameInfo::AnimFrameInfo(png_structp aPNG, png_infop aInfo)
- : mDispose(RasterImage::kDisposeKeep)
- , mBlend(RasterImage::kBlendOver)
+ : mDispose(FrameBlender::kDisposeKeep)
+ , mBlend(FrameBlender::kBlendOver)
  , 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);
@@ -82,27 +82,27 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameIn
       delay_den = 100; // so says the APNG spec
 
     // 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 = RasterImage::kDisposeRestorePrevious;
+    mDispose = FrameBlender::kDisposeRestorePrevious;
   } else if (dispose_op == PNG_DISPOSE_OP_BACKGROUND) {
-    mDispose = RasterImage::kDisposeClear;
+    mDispose = FrameBlender::kDisposeClear;
   } else {
-    mDispose = RasterImage::kDisposeKeep;
+    mDispose = FrameBlender::kDisposeKeep;
   }
 
   if (blend_op == PNG_BLEND_OP_SOURCE) {
-    mBlend = RasterImage::kBlendSource;
+    mBlend = FrameBlender::kBlendSource;
   } else {
-    mBlend = RasterImage::kBlendOver;
+    mBlend = FrameBlender::kBlendOver;
   }
 }
 #endif
 
 // First 8 bytes of a PNG file
 const uint8_t
 nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
 
@@ -177,21 +177,21 @@ void nsPNGDecoder::CreateFrame(png_uint_
 // set timeout and frame disposal method for the current frame
 void nsPNGDecoder::EndImageFrame()
 {
   if (mFrameIsHidden)
     return;
 
   mNumFrames++;
 
-  RasterImage::FrameAlpha alpha;
+  FrameBlender::FrameAlpha alpha;
   if (mFrameHasNoAlpha)
-    alpha = RasterImage::kFrameOpaque;
+    alpha = FrameBlender::kFrameOpaque;
   else
-    alpha = RasterImage::kFrameHasAlpha;
+    alpha = FrameBlender::kFrameHasAlpha;
 
 #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);
   }
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -92,18 +92,18 @@ public:
 
   struct AnimFrameInfo
   {
     AnimFrameInfo();
 #ifdef PNG_APNG_SUPPORTED
     AnimFrameInfo(png_structp aPNG, png_infop aInfo);
 #endif
 
-    RasterImage::FrameDisposalMethod mDispose;
-    RasterImage::FrameBlendMethod mBlend;
+    FrameBlender::FrameDisposalMethod mDispose;
+    FrameBlender::FrameBlendMethod 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
@@ -319,29 +319,29 @@ Decoder::PostFrameStart()
 
   // Fire notifications
   if (mObserver) {
     mObserver->OnStartFrame();
   }
 }
 
 void
-Decoder::PostFrameStop(RasterImage::FrameAlpha aFrameAlpha /* = RasterImage::kFrameHasAlpha */,
-                       RasterImage::FrameDisposalMethod aDisposalMethod /* = RasterImage::kDisposeKeep */,
+Decoder::PostFrameStop(FrameBlender::FrameAlpha aFrameAlpha /* = FrameBlender::kFrameHasAlpha */,
+                       FrameBlender::FrameDisposalMethod aDisposalMethod /* = FrameBlender::kDisposeKeep */,
                        int32_t aTimeout /* = 0 */,
-                       RasterImage::FrameBlendMethod aBlendMethod /* = RasterImage::kBlendOver */)
+                       FrameBlender::FrameBlendMethod aBlendMethod /* = FrameBlender::kBlendOver */)
 {
   // We should be mid-frame
   NS_ABORT_IF_FALSE(mInFrame, "Stopping frame when we didn't start one!");
   NS_ABORT_IF_FALSE(mCurrentFrame, "Stopping frame when we don't have one!");
 
   // Update our state
   mInFrame = false;
 
-  if (aFrameAlpha == RasterImage::kFrameOpaque) {
+  if (aFrameAlpha == FrameBlender::kFrameOpaque) {
     mCurrentFrame->SetHasNoAlpha();
   }
 
   mCurrentFrame->SetFrameDisposalMethod(aDisposalMethod);
   mCurrentFrame->SetTimeout(aTimeout);
   mCurrentFrame->SetBlendMethod(aBlendMethod);
 
   // Flush any invalidations before we finish the frame
--- a/image/src/Decoder.h
+++ b/image/src/Decoder.h
@@ -195,20 +195,20 @@ protected:
   // 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(RasterImage::FrameAlpha aFrameAlpha = RasterImage::kFrameHasAlpha,
-                     RasterImage::FrameDisposalMethod aDisposalMethod = RasterImage::kDisposeKeep,
+  void PostFrameStop(FrameBlender::FrameAlpha aFrameAlpha = FrameBlender::kFrameHasAlpha,
+                     FrameBlender::FrameDisposalMethod aDisposalMethod = FrameBlender::kDisposeKeep,
                      int32_t aTimeout = 0,
-                     RasterImage::FrameBlendMethod aBlendMethod = RasterImage::kBlendOver);
+                     FrameBlender::FrameBlendMethod aBlendMethod = FrameBlender::kBlendOver);
 
   // 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().
new file mode 100644
--- /dev/null
+++ b/image/src/FrameBlender.cpp
@@ -0,0 +1,606 @@
+/* -*- 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 "RasterImage.h"
+#include "imgFrame.h"
+
+#define PIXMAN_DONT_DEFINE_STDINT
+#include "pixman.h"
+
+using namespace mozilla;
+using namespace mozilla::image;
+
+namespace mozilla {
+namespace image {
+
+FrameBlender::FrameBlender()
+ : mAnim(nullptr)
+{}
+
+FrameBlender::~FrameBlender()
+{
+  ClearFrames();
+
+  delete mAnim;
+}
+
+imgFrame*
+FrameBlender::GetFrame(uint32_t framenum) const
+{
+  if (!mAnim) {
+    NS_ASSERTION(framenum == 0, "Don't ask for a frame > 0 if we're not animated!");
+    return mFrames.SafeElementAt(0, nullptr);
+  }
+  if (mAnim->lastCompositedFrameIndex == int32_t(framenum))
+    return mAnim->compositingFrame;
+  return mFrames.SafeElementAt(framenum, nullptr);
+}
+
+imgFrame*
+FrameBlender::RawGetFrame(uint32_t framenum) const
+{
+  if (!mAnim) {
+    NS_ASSERTION(framenum == 0, "Don't ask for a frame > 0 if we're not animated!");
+    return mFrames.SafeElementAt(0, nullptr);
+  }
+
+  return mFrames.SafeElementAt(framenum, nullptr);
+}
+
+uint32_t
+FrameBlender::GetNumFrames() const
+{
+  return mFrames.Length();
+}
+
+void
+FrameBlender::RemoveFrame(uint32_t framenum)
+{
+  NS_ABORT_IF_FALSE(framenum < mFrames.Length(), "Deleting invalid frame!");
+
+  delete mFrames[framenum];
+  mFrames[framenum] = nullptr;
+  mFrames.RemoveElementAt(framenum);
+}
+
+void
+FrameBlender::ClearFrames()
+{
+  for (uint32_t i = 0; i < mFrames.Length(); ++i) {
+    delete mFrames[i];
+  }
+  mFrames.Clear();
+}
+
+void
+FrameBlender::InsertFrame(uint32_t framenum, imgFrame* aFrame)
+{
+  NS_ABORT_IF_FALSE(framenum <= mFrames.Length(), "Inserting invalid frame!");
+  mFrames.InsertElementAt(framenum, aFrame);
+  if (GetNumFrames() > 1) {
+    EnsureAnimExists();
+  }
+}
+
+imgFrame*
+FrameBlender::SwapFrame(uint32_t framenum, imgFrame* aFrame)
+{
+  NS_ABORT_IF_FALSE(framenum < mFrames.Length(), "Swapping invalid frame!");
+  imgFrame* ret = mFrames.SafeElementAt(framenum, nullptr);
+  mFrames.RemoveElementAt(framenum);
+  if (aFrame) {
+    mFrames.InsertElementAt(framenum, aFrame);
+  }
+
+  return ret;
+}
+
+//******************************************************************************
+// 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)
+{
+  if (!aDirtyRect) {
+    return false;
+  }
+
+  imgFrame* prevFrame = mFrames[aPrevFrameIndex];
+  imgFrame* nextFrame = mFrames[aNextFrameIndex];
+  if (!prevFrame || !nextFrame) {
+    return false;
+  }
+
+  int32_t prevFrameDisposalMethod = prevFrame->GetFrameDisposalMethod();
+  if (prevFrameDisposalMethod == FrameBlender::kDisposeRestorePrevious &&
+      !mAnim->compositingPrevFrame)
+    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 (mAnim->lastCompositedFrameIndex == int32_t(aNextFrameIndex)) {
+    return true;
+  }
+
+  bool needToBlankComposite = false;
+
+  // Create the Compositing Frame
+  if (!mAnim->compositingFrame) {
+    mAnim->compositingFrame = new imgFrame();
+    nsresult rv = mAnim->compositingFrame->Init(0, 0, mSize.width, mSize.height,
+                                                gfxASurface::ImageFormatARGB32);
+    if (NS_FAILED(rv)) {
+      mAnim->compositingFrame = nullptr;
+      return false;
+    }
+    needToBlankComposite = true;
+  } else if (int32_t(aNextFrameIndex) != mAnim->lastCompositedFrameIndex+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(mAnim->compositingFrame);
+        } else {
+          // Only blank out previous frame area (both color & Mask/Alpha)
+          ClearFrame(mAnim->compositingFrame, prevFrameRect);
+        }
+        break;
+
+      case FrameBlender::kDisposeClearAll:
+        ClearFrame(mAnim->compositingFrame);
+        break;
+
+      case FrameBlender::kDisposeRestorePrevious:
+        // It would be better to copy only the area changed back to
+        // compositingFrame.
+        if (mAnim->compositingPrevFrame) {
+          CopyFrameImage(mAnim->compositingPrevFrame, mAnim->compositingFrame);
+
+          // destroy only if we don't need it for this frame's disposal
+          if (nextFrameDisposalMethod != FrameBlender::kDisposeRestorePrevious)
+            mAnim->compositingPrevFrame = nullptr;
+        } else {
+          ClearFrame(mAnim->compositingFrame);
+        }
+        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 (mAnim->lastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
+          if (isFullPrevFrame && !prevFrame->GetIsPaletted()) {
+            // Just copy the bits
+            CopyFrameImage(prevFrame, mAnim->compositingFrame);
+          } else {
+            if (needToBlankComposite) {
+              // Only blank composite when prev is transparent or not full.
+              if (prevFrame->GetHasAlpha() || !isFullPrevFrame) {
+                ClearFrame(mAnim->compositingFrame);
+              }
+            }
+            DrawFrameTo(prevFrame, mAnim->compositingFrame, prevFrameRect);
+          }
+        }
+    }
+  } else if (needToBlankComposite) {
+    // If we just created the composite, it could have anything in it's
+    // buffers. Clear them
+    ClearFrame(mAnim->compositingFrame);
+  }
+
+  // Check if the frame we are composing wants the previous image restored afer
+  // 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 (!mAnim->compositingPrevFrame) {
+      mAnim->compositingPrevFrame = new imgFrame();
+      nsresult rv = mAnim->compositingPrevFrame->Init(0, 0, mSize.width, mSize.height,
+                                                      gfxASurface::ImageFormatARGB32);
+      if (NS_FAILED(rv)) {
+        mAnim->compositingPrevFrame = nullptr;
+        return false;
+      }
+    }
+
+    CopyFrameImage(mAnim->compositingFrame, mAnim->compositingPrevFrame);
+  }
+
+  // blit next frame into it's correct spot
+  DrawFrameTo(nextFrame, mAnim->compositingFrame, nextFrameRect);
+
+  // Set timeout of CompositeFrame to timeout of frame we just composed
+  // Bug 177948
+  int32_t timeout = nextFrame->GetTimeout();
+  mAnim->compositingFrame->SetTimeout(timeout);
+
+  // Tell the image that it is fully 'downloaded'.
+  nsresult rv = mAnim->compositingFrame->ImageUpdated(mAnim->compositingFrame->GetRect());
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  // We don't want to keep composite images for 8bit frames.  Also this
+  // optimization won't work if the next frame has kDisposeRestorePrevious,
+  // because it would need to be restored into "after prev disposal but before
+  // next blend" state, not into empty frame.
+  if (isFullNextFrame &&
+      nextFrameDisposalMethod != FrameBlender::kDisposeRestorePrevious &&
+      !nextFrame->GetIsPaletted()) {
+    // We have a composited full frame
+    // Store the composited frame into the mFrames[..] so we don't have to
+    // continuously re-build it
+    // Then set the previous frame's disposal to CLEAR_ALL so we just draw the
+    // frame next time around
+    if (CopyFrameImage(mAnim->compositingFrame, nextFrame)) {
+      prevFrame->SetFrameDisposalMethod(FrameBlender::kDisposeClearAll);
+      mAnim->compositingFrame = nullptr;
+      mAnim->lastCompositedFrameIndex = -1;
+      return true;
+    }
+  }
+
+  mAnim->lastCompositedFrameIndex = 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(imgFrame* aFrame)
+{
+  AutoFrameLocker lock(aFrame);
+  if (lock.Succeeded()) {
+    ClearFrame(aFrame->GetImageData(), aFrame->GetRect());
+  }
+}
+
+//******************************************************************************
+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.height; ++row) {
+    memset(aFrameData + toClear.x * 4 + row * bytesPerRow, 0, toClear.width * 4);
+  }
+}
+
+void
+FrameBlender::ClearFrame(imgFrame* aFrame, const nsIntRect& aRectToClear)
+{
+  AutoFrameLocker lock(aFrame);
+  if (lock.Succeeded()) {
+    ClearFrame(aFrame->GetImageData(), aFrame->GetRect(), aRectToClear);
+  }
+}
+
+//******************************************************************************
+// 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(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;
+}
+
+bool
+FrameBlender::CopyFrameImage(imgFrame* aSrc, imgFrame* aDst)
+{
+  AutoFrameLocker srclock(aSrc);
+  AutoFrameLocker dstlock(aDst);
+  if (!srclock.Succeeded() || !dstlock.Succeeded()) {
+    return false;
+  }
+
+  return CopyFrameImage(aSrc->GetImageData(), aSrc->GetRect(),
+                        aDst->GetImageData(), aDst->GetRect());
+}
+
+nsresult
+FrameBlender::DrawFrameTo(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
+    uint8_t *srcPixels = aSrcData + aSrcPaletteLength;
+    uint32_t *dstPixels = reinterpret_cast<uint32_t*>(aDstPixels);
+    uint32_t *colormap = reinterpret_cast<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*>(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);
+
+    pixman_image_composite32(aBlendMethod == FrameBlender::kBlendSource ? PIXMAN_OP_SRC : PIXMAN_OP_OVER,
+                             src,
+                             nullptr,
+                             dst,
+                             0, 0,
+                             0, 0,
+                             aSrcRect.x, aSrcRect.y,
+                             aDstRect.width, aDstRect.height);
+
+    pixman_image_unref(src);
+    pixman_image_unref(dst);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+FrameBlender::DrawFrameTo(imgFrame* aSrc, imgFrame* aDst, const nsIntRect& aSrcRect)
+{
+  AutoFrameLocker srclock(aSrc);
+  AutoFrameLocker dstlock(aDst);
+  if (!srclock.Succeeded() || !dstlock.Succeeded()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (aSrc->GetIsPaletted()) {
+    return DrawFrameTo(reinterpret_cast<uint8_t*>(aSrc->GetPaletteData()),
+                       aSrcRect, aSrc->PaletteDataLength(),
+                       aSrc->GetHasAlpha(), aDst->GetImageData(),
+                       aDst->GetRect(),
+                       FrameBlendMethod(aSrc->GetBlendMethod()));
+  }
+
+  return DrawFrameTo(aSrc->GetImageData(), aSrcRect,
+                     0, aSrc->GetHasAlpha(),
+                     aDst->GetImageData(), aDst->GetRect(),
+                     FrameBlendMethod(aSrc->GetBlendMethod()));
+}
+
+void
+FrameBlender::Discard()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // 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!");
+
+  // Delete all the decoded frames, then clear the array.
+  for (uint32_t i = 0; i < mFrames.Length(); ++i)
+    delete mFrames[i];
+  mFrames.Clear();
+}
+
+size_t
+FrameBlender::SizeOfDecodedWithComputedFallbackIfHeap(gfxASurface::MemoryLocation aLocation,
+                                                      nsMallocSizeOfFun aMallocSizeOf) const
+{
+  size_t n = 0;
+  for (uint32_t i = 0; i < mFrames.Length(); ++i) {
+    imgFrame* frame = mFrames.SafeElementAt(i, nullptr);
+    NS_ABORT_IF_FALSE(frame, "Null frame in frame array!");
+    n += frame->SizeOfExcludingThisWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
+  }
+
+  if (mAnim) {
+    if (mAnim->compositingFrame) {
+      n += mAnim->compositingFrame->SizeOfExcludingThisWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
+    }
+    if (mAnim->compositingPrevFrame) {
+      n += mAnim->compositingPrevFrame->SizeOfExcludingThisWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
+    }
+  }
+
+  return n;
+}
+
+void
+FrameBlender::ResetAnimation()
+{
+  if (mAnim) {
+    mAnim->lastCompositedFrameIndex = -1;
+  }
+}
+
+} // namespace image
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/image/src/FrameBlender.h
@@ -0,0 +1,188 @@
+/* -*- 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 "nsTArray.h"
+#include "mozilla/TimeStamp.h"
+#include "gfxASurface.h"
+
+class imgFrame;
+
+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();
+  ~FrameBlender();
+
+  bool DoBlend(nsIntRect* aDirtyRect, uint32_t aPrevFrameIndex,
+               uint32_t aNextFrameIndex);
+
+  /**
+   * Get the @aIndex-th frame, including (if applicable) any results of
+   * blending.
+   */
+  imgFrame* GetFrame(uint32_t aIndex) const;
+
+  /**
+   * Get the @aIndex-th frame in the frame index, ignoring results of blending.
+   */
+  imgFrame* RawGetFrame(uint32_t aIndex) const;
+
+  void InsertFrame(uint32_t framenum, imgFrame* aFrame);
+  void RemoveFrame(uint32_t framenum);
+  imgFrame* SwapFrame(uint32_t framenum, imgFrame* aFrame);
+  void ClearFrames();
+
+  /* The total number of frames in this image. */
+  uint32_t GetNumFrames() const;
+
+  void Discard();
+
+  void SetSize(nsIntSize aSize) { mSize = aSize; }
+
+  size_t SizeOfDecodedWithComputedFallbackIfHeap(gfxASurface::MemoryLocation aLocation,
+                                                 nsMallocSizeOfFun 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:
+
+  struct Anim
+  {
+    //! Track the last composited frame for Optimizations (See DoComposite code)
+    int32_t                    lastCompositedFrameIndex;
+
+    /** 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.
+     */
+    nsAutoPtr<imgFrame>        compositingFrame;
+
+    /** 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.
+     */
+    nsAutoPtr<imgFrame>        compositingPrevFrame;
+
+    Anim() :
+      lastCompositedFrameIndex(-1)
+    {}
+  };
+
+  inline void EnsureAnimExists()
+  {
+    if (!mAnim) {
+      // Create the animation context
+      mAnim = new Anim();
+    }
+  }
+
+  /** 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 transparancy mask
+   */
+  static void ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect);
+  static void ClearFrame(imgFrame* aFrame);
+
+  //! @overload
+  static void ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect, const nsIntRect &aRectToClear);
+  static void ClearFrame(imgFrame* aFrame, const nsIntRect& aRectToClear);
+
+  //! Copy one frames's image and mask into another
+  static bool CopyFrameImage(uint8_t *aDataSrc, const nsIntRect& aRectSrc,
+                             uint8_t *aDataDest, const nsIntRect& aRectDest);
+  static bool CopyFrameImage(imgFrame* aSrc, imgFrame* aDst);
+
+  /**
+   * Draws one frames'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(uint8_t *aSrcData, const nsIntRect& aSrcRect,
+                              uint32_t aSrcPaletteLength, bool aSrcHasAlpha,
+                              uint8_t *aDstPixels, const nsIntRect& aDstRect,
+                              FrameBlendMethod aBlendMethod);
+  static nsresult DrawFrameTo(imgFrame* aSrc, imgFrame* aDst, const nsIntRect& aSrcRect);
+
+private: // data
+  //! All the frames of the image
+  // IMPORTANT: if you use mFrames in a method, call EnsureImageIsDecoded() first
+  // to ensure that the frames actually exist (they may have been discarded to save
+  // memory, or we may be decoding on draw).
+  nsTArray<imgFrame*> mFrames;
+
+  nsIntSize mSize;
+
+  // 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).
+  Anim* mAnim;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif /* mozilla_imagelib_FrameBlender_h_ */
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -445,27 +445,23 @@ RasterImage::~RasterImage()
     MutexAutoLock lock(mDecodingMutex);
     DecodePool::StopDecoding(this);
     mDecoder = nullptr;
 
     // Unlock the last frame (if we have any). Our invariant is that, while we
     // have a decoder open, the last frame is always locked.
     // This would be done in ShutdownDecoder, but since mDecoder is non-null,
     // we didn't call ShutdownDecoder and we need to do it manually.
-    if (mFrames.Length() > 0) {
-      imgFrame *curframe = mFrames.ElementAt(mFrames.Length() - 1);
+    if (GetNumFrames() > 0) {
+      imgFrame *curframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1);
       curframe->UnlockImageData();
     }
   }
 
   delete mAnim;
-
-  for (unsigned int i = 0; i < mFrames.Length(); ++i)
-    delete mFrames[i];
-
   delete mMultipartDecodedFrame;
 
   // Total statistics
   num_containers--;
   total_source_bytes -= mSourceData.Length();
 
   if (NS_IsMainThread()) {
     DiscardTracker::Remove(&mDiscardTrackerNode);
@@ -544,18 +540,18 @@ RasterImage::GetSingleLoopTime() const
   }
 
   // If we're not looping, a single loop time has no meaning
   if (mLoopCount == 0) {
     return 0;
   }
 
   uint32_t looptime = 0;
-  for (uint32_t i = 0; i < mFrames.Length(); ++i) {
-    int32_t timeout = mFrames[i]->GetTimeout();
+  for (uint32_t i = 0; i < GetNumFrames(); ++i) {
+    int32_t timeout = mFrameBlender.RawGetFrame(i)->GetTimeout();
     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 0;
     }
@@ -565,103 +561,83 @@ RasterImage::GetSingleLoopTime() const
 }
 
 bool
 RasterImage::AdvanceFrame(TimeStamp aTime, nsIntRect* aDirtyRect)
 {
   NS_ASSERTION(aTime <= TimeStamp::Now(),
                "Given time appears to be in the future");
 
-  imgFrame* nextFrame = nullptr;
   uint32_t currentFrameIndex = mAnim->currentAnimationFrameIndex;
   uint32_t nextFrameIndex = mAnim->currentAnimationFrameIndex + 1;
   uint32_t timeout = 0;
 
   // Figure out if we have the next full frame. This is more complicated than
-  // just checking for mFrames.Length() because decoders append their frames
+  // just checking GetNumFrames() because decoders append their frames
   // before they're filled in.
-  NS_ABORT_IF_FALSE(mDecoder || nextFrameIndex <= mFrames.Length(),
+  NS_ABORT_IF_FALSE(mDecoder || nextFrameIndex <= GetNumFrames(),
                     "How did we get 2 indices too far by incrementing?");
 
   // If we don't have a decoder, we know we've got everything we're going to
   // get. If we do, we only display fully-downloaded frames; everything else
   // gets delayed.
   bool haveFullNextFrame = (mMultipart && mBytesDecoded == 0) || !mDecoder ||
-                           nextFrameIndex < mDecoder->GetCompleteFrameCount();
+                            nextFrameIndex < mDecoder->GetCompleteFrameCount();
 
   // If we're done decoding the next frame, go ahead and display it now and
   // reinit with the next frame's delay time.
   if (haveFullNextFrame) {
-    if (mFrames.Length() == nextFrameIndex) {
+    if (GetNumFrames() == nextFrameIndex) {
       // End of Animation, unless we are looping forever
 
       // If animation mode is "loop once", it's time to stop animating
       if (mAnimationMode == kLoopOnceAnimMode || mLoopCount == 0) {
         mAnimationFinished = true;
         EvaluateAnimation();
       }
 
-      // We may have used compositingFrame to build a frame, and then copied
-      // it back into mFrames[..].  If so, delete composite to save memory
-      if (mAnim->compositingFrame && mAnim->lastCompositedFrameIndex == -1) {
-        mAnim->compositingFrame = nullptr;
-      }
-
       nextFrameIndex = 0;
 
       if (mLoopCount > 0) {
         mLoopCount--;
       }
 
       if (!mAnimating) {
         // break out early if we are actually done animating
         return false;
       }
     }
 
-    if (!(nextFrame = mFrames[nextFrameIndex])) {
-      // something wrong with the next frame, skip it
-      mAnim->currentAnimationFrameIndex = nextFrameIndex;
-      return false;
-    }
-
-    timeout = nextFrame->GetTimeout();
+    timeout = mFrameBlender.GetFrame(nextFrameIndex)->GetTimeout();
 
   } else {
     // Uh oh, the frame we want to show is currently being decoded (partial)
     // Wait until the next refresh driver tick and try again
     return false;
   }
 
   if (!(timeout > 0)) {
     mAnimationFinished = true;
     EvaluateAnimation();
   }
 
   if (nextFrameIndex == 0) {
     *aDirtyRect = mAnim->firstFrameRefreshArea;
   } else {
-    imgFrame *curFrame = mFrames[currentFrameIndex];
-
-    if (!curFrame) {
-      return false;
-    }
-
     // Change frame
-    if (NS_FAILED(DoComposite(aDirtyRect, curFrame,
-                              nextFrame, nextFrameIndex))) {
+    if (!mFrameBlender.DoBlend(aDirtyRect, currentFrameIndex, nextFrameIndex)) {
       // something went wrong, move on to next
       NS_WARNING("RasterImage::AdvanceFrame(): Compositing of frame failed");
-      nextFrame->SetCompositingFailed(true);
+      mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(true);
       mAnim->currentAnimationFrameTime = GetCurrentImgFrameEndTime();
       mAnim->currentAnimationFrameIndex = nextFrameIndex;
       return false;
     }
 
-    nextFrame->SetCompositingFailed(false);
+    mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(false);
   }
 
   mAnim->currentAnimationFrameTime = GetCurrentImgFrameEndTime();
 
   // If we can get closer to the current time by a multiple of the image's loop
   // time, we should.
   uint32_t loopTime = GetSingleLoopTime();
   if (loopTime > 0) {
@@ -684,17 +660,16 @@ RasterImage::AdvanceFrame(TimeStamp aTim
 // [notxpcom] void requestRefresh ([const] in TimeStamp aTime);
 NS_IMETHODIMP_(void)
 RasterImage::RequestRefresh(const mozilla::TimeStamp& aTime)
 {
   if (!ShouldAnimate()) {
     return;
   }
 
-  EnsureAnimExists();
   EvaluateAnimation();
 
   // only advance the frame if the current time is greater than or
   // equal to the current frame's end time.
   TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime();
   bool frameAdvanced = false;
 
   // The dirtyRect variable will contain an accumulation of the sub-rectangles
@@ -812,21 +787,19 @@ RasterImage::GetType()
   return imgIContainer::TYPE_RASTER;
 }
 
 imgFrame*
 RasterImage::GetImgFrameNoDecode(uint32_t framenum)
 {
   if (!mAnim) {
     NS_ASSERTION(framenum == 0, "Don't ask for a frame > 0 if we're not animated!");
-    return mFrames.SafeElementAt(0, nullptr);
+    return mFrameBlender.GetFrame(0);
   }
-  if (mAnim->lastCompositedFrameIndex == int32_t(framenum))
-    return mAnim->compositingFrame;
-  return mFrames.SafeElementAt(framenum, nullptr);
+  return mFrameBlender.GetFrame(framenum);
 }
 
 imgFrame*
 RasterImage::GetImgFrame(uint32_t framenum)
 {
   nsresult rv = WantDecodedFrames();
   CONTAINER_ENSURE_TRUE(NS_SUCCEEDED(rv), nullptr);
   return GetImgFrameNoDecode(framenum);
@@ -862,17 +835,17 @@ RasterImage::GetCurrentImgFrameIndex() c
     return mAnim->currentAnimationFrameIndex;
 
   return 0;
 }
 
 TimeStamp
 RasterImage::GetCurrentImgFrameEndTime() const
 {
-  imgFrame* currentFrame = mFrames[mAnim->currentAnimationFrameIndex];
+  imgFrame* currentFrame = mFrameBlender.RawGetFrame(mAnim->currentAnimationFrameIndex);
   TimeStamp currentFrameTime = mAnim->currentAnimationFrameTime;
   int64_t timeout = currentFrame->GetTimeout();
 
   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. The reason
     // this positive infinity was chosen was because it works with the loop in
     // RequestRefresh() above.
@@ -949,19 +922,19 @@ RasterImage::FrameRect(uint32_t aWhichFr
 
 uint32_t
 RasterImage::GetCurrentFrameIndex()
 {
   return GetCurrentImgFrameIndex();
 }
 
 uint32_t
-RasterImage::GetNumFrames()
+RasterImage::GetNumFrames() const
 {
-  return mFrames.Length();
+  return mFrameBlender.GetNumFrames();
 }
 
 //******************************************************************************
 /* readonly attribute boolean animated; */
 NS_IMETHODIMP
 RasterImage::GetAnimated(bool *aAnimated)
 {
   if (mError)
@@ -993,17 +966,17 @@ RasterImage::GetFirstFrameDelay()
 {
   if (mError)
     return -1;
 
   bool animated = false;
   if (NS_FAILED(GetAnimated(&animated)) || !animated)
     return -1;
 
-  return mFrames[0]->GetTimeout();
+  return mFrameBlender.GetFrame(0)->GetTimeout();
 }
 
 nsresult
 RasterImage::CopyFrame(uint32_t aWhichFrame,
                        uint32_t aFlags,
                        gfxImageSurface **_retval)
 {
   if (aWhichFrame > FRAME_MAX_VALUE)
@@ -1221,22 +1194,17 @@ RasterImage::HeapSizeOfSourceWithCompute
   }
   return n;
 }
 
 size_t
 RasterImage::SizeOfDecodedWithComputedFallbackIfHeap(gfxASurface::MemoryLocation aLocation,
                                                      nsMallocSizeOfFun aMallocSizeOf) const
 {
-  size_t n = 0;
-  for (uint32_t i = 0; i < mFrames.Length(); ++i) {
-    imgFrame* frame = mFrames.SafeElementAt(i, nullptr);
-    NS_ABORT_IF_FALSE(frame, "Null frame in frame array!");
-    n += frame->SizeOfExcludingThisWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
-  }
+  size_t n = mFrameBlender.SizeOfDecodedWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
 
   if (mScaleResult.status == SCALE_DONE) {
     n += mScaleResult.frame->SizeOfExcludingThisWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
   }
 
   return n;
 }
 
@@ -1256,49 +1224,40 @@ RasterImage::NonHeapSizeOfDecoded() cons
 
 size_t
 RasterImage::OutOfProcessSizeOfDecoded() const
 {
   return SizeOfDecodedWithComputedFallbackIfHeap(gfxASurface::MEMORY_OUT_OF_PROCESS,
                                                  NULL);
 }
 
-void
-RasterImage::DeleteImgFrame(uint32_t framenum)
-{
-  NS_ABORT_IF_FALSE(framenum < mFrames.Length(), "Deleting invalid frame!");
-
-  delete mFrames[framenum];
-  mFrames[framenum] = nullptr;
-}
-
 nsresult
 RasterImage::InternalAddFrameHelper(uint32_t framenum, imgFrame *aFrame,
                                     uint8_t **imageData, uint32_t *imageLength,
                                     uint32_t **paletteData, uint32_t *paletteLength,
                                     imgFrame** aRetFrame)
 {
-  NS_ABORT_IF_FALSE(framenum <= mFrames.Length(), "Invalid frame index!");
-  if (framenum > mFrames.Length())
+  NS_ABORT_IF_FALSE(framenum <= GetNumFrames(), "Invalid frame index!");
+  if (framenum > GetNumFrames())
     return NS_ERROR_INVALID_ARG;
 
   nsAutoPtr<imgFrame> frame(aFrame);
 
   // We are in the middle of decoding. This will be unlocked when we finish
   // decoding or switch to another frame.
   frame->LockImageData();
 
   if (paletteData && paletteLength)
     frame->GetPaletteData(paletteData, paletteLength);
 
   frame->GetImageData(imageData, imageLength);
 
   *aRetFrame = frame;
 
-  mFrames.InsertElementAt(framenum, frame.forget());
+  mFrameBlender.InsertFrame(framenum, frame.forget());
 
   return NS_OK;
 }
 
 nsresult
 RasterImage::InternalAddFrame(uint32_t framenum,
                               int32_t aX, int32_t aY,
                               int32_t aWidth, int32_t aHeight,
@@ -1310,52 +1269,52 @@ RasterImage::InternalAddFrame(uint32_t f
                               uint32_t *paletteLength,
                               imgFrame** aRetFrame)
 {
   // We assume that we're in the middle of decoding because we unlock the
   // previous frame when we create a new frame, and only when decoding do we
   // lock frames.
   NS_ABORT_IF_FALSE(mDecoder, "Only decoders may add frames!");
 
-  NS_ABORT_IF_FALSE(framenum <= mFrames.Length(), "Invalid frame index!");
-  if (framenum > mFrames.Length())
+  NS_ABORT_IF_FALSE(framenum <= GetNumFrames(), "Invalid frame index!");
+  if (framenum > GetNumFrames())
     return NS_ERROR_INVALID_ARG;
 
   nsAutoPtr<imgFrame> frame(new imgFrame());
 
   nsresult rv = frame->Init(aX, aY, aWidth, aHeight, aFormat, aPaletteDepth);
   if (!(mSize.width > 0 && mSize.height > 0))
     NS_WARNING("Shouldn't call InternalAddFrame with zero size");
   if (!NS_SUCCEEDED(rv))
     NS_WARNING("imgFrame::Init should succeed");
   NS_ENSURE_SUCCESS(rv, rv);
 
   // We know we are in a decoder. Therefore, we must unlock the previous frame
   // when we move on to decoding into the next frame.
-  if (mFrames.Length() > 0) {
-    imgFrame *prevframe = mFrames.ElementAt(mFrames.Length() - 1);
+  if (GetNumFrames() > 0) {
+    imgFrame *prevframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1);
     prevframe->UnlockImageData();
   }
 
-  if (mFrames.Length() == 0) {
+  if (GetNumFrames() == 0) {
     return InternalAddFrameHelper(framenum, frame.forget(), imageData, imageLength,
                                   paletteData, paletteLength, aRetFrame);
   }
 
-  if (mFrames.Length() == 1) {
+  if (GetNumFrames() == 1) {
     // Since we're about to add our second frame, initialize animation stuff
     EnsureAnimExists();
 
     // 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 = mFrames[0]->GetFrameDisposalMethod();
-    if (frameDisposalMethod == kDisposeClear ||
-        frameDisposalMethod == kDisposeRestorePrevious)
-      mAnim->firstFrameRefreshArea = mFrames[0]->GetRect();
+    int32_t frameDisposalMethod = mFrameBlender.RawGetFrame(0)->GetFrameDisposalMethod();
+    if (frameDisposalMethod == FrameBlender::kDisposeClear ||
+        frameDisposalMethod == FrameBlender::kDisposeRestorePrevious)
+      mAnim->firstFrameRefreshArea = mFrameBlender.RawGetFrame(0)->GetRect();
   }
 
   // Calculate firstFrameRefreshArea
   // Some gifs are huge but only have a small area that they animate
   // We only need to refresh that small area when Frame 0 comes around again
   nsIntRect frameRect = frame->GetRect();
   mAnim->firstFrameRefreshArea.UnionRect(mAnim->firstFrameRefreshArea,
                                          frameRect);
@@ -1413,16 +1372,18 @@ RasterImage::SetSize(int32_t aWidth, int
     DoError();
     return NS_ERROR_UNEXPECTED;
   }
 
   // Set the size and flag that we have it
   mSize.SizeTo(aWidth, aHeight);
   mHasSize = true;
 
+  mFrameBlender.SetSize(mSize);
+
   return NS_OK;
 }
 
 nsresult
 RasterImage::EnsureFrame(uint32_t aFrameNum, int32_t aX, int32_t aY,
                          int32_t aWidth, int32_t aHeight,
                          gfxASurface::gfxImageFormat aFormat,
                          uint8_t aPaletteDepth,
@@ -1431,37 +1392,39 @@ RasterImage::EnsureFrame(uint32_t aFrame
                          imgFrame** aRetFrame)
 {
   if (mError)
     return NS_ERROR_FAILURE;
 
   NS_ENSURE_ARG_POINTER(imageData);
   NS_ENSURE_ARG_POINTER(imageLength);
   NS_ENSURE_ARG_POINTER(aRetFrame);
-  NS_ABORT_IF_FALSE(aFrameNum <= mFrames.Length(), "Invalid frame index!");
+  NS_ABORT_IF_FALSE(aFrameNum <= GetNumFrames(), "Invalid frame index!");
 
   if (aPaletteDepth > 0) {
     NS_ENSURE_ARG_POINTER(paletteData);
     NS_ENSURE_ARG_POINTER(paletteLength);
   }
 
-  if (aFrameNum > mFrames.Length())
+  if (aFrameNum > GetNumFrames())
     return NS_ERROR_INVALID_ARG;
 
   // Adding a frame that doesn't already exist.
-  if (aFrameNum == mFrames.Length())
+  if (aFrameNum == GetNumFrames()) {
     return InternalAddFrame(aFrameNum, aX, aY, aWidth, aHeight, aFormat,
                             aPaletteDepth, imageData, imageLength,
                             paletteData, paletteLength, aRetFrame);
-
-  imgFrame *frame = GetImgFrameNoDecode(aFrameNum);
-  if (!frame)
+  }
+
+  imgFrame *frame = mFrameBlender.RawGetFrame(aFrameNum);
+  if (!frame) {
     return InternalAddFrame(aFrameNum, aX, aY, aWidth, aHeight, aFormat,
                             aPaletteDepth, imageData, imageLength,
                             paletteData, paletteLength, aRetFrame);
+  }
 
   // See if we can re-use the frame that already exists.
   nsIntRect rect = frame->GetRect();
   if (rect.x == aX && rect.y == aY && rect.width == aWidth &&
       rect.height == aHeight && frame->GetFormat() == aFormat &&
       frame->GetPaletteDepth() == aPaletteDepth) {
     frame->GetImageData(imageData, imageLength);
     if (paletteData) {
@@ -1480,18 +1443,17 @@ RasterImage::EnsureFrame(uint32_t aFrame
   }
 
   // Not reusable, so replace the frame directly.
 
   // We know this frame is already locked, because it's the one we're currently
   // writing to.
   frame->UnlockImageData();
 
-  DeleteImgFrame(aFrameNum);
-  mFrames.RemoveElementAt(aFrameNum);
+  mFrameBlender.RemoveFrame(aFrameNum);
   nsAutoPtr<imgFrame> newFrame(new imgFrame());
   nsresult rv = newFrame->Init(aX, aY, aWidth, aHeight, aFormat, aPaletteDepth);
   NS_ENSURE_SUCCESS(rv, rv);
   return InternalAddFrameHelper(aFrameNum, newFrame.forget(), imageData,
                                 imageLength, paletteData, paletteLength,
                                 aRetFrame);
 }
 
@@ -1510,21 +1472,21 @@ RasterImage::EnsureFrame(uint32_t aFrame
 }
 
 nsresult
 RasterImage::SetFrameAsNonPremult(uint32_t aFrameNum, bool aIsNonPremult)
 {
   if (mError)
     return NS_ERROR_FAILURE;
 
-  NS_ABORT_IF_FALSE(aFrameNum < mFrames.Length(), "Invalid frame index!");
-  if (aFrameNum >= mFrames.Length())
+  NS_ABORT_IF_FALSE(aFrameNum < GetNumFrames(), "Invalid frame index!");
+  if (aFrameNum >= GetNumFrames())
     return NS_ERROR_INVALID_ARG;
 
-  imgFrame* frame = GetImgFrameNoDecode(aFrameNum);
+  imgFrame* frame = mFrameBlender.RawGetFrame(aFrameNum);
   NS_ABORT_IF_FALSE(frame, "Calling SetFrameAsNonPremult on frame that doesn't exist!");
   NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
 
   frame->SetAsNonPremult(aIsNonPremult);
 
   return NS_OK;
 }
 
@@ -1552,37 +1514,34 @@ RasterImage::DecodingComplete()
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
   // If there's only 1 frame, optimize it. Optimizing animated images
   // is not supported.
   //
   // We don't optimize the frame for multipart images because we reuse
   // the frame.
-  if ((mFrames.Length() == 1) && !mMultipart) {
-    rv = mFrames[0]->Optimize();
+  if ((GetNumFrames() == 1) && !mMultipart) {
+    rv = mFrameBlender.RawGetFrame(0)->Optimize();
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Double-buffer our frame in the multipart case, since we'll start decoding
-  // into mFrames again immediately and this produces severe tearing.
+  // into the first frame again immediately and this produces severe tearing.
   if (mMultipart) {
-    if (mFrames.Length() == 1) {
-      imgFrame* swapFrame = mMultipartDecodedFrame;
-      mMultipartDecodedFrame = GetImgFrameNoDecode(GetCurrentFrameIndex());
-      mFrames.Clear();
-      if (swapFrame) {
-        mFrames.AppendElement(swapFrame);
-      }
+    if (GetNumFrames() == 1) {
+      mMultipartDecodedFrame = mFrameBlender.SwapFrame(GetCurrentFrameIndex(),
+                                                       mMultipartDecodedFrame);
     } else {
       // Don't double buffer for animated multipart images. It entails more
       // complexity and it's not really needed since we already are smart about
       // not displaying the still-decoding frame of an animated image. We may
       // have already stored an extra frame, though, so we'll release it here.
       delete mMultipartDecodedFrame;
+      mMultipartDecodedFrame = nullptr;
     }
   }
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* void StartAnimation () */
@@ -1638,17 +1597,18 @@ RasterImage::ResetAnimation()
       !mAnim || mAnim->currentAnimationFrameIndex == 0)
     return NS_OK;
 
   mAnimationFinished = false;
 
   if (mAnimating)
     StopAnimation();
 
-  mAnim->lastCompositedFrameIndex = -1;
+  mFrameBlender.ResetAnimation();
+
   mAnim->currentAnimationFrameIndex = 0;
   UpdateImageContainer();
 
   // Note - We probably want to kick off a redecode somewhere around here when
   // we fix bug 500402.
 
   // Update display if we were animating before
   if (mAnimating && mStatusTracker) {
@@ -1737,22 +1697,19 @@ RasterImage::AddSourceData(const char *a
       mAnimating = false;
     }
     mAnimationFinished = false;
     if (mAnim) {
       delete mAnim;
       mAnim = nullptr;
     }
     // If there's only one frame, this could cause flickering
-    int old_frame_count = mFrames.Length();
+    int old_frame_count = GetNumFrames();
     if (old_frame_count > 1) {
-      for (int i = 0; i < old_frame_count; ++i) {
-        DeleteImgFrame(i);
-      }
-      mFrames.Clear();
+      mFrameBlender.ClearFrames();
     }
   }
 
   // If we're not storing source data and we've previously gotten the size,
   // write the data directly to the decoder. (If we haven't gotten the size,
   // we'll queue up the data and write it out when we do.)
   if (!StoringSourceData() && mHasSize) {
     mDecoder->SetSynchronous(true);
@@ -1981,468 +1938,16 @@ RasterImage::OnNewSourceData()
 nsresult
 RasterImage::SetSourceSizeHint(uint32_t sizeHint)
 {
   if (sizeHint && StoringSourceData())
     return mSourceData.SetCapacity(sizeHint) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
   return NS_OK;
 }
 
-//******************************************************************************
-// DoComposite gets called when the timer for animation get fired and we have to
-// update the composited frame of the animation.
-nsresult
-RasterImage::DoComposite(nsIntRect* aDirtyRect,
-                         imgFrame* aPrevFrame,
-                         imgFrame* aNextFrame,
-                         int32_t aNextFrameIndex)
-{
-  NS_ENSURE_ARG_POINTER(aDirtyRect);
-  NS_ENSURE_ARG_POINTER(aPrevFrame);
-  NS_ENSURE_ARG_POINTER(aNextFrame);
-
-  int32_t prevFrameDisposalMethod = aPrevFrame->GetFrameDisposalMethod();
-  if (prevFrameDisposalMethod == kDisposeRestorePrevious &&
-      !mAnim->compositingPrevFrame)
-    prevFrameDisposalMethod = kDisposeClear;
-
-  nsIntRect prevFrameRect = aPrevFrame->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 == kDisposeClear))
-    prevFrameDisposalMethod = kDisposeClearAll;
-
-  int32_t nextFrameDisposalMethod = aNextFrame->GetFrameDisposalMethod();
-  nsIntRect nextFrameRect = aNextFrame->GetRect();
-  bool isFullNextFrame = (nextFrameRect.x == 0 && nextFrameRect.y == 0 &&
-                          nextFrameRect.width == mSize.width &&
-                          nextFrameRect.height == mSize.height);
-
-  if (!aNextFrame->GetIsPaletted()) {
-    // Optimization: Skip compositing if the previous frame wants to clear the
-    //               whole image
-    if (prevFrameDisposalMethod == kDisposeClearAll) {
-      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
-      return NS_OK;
-    }
-
-    // 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 != kDisposeRestorePrevious) &&
-        !aNextFrame->GetHasAlpha()) {
-      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
-      return NS_OK;
-    }
-  }
-
-  // Calculate area that needs updating
-  switch (prevFrameDisposalMethod) {
-    default:
-    case kDisposeNotSpecified:
-    case kDisposeKeep:
-      *aDirtyRect = nextFrameRect;
-      break;
-
-    case kDisposeClearAll:
-      // Whole image container is cleared
-      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
-      break;
-
-    case 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 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 (mAnim->lastCompositedFrameIndex == aNextFrameIndex) {
-    return NS_OK;
-  }
-
-  bool needToBlankComposite = false;
-
-  // Create the Compositing Frame
-  if (!mAnim->compositingFrame) {
-    mAnim->compositingFrame = new imgFrame();
-    nsresult rv = mAnim->compositingFrame->Init(0, 0, mSize.width, mSize.height,
-                                                gfxASurface::ImageFormatARGB32);
-    if (NS_FAILED(rv)) {
-      mAnim->compositingFrame = nullptr;
-      return rv;
-    }
-    needToBlankComposite = true;
-  } else if (aNextFrameIndex != mAnim->lastCompositedFrameIndex+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 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 (!aNextFrame->GetHasAlpha() &&
-      nextFrameDisposalMethod != 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 kDisposeClear:
-        if (needToBlankComposite) {
-          // If we just created the composite, it could have anything in it's
-          // buffer. Clear whole frame
-          ClearFrame(mAnim->compositingFrame);
-        } else {
-          // Only blank out previous frame area (both color & Mask/Alpha)
-          ClearFrame(mAnim->compositingFrame, prevFrameRect);
-        }
-        break;
-
-      case kDisposeClearAll:
-        ClearFrame(mAnim->compositingFrame);
-        break;
-
-      case kDisposeRestorePrevious:
-        // It would be better to copy only the area changed back to
-        // compositingFrame.
-        if (mAnim->compositingPrevFrame) {
-          CopyFrameImage(mAnim->compositingPrevFrame, mAnim->compositingFrame);
-
-          // destroy only if we don't need it for this frame's disposal
-          if (nextFrameDisposalMethod != kDisposeRestorePrevious)
-            mAnim->compositingPrevFrame = nullptr;
-        } else {
-          ClearFrame(mAnim->compositingFrame);
-        }
-        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 DoComposite(), so (aNextFrameIndex - 1) will
-        // always be a valid frame number.
-        if (mAnim->lastCompositedFrameIndex != aNextFrameIndex - 1) {
-          if (isFullPrevFrame && !aPrevFrame->GetIsPaletted()) {
-            // Just copy the bits
-            CopyFrameImage(aPrevFrame, mAnim->compositingFrame);
-          } else {
-            if (needToBlankComposite) {
-              // Only blank composite when prev is transparent or not full.
-              if (aPrevFrame->GetHasAlpha() || !isFullPrevFrame) {
-                ClearFrame(mAnim->compositingFrame);
-              }
-            }
-            DrawFrameTo(aPrevFrame, mAnim->compositingFrame, prevFrameRect);
-          }
-        }
-    }
-  } else if (needToBlankComposite) {
-    // If we just created the composite, it could have anything in it's
-    // buffers. Clear them
-    ClearFrame(mAnim->compositingFrame);
-  }
-
-  // Check if the frame we are composing wants the previous image restored afer
-  // it is done. Don't store it (again) if last frame wanted its image restored
-  // too
-  if ((nextFrameDisposalMethod == kDisposeRestorePrevious) &&
-      (prevFrameDisposalMethod != kDisposeRestorePrevious)) {
-    // We are storing the whole image.
-    // It would be better if we just stored the area that nextFrame is going to
-    // overwrite.
-    if (!mAnim->compositingPrevFrame) {
-      mAnim->compositingPrevFrame = new imgFrame();
-      nsresult rv = mAnim->compositingPrevFrame->Init(0, 0, mSize.width, mSize.height,
-                                                      gfxASurface::ImageFormatARGB32);
-      if (NS_FAILED(rv)) {
-        mAnim->compositingPrevFrame = nullptr;
-        return rv;
-      }
-    }
-
-    CopyFrameImage(mAnim->compositingFrame, mAnim->compositingPrevFrame);
-  }
-
-  // blit next frame into it's correct spot
-  DrawFrameTo(aNextFrame, mAnim->compositingFrame, nextFrameRect);
-
-  // Set timeout of CompositeFrame to timeout of frame we just composed
-  // Bug 177948
-  int32_t timeout = aNextFrame->GetTimeout();
-  mAnim->compositingFrame->SetTimeout(timeout);
-
-  // Tell the image that it is fully 'downloaded'.
-  nsresult rv = mAnim->compositingFrame->ImageUpdated(mAnim->compositingFrame->GetRect());
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  // We don't want to keep composite images for 8bit frames.
-  // Also this optimization won't work if the next frame has
-  // kDisposeRestorePrevious, because it would need to be restored
-  // into "after prev disposal but before next blend" state,
-  // not into empty frame.
-  if (isFullNextFrame && mAnimationMode == kNormalAnimMode && mLoopCount != 0 &&
-      nextFrameDisposalMethod != kDisposeRestorePrevious &&
-      !aNextFrame->GetIsPaletted()) {
-    // We have a composited full frame
-    // Store the composited frame into the mFrames[..] so we don't have to
-    // continuously re-build it
-    // Then set the previous frame's disposal to CLEAR_ALL so we just draw the
-    // frame next time around
-    if (CopyFrameImage(mAnim->compositingFrame, aNextFrame)) {
-      aPrevFrame->SetFrameDisposalMethod(kDisposeClearAll);
-      mAnim->lastCompositedFrameIndex = -1;
-      return NS_OK;
-    }
-  }
-
-  mAnim->lastCompositedFrameIndex = aNextFrameIndex;
-
-  return NS_OK;
-}
-
-//******************************************************************************
-// Fill aFrame with black. Does also clears the mask.
-void
-RasterImage::ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect)
-{
-  if (!aFrameData)
-    return;
-
-  memset(aFrameData, 0, aFrameRect.width * aFrameRect.height * 4);
-}
-
-void
-RasterImage::ClearFrame(imgFrame* aFrame)
-{
-  AutoFrameLocker lock(aFrame);
-  if (lock.Succeeded()) {
-    ClearFrame(aFrame->GetImageData(), aFrame->GetRect());
-  }
-}
-
-//******************************************************************************
-void
-RasterImage::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.height; ++row) {
-    memset(aFrameData + toClear.x * 4 + row * bytesPerRow, 0, toClear.width * 4);
-  }
-}
-
-void
-RasterImage::ClearFrame(imgFrame* aFrame, const nsIntRect& aRectToClear)
-{
-  AutoFrameLocker lock(aFrame);
-  if (lock.Succeeded()) {
-    ClearFrame(aFrame->GetImageData(), aFrame->GetRect(), aRectToClear);
-  }
-}
-
-//******************************************************************************
-// 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
-RasterImage::CopyFrameImage(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;
-}
-
-bool
-RasterImage::CopyFrameImage(imgFrame* aSrc, imgFrame* aDst)
-{
-  AutoFrameLocker srclock(aSrc);
-  AutoFrameLocker dstlock(aDst);
-  if (!srclock.Succeeded() || !dstlock.Succeeded()) {
-    return false;
-  }
-
-  return CopyFrameImage(aSrc->GetImageData(), aSrc->GetRect(),
-                        aDst->GetImageData(), aDst->GetRect());
-}
-
-nsresult
-RasterImage::DrawFrameTo(uint8_t *aSrcData, const nsIntRect& aSrcRect,
-                         uint32_t aSrcPaletteLength, bool aSrcHasAlpha,
-                         uint8_t *aDstPixels, const nsIntRect& aDstRect,
-                         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("RasterImage::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),
-                "RasterImage::DrawFrameTo: Invalid aSrcRect");
-
-    // clipped image size may be smaller than source, but not larger
-    NS_ASSERTION((width <= aSrcRect.width) && (height <= aSrcRect.height),
-                 "RasterImage::DrawFrameTo: source must be smaller than dest");
-
-    // Get pointers to image data
-    uint8_t *srcPixels = aSrcData + aSrcPaletteLength;
-    uint32_t *dstPixels = reinterpret_cast<uint32_t*>(aDstPixels);
-    uint32_t *colormap = reinterpret_cast<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*>(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);
-
-    pixman_image_composite32(aBlendMethod == kBlendSource ? PIXMAN_OP_SRC : PIXMAN_OP_OVER,
-                             src,
-                             nullptr,
-                             dst,
-                             0, 0,
-                             0, 0,
-                             aSrcRect.x, aSrcRect.y,
-                             aDstRect.width, aDstRect.height);
-
-    pixman_image_unref(src);
-    pixman_image_unref(dst);
-  }
-
-  return NS_OK;
-}
-
-nsresult
-RasterImage::DrawFrameTo(imgFrame* aSrc, imgFrame* aDst, const nsIntRect& aSrcRect)
-{
-  AutoFrameLocker srclock(aSrc);
-  AutoFrameLocker dstlock(aDst);
-  if (!srclock.Succeeded() || !dstlock.Succeeded()) {
-    return NS_ERROR_FAILURE;
-  }
-
-  if (aSrc->GetIsPaletted()) {
-    return DrawFrameTo(reinterpret_cast<uint8_t*>(aSrc->GetPaletteData()),
-                       aSrcRect, aSrc->PaletteDataLength(),
-                       aSrc->GetHasAlpha(), aDst->GetImageData(),
-                       aDst->GetRect(),
-                       FrameBlendMethod(aSrc->GetBlendMethod()));
-  }
-
-  return DrawFrameTo(aSrc->GetImageData(), aSrcRect,
-                     0, aSrc->GetHasAlpha(),
-                     aDst->GetImageData(), aDst->GetRect(),
-                     FrameBlendMethod(aSrc->GetBlendMethod()));
-}
-
 /********* Methods to implement lazy allocation of nsIProperties object *************/
 NS_IMETHODIMP
 RasterImage::Get(const char *prop, const nsIID & iid, void * *result)
 {
   if (!mProperties)
     return NS_ERROR_FAILURE;
   return mProperties->Get(prop, iid, result);
 }
@@ -2498,22 +2003,20 @@ RasterImage::Discard(bool force)
   // We should never discard when we have an active decoder
   NS_ABORT_IF_FALSE(!mDecoder, "Asked to discard with open decoder!");
 
   // 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 = mFrames.Length();
-
-  // Delete all the decoded frames, then clear the array.
-  for (int i = 0; i < old_frame_count; ++i)
-    delete mFrames[i];
-  mFrames.Clear();
+  int old_frame_count = GetNumFrames();
+
+  // Delete all the decoded frames
+  mFrameBlender.Discard();
 
   // Clear our downscaled frame.
   mScaleResult.status = SCALE_INVALID;
   mScaleResult.frame = nullptr;
 
   // Clear the last decoded multipart frame.
   delete mMultipartDecodedFrame;
   mMultipartDecodedFrame = nullptr;
@@ -2534,17 +2037,17 @@ RasterImage::Discard(bool force)
   PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
          ("CompressedImageAccounting: discarded uncompressed image "
           "data from RasterImage %p (%s) - %d frames (cached count: %d); "
           "Total Containers: %d, Discardable containers: %d, "
           "Total source bytes: %lld, Source bytes for discardable containers %lld",
           this,
           mSourceDataMimeType.get(),
           old_frame_count,
-          mFrames.Length(),
+          GetNumFrames(),
           num_containers,
           num_discardable_containers,
           total_source_bytes,
           discardable_source_bytes));
 }
 
 // Helper method to determine if we can discard an image
 bool
@@ -2631,18 +2134,18 @@ RasterImage::InitDecoder(bool aDoSizeDec
 #endif
     default:
       NS_ABORT_IF_FALSE(0, "Shouldn't get here!");
   }
 
   // If we already have frames, we're probably in the multipart/x-mixed-replace
   // case. Regardless, we need to lock the last frame. Our invariant is that,
   // while we have a decoder open, the last frame is always locked.
-  if (mFrames.Length() > 0) {
-    imgFrame *curframe = mFrames.ElementAt(mFrames.Length() - 1);
+  if (GetNumFrames() > 0) {
+    imgFrame *curframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1);
     curframe->LockImageData();
   }
 
   // Initialize the decoder
   if (!mDecodeRequest) {
     mDecodeRequest = new DecodeRequest(this);
   }
   mDecoder->SetObserver(mDecodeRequest->mStatusTracker->GetDecoderObserver());
@@ -2700,18 +2203,18 @@ RasterImage::ShutdownDecoder(eShutdownIn
   // Ensure that the decoder is initialized
   NS_ABORT_IF_FALSE(mDecoder, "Calling ShutdownDecoder() with no active decoder!");
 
   // Figure out what kind of decode we were doing before we get rid of our decoder
   bool wasSizeDecode = mDecoder->IsSizeDecode();
 
   // Unlock the last frame (if we have any). Our invariant is that, while we
   // have a decoder open, the last frame is always locked.
-  if (mFrames.Length() > 0) {
-    imgFrame *curframe = mFrames.ElementAt(mFrames.Length() - 1);
+  if (GetNumFrames() > 0) {
+    imgFrame *curframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1);
     curframe->UnlockImageData();
   }
 
   // Finalize the decoder
   // null out mDecoder, _then_ check for errors on the close (otherwise the
   // error routine might re-invoke ShutdownDecoder)
   nsRefPtr<Decoder> decoder = mDecoder;
   mDecoder = nullptr;
@@ -3485,17 +2988,17 @@ RasterImage::WriteToRasterImage(nsIInput
   *aWriteCount = aCount;
 
   return NS_OK;
 }
 
 bool
 RasterImage::ShouldAnimate()
 {
-  return ImageResource::ShouldAnimate() && mFrames.Length() >= 2 &&
+  return ImageResource::ShouldAnimate() && GetNumFrames() >= 2 &&
          !mAnimationFinished;
 }
 
 /* readonly attribute uint32_t framesNotified; */
 #ifdef DEBUG
 NS_IMETHODIMP
 RasterImage::GetFramesNotified(uint32_t *aFramesNotified)
 {
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -13,16 +13,17 @@
  * @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 "nsITimer.h"
 #include "nsIRequest.h"
 #include "nsTArray.h"
 #include "imgFrame.h"
 #include "nsThreadUtils.h"
@@ -168,17 +169,17 @@ public:
                                       uint32_t aToOffset, uint32_t aCount,
                                       uint32_t* aWriteCount);
 
   /* The index of the current frame that would be drawn if the image was to be
    * drawn now. */
   uint32_t GetCurrentFrameIndex();
 
   /* The total number of frames in this image. */
-  uint32_t GetNumFrames();
+  uint32_t GetNumFrames() const;
 
   virtual size_t HeapSizeOfSourceWithComputedFallback(nsMallocSizeOfFun aMallocSizeOf) const;
   virtual size_t HeapSizeOfDecodedWithComputedFallback(nsMallocSizeOfFun aMallocSizeOf) const;
   virtual size_t NonHeapSizeOfDecoded() const;
   virtual size_t OutOfProcessSizeOfDecoded() const;
 
   /* Triggers discarding. */
   void Discard(bool force = false);
@@ -269,44 +270,16 @@ public:
   void SetRequestedResolution(const nsIntSize requestedResolution) {
     mRequestedResolution = requestedResolution;
   }
 
   nsIntSize GetRequestedResolution() {
     return mRequestedResolution;
   }
 
-  // "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
-  };
-
  nsCString GetURIString() {
     nsCString spec;
     if (GetURI()) {
       GetURI()->GetSpec(spec);
     }
     return spec;
   }
 
@@ -354,40 +327,19 @@ private:
   {
     //! Area of the first frame that needs to be redrawn on subsequent loops.
     nsIntRect                  firstFrameRefreshArea;
     uint32_t                   currentAnimationFrameIndex; // 0 to numFrames-1
 
     // the time that the animation advanced to the current frame
     TimeStamp                  currentAnimationFrameTime;
 
-    //! Track the last composited frame for Optimizations (See DoComposite code)
-    int32_t                    lastCompositedFrameIndex;
-    /** 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.
-     */
-    nsAutoPtr<imgFrame>        compositingFrame;
-    /** 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.
-     */
-    nsAutoPtr<imgFrame>        compositingPrevFrame;
-
     Anim() :
-      firstFrameRefreshArea(),
-      currentAnimationFrameIndex(0),
-      lastCompositedFrameIndex(-1) {}
-    ~Anim() {}
+      currentAnimationFrameIndex(0)
+    {}
   };
 
   /**
    * Each RasterImage has a pointer to one or zero heap-allocated
    * DecodeRequests.
    */
   struct DecodeRequest
   {
@@ -586,16 +538,17 @@ private:
                                     const gfxMatrix &aUserSpaceToImageSpace,
                                     const gfxRect &aFill,
                                     const nsIntRect &aSubimage,
                                     uint32_t aFlags);
 
   nsresult CopyFrame(uint32_t aWhichFrame,
                      uint32_t aFlags,
                      gfxImageSurface **_retval);
+
   /**
    * Advances the animation. Typically, this will advance a single frame, but it
    * may advance multiple frames. This may happen if we have infrequently
    * "ticking" refresh drivers (e.g. in background tabs), or extremely short-
    * lived animation frames.
    *
    * @param aTime the time that the animation should advance to. This will
    *              typically be <= TimeStamp::Now().
@@ -656,68 +609,16 @@ private:
       // is acceptable for the moment.
       LockImage();
 
       // Notify our observers that we are starting animation.
       CurrentStatusTracker().RecordImageIsAnimated();
     }
   }
 
-  /** Function for doing the frame compositing of animations
-   *
-   * @param aDirtyRect  Area that the display will need to update
-   * @param aPrevFrame  Last Frame seen/processed
-   * @param aNextFrame  Frame we need to incorperate/display
-   * @param aNextFrameIndex Position of aNextFrame in mFrames list
-   */
-  nsresult DoComposite(nsIntRect* aDirtyRect,
-                       imgFrame* aPrevFrame,
-                       imgFrame* aNextFrame,
-                       int32_t aNextFrameIndex);
-
-  /** 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 transparancy mask
-   */
-  static void ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect);
-  static void ClearFrame(imgFrame* aFrame);
-
-  //! @overload
-  static void ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect, const nsIntRect &aRectToClear);
-  static void ClearFrame(imgFrame* aFrame, const nsIntRect& aRectToClear);
-
-  //! Copy one frames's image and mask into another
-  static bool CopyFrameImage(uint8_t *aDataSrc, const nsIntRect& aRectSrc,
-                             uint8_t *aDataDest, const nsIntRect& aRectDest);
-  static bool CopyFrameImage(imgFrame* aSrc, imgFrame* aDst);
-
-  /**
-   * Draws one frames'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(uint8_t *aSrcData, const nsIntRect& aSrcRect,
-                              uint32_t aSrcPaletteLength, bool aSrcHasAlpha,
-                              uint8_t *aDstPixels, const nsIntRect& aDstRect,
-                              FrameBlendMethod aBlendMethod);
-  static nsresult DrawFrameTo(imgFrame* aSrc, imgFrame* aDst, const nsIntRect& aSrcRect);
-
   nsresult InternalAddFrameHelper(uint32_t framenum, imgFrame *frame,
                                   uint8_t **imageData, uint32_t *imageLength,
                                   uint32_t **paletteData, uint32_t *paletteLength,
                                   imgFrame** aRetFrame);
   nsresult InternalAddFrame(uint32_t framenum, int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight,
                             gfxASurface::gfxImageFormat aFormat, uint8_t aPaletteDepth,
                             uint8_t **imageData, uint32_t *imageLength,
                             uint32_t **paletteData, uint32_t *paletteLength,
@@ -743,31 +644,28 @@ private:
   // that for animated images because in EnsureAnimExists we lock the image and
   // never unlock so that animated images always have their lock count >= 1. In
   // that case we use our animation consumers count as a proxy for lock count.
   bool IsUnlocked() { return (mLockCount == 0 || (mAnim && mAnimationConsumers == 0)); }
 
 private: // data
   nsIntSize                  mSize;
 
-  // Whether mFrames below were decoded using any special flags.
+  // Whether our frames were decoded using any special flags.
   // Some flags (e.g. unpremultiplied data) may not be compatible
   // 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
-  // IMPORTANT: if you use mFrames in a method, call EnsureImageIsDecoded() first
-  // to ensure that the frames actually exist (they may have been discarded to save
-  // memory, or we may be decoding on draw).
-  nsTArray<imgFrame*>        mFrames;
+  FrameBlender              mFrameBlender;
 
   // The last frame we decoded for multipart images.
   imgFrame*                  mMultipartDecodedFrame;
 
   nsCOMPtr<nsIProperties>    mProperties;
 
   // 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
--- a/image/src/moz.build
+++ b/image/src/moz.build
@@ -12,16 +12,17 @@ EXPORTS += [
     'imgRequest.h',
     'imgRequestProxy.h',
 ]
 
 CPP_SOURCES += [
     'ClippedImage.cpp',
     'Decoder.cpp',
     'DiscardTracker.cpp',
+    'FrameBlender.cpp',
     'FrozenImage.cpp',
     'Image.cpp',
     'ImageFactory.cpp',
     'ImageMetadata.cpp',
     'ImageOps.cpp',
     'ImageWrapper.cpp',
     'RasterImage.cpp',
     'SVGDocumentWrapper.cpp',