Bug 486918. Part 2: Add the ability to pre-downscale using a high-quality scaler on a separate thread. r=joe,jlebar
authorTatiana Meshkova <tanya.meshkova@gmail.com>
Tue, 03 Apr 2012 14:57:22 -0700
changeset 108669 27e0c22b96e5
parent 108668 a59944cc3781
child 108670 aaf9e3020132
push id15624
push userphilringnalda@gmail.com
push dateSun, 30 Sep 2012 05:00:09 +0000
treeherdermozilla-inbound@aaf9e3020132 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjoe, jlebar
bugs486918
milestone18.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 486918. Part 2: Add the ability to pre-downscale using a high-quality scaler on a separate thread. r=joe,jlebar
gfx/thebes/gfx2DGlue.h
image/decoders/nsBMPDecoder.cpp
image/decoders/nsJPEGDecoder.cpp
image/decoders/nsPNGDecoder.cpp
image/src/RasterImage.cpp
image/src/RasterImage.h
--- a/gfx/thebes/gfx2DGlue.h
+++ b/gfx/thebes/gfx2DGlue.h
@@ -191,16 +191,33 @@ inline gfxASurface::gfxImageFormat Surfa
     return gfxASurface::ImageFormatRGB16_565;
   case FORMAT_A8:
     return gfxASurface::ImageFormatA8;
   default:
     return gfxASurface::ImageFormatUnknown;
   }
 }
 
