Bug 572680 - Create gfxUtils::DrawPixelSnapped and make imgFrame use it. r=joe
authorMarkus Stange <mstange@themasta.com>
Fri, 13 Aug 2010 15:30:02 +0200
changeset 50417 97188fb7b44a021ae311bbd50eccc12a9c4af739
parent 50416 4b75be253a5ad410e830d1df407d2805dc6d6b89
child 50418 0fa683b7233de09df0471a31a9b337e2d507045e
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjoe
bugs572680
milestone2.0b4pre
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 572680 - Create gfxUtils::DrawPixelSnapped and make imgFrame use it. r=joe
gfx/thebes/gfxUtils.cpp
gfx/thebes/gfxUtils.h
modules/libpr0n/src/imgFrame.cpp
modules/libpr0n/src/imgFrame.h
--- a/gfx/thebes/gfxUtils.cpp
+++ b/gfx/thebes/gfxUtils.cpp
@@ -30,18 +30,23 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-#include "gfxImageSurface.h"
 #include "gfxUtils.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+
+#if defined(XP_WIN) || defined(WINCE)
+#include "gfxWindowsPlatform.h"
+#endif
 
 static PRUint8 sUnpremultiplyTable[256*256];
 static PRUint8 sPremultiplyTable[256*256];
 static PRBool sTablesInitialized = PR_FALSE;
 
 static const PRUint8 PremultiplyValue(PRUint8 a, PRUint8 v) {
     return sPremultiplyTable[a*256+v];
 }
@@ -191,8 +196,296 @@ gfxUtils::UnpremultiplyImageSurface(gfxI
 
         *dst++ = a;
         *dst++ = UnpremultiplyValue(a, r);
         *dst++ = UnpremultiplyValue(a, g);
         *dst++ = UnpremultiplyValue(a, b);
 #endif
     }
 }