+inline SurfaceFormat ImageFormatToSurfaceFormat(gfxASurface::gfxImageFormat aFormat)
+{
+  switch (aFormat) {
+  case gfxASurface::ImageFormatARGB32:
+    return FORMAT_B8G8R8A8;
+  case gfxASurface::ImageFormatRGB24:
+    return FORMAT_B8G8R8X8;
+  case gfxASurface::ImageFormatRGB16_565:
+    return FORMAT_R5G6B5;
+  case gfxASurface::ImageFormatA8:
+    return FORMAT_A8;
+  default:
+  case gfxASurface::ImageFormatUnknown:
+    return FORMAT_B8G8R8A8;
+  }
+}
+
 inline gfxASurface::gfxContentType ContentForFormat(const SurfaceFormat &aFormat)
 {
   switch (aFormat) {
   case FORMAT_R5G6B5:
   case FORMAT_B8G8R8X8:
     return gfxASurface::CONTENT_COLOR;
   case FORMAT_A8:
     return gfxASurface::CONTENT_ALPHA;
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -4,23 +4,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 /* I got the format description from http://www.daubnet.com/formats/BMP.html */
 
 /* This is a Cross-Platform BMP Decoder, which should work everywhere, including
  * Big-Endian machines like the PowerPC. */
 
 #include <stdlib.h>
 
+#include "ImageLogging.h"
 #include "EndianMacros.h"
 #include "nsBMPDecoder.h"
 
 #include "nsIInputStream.h"
 #include "RasterImage.h"
 #include "imgIContainerObserver.h"
-#include "ImageLogging.h"
 
 namespace mozilla {
 namespace image {
 
 #ifdef PR_LOGGING
 PRLogModuleInfo *gBMPLog = PR_NewLogModule("BMPDecoder");
 #endif
 
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -1,16 +1,16 @@
 /* -*- 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 "ImageLogging.h"
 #include "nsJPEGDecoder.h"
-#include "ImageLogging.h"
 
 #include "imgIContainerObserver.h"
 
 #include "nsIInputStream.h"
 
 #include "nspr.h"
 #include "nsCRT.h"
 #include "gfxColor.h"
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -1,16 +1,16 @@
 /* -*- 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 "ImageLogging.h"
 #include "nsPNGDecoder.h"
-#include "ImageLogging.h"
 
 #include "nsMemory.h"
 #include "nsRect.h"
 
 #include "nsIInputStream.h"
 
 #include "RasterImage.h"
 #include "imgIContainerObserver.h"
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -1,44 +1,98 @@
 /* -*- 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 "base/histogram.h"
+#include "ImageLogging.h"
 #include "nsComponentManagerUtils.h"
 #include "imgIContainerObserver.h"
 #include "nsError.h"
 #include "Decoder.h"
 #include "imgIDecoderObserver.h"
 #include "RasterImage.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsAutoPtr.h"
 #include "nsStringStream.h"
 #include "prmem.h"
 #include "prenv.h"
-#include "ImageLogging.h"
 #include "ImageContainer.h"
 #include "Layers.h"
 
 #include "nsPNGDecoder.h"
 #include "nsGIFDecoder2.h"
 #include "nsJPEGDecoder.h"
 #include "nsBMPDecoder.h"
 #include "nsICODecoder.h"
 #include "nsIconDecoder.h"
 
 #include "gfxContext.h"
+#include "gfx2DGlue.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/StandardInteger.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/gfx/Scale.h"
+
+// The high-quality scaler requires Skia.
+#ifdef MOZ_ENABLE_SKIA
+
+static bool
+ScaleFrameImage(imgFrame *aSrcFrame, imgFrame *aDstFrame,
+                const gfxSize &aScaleFactors)
+{
+  if (aScaleFactors.width <= 0 || aScaleFactors.height <= 0)
+    return false;
+
+  imgFrame *srcFrame = aSrcFrame;
+  nsIntRect srcRect = srcFrame->GetRect();
+  uint32_t dstWidth = NSToIntRoundUp(srcRect.width * aScaleFactors.width);
+  uint32_t dstHeight = NSToIntRoundUp(srcRect.height * aScaleFactors.height);
+
+  // Destination is unconditionally ARGB32 because that's what the scaler
+  // outputs.
+  nsresult rv = aDstFrame->Init(0, 0, dstWidth, dstHeight,
+                                gfxASurface::ImageFormatARGB32);
+  if (!NS_FAILED(rv)) {
+    uint8_t* srcData;
+    uint32_t srcDataLength;
+    // Source frame data is locked/unlocked on the main thread.
+    srcFrame->GetImageData(&srcData, &srcDataLength);
+    NS_ASSERTION(srcData != nullptr, "Source data is unavailable! Is it locked?");
+
+    uint8_t* dstData;
+    uint32_t dstDataLength;
+    aDstFrame->LockImageData();
+    aDstFrame->GetImageData(&dstData, &dstDataLength);
+
+    // This returns an SkBitmap backed by dstData; since it wrote to dstData,
+    // we don't need to look at that SkBitmap.
+    mozilla::gfx::Scale(srcData, srcRect.width, srcRect.height, aSrcFrame->GetImageBytesPerRow(),
+                        dstData, dstWidth, dstHeight, aDstFrame->GetImageBytesPerRow(),
+                        mozilla::gfx::ImageFormatToSurfaceFormat(aSrcFrame->GetFormat()));
+
+    aDstFrame->UnlockImageData();
+    return true;
+  }
+
+  return false;
+}
+#else // MOZ_ENABLE_SKIA
+static bool
+ScaleFrameImage(imgFrame *aSrcFrame, imgFrame *aDstFrame,
+                const gfxSize &aScaleFactors)
+{
+  return false;
+}
+#endif // MOZ_ENABLE_SKIA
 
 using namespace mozilla;
 using namespace mozilla::image;
 using namespace mozilla::layers;
 
 // a mask for flags that will affect the decoding
 #define DECODE_FLAGS_MASK (imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA | imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION)
 #define DECODE_FLAGS_DEFAULT 0
@@ -131,16 +185,22 @@ DiscardingEnabled()
   return enabled;
 }
 
 namespace mozilla {
 namespace image {
 
 /* static */ StaticRefPtr<RasterImage::DecodeWorker> RasterImage::DecodeWorker::sSingleton;
 
+#define PRE_DOWNSCALE_MIN_FACTOR 0.9
+
+/* static */ nsRefPtr<RasterImage::ScaleWorker> RasterImage::ScaleWorker::sSingleton;
+/* static */ nsRefPtr<RasterImage::DrawWorker> RasterImage::DrawWorker::sSingleton;
+static nsCOMPtr<nsIThread> sScaleWorkerThread = nullptr;
+
 #ifndef DEBUG
 NS_IMPL_ISUPPORTS3(RasterImage, imgIContainer, nsIProperties,
                    nsISupportsWeakReference)
 #else
 NS_IMPL_ISUPPORTS4(RasterImage, imgIContainer, nsIProperties,
                    imgIContainerDebug, nsISupportsWeakReference)
 #endif
 
@@ -165,30 +225,33 @@ RasterImage::RasterImage(imgStatusTracke
   mMultipart(false),
   mDiscardable(false),
   mHasSourceData(false),
   mDecoded(false),
   mHasBeenDecoded(false),
   mInDecoder(false),
   mAnimationFinished(false),
   mFinishing(false),
-  mInUpdateImageContainer(false)
+  mInUpdateImageContainer(false),
+  mScaleRequest(this)
 {
   // Set up the discard tracker node.
   mDiscardTrackerNode.img = this;
   Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0);
 
   // Statistics
   num_containers++;
 
 }
 
 //******************************************************************************
 RasterImage::~RasterImage()
 {
+  ScaleRequest::Stop(mScaleRequest.image);
+
   delete mAnim;
 
   for (unsigned int i = 0; i < mFrames.Length(); ++i)
     delete mFrames[i];
 
   // Discardable statistics
   if (mDiscardable) {
     num_discardable_containers--;
@@ -221,16 +284,18 @@ RasterImage::~RasterImage()
 void
 RasterImage::Initialize()
 {
   InitPrefCaches();
 
   // Create our singletons now, so we don't have to worry about what thread
   // they're created on.
   DecodeWorker::Singleton();
+  DrawWorker::Singleton();
+  ScaleWorker::Singleton();
 }
 
 nsresult
 RasterImage::Init(imgIDecoderObserver *aObserver,
                   const char* aMimeType,
                   const char* aURIString,
                   uint32_t aFlags)
 {
@@ -2579,16 +2644,229 @@ RasterImage::SyncDecode()
     rv = ShutdownDecoder(eShutdownIntent_Done);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
   // All good if no errors!
   return mError ? NS_ERROR_FAILURE : NS_OK;
 }
 
+/* static */ RasterImage::ScaleWorker*
+RasterImage::ScaleWorker::Singleton()
+{
+  if (!sSingleton) {
+    sSingleton = new ScaleWorker();
+    ClearOnShutdown(&sSingleton);
+  }
+
+  return sSingleton;
+}
+
+nsresult
+RasterImage::ScaleWorker::Run()
+{
+  if (!mInitialized) {
+    PR_SetCurrentThreadName("Image Scaler");
+    mInitialized = true;
+  }
+
+  ScaleRequest* request;
+  gfxSize scale;
+  imgFrame* frame;
+  {
+    MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
+    request = mScaleRequests.popFirst();
+    if (!request)
+      return NS_OK;
+
+    scale = request->scale;
+    frame = request->srcFrame;
+  }
+
+  nsAutoPtr<imgFrame> scaledFrame(new imgFrame());
+  bool scaled = ScaleFrameImage(frame, scaledFrame, scale);
+
+  // OK, we've got a new scaled image. Let's get the main thread to unlock and
+  // redraw it.
+  {
+    MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
+    if (scaled && scale == request->scale && !request->isInList()) {
+      request->dstFrame = scaledFrame;
+      request->done = true;
+    }
+
+    DrawWorker::Singleton()->RequestDraw(request->image);
+  }
+  return NS_OK;
+}
+
+// Note: you MUST call RequestScale with the ScaleWorker mutex held.
+void
+RasterImage::ScaleWorker::RequestScale(RasterImage* aImg)
+{
+  mRequestsMutex.AssertCurrentThreadOwns();
+
+  ScaleRequest* request = &aImg->mScaleRequest;
+  if (request->isInList())
+    return;
+
+  mScaleRequests.insertBack(request);
+
+  if (!sScaleWorkerThread) {
+    NS_NewThread(getter_AddRefs(sScaleWorkerThread), this, NS_DISPATCH_NORMAL);
+    ClearOnShutdown(&sScaleWorkerThread);
+  }
+  else {
+    sScaleWorkerThread->Dispatch(this, NS_DISPATCH_NORMAL);
+  }
+}
+
+/* static */ RasterImage::DrawWorker*
+RasterImage::DrawWorker::Singleton()
+{
+  if (!sSingleton) {
+    sSingleton = new DrawWorker();
+    ClearOnShutdown(&sSingleton);
+  }
+
+  return sSingleton;
+}
+
+nsresult
+RasterImage::DrawWorker::Run()
+{
+  ScaleRequest* request;
+  {
+    MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
+    request = mDrawRequests.popFirst();
+  }
+  if (request) {
+    // ScaleWorker is finished with this request, so we can unlock the data now.
+    request->UnlockSourceData();
+    // We have to reset dstFrame if request was stopped while ScaleWorker was scaling.
+    if (request->stopped) {
+      ScaleRequest::Stop(request->image);
+    }
+    nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(request->image->mObserver));
+    if (request->done && observer) {
+      imgFrame *scaledFrame = request->dstFrame.get();
+      scaledFrame->ImageUpdated(scaledFrame->GetRect());
+      nsIntRect frameRect = request->srcFrame->GetRect();
+      observer->FrameChanged(nullptr, request->image, &frameRect);
+    }
+  }
+
+  return NS_OK;
+}
+
+void
+RasterImage::DrawWorker::RequestDraw(RasterImage* aImg)
+{
+  ScaleRequest* request = &aImg->mScaleRequest;
+  mDrawRequests.insertBack(request);
+  NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
+}
+
+void
+RasterImage::ScaleRequest::Stop(RasterImage* aImg)
+{
+  ScaleRequest* request = &aImg->mScaleRequest;
+  // It's safe to unlock source image data only if request is in the list.
+  // Otherwise we may be reading from the source while performing scaling
+  // and can't interrupt immediately.
+  if (request->isInList()) {
+    request->remove();
+    request->UnlockSourceData();
+  }
+  // We have to check if request is finished before dropping the destination
+  // frame. Otherwise we may be writing to the dest while performing scaling.
+  if (request->done) {
+    request->done = false;
+    request->dstFrame = nullptr;
+    request->scale.width = 0;
+    request->scale.height = 0;
+  }
+  request->stopped = true;
+}
+
+bool
+RasterImage::CanScale(gfxPattern::GraphicsFilter aFilter,
+                      gfxSize aScale)
+{
+// The high-quality scaler requires Skia.
+#ifdef MOZ_ENABLE_SKIA
+  return (aFilter == gfxPattern::FILTER_GOOD) &&
+          !mAnim && mDecoded &&
+          (aScale.width <= 1.0 && aScale.height <= 1.0) &&
+          (aScale.width < PRE_DOWNSCALE_MIN_FACTOR ||
+           aScale.height < PRE_DOWNSCALE_MIN_FACTOR);
+#else
+  return false;
+#endif
+}
+
+void
+RasterImage::DrawWithPreDownscaleIfNeeded(imgFrame *aFrame,
+                                          gfxContext *aContext,
+                                          gfxPattern::GraphicsFilter aFilter,
+                                          const gfxMatrix &aUserSpaceToImageSpace,
+                                          const gfxRect &aFill,
+                                          const nsIntRect &aSubimage)
+{
+  imgFrame *frame = aFrame;
+  nsIntRect framerect = frame->GetRect();
+  gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace;
+  gfxMatrix imageSpaceToUserSpace = aUserSpaceToImageSpace;
+  imageSpaceToUserSpace.Invert();
+  gfxSize scale = imageSpaceToUserSpace.ScaleFactors(true);
+  nsIntRect subimage = aSubimage;
+
+  if (CanScale(aFilter, scale)) {
+    MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
+    // If scale factor is still the same that we scaled for and
+    // ScaleWorker has done it's job, then we can use pre-downscaled frame.
+    // If scale factor has changed, order new request.
+    if (mScaleRequest.scale == scale) {
+      if (mScaleRequest.done) {
+        frame = mScaleRequest.dstFrame.get();
+        userSpaceToImageSpace.Multiply(gfxMatrix().Scale(scale.width, scale.height));
+
+        // Since we're switching to a scaled image, we we need to transform the
+        // area of the subimage to draw accordingly, since imgFrame::Draw()
+        // doesn't know about scaled frames.
+        subimage.ScaleRoundOut(scale.width, scale.height);
+      }
+    } else {
+      // FIXME: Current implementation doesn't support pre-downscale
+      // mechanism for multiple images from same src, since we cache
+      // pre-downscaled frame only for the latest requested scale.
+      // The solution is to cache more than one scaled image frame
+      // for each RasterImage.
+      int scaling = mScaleRequest.srcDataLocked ? 1 : 0;
+      if (mLockCount - scaling == 1) {
+        ScaleRequest::Stop(this);
+        mScaleRequest.srcFrame = frame;
+        mScaleRequest.scale = scale;
+        mScaleRequest.stopped = false;
+
+        // We need to make sure that source data is available before asking to scale.
+        if (mScaleRequest.LockSourceData()) {
+          ScaleWorker::Singleton()->RequestScale(this);
+        }
+      }
+    }
+  }
+
+  nsIntMargin padding(framerect.x, framerect.y,
+                      mSize.width - framerect.XMost(),
+                      mSize.height - framerect.YMost());
+
+  frame->Draw(aContext, aFilter, userSpaceToImageSpace, aFill, padding, subimage);
+}
+
 //******************************************************************************
 /* [noscript] void draw(in gfxContext aContext,
  *                      in gfxGraphicsFilter aFilter,
  *                      [const] in gfxMatrix aUserSpaceToImageSpace,
  *                      [const] in gfxRect aFill,
  *                      [const] in nsIntRect aSubimage,
  *                      [const] in nsIntSize aViewportSize,
  *                      in uint32_t aFlags); */
@@ -2650,29 +2928,25 @@ RasterImage::Draw(gfxContext *aContext,
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   imgFrame *frame = GetCurrentDrawableImgFrame();
   if (!frame) {
     return NS_OK; // Getting the frame (above) touches the image and kicks off decoding
   }
 
-  nsIntRect framerect = frame->GetRect();
-  nsIntMargin padding(framerect.x, framerect.y, 
-                      mSize.width - framerect.XMost(),
-                      mSize.height - framerect.YMost());
-
-  frame->Draw(aContext, aFilter, aUserSpaceToImageSpace, aFill, padding, aSubimage, aFlags);
+  DrawWithPreDownscaleIfNeeded(frame, aContext, aFilter, aUserSpaceToImageSpace, aFill, aSubimage);
 
   if (mDecoded && !mDrawStartTime.IsNull()) {
       TimeDuration drawLatency = TimeStamp::Now() - mDrawStartTime;
       Telemetry::Accumulate(Telemetry::IMAGE_DECODE_ON_DRAW_LATENCY, int32_t(drawLatency.ToMicroseconds()));
       // clear the value of mDrawStartTime
       mDrawStartTime = TimeStamp();
   }
+
   return NS_OK;
 }
 
 //******************************************************************************
 /* [notxpcom] nsIFrame GetRootLayoutFrame() */
 nsIFrame*
 RasterImage::GetRootLayoutFrame()
 {
@@ -2711,16 +2985,21 @@ RasterImage::UnlockImage()
     return NS_ERROR_ABORT;
 
   // We're locked, so discarding should not be active
   NS_ABORT_IF_FALSE(!DiscardingActive(), "Locked, but discarding activated");
 
   // Decrement our lock count
   mLockCount--;
 
+  if (ScaleWorker::sSingleton && mLockCount == 0) {
+    MutexAutoLock lock(ScaleWorker::Singleton()->mRequestsMutex);
+    ScaleRequest::Stop(this);
+  }
+
   // If we've decoded this image once before, we're currently decoding again,
   // and our lock count is now zero (so nothing is forcing us to keep the
   // decoded data around), try to cancel the decode and throw away whatever
   // we've decoded.
   if (mHasBeenDecoded && mDecoder &&
       mLockCount == 0 && CanForciblyDiscard()) {
     PR_LOG(gCompressedImageAccountingLog, PR_LOG_DEBUG,
            ("RasterImage[0x%p] canceling decode because image "
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -12,16 +12,17 @@
  * @author  Chris Saari <saari@netscape.com>
  * @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 "mozilla/Mutex.h"
 #include "Image.h"
 #include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "imgIContainer.h"
 #include "nsIProperties.h"
 #include "nsITimer.h"
 #include "nsWeakReference.h"
 #include "nsTArray.h"
@@ -465,16 +466,119 @@ private:
     LinkedList<DecodeRequest> mASAPDecodeRequests;
     LinkedList<DecodeRequest> mNormalDecodeRequests;
 
     /* True if we've posted ourselves to the event loop and expect Run() to
      * be called sometime in the future. */
     bool mPendingInEventLoop;
   };
 
+  struct ScaleRequest : public LinkedListElement<ScaleRequest>
+  {
+    ScaleRequest(RasterImage* aImage)
+      : image(aImage)
+      , srcFrame(nullptr)
+      , dstFrame(nullptr)
+      , scale(0, 0)
+      , done(false)
+      , stopped(false)
+      , srcDataLocked(false)
+    {};
+
+    bool LockSourceData()
+    {
+      if (!srcDataLocked) {
+        bool success = true;
+        success = success && NS_SUCCEEDED(image->LockImage());
+        success = success && NS_SUCCEEDED(srcFrame->LockImageData());
+        srcDataLocked = success;
+      }
+      return srcDataLocked;
+    }
+
+    bool UnlockSourceData()
+    {
+      bool success = true;
+      if (srcDataLocked) {
+        success = success && NS_SUCCEEDED(image->UnlockImage());
+        success = success && NS_SUCCEEDED(srcFrame->UnlockImageData());
+
+        // If unlocking fails, there's nothing we can do to make it work, so we
+        // claim that we're not locked regardless.
+        srcDataLocked = false;
+      }
+      return success;
+    }
+
+    static void Stop(RasterImage* aImg);
+
+    RasterImage* const image;
+    imgFrame *srcFrame;
+    nsAutoPtr<imgFrame> dstFrame;
+    gfxSize scale;
+    bool done;
+    bool stopped;
+    bool srcDataLocked;
+  };
+
+  class ScaleWorker : public nsRunnable
+  {
+  public:
+    static ScaleWorker* Singleton();
+
+    NS_IMETHOD Run();
+
+  /* statics */
+    static nsRefPtr<ScaleWorker> sSingleton;
+
+  private: /* methods */
+    ScaleWorker()
+      : mRequestsMutex("RasterImage.ScaleWorker.mRequestsMutex")
+      , mInitialized(false)
+    {};
+
+    // Note: you MUST call RequestScale with the ScaleWorker mutex held.
+    void RequestScale(RasterImage* aImg);
+
+  private: /* members */
+
+    friend class RasterImage;
+    LinkedList<ScaleRequest> mScaleRequests;
+    Mutex mRequestsMutex;
+    bool mInitialized;
+  };
+
+  class DrawWorker : public nsRunnable
+  {
+  public:
+    static DrawWorker* Singleton();
+
+    NS_IMETHOD Run();
+
+  /* statics */
+    static nsRefPtr<DrawWorker> sSingleton;
+
+  private: /* methods */
+    DrawWorker() {};
+
+    void RequestDraw(RasterImage* aImg);
+
+  private: /* members */
+
+    friend class RasterImage;
+    LinkedList<ScaleRequest> mDrawRequests;
+  };
+
+  void DrawWithPreDownscaleIfNeeded(imgFrame *aFrame,
+                                    gfxContext *aContext,
+                                    gfxPattern::GraphicsFilter aFilter,
+                                    const gfxMatrix &aUserSpaceToImageSpace,
+                                    const gfxRect &aFill,
+                                    const nsIntRect &aSubimage);
+
   /**
    * 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().
@@ -670,16 +774,19 @@ private: // data
   nsresult WantDecodedFrames();
   nsresult SyncDecode();
   nsresult InitDecoder(bool aDoSizeDecode);
   nsresult WriteToDecoder(const char *aBuffer, uint32_t aCount);
   nsresult DecodeSomeData(uint32_t aMaxBytes);
   bool     IsDecodeFinished();
   TimeStamp mDrawStartTime;
 
+  inline bool CanScale(gfxPattern::GraphicsFilter aFilter, gfxSize aScale);
+  ScaleRequest mScaleRequest;
+
   // Decoder shutdown
   enum eShutdownIntent {
     eShutdownIntent_Done        = 0,
     eShutdownIntent_Interrupted = 1,
     eShutdownIntent_Error       = 2,
     eShutdownIntent_AllCount    = 3
   };
   nsresult ShutdownDecoder(eShutdownIntent aIntent);