+
+static PRBool
+IsSafeImageTransformComponent(gfxFloat aValue)
+{
+  return aValue >= -32768 && aValue <= 32767;
+}
+
+static void
+SetExtendAndFilterOnPattern(gfxPattern* aPattern,
+                            const gfxMatrix& aDeviceToImage,
+                            const gfxASurface::gfxSurfaceType aSurfaceType,
+                            const gfxPattern::GraphicsFilter aDefaultFilter)
+{
+    // In theory we can handle this using cairo's EXTEND_PAD,
+    // but implementation limitations mean we have to consult
+    // the surface type.
+    switch (aSurfaceType) {
+        case gfxASurface::SurfaceTypeXlib:
+        case gfxASurface::SurfaceTypeXcb:
+        {
+            // See bug 324698.  This is a workaround for EXTEND_PAD not being
+            // implemented correctly on linux in the X server.
+            //
+            // Set the filter to CAIRO_FILTER_FAST --- otherwise,
+            // pixman's sampling will sample transparency for the outside edges and we'll
+            // get blurry edges.  CAIRO_EXTEND_PAD would also work here, if
+            // available
+            //
+            // But don't do this for simple downscales because it's horrible.
+            // Downscaling means that device-space coordinates are
+            // scaled *up* to find the image pixel coordinates.
+            //
+            // aDeviceToImage is slightly stale because up above we may
+            // have adjusted the pattern's matrix ... but the adjustment
+            // is only a translation so the scale factors in aDeviceToImage
+            // are still valid.
+            PRBool isDownscale =
+                aDeviceToImage.xx >= 1.0 && aDeviceToImage.yy >= 1.0 &&
+                aDeviceToImage.xy == 0.0 && aDeviceToImage.yx == 0.0;
+            if (!isDownscale) {
+                aPattern->SetFilter(gfxPattern::FILTER_FAST);
+            }
+            break;
+        }
+
+        case gfxASurface::SurfaceTypeQuartz:
+        case gfxASurface::SurfaceTypeQuartzImage:
+            // Don't set EXTEND_PAD, Mac seems to be OK. Really?
+            aPattern->SetFilter(aDefaultFilter);
+            break;
+
+        default:
+            // turn on EXTEND_PAD.
+            // This is what we really want for all surface types, if the
+            // implementation was universally good.
+            aPattern->SetExtend(gfxPattern::EXTEND_PAD);
+            aPattern->SetFilter(aDefaultFilter);
+            break;
+    }
+}
+
+// EXTEND_PAD won't help us here; we have to create a temporary surface to hold
+// the subimage of pixels we're allowed to sample.
+static already_AddRefed<gfxPattern>
+CreateSamplingRestrictedPattern(gfxASurface* aSurface,
+                                gfxContext* aContext,
+                                const gfxMatrix& aUserSpaceToImageSpace,
+                                const gfxRect& aSourceRect,
+                                const gfxRect& aSubimage,
+                                const gfxImageSurface::gfxImageFormat aFormat)
+{
+    gfxRect userSpaceClipExtents = aContext->GetClipExtents();
+    // This isn't optimal --- if aContext has a rotation then GetClipExtents
+    // will have to do a bounding-box computation, and TransformBounds might
+    // too, so we could get a better result if we computed image space clip
+    // extents in one go --- but it doesn't really matter and this is easier
+    // to understand.
+    gfxRect imageSpaceClipExtents =
+        aUserSpaceToImageSpace.TransformBounds(userSpaceClipExtents);
+    // Inflate by one pixel because bilinear filtering will sample at most
+    // one pixel beyond the computed image pixel coordinate.
+    imageSpaceClipExtents.Outset(1.0);
+
+    gfxRect needed = imageSpaceClipExtents.Intersect(aSourceRect);
+    needed = needed.Intersect(aSubimage);
+    needed.RoundOut();
+
+    // if 'needed' is empty, nothing will be drawn since aFill
+    // must be entirely outside the clip region, so it doesn't
+    // matter what we do here, but we should avoid trying to
+    // create a zero-size surface.
+    if (needed.IsEmpty())
+        return nsnull;
+
+    gfxIntSize size(PRInt32(needed.Width()), PRInt32(needed.Height()));
+    nsRefPtr<gfxASurface> temp =
+        gfxPlatform::GetPlatform()->CreateOffscreenSurface(size, aFormat);
+    if (!temp || temp->CairoStatus())
+        return nsnull;
+
+    nsRefPtr<gfxPattern> tmpPattern = new gfxPattern(aSurface);
+    if (!tmpPattern)
+        return nsnull;
+
+    tmpPattern->SetExtend(gfxPattern::EXTEND_REPEAT);
+    tmpPattern->SetMatrix(gfxMatrix().Translate(needed.pos));
+
+    gfxContext tmpCtx(temp);
+    tmpCtx.SetOperator(gfxContext::OPERATOR_SOURCE);
+    tmpCtx.SetPattern(tmpPattern);
+    tmpCtx.Paint();
+
+    nsRefPtr<gfxPattern> resultPattern = new gfxPattern(temp);
+    if (!resultPattern)
+        return nsnull;
+
+    resultPattern->SetMatrix(
+        gfxMatrix(aUserSpaceToImageSpace).Multiply(gfxMatrix().Translate(-needed.pos)));
+    return resultPattern.forget();
+}
+
+// working around cairo/pixman bug (bug 364968)
+// Our device-space-to-image-space transform may not be acceptable to pixman.
+struct NS_STACK_CLASS AutoCairoPixmanBugWorkaround
+{
+    AutoCairoPixmanBugWorkaround(gfxContext*      aContext,
+                                 const gfxMatrix& aDeviceSpaceToImageSpace,
+                                 const gfxRect&   aFill,
+                                 const gfxASurface::gfxSurfaceType& aSurfaceType)
+     : mContext(aContext), mSucceeded(PR_TRUE), mPushedGroup(PR_FALSE)
+    {
+        // Quartz's limits for matrix are much larger than pixman
+        if (aSurfaceType == gfxASurface::SurfaceTypeQuartz)
+            return;
+
+        if (!IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.xx) ||
+            !IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.xy) ||
+            !IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.yx) ||
+            !IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.yy)) {
+            NS_WARNING("Scaling up too much, bailing out");
+            mSucceeded = PR_FALSE;
+            return;
+        }
+
+        if (IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.x0) &&
+            IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.y0))
+            return;
+
+        // We'll push a group, which will hopefully reduce our transform's
+        // translation so it's in bounds.
+        gfxMatrix currentMatrix = mContext->CurrentMatrix();
+        mContext->Save();
+
+        // Clip the rounded-out-to-device-pixels bounds of the
+        // transformed fill area. This is the area for the group we
+        // want to push.
+        mContext->IdentityMatrix();
+        gfxRect bounds = currentMatrix.TransformBounds(aFill);
+        bounds.RoundOut();
+        mContext->Clip(bounds);
+        mContext->SetMatrix(currentMatrix);
+        mContext->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA);
+        mContext->SetOperator(gfxContext::OPERATOR_OVER);
+
+        mPushedGroup = PR_TRUE;
+    }
+
+    ~AutoCairoPixmanBugWorkaround()
+    {
+        if (mPushedGroup) {
+            mContext->PopGroupToSource();
+            mContext->Paint();
+            mContext->Restore();
+        }
+    }
+
+    PRBool PushedGroup() { return mPushedGroup; }
+    PRBool Succeeded() { return mSucceeded; }
+
+private:
+    gfxContext* mContext;
+    PRPackedBool mSucceeded;
+    PRPackedBool mPushedGroup;
+};
+
+/**
+ * This returns the fastest operator to use for solid surfaces which have no
+ * alpha channel or their alpha channel is uniformly opaque.
+ * This differs per render mode.
+ */
+static gfxContext::GraphicsOperator
+OptimalFillOperator()
+{
+#ifdef XP_WIN
+    if (gfxWindowsPlatform::GetPlatform()->GetRenderMode() ==
+        gfxWindowsPlatform::RENDER_DIRECT2D) {
+        // D2D -really- hates operator source.
+        return gfxContext::OPERATOR_OVER;
+    } else {
+#endif
+        return gfxContext::OPERATOR_SOURCE;
+#ifdef XP_WIN
+    }
+#endif
+}
+
+static gfxMatrix
+DeviceToImageTransform(gfxContext* aContext,
+                       const gfxMatrix& aUserSpaceToImageSpace)
+{
+    gfxFloat deviceX, deviceY;
+    nsRefPtr<gfxASurface> currentTarget =
+        aContext->CurrentSurface(&deviceX, &deviceY);
+    gfxMatrix currentMatrix = aContext->CurrentMatrix();
+    gfxMatrix deviceToUser = gfxMatrix(currentMatrix).Invert();
+    deviceToUser.Translate(-gfxPoint(-deviceX, -deviceY));
+    return gfxMatrix(deviceToUser).Multiply(aUserSpaceToImageSpace);
+}
+
+/* static */ void
+gfxUtils::DrawPixelSnapped(gfxContext*      aContext,
+                           gfxASurface*     aSurface,
+                           const gfxMatrix& aUserSpaceToImageSpace,
+                           const gfxRect&   aSubimage,
+                           const gfxRect&   aSourceRect,
+                           const gfxRect&   aImageRect,
+                           const gfxRect&   aFill,
+                           const gfxImageSurface::gfxImageFormat aFormat,
+                           const gfxPattern::GraphicsFilter& aFilter)
+{
+    PRBool doTile = !aImageRect.Contains(aSourceRect);
+
+    nsRefPtr<gfxASurface> currentTarget = aContext->CurrentSurface();
+    gfxASurface::gfxSurfaceType surfaceType = currentTarget->GetType();
+    gfxMatrix currentMatrix = aContext->CurrentMatrix();
+    gfxMatrix deviceSpaceToImageSpace =
+        DeviceToImageTransform(aContext, aUserSpaceToImageSpace);
+
+    AutoCairoPixmanBugWorkaround workaround(aContext, deviceSpaceToImageSpace,
+                                            aFill, surfaceType);
+    if (!workaround.Succeeded())
+        return;
+
+    nsRefPtr<gfxPattern> pattern = new gfxPattern(aSurface);
+    pattern->SetMatrix(aUserSpaceToImageSpace);
+
+    // OK now, the hard part left is to account for the subimage sampling
+    // restriction. If all the transforms involved are just integer
+    // translations, then we assume no resampling will occur so there's
+    // nothing to do.
+    // XXX if only we had source-clipping in cairo!
+    if (!currentMatrix.HasNonIntegerTranslation() &&
+        !aUserSpaceToImageSpace.HasNonIntegerTranslation()) {
+        if (doTile) {
+            pattern->SetExtend(gfxPattern::EXTEND_REPEAT);
+        }
+    } else {
+        if (doTile || !aSubimage.Contains(aImageRect)) {
+            nsRefPtr<gfxPattern> restrictedPattern =
+                CreateSamplingRestrictedPattern(aSurface, aContext,
+                                                aUserSpaceToImageSpace,
+                                                aSourceRect, aSubimage, aFormat);
+            if (restrictedPattern) {
+                pattern.swap(restrictedPattern);
+            }
+        }
+        SetExtendAndFilterOnPattern(pattern, deviceSpaceToImageSpace, surfaceType,
+                                    aFilter);
+    }
+
+    gfxContext::GraphicsOperator op = aContext->CurrentOperator();
+    if ((op == gfxContext::OPERATOR_OVER || workaround.PushedGroup()) &&
+        aFormat == gfxASurface::ImageFormatRGB24) {
+        aContext->SetOperator(OptimalFillOperator());
+    }
+
+    // Phew! Now we can actually draw this image
+    aContext->NewPath();
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+    pattern->SetFilter(gfxPattern::FILTER_FAST); 
+#endif
+    aContext->SetPattern(pattern);
+    aContext->Rectangle(aFill);
+    aContext->Fill();
+
+    aContext->SetOperator(op);
+}
+
--- a/gfx/thebes/gfxUtils.h
+++ b/gfx/thebes/gfxUtils.h
@@ -34,18 +34,18 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef GFX_UTILS_H
 #define GFX_UTILS_H
 
 #include "gfxTypes.h"
-
-class gfxImageSurface;
+#include "gfxPattern.h"
+#include "gfxImageSurface.h"
 
 class THEBES_API gfxUtils {
 public:
     /*
      * Premultiply or Unpremultiply aSourceSurface, writing the result
      * to aDestSurface or back into aSourceSurface if aDestSurface is null.
      *
      * If aDestSurface is given, it must have identical format, dimensions, and
@@ -53,11 +53,34 @@ public:
      *
      * If the source is not ImageFormatARGB32, no operation is performed.  If
      * aDestSurface is given, the data is copied over.
      */
     static void PremultiplyImageSurface(gfxImageSurface *aSourceSurface,
                                         gfxImageSurface *aDestSurface = nsnull);
     static void UnpremultiplyImageSurface(gfxImageSurface *aSurface,
                                           gfxImageSurface *aDestSurface = nsnull);
+
+    /**
+     * Draw a surface while working around limitations like bad support
+     * for EXTEND_PAD, lack of source-clipping, or cairo / pixman bugs with
+     * extreme user-space-to-image-space transforms.
+     *
+     * The input parameters here usually come from the output of our image
+     * snapping algorithm in nsLayoutUtils.cpp.
+     * This method is split from nsLayoutUtils::DrawPixelSnapped to allow for
+     * adjusting the parameters. For example, certain images with transparent
+     * margins only have a drawable subimage. For those images, imgFrame::Draw
+     * will tweak the rects and transforms that it gets from the pixel snapping
+     * algorithm before passing them on to this method.
+     */
+    static void DrawPixelSnapped(gfxContext*      aContext,
+                                 gfxASurface*     aSurface,
+                                 const gfxMatrix& aUserSpaceToImageSpace,
+                                 const gfxRect&   aSubimage,
+                                 const gfxRect&   aSourceRect,
+                                 const gfxRect&   aImageRect,
+                                 const gfxRect&   aFill,
+                                 const gfxImageSurface::gfxImageFormat aFormat,
+                                 const gfxPattern::GraphicsFilter& aFilter);
 };
 
 #endif
--- a/modules/libpr0n/src/imgFrame.cpp
+++ b/modules/libpr0n/src/imgFrame.cpp
@@ -39,16 +39,17 @@
 #include "imgFrame.h"
 
 #include <limits.h>
 
 #include "prmem.h"
 #include "prenv.h"
 
 #include "gfxPlatform.h"
+#include "gfxUtils.h"
 
 static PRBool gDisableOptimize = PR_FALSE;
 
 /*XXX get CAIRO_HAS_DDRAW_SURFACE */
 #include "cairo.h"
 
 #ifdef CAIRO_HAS_DDRAW_SURFACE
 #include "gfxDDrawSurface.h"
@@ -370,288 +371,133 @@ nsresult imgFrame::Optimize()
 #ifdef XP_MACOSX
     mQuartzSurface = nsnull;
 #endif
   }
 
   return NS_OK;
 }
 
-static PRBool                                                                                                       
-IsSafeImageTransformComponent(gfxFloat aValue)
+static void
+DoSingleColorFastPath(gfxContext*    aContext,
+                      const gfxRGBA& aSinglePixelColor,
+                      const gfxRect& aFill)
+{
+  // if a == 0, it's a noop
+  if (aSinglePixelColor.a == 0.0)
+    return;
+
+  gfxContext::GraphicsOperator op = aContext->CurrentOperator();
+  if (op == gfxContext::OPERATOR_OVER && aSinglePixelColor.a == 1.0) {
+    aContext->SetOperator(gfxContext::OPERATOR_SOURCE);
+  }
+
+  aContext->SetDeviceColor(aSinglePixelColor);
+  aContext->NewPath();
+  aContext->Rectangle(aFill);
+  aContext->Fill();
+  aContext->SetOperator(op);
+  aContext->SetDeviceColor(gfxRGBA(0,0,0,0));
+}
+
+imgFrame::SurfaceWithFormat
+imgFrame::SurfaceForDrawing(PRBool             aDoPadding,
+                            PRBool             aDoPartialDecode,
+                            PRBool             aDoTile,
+                            const nsIntMargin& aPadding,
+                            gfxMatrix&         aUserSpaceToImageSpace,
+                            gfxRect&           aFill,
+                            gfxRect&           aSubimage,
+                            gfxRect&           aSourceRect,
+                            gfxRect&           aImageRect)
 {
-  return aValue >= -32768 && aValue <= 32767;
+  if (!aDoPadding && !aDoPartialDecode) {
+    NS_ASSERTION(!mSinglePixel, "This should already have been handled");
+    return SurfaceWithFormat(ThebesSurface(), mFormat);
+  }
+
+  gfxRect available = gfxRect(mDecoded.x, mDecoded.y, mDecoded.width, mDecoded.height);
+
+  if (aDoTile || mSinglePixel) {
+    // Create a temporary surface.
+    gfxIntSize size(PRInt32(aImageRect.Width()), PRInt32(aImageRect.Height()));
+    // Give this surface an alpha channel because there are
+    // transparent pixels in the padding or undecoded area
+    gfxImageSurface::gfxImageFormat format = gfxASurface::ImageFormatARGB32;
+    nsRefPtr<gfxASurface> surface =
+      gfxPlatform::GetPlatform()->CreateOffscreenSurface(size, format);
+    if (!surface || surface->CairoStatus())
+      return SurfaceWithFormat();
+
+    // Fill 'available' with whatever we've got
+    gfxContext tmpCtx(surface);
+    tmpCtx.SetOperator(gfxContext::OPERATOR_SOURCE);
+    if (mSinglePixel) {
+      tmpCtx.SetDeviceColor(mSinglePixelColor);
+    } else {
+      tmpCtx.SetSource(ThebesSurface(), gfxPoint(aPadding.left, aPadding.top));
+    }
+    tmpCtx.Rectangle(available);
+    tmpCtx.Fill();
+    return SurfaceWithFormat(surface, format);
+  }
+
+  // Not tiling, and we have a surface, so we can account for
+  // padding and/or a partial decode just by twiddling parameters.
+  // First, update our user-space fill rect.
+  aSourceRect = aSourceRect.Intersect(available);
+  gfxMatrix imageSpaceToUserSpace = aUserSpaceToImageSpace;
+  imageSpaceToUserSpace.Invert();
+  aFill = imageSpaceToUserSpace.Transform(aSourceRect);
+
+  aSubimage = aSubimage.Intersect(available) - gfxPoint(aPadding.left, aPadding.top);
+  aUserSpaceToImageSpace.Multiply(gfxMatrix().Translate(-gfxPoint(aPadding.left, aPadding.top)));
+  aSourceRect = aSourceRect - gfxPoint(aPadding.left, aPadding.top);
+  aImageRect = gfxRect(0, 0, mSize.width, mSize.height);
+
+  return SurfaceWithFormat(ThebesSurface(), mFormat);
 }
 
 void imgFrame::Draw(gfxContext *aContext, gfxPattern::GraphicsFilter aFilter,
                     const gfxMatrix &aUserSpaceToImageSpace, const gfxRect& aFill,
                     const nsIntMargin &aPadding, const nsIntRect &aSubimage)
 {
   NS_ASSERTION(!aFill.IsEmpty(), "zero dest size --- fix caller");
   NS_ASSERTION(!aSubimage.IsEmpty(), "zero source size --- fix caller");
   NS_ASSERTION(!mPalettedImageData, "Directly drawing a paletted image!");
 
   PRBool doPadding = aPadding != nsIntMargin(0,0,0,0);
   PRBool doPartialDecode = !ImageComplete();
-  gfxContext::GraphicsOperator op = aContext->CurrentOperator();
 
-  if (mSinglePixel && !doPadding && ImageComplete()) {
-    // Single-color fast path
-    // if a == 0, it's a noop
-    if (mSinglePixelColor.a == 0.0)
-      return;
-
-    if (op == gfxContext::OPERATOR_OVER && mSinglePixelColor.a == 1.0)
-      aContext->SetOperator(gfxContext::OPERATOR_SOURCE);
-
-    aContext->SetDeviceColor(mSinglePixelColor);
-    aContext->NewPath();
-    aContext->Rectangle(aFill);
-    aContext->Fill();
-    aContext->SetOperator(op);
-    aContext->SetDeviceColor(gfxRGBA(0,0,0,0));
+  if (mSinglePixel && !doPadding && !doPartialDecode) {
+    DoSingleColorFastPath(aContext, mSinglePixelColor, aFill);
     return;
   }
 
   gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace;
   gfxRect sourceRect = userSpaceToImageSpace.Transform(aFill);
-  gfxRect imageRect(0, 0, mSize.width + aPadding.LeftRight(), mSize.height + aPadding.TopBottom());
+  gfxRect imageRect(0, 0, mSize.width + aPadding.LeftRight(),
+                    mSize.height + aPadding.TopBottom());
   gfxRect subimage(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height);
   gfxRect fill = aFill;
-  nsRefPtr<gfxASurface> surface;
-  gfxImageSurface::gfxImageFormat format;
 
   NS_ASSERTION(!sourceRect.Intersect(subimage).IsEmpty(),
                "We must be allowed to sample *some* source pixels!");
 
   PRBool doTile = !imageRect.Contains(sourceRect);
-  if (doPadding || doPartialDecode) {
-    gfxRect available = gfxRect(mDecoded.x, mDecoded.y, mDecoded.width, mDecoded.height);
-
-    if (!doTile && !mSinglePixel) {
-      // Not tiling, and we have a surface, so we can account for
-      // padding and/or a partial decode just by twiddling parameters.
-      // First, update our user-space fill rect.
-      sourceRect = sourceRect.Intersect(available);
-      gfxMatrix imageSpaceToUserSpace = userSpaceToImageSpace;
-      imageSpaceToUserSpace.Invert();
-      fill = imageSpaceToUserSpace.Transform(sourceRect);
-
-      surface = ThebesSurface();
-      format = mFormat;
-      subimage = subimage.Intersect(available) - gfxPoint(aPadding.left, aPadding.top);
-      userSpaceToImageSpace.Multiply(gfxMatrix().Translate(-gfxPoint(aPadding.left, aPadding.top)));
-      sourceRect = sourceRect - gfxPoint(aPadding.left, aPadding.top);
-      imageRect = gfxRect(0, 0, mSize.width, mSize.height);
-    } else {
-      // Create a temporary surface
-      gfxIntSize size(PRInt32(imageRect.Width()), PRInt32(imageRect.Height()));
-      // Give this surface an alpha channel because there are
-      // transparent pixels in the padding or undecoded area
-      format = gfxASurface::ImageFormatARGB32;
-      surface = gfxPlatform::GetPlatform()->CreateOffscreenSurface(size, format);
-      if (!surface || surface->CairoStatus() != 0)
-        return;
-
-      // Fill 'available' with whatever we've got
-      gfxContext tmpCtx(surface);
-      tmpCtx.SetOperator(gfxContext::OPERATOR_SOURCE);
-      if (mSinglePixel) {
-        tmpCtx.SetDeviceColor(mSinglePixelColor);
-      } else {
-        tmpCtx.SetSource(ThebesSurface(), gfxPoint(aPadding.left, aPadding.top));
-      }
-      tmpCtx.Rectangle(available);
-      tmpCtx.Fill();
-    }
-  } else {
-    NS_ASSERTION(!mSinglePixel, "This should already have been handled");
-    surface = ThebesSurface();
-    format = mFormat;
-  }
-  // At this point, we've taken care of mSinglePixel images, images with
-  // aPadding, and partially-decoded images.
-
-  // Compute device-space-to-image-space transform. We need to sanity-
-  // check it to work around a pixman bug :-(
-  // XXX should we only do this for certain surface types?
-  gfxFloat deviceX, deviceY;
-  nsRefPtr<gfxASurface> currentTarget =
-    aContext->CurrentSurface(&deviceX, &deviceY);
-  gfxMatrix currentMatrix = aContext->CurrentMatrix();
-  gfxMatrix deviceToUser = currentMatrix;
-  deviceToUser.Invert();
-  deviceToUser.Translate(-gfxPoint(-deviceX, -deviceY));
-  gfxMatrix deviceToImage = deviceToUser;
-  deviceToImage.Multiply(userSpaceToImageSpace);
-
-  PRBool pushedGroup = PR_FALSE;
-  if (currentTarget->GetType() != gfxASurface::SurfaceTypeQuartz) {
-    // BEGIN working around cairo/pixman bug (bug 364968)
-    // Quartz's limits for matrix are much larger than pixman
-      
-    // Our device-space-to-image-space transform may not be acceptable to pixman.
-    if (!IsSafeImageTransformComponent(deviceToImage.xx) ||
-        !IsSafeImageTransformComponent(deviceToImage.xy) ||
-        !IsSafeImageTransformComponent(deviceToImage.yx) ||
-        !IsSafeImageTransformComponent(deviceToImage.yy)) {
-      NS_WARNING("Scaling up too much, bailing out");
-      return;
-    }
-
-    if (!IsSafeImageTransformComponent(deviceToImage.x0) ||
-        !IsSafeImageTransformComponent(deviceToImage.y0)) {
-      // We'll push a group, which will hopefully reduce our transform's
-      // translation so it's in bounds
-      aContext->Save();
-
-      // Clip the rounded-out-to-device-pixels bounds of the
-      // transformed fill area. This is the area for the group we
-      // want to push.
-      aContext->IdentityMatrix();
-      gfxRect bounds = currentMatrix.TransformBounds(fill);
-      bounds.RoundOut();
-      aContext->Clip(bounds);
-      aContext->SetMatrix(currentMatrix);
-
-      aContext->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA);
-      aContext->SetOperator(gfxContext::OPERATOR_OVER);
-      pushedGroup = PR_TRUE;
-    }
-    // END working around cairo/pixman bug (bug 364968)
-  }
-
-  nsRefPtr<gfxPattern> pattern = new gfxPattern(surface);
-  pattern->SetMatrix(userSpaceToImageSpace);
+  SurfaceWithFormat surfaceResult =
+    SurfaceForDrawing(doPadding, doPartialDecode, doTile, aPadding,
+                      userSpaceToImageSpace, fill, subimage, sourceRect,
+                      imageRect);
 
-  // OK now, the hard part left is to account for the subimage sampling
-  // restriction. If all the transforms involved are just integer
-  // translations, then we assume no resampling will occur so there's
-  // nothing to do.
-  // XXX if only we had source-clipping in cairo!
-  if (!currentMatrix.HasNonIntegerTranslation() &&
-      !userSpaceToImageSpace.HasNonIntegerTranslation()) {
-    if (doTile) {
-      pattern->SetExtend(gfxPattern::EXTEND_REPEAT);
-    }
-  } else {
-    if (doTile || !subimage.Contains(imageRect)) {
-      // EXTEND_PAD won't help us here; we have to create a temporary
-      // surface to hold the subimage of pixels we're allowed to
-      // sample
-
-      gfxRect userSpaceClipExtents = aContext->GetClipExtents();
-      // This isn't optimal --- if aContext has a rotation then GetClipExtents
-      // will have to do a bounding-box computation, and TransformBounds might
-      // too, so we could get a better result if we computed image space clip
-      // extents in one go --- but it doesn't really matter and this is easier
-      // to understand.
-      gfxRect imageSpaceClipExtents = userSpaceToImageSpace.TransformBounds(userSpaceClipExtents);
-      // Inflate by one pixel because bilinear filtering will sample at most
-      // one pixel beyond the computed image pixel coordinate.
-      imageSpaceClipExtents.Outset(1.0);
-
-      gfxRect needed = imageSpaceClipExtents.Intersect(sourceRect).Intersect(subimage);
-      needed.RoundOut();
-
-      // if 'needed' is empty, nothing will be drawn since aFill
-      // must be entirely outside the clip region, so it doesn't
-      // matter what we do here, but we should avoid trying to
-      // create a zero-size surface.
-      if (!needed.IsEmpty()) {
-        gfxIntSize size(PRInt32(needed.Width()), PRInt32(needed.Height()));
-        nsRefPtr<gfxASurface> temp =
-          gfxPlatform::GetPlatform()->CreateOffscreenSurface(size, format);
-        if (temp && temp->CairoStatus() == 0) {
-          gfxContext tmpCtx(temp);
-          tmpCtx.SetOperator(gfxContext::OPERATOR_SOURCE);
-          nsRefPtr<gfxPattern> tmpPattern = new gfxPattern(surface);
-          if (tmpPattern) {
-            tmpPattern->SetExtend(gfxPattern::EXTEND_REPEAT);
-            tmpPattern->SetMatrix(gfxMatrix().Translate(needed.pos));
-            tmpCtx.SetPattern(tmpPattern);
-            tmpCtx.Paint();
-            tmpPattern = new gfxPattern(temp);
-            if (tmpPattern) {
-              pattern.swap(tmpPattern);
-              pattern->SetMatrix(
-                  gfxMatrix(userSpaceToImageSpace).Multiply(gfxMatrix().Translate(-needed.pos)));
-            }
-          }
-        }
-      }
-    }
-
-    // In theory we can handle this using cairo's EXTEND_PAD,
-    // but implementation limitations mean we have to consult
-    // the surface type.
-    switch (currentTarget->GetType()) {
-      case gfxASurface::SurfaceTypeXlib:
-      case gfxASurface::SurfaceTypeXcb:
-      {
-        // See bug 324698.  This is a workaround for EXTEND_PAD not being
-        // implemented correctly on linux in the X server.
-        //
-        // Set the filter to CAIRO_FILTER_FAST --- otherwise,
-        // pixman's sampling will sample transparency for the outside edges and we'll
-        // get blurry edges.  CAIRO_EXTEND_PAD would also work here, if
-        // available
-        //
-        // But don't do this for simple downscales because it's horrible.
-        // Downscaling means that device-space coordinates are
-        // scaled *up* to find the image pixel coordinates.
-        //
-        // deviceToImage is slightly stale because up above we may
-        // have adjusted the pattern's matrix ... but the adjustment
-        // is only a translation so the scale factors in deviceToImage
-        // are still valid.
-        PRBool isDownscale =
-          deviceToImage.xx >= 1.0 && deviceToImage.yy >= 1.0 &&
-          deviceToImage.xy == 0.0 && deviceToImage.yx == 0.0;
-        if (!isDownscale) {
-          pattern->SetFilter(gfxPattern::FILTER_FAST);
-        }
-        break;
-      }
-
-      case gfxASurface::SurfaceTypeQuartz:
-      case gfxASurface::SurfaceTypeQuartzImage:
-        // Don't set EXTEND_PAD, Mac seems to be OK. Really?
-        pattern->SetFilter(aFilter);
-        break;
-
-      default:
-        // turn on EXTEND_PAD.
-        // This is what we really want for all surface types, if the
-        // implementation was universally good.
-        pattern->SetExtend(gfxPattern::EXTEND_PAD);
-        pattern->SetFilter(aFilter);
-        break;
-    }
-  }
-
-  if ((op == gfxContext::OPERATOR_OVER || pushedGroup) &&
-      format == gfxASurface::ImageFormatRGB24) {
-    aContext->SetOperator(OptimalFillOperator());
-  }
-
-  // Phew! Now we can actually draw this image
-  aContext->NewPath();
-#ifdef MOZ_GFX_OPTIMIZE_MOBILE
-  pattern->SetFilter(gfxPattern::FILTER_FAST); 
-#endif
-  aContext->SetPattern(pattern);
-  aContext->Rectangle(fill);
-  aContext->Fill();
-
-  aContext->SetOperator(op);
-  if (pushedGroup) {
-    aContext->PopGroupToSource();
-    aContext->Paint();
-    aContext->Restore();
+  if (surfaceResult.IsValid()) {
+    gfxUtils::DrawPixelSnapped(aContext, surfaceResult.mSurface,
+                               userSpaceToImageSpace,
+                               subimage, sourceRect, imageRect, fill,
+                               surfaceResult.mFormat, aFilter);
   }
 }
 
 nsresult imgFrame::Extract(const nsIntRect& aRegion, imgFrame** aResult)
 {
   nsAutoPtr<imgFrame> subImage(new imgFrame());
   if (!subImage)
     return NS_ERROR_OUT_OF_MEMORY;
@@ -963,31 +809,16 @@ PRBool imgFrame::GetCompositingFailed() 
   return mCompositingFailed;
 }
 
 void imgFrame::SetCompositingFailed(PRBool val)
 {
   mCompositingFailed = val;
 }
 
-gfxContext::GraphicsOperator imgFrame::OptimalFillOperator()
-{
-#ifdef XP_WIN
-  if (gfxWindowsPlatform::GetPlatform()->GetRenderMode() ==
-      gfxWindowsPlatform::RENDER_DIRECT2D) {
-        // D2D -really- hates operator source.
-        return gfxContext::OPERATOR_OVER;
-  } else {
-#endif
-    return gfxContext::OPERATOR_SOURCE;
-#ifdef XP_WIN
-  }
-#endif
-}
-
 PRUint32 imgFrame::EstimateMemoryUsed() const
 {
   PRUint32 size = 0;
 
   if (mSinglePixel) {
     size += sizeof(gfxRGBA);
   }
 
--- a/modules/libpr0n/src/imgFrame.h
+++ b/modules/libpr0n/src/imgFrame.h
@@ -137,22 +137,34 @@ public:
   // returns an estimate of the memory used by this imgFrame
   PRUint32 EstimateMemoryUsed() const;
 
 private: // methods
   PRUint32 PaletteDataLength() const {
     return ((1 << mPaletteDepth) * sizeof(PRUint32));
   }
 
-  /**
-   * This returns the fastest operator to use for solid surfaces which have no
-   * alpha channel or their alpha channel is uniformly opaque.
-   * This differs per render mode.
-   */
-  gfxContext::GraphicsOperator OptimalFillOperator();
+  struct SurfaceWithFormat {
+    nsRefPtr<gfxASurface> mSurface;
+    gfxImageSurface::gfxImageFormat mFormat;
+    SurfaceWithFormat() {}
+    SurfaceWithFormat(gfxASurface* aSurface, gfxImageSurface::gfxImageFormat aFormat)
+     : mSurface(aSurface), mFormat(aFormat) {}
+    PRBool IsValid() { return !!mSurface; }
+  };
+
+  SurfaceWithFormat SurfaceForDrawing(PRBool             aDoPadding,
+                                      PRBool             aDoPartialDecode,
+                                      PRBool             aDoTile,
+                                      const nsIntMargin& aPadding,
+                                      gfxMatrix&         aUserSpaceToImageSpace,
+                                      gfxRect&           aFill,
+                                      gfxRect&           aSubimage,
+                                      gfxRect&           aSourceRect,
+                                      gfxRect&           aImageRect);
 
 private: // data
   nsRefPtr<gfxImageSurface> mImageSurface;
   nsRefPtr<gfxASurface> mOptSurface;
 #if defined(XP_WIN) && !defined(WINCE)
   nsRefPtr<gfxWindowsSurface> mWinSurface;
 #elif defined(XP_MACOSX)
   nsRefPtr<gfxQuartzImageSurface> mQuartzSurface;