Bug 458487 - 'Rework image snapping logic'. r=vlad+joedrew, sr=dbaron
authorRobert O'Callahan <robert@ocallahan.org>
Tue, 04 Nov 2008 14:01:21 -0800
changeset 21323 49dd935efff37bfb2e4f145272c09af5b8aed113
parent 21322 2d35868314cf0b9b06b38c1b655405893c3eab72
child 21324 469a48d3728051c816156b0ff29049382aebd28d
push idunknown
push userunknown
push dateunknown
reviewersvlad, dbaron
bugs458487
milestone1.9.1b2pre
Bug 458487 - 'Rework image snapping logic'. r=vlad+joedrew, sr=dbaron
gfx/public/nsIImage.h
gfx/public/nsIRenderingContext.h
gfx/src/thebes/nsThebesImage.cpp
gfx/src/thebes/nsThebesImage.h
gfx/src/thebes/nsThebesRenderingContext.cpp
gfx/src/thebes/nsThebesRenderingContext.h
gfx/thebes/public/gfxContext.h
gfx/thebes/public/gfxMatrix.h
gfx/thebes/public/gfxWindowsNativeDrawing.h
gfx/thebes/src/gfxContext.cpp
layout/base/nsCSSRendering.cpp
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/generic/nsBulletFrame.cpp
layout/generic/nsImageFrame.cpp
layout/reftests/bugs/364968-1-ref.html
layout/reftests/bugs/364968-1.xul
layout/reftests/bugs/433640-1-ref.html
layout/reftests/bugs/446100-1a.html
layout/reftests/bugs/446100-1b.html
layout/reftests/bugs/446100-1c.html
layout/reftests/bugs/446100-1d.html
layout/reftests/bugs/446100-1e.html
layout/reftests/bugs/446100-1f.html
layout/reftests/bugs/446100-1g.html
layout/reftests/bugs/446100-1h.html
layout/reftests/bugs/458487-1-ref.html
layout/reftests/bugs/458487-1a.html
layout/reftests/bugs/458487-1b.html
layout/reftests/bugs/458487-1c.html
layout/reftests/bugs/458487-1d.html
layout/reftests/bugs/458487-1e.html
layout/reftests/bugs/458487-1f.html
layout/reftests/bugs/458487-1g.html
layout/reftests/bugs/458487-1h.html
layout/reftests/bugs/458487-2-ref.html
layout/reftests/bugs/458487-2.html
layout/reftests/bugs/458487-3-iframe.html
layout/reftests/bugs/458487-3-ref.html
layout/reftests/bugs/458487-3.html
layout/reftests/bugs/458487-4-ref.html
layout/reftests/bugs/458487-4a.html
layout/reftests/bugs/458487-4b.html
layout/reftests/bugs/458487-4c.html
layout/reftests/bugs/458487-5-ref.html
layout/reftests/bugs/458487-5a.html
layout/reftests/bugs/458487-5b.html
layout/reftests/bugs/reftest.list
layout/xul/base/src/nsImageBoxFrame.cpp
layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp
widget/src/xpwidgets/nsBaseDragService.cpp
--- a/gfx/public/nsIImage.h
+++ b/gfx/public/nsIImage.h
@@ -34,22 +34,24 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsIImage_h___
 #define nsIImage_h___
 
 #include "nsISupports.h"
-#include "nsIRenderingContext.h"
+#include "nsMargin.h"
 #include "nsRect.h"
-#include "gfxRect.h"
 
 class gfxASurface;
 class gfxPattern;
+class gfxMatrix;
+class gfxRect;
+class gfxContext;
 
 class nsIDeviceContext;
 
 struct nsColorMap
 {
   //I lifted this from the image lib. The difference is that
   //this uses nscolor instead of NI_RGB. Multiple color pollution
   //is a bad thing. MMP
@@ -67,20 +69,20 @@ typedef enum {
     nsMaskRequirements_kNeeds8Bit
 } nsMaskRequirements;
 
 
 #define  nsImageUpdateFlags_kColorMapChanged 0x1
 #define  nsImageUpdateFlags_kBitsChanged     0x2
 
 // IID for the nsIImage interface
-// 96d9d7ce-e575-4265-8507-35555112a430
+// 455fc276-01de-488f-9f8f-19b85a6b112d
 #define NS_IIMAGE_IID \
-{ 0x96d9d7ce, 0xe575, 0x4265, \
-  { 0x85, 0x07, 0x35, 0x55, 0x51, 0x12, 0xa4, 0x30 } }
+  { 0x455fc276, 0x01de, 0x488f, \
+    { 0x9f, 0x8f, 0x19, 0xb8, 0x5a, 0x6b, 0x11, 0x2d } }
 
 // Interface to Images
 class nsIImage : public nsISupports
 {
 
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IIMAGE_IID)
 
@@ -184,29 +186,42 @@ public:
   /**
    * Get the colormap for the nsIImage
    * @update - dwc 2/1/99
    * @return if non null, the colormap for the pixelmap,otherwise the image is not color mapped
    */
   virtual nsColorMap * GetColorMap() = 0;
 
   /**
-   * BitBlit the nsIImage to a device, the source and dest can be scaled
-   * @param aSourceRect  source rectangle, in image pixels
-   * @param aSubimageRect the subimage that we're extracting the contents from.
-   * It must contain aSourceRect. Pixels outside this rectangle must not
+   * BitBlit the nsIImage to a device, the source and dest can be scaled.
+   * @param aContext the destination
+   * @param aUserSpaceToImageSpace the transform that maps user-space
+   * coordinates to coordinates in (tiled, post-padding) image pixels
+   * @param aFill the area to fill with tiled images
+   * @param aPadding the padding to be added to this image before tiling,
+   * in image pixels
+   * @param aSubimage the subimage in padded+tiled image space that we're
+   * extracting the contents from. Pixels outside this rectangle must not
    * be sampled.
-   * @param aDestRect  destination rectangle, in device pixels
+   * 
+   * So this is supposed to
+   * -- add aPadding transparent pixels around the image
+   * -- use that image to tile the plane
+   * -- replace everything outside the aSubimage region with the nearest
+   * border pixel of that region (like EXTEND_PAD)
+   * -- fill aFill with the image, using aImageSpaceToDeviceSpace as the
+   * image-space-to-device-space transform
    */
-  NS_IMETHOD Draw(nsIRenderingContext &aContext,
-                  const gfxRect &aSourceRect,
-                  const gfxRect &aSubimageRect,
-                  const gfxRect &aDestRect) = 0;
+  virtual void Draw(gfxContext*        aContext,
+                    const gfxMatrix&   aUserSpaceToImageSpace,
+                    const gfxRect&     aFill,
+                    const nsIntMargin& aPadding,
+                    const nsIntRect&   aSubimage) = 0;
 
-  /**
+  /** 
    * Get the alpha depth for the image mask
    * @update - lordpixel 2001/05/16
    * @return  the alpha mask depth for the image, ie, 0, 1 or 8
    */
   virtual PRInt8 GetAlphaDepth() = 0;
 
   /**
    * Return information about the bits for this structure
--- a/gfx/public/nsIRenderingContext.h
+++ b/gfx/public/nsIRenderingContext.h
@@ -92,19 +92,20 @@ typedef enum
 typedef enum
 {
   nsPenMode_kNone   = 0,
   nsPenMode_kInvert = 1
 } nsPenMode;
 
 
 // IID for the nsIRenderingContext interface
-// a67de6b9-fffa-465c-abea-d7b394588a07
+// 3a6209e8-d80d-42ab-ad6a-b8832f7fb09f
 #define NS_IRENDERING_CONTEXT_IID \
- { 0xa67de6b9, 0xfffa, 0x465c,{0xab, 0xea, 0xd7, 0xb3, 0x94, 0x58, 0x8a, 0x07}}
+{ 0x3a6209e8, 0xd80d, 0x42ab, \
+  { 0xad, 0x6a, 0xb8, 0x83, 0x2f, 0x7f, 0xb0, 0x9f } }
 
 //----------------------------------------------------------------------
 
 // RenderingContext interface
 class nsIRenderingContext : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IRENDERING_CONTEXT_IID)
@@ -581,31 +582,16 @@ public:
   NS_IMETHOD SetRightToLeftText(PRBool aIsRTL) = 0;
 
   /**
    * This sets the direction of the text; all characters should be
    * overridden to have this direction.
    */
   virtual void SetTextRunRTL(PRBool aIsRTL) = 0;
 
-  /*
-   * Tiles an image over an area
-   * @param aImage       Image to tile
-   * @param aXImageStart x location where the origin (0,0) of the image starts
-   * @param aYImageStart y location where the origin (0,0) of the image starts
-   * @param aTargetRect  area to draw to
-   * @param aSubimageRect the subimage (in tile space) which we expect to
-   * sample from; may be null to indicate that the whole image is
-   * OK to sample from
-   */
-  NS_IMETHOD DrawTile(imgIContainer *aImage,
-                      nscoord aXImageStart, nscoord aYImageStart,
-                      const nsRect * aTargetRect,
-                      const nsIntRect * aSubimageRect) = 0;
-
   /**
    * Find the closest cursor position for a given x coordinate.
    *
    * This will find the closest byte index for a given x coordinate.
    * This takes into account grapheme clusters and bidi text.
    *
    * @param aText Text on which to operate.
    * @param aLength Length of the text.
--- a/gfx/src/thebes/nsThebesImage.cpp
+++ b/gfx/src/thebes/nsThebesImage.cpp
@@ -426,421 +426,255 @@ nsThebesImage::UnlockImagePixels(PRBool 
     mOptSurface = nsnull;
 #ifdef XP_MACOSX
     if (mQuartzSurface)
         mQuartzSurface->Flush();
 #endif
     return NS_OK;
 }
 
-/* NB: These are pixels, not twips. */
-NS_IMETHODIMP
-nsThebesImage::Draw(nsIRenderingContext &aContext,
-                    const gfxRect &aSourceRect,
-                    const gfxRect &aSubimageRect,
-                    const gfxRect &aDestRect)
+static PRBool
+IsSafeImageTransformComponent(gfxFloat aValue)
 {
-    if (NS_UNLIKELY(aDestRect.IsEmpty())) {
-        NS_ERROR("nsThebesImage::Draw zero dest size - please fix caller.");
-        return NS_OK;
-    }
+    return aValue >= -32768 && aValue <= 32767;
+}
 
-    nsThebesRenderingContext *thebesRC = static_cast<nsThebesRenderingContext*>(&aContext);
-    gfxContext *ctx = thebesRC->ThebesContext();
+void
+nsThebesImage::Draw(gfxContext*        aContext,
+                    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");
 
-#if 0
-    fprintf (stderr, "nsThebesImage::Draw src [%f %f %f %f] dest [%f %f %f %f] trans: [%f %f] dec: [%f %f]\n",
-             aSourceRect.pos.x, aSourceRect.pos.y, aSourceRect.size.width, aSourceRect.size.height,
-             aDestRect.pos.x, aDestRect.pos.y, aDestRect.size.width, aDestRect.size.height,
-             ctx->CurrentMatrix().GetTranslation().x, ctx->CurrentMatrix().GetTranslation().y,
-             mDecoded.x, mDecoded.y, mDecoded.width, mDecoded.height);
-#endif
+    PRBool doPadding = aPadding != nsIntMargin(0,0,0,0);
+    PRBool doPartialDecode = !GetIsImageComplete();
+    gfxContext::GraphicsOperator op = aContext->CurrentOperator();
 
-    if (mSinglePixel) {
+    if (mSinglePixel && !doPadding && !doPartialDecode) {
+        // Single-color fast path
         // if a == 0, it's a noop
         if (mSinglePixelColor.a == 0.0)
-            return NS_OK;
-
-        // otherwise
-        gfxContext::GraphicsOperator op = ctx->CurrentOperator();
-        if (op == gfxContext::OPERATOR_OVER && mSinglePixelColor.a == 1.0)
-            ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
+            return;
 
-        ctx->SetDeviceColor(mSinglePixelColor);
-        ctx->NewPath();
-        ctx->Rectangle(aDestRect, PR_TRUE);
-        ctx->Fill();
-        ctx->SetOperator(op);
-        return NS_OK;
-    }
-
-    gfxFloat xscale = aDestRect.size.width / aSourceRect.size.width;
-    gfxFloat yscale = aDestRect.size.height / aSourceRect.size.height;
+        if (op == gfxContext::OPERATOR_OVER && mSinglePixelColor.a == 1.0)
+            aContext->SetOperator(gfxContext::OPERATOR_SOURCE);
 
-    gfxRect srcRect(aSourceRect);
-    gfxRect subimageRect(aSubimageRect);
-    gfxRect destRect(aDestRect);
-
-    if (!GetIsImageComplete()) {
-        gfxRect decoded = gfxRect(mDecoded.x, mDecoded.y,
-                                  mDecoded.width, mDecoded.height);
-        srcRect = srcRect.Intersect(decoded);
-        subimageRect = subimageRect.Intersect(decoded);
-
-        // This happens when mDecoded.width or height is zero. bug 368427.
-        if (NS_UNLIKELY(srcRect.size.width == 0 || srcRect.size.height == 0))
-            return NS_OK;
-
-        destRect.pos.x += (srcRect.pos.x - aSourceRect.pos.x)*xscale;
-        destRect.pos.y += (srcRect.pos.y - aSourceRect.pos.y)*yscale;
-
-        destRect.size.width  = srcRect.size.width * xscale;
-        destRect.size.height = srcRect.size.height * yscale;
+        aContext->SetDeviceColor(mSinglePixelColor);
+        aContext->NewPath();
+        aContext->Rectangle(aFill);
+        aContext->Fill();
+        aContext->SetOperator(op);
+        aContext->SetDeviceColor(gfxRGBA(0,0,0,0));
+        return;
     }
 
-    // if either rectangle is empty now (possibly after the image complete check)
-    if (srcRect.IsEmpty() || destRect.IsEmpty())
-        return NS_OK;
-
-    // Reject over-wide or over-tall images.
-    if (!AllowedImageSize(destRect.size.width + 1, destRect.size.height + 1))
-        return NS_ERROR_FAILURE;
-
-    // Expand the subimageRect to place its edges on integer coordinates.
-    // Basically, if we're allowed to sample part of a pixel we can
-    // sample the whole pixel.
-    subimageRect.RoundOut();
-
-    nsRefPtr<gfxPattern> pat;
-    PRBool ctxHasNonTranslation = ctx->CurrentMatrix().HasNonTranslation();
-    if ((xscale == 1.0 && yscale == 1.0 && !ctxHasNonTranslation) ||
-        subimageRect == gfxRect(0, 0, mWidth, mHeight))
-    {
-        // No need to worry about sampling outside the subimage rectangle,
-        // so no need for a temporary
-        // XXX should we also check for situations where the source rect
-        // is well inside the subimage so we can't sample outside?
-        pat = new gfxPattern(ThebesSurface());
-    } else {
-        // Because of the RoundOut above, the subimageRect has
-        // integer width and height.
-        gfxIntSize size(PRInt32(subimageRect.Width()),
-                        PRInt32(subimageRect.Height()));
-        nsRefPtr<gfxASurface> temp =
-            gfxPlatform::GetPlatform()->CreateOffscreenSurface(size, mFormat);
-        if (!temp || temp->CairoStatus() != 0)
-            return NS_ERROR_FAILURE;
-
-        gfxContext tempctx(temp);
-        tempctx.SetSource(ThebesSurface(), -subimageRect.pos);
-        tempctx.SetOperator(gfxContext::OPERATOR_SOURCE);
-        tempctx.Paint();
-
-        pat = new gfxPattern(temp);
-        srcRect.MoveBy(-subimageRect.pos);
-    }
-
-    /* See bug 364968 to understand the necessity of this goop; we basically
-     * have to pre-downscale any image that would fall outside of a scaled 16-bit
-     * coordinate space.
-     */
-    if (aDestRect.pos.x * (1.0 / xscale) >= 32768.0 ||
-        aDestRect.pos.y * (1.0 / yscale) >= 32768.0)
-    {
-        gfxIntSize dim(NS_lroundf(destRect.size.width),
-                       NS_lroundf(destRect.size.height));
-
-        // nothing to do in this case
-        if (dim.width == 0 || dim.height == 0)
-            return NS_OK;
-
-        nsRefPtr<gfxASurface> temp =
-            gfxPlatform::GetPlatform()->CreateOffscreenSurface (dim,  mFormat);
-        if (!temp || temp->CairoStatus() != 0)
-            return NS_ERROR_FAILURE;
-
-        gfxContext tempctx(temp);
+    gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace;
+    gfxRect sourceRect = userSpaceToImageSpace.Transform(aFill);
+    gfxRect imageRect(0, 0, mWidth + aPadding.LeftRight(), mHeight + aPadding.TopBottom());
+    gfxRect subimage(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height);
+    gfxRect fill = aFill;
+    nsRefPtr<gfxASurface> surface;
+    gfxImageSurface::gfxImageFormat format;
 
-        gfxMatrix mat;
-        mat.Translate(srcRect.pos);
-        mat.Scale(1.0 / xscale, 1.0 / yscale);
-        pat->SetMatrix(mat);
-
-        tempctx.SetPattern(pat);
-        tempctx.SetOperator(gfxContext::OPERATOR_SOURCE);
-        tempctx.NewPath();
-        tempctx.Rectangle(gfxRect(0.0, 0.0, dim.width, dim.height));
-        tempctx.Fill();
-
-        pat = new gfxPattern(temp);
-
-        srcRect.pos.x = 0.0;
-        srcRect.pos.y = 0.0;
-        srcRect.size.width = dim.width;
-        srcRect.size.height = dim.height;
-
-        xscale = 1.0;
-        yscale = 1.0;
+    PRBool doTile = !imageRect.Contains(sourceRect);
+    if (doPadding || doPartialDecode) {
+        gfxRect available = gfxRect(mDecoded.x, mDecoded.y, mDecoded.width, mDecoded.height) +
+            gfxPoint(aPadding.left, aPadding.top);
+  
+        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, mWidth, mHeight);
+        } 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;
     }
-
-    gfxMatrix mat;
-    mat.Translate(srcRect.pos);
-    mat.Scale(1.0/xscale, 1.0/yscale);
-
-    /* Translate the start point of the image (srcRect.pos)
-     * to coincide with the destination rectangle origin
-     */
-    mat.Translate(-destRect.pos);
+    // At this point, we've taken care of mSinglePixel images, images with
+    // aPadding, and partially-decoded images.
 
-    pat->SetMatrix(mat);
-
-    nsRefPtr<gfxASurface> target = ctx->CurrentSurface();
-    switch (target->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 if we're scaling up -- 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
-        //
-        // This effectively disables smooth upscaling for images.
-        if (xscale > 1.0 || yscale > 1.0 || ctxHasNonTranslation)
-            pat->SetFilter(0);
-        break;
-
-    case gfxASurface::SurfaceTypeQuartz:
-    case gfxASurface::SurfaceTypeQuartzImage:
-        // Do nothing, Mac seems to be OK. Really?
-        break;
-
-    default:
-        // turn on EXTEND_PAD.
-        // This is what we really want for all surface types, if the
-        // implementation was universally good.
-        if (xscale != 1.0 || yscale != 1.0 || ctxHasNonTranslation)
-            pat->SetExtend(gfxPattern::EXTEND_PAD);
-        break;
+    if (!AllowedImageSize(fill.size.width + 1, fill.size.height + 1)) {
+        NS_WARNING("Destination area too large, bailing out");
+        return;
     }
 
-    gfxContext::GraphicsOperator op = ctx->CurrentOperator();
-    if (op == gfxContext::OPERATOR_OVER && mFormat == gfxASurface::ImageFormatRGB24)
-        ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
-
-    ctx->NewPath();
-    ctx->SetPattern(pat);
-#ifdef MOZ_GFX_OPTIMIZE_MOBILE
-    ctx->Rectangle(destRect, PR_TRUE);
-#else
-    ctx->Rectangle(destRect);
-#endif
-    ctx->Fill();
-
-    ctx->SetOperator(op);
-    ctx->SetDeviceColor(gfxRGBA(0,0,0,0));
-
-    return NS_OK;
-}
-
-nsresult
-nsThebesImage::ThebesDrawTile(gfxContext *thebesContext,
-                              nsIDeviceContext* dx,
-                              const gfxPoint& offset,
-                              const gfxRect& targetRect,
-                              const nsIntRect& aSubimageRect,
-                              const PRInt32 xPadding,
-                              const PRInt32 yPadding)
-{
-    NS_ASSERTION(xPadding >= 0 && yPadding >= 0, "negative padding");
-
-    if (targetRect.size.width <= 0.0 || targetRect.size.height <= 0.0)
-        return NS_OK;
-
-    // don't do anything if we have a transparent pixel source
-    if (mSinglePixel && mSinglePixelColor.a == 0.0)
-        return NS_OK;
-
-#ifdef MOZ_GFX_OPTIMIZE_MOBILE
-    PRBool doSnap = PR_TRUE;
-#else
-    PRBool doSnap = !(thebesContext->CurrentMatrix().HasNonTranslation());
-#endif
-    PRBool hasPadding = ((xPadding != 0) || (yPadding != 0));
-    gfxImageSurface::gfxImageFormat format = mFormat;
-    
-    gfxPoint tmpOffset = offset;
-
-    if (mSinglePixel && !hasPadding) {
-        thebesContext->SetDeviceColor(mSinglePixelColor);
-    } else {
-        nsRefPtr<gfxASurface> surface;
-        PRInt32 width, height;
-
-        if (hasPadding) {
-            /* Ugh we have padding; create a temporary surface that's the size of the surface + pad area,
-             * and render the image into it first.  Then we'll tile that surface. */
-            width = mWidth + xPadding;
-            height = mHeight + yPadding;
-
-            // Reject over-wide or over-tall images.
-            if (!AllowedImageSize(width, height))
-                return NS_ERROR_FAILURE;
-
-            format = gfxASurface::ImageFormatARGB32;
-            surface = gfxPlatform::GetPlatform()->CreateOffscreenSurface(
-                    gfxIntSize(width, height), format);
-            if (!surface || surface->CairoStatus()) {
-                return NS_ERROR_OUT_OF_MEMORY;
-            }
-
-            gfxContext tmpContext(surface);
-            if (mSinglePixel) {
-                tmpContext.SetDeviceColor(mSinglePixelColor);
-            } else {
-                tmpContext.SetSource(ThebesSurface());
-            }
-            tmpContext.SetOperator(gfxContext::OPERATOR_SOURCE);
-            tmpContext.Rectangle(gfxRect(0, 0, mWidth, mHeight));
-            tmpContext.Fill();
-        } else {
-            width = mWidth;
-            height = mHeight;
-            surface = ThebesSurface();
-        }
-        
-        // Scale factor to account for CSS pixels; note that the offset (and 
-        // therefore p0) is in device pixels, while the width and height are in
-        // CSS pixels.
-        gfxFloat scale = gfxFloat(dx->AppUnitsPerDevPixel()) /
-                         gfxFloat(nsIDeviceContext::AppUnitsPerCSSPixel());
-
-        if ((aSubimageRect.width < width || aSubimageRect.height < height) &&
-            (thebesContext->CurrentMatrix().HasNonTranslation() || scale != 1.0)) {
-            // Some of the source image should not be drawn, and we're going
-            // to be doing more than just translation, so we might accidentally
-            // sample the non-drawn pixels. Avoid that by creating a
-            // temporary image representing the portion that will be drawn,
-            // with built-in padding since we can't use EXTEND_PAD and
-            // EXTEND_REPEAT at the same time for different axes.
-            PRInt32 padX = aSubimageRect.width < width ? 1 : 0;
-            PRInt32 padY = aSubimageRect.height < height ? 1 : 0;
-            PRInt32 tileWidth = PR_MIN(aSubimageRect.width, width);
-            PRInt32 tileHeight = PR_MIN(aSubimageRect.height, height);
-            
-            // This tmpSurface will contain a snapshot of the repeated
-            // tile image at (aSubimageRect.x, aSubimageRect.y,
-            // tileWidth, tileHeight), with padX padding added to the left
-            // and right sides and padY padding added to the top and bottom
-            // sides.
-            nsRefPtr<gfxASurface> tmpSurface;
-            tmpSurface = gfxPlatform::GetPlatform()->CreateOffscreenSurface(
-                    gfxIntSize(tileWidth + 2*padX, tileHeight + 2*padY), format);
-            if (!tmpSurface || tmpSurface->CairoStatus()) {
-                return NS_ERROR_OUT_OF_MEMORY;
-            }
-
-            gfxContext tmpContext(tmpSurface);
-            tmpContext.SetOperator(gfxContext::OPERATOR_SOURCE);
-            gfxPattern pat(surface);
-            pat.SetExtend(gfxPattern::EXTEND_REPEAT);
-            
-            // Copy the needed portion of the source image to the temporary
-            // surface. We also copy over horizontal and/or vertical padding
-            // strips one pixel wide, plus the corner pixels if necessary.
-            // So in the most general case the temporary surface ends up
-            // looking like
-            //     P P P ... P P P
-            //     P X X ... X X P
-            //     P X X ... X X P
-            //     ...............
-            //     P X X ... X X P
-            //     P X X ... X X P
-            //     P P P ... P P P
-            // Where each P pixel has the color of its nearest source X
-            // pixel. We implement this as a loop over all nine possible
-            // areas, [padding, body, padding] x [padding, body, padding].
-            // Note that we will not need padding on both axes unless
-            // we are painting just a single tile, in which case this
-            // will hardly ever get called since nsCSSRendering converts
-            // the single-tile case to nsLayoutUtils::DrawImage. But this
-            // could be called on other paths (XUL trees?) and it's simpler
-            // and clearer to do it the general way.
-            PRInt32 destY = 0;
-            for (PRInt32 y = -1; y <= 1; ++y) {
-                PRInt32 stripHeight = y == 0 ? tileHeight : padY;
-                if (stripHeight == 0)
-                    continue;
-                PRInt32 srcY = y == 1 ? aSubimageRect.YMost() - padY : aSubimageRect.y;
-                
-                PRInt32 destX = 0;
-                for (PRInt32 x = -1; x <= 1; ++x) {
-                    PRInt32 stripWidth = x == 0 ? tileWidth : padX;
-                    if (stripWidth == 0)
-                        continue;
-                    PRInt32 srcX = x == 1 ? aSubimageRect.XMost() - padX : aSubimageRect.x;
-
-                    gfxMatrix patMat;
-                    patMat.Translate(gfxPoint(srcX - destX, srcY - destY));
-                    pat.SetMatrix(patMat);
-                    tmpContext.SetPattern(&pat);
-                    tmpContext.Rectangle(gfxRect(destX, destY, stripWidth, stripHeight));
-                    tmpContext.Fill();
-                    tmpContext.NewPath();
-                    
-                    destX += stripWidth;
-                }
-                destY += stripHeight;
-            }
-
-            // tmpOffset was the top-left of the old tile image. Make it
-            // the top-left of the new tile image. Note that tmpOffset is
-            // in destination coordinate space so we have to scale our
-            // CSS pixels.
-            tmpOffset += gfxPoint(aSubimageRect.x - padX, aSubimageRect.y - padY)/scale;
-            
-            surface = tmpSurface;
-        }
-
-        gfxMatrix patMat;
-        gfxPoint p0;
-
-        p0.x = - floor(tmpOffset.x + 0.5);
-        p0.y = - floor(tmpOffset.y + 0.5);
-        patMat.Scale(scale, scale);
-        patMat.Translate(p0);
-
-        gfxPattern pat(surface);
-        pat.SetExtend(gfxPattern::EXTEND_REPEAT);
-        pat.SetMatrix(patMat);
-
-#ifndef XP_MACOSX
-        if (scale < 1.0) {
-            // See bug 324698.  This is a workaround.  See comments
-            // by the earlier SetFilter call.
-            pat.SetFilter(0);
-        }
-#endif
-
-        thebesContext->SetPattern(&pat);
+    // BEGIN working around cairo/pixman bug (bug 364968)
+    // 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);
+  
+    // 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;
     }
 
-    gfxContext::GraphicsOperator op = thebesContext->CurrentOperator();
-    if (op == gfxContext::OPERATOR_OVER && format == gfxASurface::ImageFormatRGB24)
-        thebesContext->SetOperator(gfxContext::OPERATOR_SOURCE);
+    PRBool pushedGroup = PR_FALSE;
+    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);
 
-    thebesContext->NewPath();
-    thebesContext->Rectangle(targetRect, doSnap);
-    thebesContext->Fill();
+    // 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 needed = subimage.Intersect(sourceRect);
+            needed.RoundOut();
+            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
+            //
+            // This effectively disables smooth upscaling for images.
+            pattern->SetFilter(0);
+            break;
+  
+        case gfxASurface::SurfaceTypeQuartz:
+        case gfxASurface::SurfaceTypeQuartzImage:
+            // Do nothing, Mac seems to be OK. Really?
+            break;
 
-    thebesContext->SetOperator(op);
-    thebesContext->SetDeviceColor(gfxRGBA(0,0,0,0));
+        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);
+            break;
+        }
+    }
 
-    return NS_OK;
+    if ((op == gfxContext::OPERATOR_OVER || pushedGroup) &&
+        format == gfxASurface::ImageFormatRGB24) {
+        aContext->SetOperator(gfxContext::OPERATOR_SOURCE);
+    }
+
+    // Phew! Now we can actually draw this image
+    aContext->NewPath();
+    aContext->SetPattern(pattern);
+    aContext->Rectangle(fill);
+    aContext->Fill();
+  
+    aContext->SetOperator(op);
+    if (pushedGroup) {
+        aContext->PopGroupToSource();
+        aContext->Paint();
+        aContext->Restore();
+    }
 }
 
 PRBool
 nsThebesImage::ShouldUseImageSurfaces()
 {
 #ifdef XP_WIN
     static const DWORD kGDIObjectsHighWaterMark = 7000;
 
--- a/gfx/src/thebes/nsThebesImage.h
+++ b/gfx/src/thebes/nsThebesImage.h
@@ -70,28 +70,21 @@ public:
     virtual PRBool GetHasAlphaMask();
     virtual PRUint8 *GetAlphaBits();
     virtual PRInt32 GetAlphaLineStride();
     virtual PRBool GetIsImageComplete();
     virtual nsresult ImageUpdated(nsIDeviceContext *aContext, PRUint8 aFlags, nsRect *aUpdateRect);
     virtual nsresult Optimize(nsIDeviceContext* aContext);
     virtual nsColorMap *GetColorMap();
 
-    NS_IMETHOD Draw(nsIRenderingContext &aContext,
-                    const gfxRect &aSourceRect,
-                    const gfxRect &aSubimageRect,
-                    const gfxRect &aDestRect);
-
-    nsresult ThebesDrawTile(gfxContext *thebesContext,
-                            nsIDeviceContext* dx,
-                            const gfxPoint& aOffset,
-                            const gfxRect& aTileRect,
-                            const nsIntRect& aSubimageRect,
-                            const PRInt32 aXPadding,
-                            const PRInt32 aYPadding);
+    virtual void Draw(gfxContext*        aContext,
+                      const gfxMatrix&   aUserSpaceToImageSpace,
+                      const gfxRect&     aFill,
+                      const nsIntMargin& aPadding,
+                      const nsIntRect&   aSubimage);
 
     virtual PRInt8 GetAlphaDepth();
     virtual void* GetBitInfo();
     NS_IMETHOD LockImagePixels(PRBool aMaskPixels);
     NS_IMETHOD UnlockImagePixels(PRBool aMaskPixels);
 
     NS_IMETHOD GetSurface(gfxASurface **aSurface) {
         *aSurface = ThebesSurface();
--- a/gfx/src/thebes/nsThebesRenderingContext.cpp
+++ b/gfx/src/thebes/nsThebesRenderingContext.cpp
@@ -755,86 +755,16 @@ nsThebesRenderingContext::PopFilter()
 
         mThebes->Restore();
     }
 
 
     return NS_OK;
 }
 
-NS_IMETHODIMP
-nsThebesRenderingContext::DrawTile(imgIContainer *aImage,
-                                   nscoord twXOffset, nscoord twYOffset,
-                                   const nsRect *twTargetRect,
-                                   const nsIntRect *subimageRect)
-{
-    PR_LOG(gThebesGFXLog, PR_LOG_DEBUG, ("## %p nsTRC::DrawTile %p %f %f [%f,%f,%f,%f]\n",
-                                         this, aImage, FROM_TWIPS(twXOffset), FROM_TWIPS(twYOffset),
-                                         FROM_TWIPS(twTargetRect->x), FROM_TWIPS(twTargetRect->y),
-                                         FROM_TWIPS(twTargetRect->width), FROM_TWIPS(twTargetRect->height)));
-
-    nscoord containerWidth, containerHeight;
-    aImage->GetWidth(&containerWidth);
-    aImage->GetHeight(&containerHeight);
-
-    nsCOMPtr<gfxIImageFrame> imgFrame;
-    aImage->GetCurrentFrame(getter_AddRefs(imgFrame));
-    if (!imgFrame) return NS_ERROR_FAILURE;
-
-    nsRect imgFrameRect;
-    imgFrame->GetRect(imgFrameRect);
-
-    nsCOMPtr<nsIImage> img(do_GetInterface(imgFrame));
-    if (!img) return NS_ERROR_FAILURE;
-    
-    nsThebesImage *thebesImage = static_cast<nsThebesImage*>((nsIImage*) img.get());
-
-    /* Phase offset of the repeated image from the origin */
-    gfxPoint phase(FROM_TWIPS(twXOffset), FROM_TWIPS(twYOffset));
-
-    /* The image may be smaller than the container (bug 113561),
-     * so we need to make sure that there is the right amount of padding
-     * in between each tile of the nsIImage.  This problem goes away
-     * when we change the way the GIF decoder works to have it store
-     * full frames that are ready to be composited.
-     */
-    PRInt32 xPadding = 0;
-    PRInt32 yPadding = 0;
-
-    nsIntRect tmpSubimageRect;
-    if (subimageRect) {
-        tmpSubimageRect = *subimageRect;
-    } else {
-        tmpSubimageRect = nsIntRect(0, 0, containerWidth, containerHeight);
-    }
-
-    if (imgFrameRect.width != containerWidth ||
-        imgFrameRect.height != containerHeight)
-    {
-        xPadding = containerWidth - imgFrameRect.width;
-        yPadding = containerHeight - imgFrameRect.height;
-
-        // XXXroc shouldn't we be adding to 'phase' here? it's tbe origin
-        // at which the image origin should be drawn, and ThebesDrawTile
-        // just draws the origin of its "frame" there, so we should be
-        // adding imgFrameRect.x/y. so that the imgFrame draws in the
-        // right place.
-        phase.x -= imgFrameRect.x;
-        phase.y -= imgFrameRect.y;
-
-        tmpSubimageRect.x -= imgFrameRect.x;
-        tmpSubimageRect.y -= imgFrameRect.y;
-    }
-
-    return thebesImage->ThebesDrawTile (mThebes, mDeviceContext, phase,
-                                        GFX_RECT_FROM_TWIPS_RECT(*twTargetRect),
-                                        tmpSubimageRect,
-                                        xPadding, yPadding);
-}
-
 //
 // text junk
 //
 NS_IMETHODIMP
 nsThebesRenderingContext::SetRightToLeftText(PRBool aIsRTL)
 {
     return mFontMetrics->SetRightToLeftText(aIsRTL);
 }
--- a/gfx/src/thebes/nsThebesRenderingContext.h
+++ b/gfx/src/thebes/nsThebesRenderingContext.h
@@ -178,18 +178,16 @@ public:
     NS_IMETHOD PopFilter();
 
     virtual void* GetNativeGraphicData(GraphicDataType aType);
 
     NS_IMETHOD PushTranslation(PushedTranslation* aState);
     NS_IMETHOD PopTranslation(PushedTranslation* aState);
     NS_IMETHOD SetTranslation(nscoord aX, nscoord aY);
 
-    NS_IMETHOD DrawTile(imgIContainer *aImage, nscoord aXOffset, nscoord aYOffset,
-                        const nsRect * aTargetRect, const nsIntRect * aSubimageRect);
     NS_IMETHOD SetRightToLeftText(PRBool aIsRTL);
     NS_IMETHOD GetRightToLeftText(PRBool* aIsRTL);
     virtual void SetTextRunRTL(PRBool aIsRTL);
 
     virtual PRInt32 GetPosition(const PRUnichar *aText,
                                 PRUint32 aLength,
                                 nsPoint aPt);
     NS_IMETHOD GetRangeWidth(const PRUnichar *aText,
--- a/gfx/thebes/public/gfxContext.h
+++ b/gfx/thebes/public/gfxContext.h
@@ -334,16 +334,29 @@ public:
      *
      * If ignoreScale is PR_TRUE, then snapping will take place even if
      * the CTM has a scale applied.  Snapping never takes place if
      * there is a rotation in the CTM.
      */
     PRBool UserToDevicePixelSnapped(gfxRect& rect, PRBool ignoreScale = PR_FALSE) const;
 
     /**
+     * Takes the given point and tries to align it to device pixels.  If
+     * this succeeds, the method will return PR_TRUE, and the point will
+     * be in device coordinates (already transformed by the CTM).  If it 
+     * fails, the method will return PR_FALSE, and the point will not be
+     * changed.
+     *
+     * If ignoreScale is PR_TRUE, then snapping will take place even if
+     * the CTM has a scale applied.  Snapping never takes place if
+     * there is a rotation in the CTM.
+     */
+    PRBool UserToDevicePixelSnapped(gfxPoint& pt, PRBool ignoreScale = PR_FALSE) const;
+
+    /**
      * Attempts to pixel snap the rectangle, add it to the current
      * path, and to set pattern as the current painting source.  This
      * should be used for drawing filled pixel-snapped rectangles (like
      * images), because the CTM at the time of the SetPattern call needs
      * to have a snapped translation, or you get smeared images.
      */
     void PixelSnappedRectangleAndSetPattern(const gfxRect& rect, gfxPattern *pattern);
 
--- a/gfx/thebes/public/gfxMatrix.h
+++ b/gfx/thebes/public/gfxMatrix.h
@@ -37,16 +37,17 @@
 
 #ifndef GFX_MATRIX_H
 #define GFX_MATRIX_H
 
 #include "gfxPoint.h"
 #include "gfxTypes.h"
 #include "gfxRect.h"
 #include "gfxUtils.h"
+#include "nsMathUtils.h"
 
 // XX - I don't think this class should use gfxFloat at all,
 // but should use 'double' and be called gfxDoubleMatrix;
 // we can then typedef that to gfxMatrix where we typedef
 // double to be gfxFloat.
 
 /**
  * A matrix that represents an affine transformation. Projective
@@ -171,40 +172,50 @@ public:
     /**
      * Returns the translation component of this matrix.
      */
     gfxPoint GetTranslation() const {
         return gfxPoint(x0, y0);
     }
 
     /**
+     * Returns true if the matrix is anything other than a straight
+     * translation by integers.
+     */
+    PRBool HasNonIntegerTranslation() const {
+        return HasNonTranslation() ||
+            !gfxUtils::FuzzyEqual(x0, NS_floor(x0 + 0.5)) ||
+            !gfxUtils::FuzzyEqual(y0, NS_floor(y0 + 0.5));
+    }
+
+    /**
      * Returns true if the matrix has any transform other
      * than a straight translation
      */
-    bool HasNonTranslation() const {
+    PRBool HasNonTranslation() const {
         return !gfxUtils::FuzzyEqual(xx, 1.0) || !gfxUtils::FuzzyEqual(yy, 1.0) ||
                !gfxUtils::FuzzyEqual(xy, 0.0) || !gfxUtils::FuzzyEqual(yx, 0.0);
     }
 
     /**
      * Returns true if the matrix has any transform other
      * than a translation or a -1 y scale (y axis flip)
      */
-    bool HasNonTranslationOrFlip() const {
+    PRBool HasNonTranslationOrFlip() const {
         return !gfxUtils::FuzzyEqual(xx, 1.0) ||
                (!gfxUtils::FuzzyEqual(yy, 1.0) && !gfxUtils::FuzzyEqual(yy, -1.0)) ||
                !gfxUtils::FuzzyEqual(xy, 0.0) || !gfxUtils::FuzzyEqual(yx, 0.0);
     }
 
     /**
      * Returns true if the matrix has any transform other
      * than a translation or scale; this is, if there is
      * no rotation.
      */
-    bool HasNonAxisAlignedTransform() const {
+    PRBool HasNonAxisAlignedTransform() const {
         return !gfxUtils::FuzzyEqual(xy, 0.0) || !gfxUtils::FuzzyEqual(yx, 0.0);
     }
 
     /**
      * Computes the determinant of this matrix.
      */
     double Determinant() const {
         return xx*yy - yx*xy;
--- a/gfx/thebes/public/gfxWindowsNativeDrawing.h
+++ b/gfx/thebes/public/gfxWindowsNativeDrawing.h
@@ -66,31 +66,19 @@ public:
         /* If we have to do transforms with cairo, should we use nearest-neighbour filtering? */
         DO_NEAREST_NEIGHBOR_FILTERING = 1 << 3,
         DO_BILINEAR_FILTERING         = 0 << 3
     };
 
     /* Create native win32 drawing for a rectangle bounded by
      * nativeRect.
      *
-     * This class assumes that native drawing can take place only if
-     * the destination surface has a content type of COLOR (that is,
-     * RGB24), and that the transformation matrix consists of only a
-     * translation (in which case the coordinates are munged directly)
-     * or a translation and scale (in which case SetWorldTransform is used).
-     *
-     * If the destination is of a non-win32 surface type, a win32
-     * surface of content COLOR_ALPHA, or if there is a complex
-     * transform (i.e., one with rotation) set, then the native drawing
-     * code will fall back to alpha recovery, but will still take advantage
-     * of native axis-aligned scaling.
-     *
      * Typical usage looks like:
      *
-     *   gfxWindowsNativeDrawing nativeDraw(ctx, destGfxRect);
+     *   gfxWindowsNativeDrawing nativeDraw(ctx, destGfxRect, capabilities);
      *   do {
      *     HDC dc = nativeDraw.BeginNativeDrawing();
      *     if (!dc)
      *       return NS_ERROR_FAILURE;
      *
      *     RECT winRect;
      *     nativeDraw.TransformToNativeRect(rect, winRect);
      *
--- a/gfx/thebes/src/gfxContext.cpp
+++ b/gfx/thebes/src/gfxContext.cpp
@@ -422,16 +422,36 @@ gfxContext::UserToDevicePixelSnapped(gfx
     gfxPoint pd = p2 - p1;
 
     rect.pos = p1;
     rect.size = gfxSize(pd.x, pd.y);
 
     return PR_TRUE;
 }
 
+PRBool
+gfxContext::UserToDevicePixelSnapped(gfxPoint& pt, PRBool ignoreScale) const
+{
+    if (GetFlags() & FLAG_DISABLE_SNAPPING)
+        return PR_FALSE;
+
+    // if we're not at 1.0 scale, don't snap, unless we're
+    // ignoring the scale.  If we're not -just- a scale,
+    // never snap.
+    cairo_matrix_t mat;
+    cairo_get_matrix(mCairo, &mat);
+    if ((!ignoreScale && (mat.xx != 1.0 || mat.yy != 1.0)) ||
+        (mat.xy != 0.0 || mat.yx != 0.0))
+        return PR_FALSE;
+
+    pt = UserToDevice(pt);
+    pt.Round();
+    return PR_TRUE;
+}
+
 void
 gfxContext::PixelSnappedRectangleAndSetPattern(const gfxRect& rect,
                                                gfxPattern *pattern)
 {
     gfxRect r(rect);
 
     // Bob attempts to pixel-snap the rectangle, and returns true if
     // the snapping succeeds.  If it does, we need to set up an
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -783,118 +783,63 @@ nsCSSRendering::PaintFocus(nsPresContext
 }
 
 // Thebes Border Rendering Code End
 //----------------------------------------------------------------------
 
 
 //----------------------------------------------------------------------
 
-// Returns the anchor point to use for the background image. The
-// anchor point is the (x, y) location where the first tile should
-// be placed
-//
-// For repeated tiling, the anchor values are normalized wrt to the upper-left
-// edge of the bounds, and are always in the range:
-// -(aTileWidth - 1) <= anchor.x <= 0
-// -(aTileHeight - 1) <= anchor.y <= 0
-//
-// i.e., they are either 0 or a negative number whose absolute value is
-// less than the tile size in that dimension
-//
-// aOriginBounds is the box to which the tiling position should be relative
-// aClipBounds is the box in which the tiling will actually be done
-// They should correspond to 'background-origin' and 'background-clip',
-// except when painting on the canvas, in which case the origin bounds
-// should be the bounds of the root element's frame and the clip bounds
-// should be the bounds of the canvas frame.
+/**
+ * Computes the placement of a background image.
+ *
+ * @param aOriginBounds is the box to which the tiling position should be
+ * relative
+ * This should correspond to 'background-origin' for the frame,
+ * except when painting on the canvas, in which case the origin bounds
+ * should be the bounds of the root element's frame.
+ * @param aTopLeft the top-left corner where an image tile should be drawn
+ * @param aAnchorPoint a point which should be pixel-aligned by
+ * nsLayoutUtils::DrawImage. This is the same as aTopLeft, unless CSS
+ * specifies a percentage (including 'right' or 'bottom'), in which case
+ * it's that percentage within of aOriginBounds. So 'right' would set
+ * aAnchorPoint.x to aOriginBounds.XMost().
+ * 
+ * Points are returned relative to aOriginBounds.
+ */
 static void
 ComputeBackgroundAnchorPoint(const nsStyleBackground& aColor,
-                             const nsRect& aOriginBounds,
-                             const nsRect& aClipBounds,
-                             nscoord aTileWidth, nscoord aTileHeight,
-                             nsPoint& aResult)
+                             const nsSize& aOriginBounds,
+                             const nsSize& aImageSize,
+                             nsPoint* aTopLeft,
+                             nsPoint* aAnchorPoint)
 {
-  nscoord x;
   if (NS_STYLE_BG_X_POSITION_LENGTH & aColor.mBackgroundFlags) {
-    x = aColor.mBackgroundXPosition.mCoord;
+    aTopLeft->x = aAnchorPoint->x = aColor.mBackgroundXPosition.mCoord;
   }
   else if (NS_STYLE_BG_X_POSITION_PERCENT & aColor.mBackgroundFlags) {
-    PRFloat64 percent = PRFloat64(aColor.mBackgroundXPosition.mFloat);
-    nscoord tilePos = nscoord(percent * PRFloat64(aTileWidth));
-    nscoord boxPos = nscoord(percent * PRFloat64(aOriginBounds.width));
-    x = boxPos - tilePos;
+    double percent = aColor.mBackgroundXPosition.mFloat;
+    aAnchorPoint->x = NSToCoordRound(percent*aOriginBounds.width);
+    aTopLeft->x = NSToCoordRound(percent*(aOriginBounds.width - aImageSize.width));
   }
   else {
-    x = 0;
+    aTopLeft->x = aAnchorPoint->x = 0;
   }
-  x += aOriginBounds.x - aClipBounds.x;
-  if (NS_STYLE_BG_REPEAT_X & aColor.mBackgroundRepeat) {
-    // When we are tiling in the x direction the loop will run from
-    // the left edge of the box to the right edge of the box. We need
-    // to adjust the starting coordinate to lie within the band being
-    // rendered.
-    if (x < 0) {
-      x = -x;
-      if (x < 0) {
-        // Some joker gave us max-negative-integer.
-        x = 0;
-      }
-      x %= aTileWidth;
-      x = -x;
-    }
-    else if (x != 0) {
-      x %= aTileWidth;
-      if (x > 0) {
-        x = x - aTileWidth;
-      }
-    }
 
-    NS_POSTCONDITION((x >= -(aTileWidth - 1)) && (x <= 0), "bad computed anchor value");
+  if (NS_STYLE_BG_Y_POSITION_LENGTH & aColor.mBackgroundFlags) {
+    aTopLeft->y = aAnchorPoint->y = aColor.mBackgroundYPosition.mCoord;
   }
-  aResult.x = x;
-
-  nscoord y;
-  if (NS_STYLE_BG_Y_POSITION_LENGTH & aColor.mBackgroundFlags) {
-    y = aColor.mBackgroundYPosition.mCoord;
-  }
-  else if (NS_STYLE_BG_Y_POSITION_PERCENT & aColor.mBackgroundFlags){
-    PRFloat64 percent = PRFloat64(aColor.mBackgroundYPosition.mFloat);
-    nscoord tilePos = nscoord(percent * PRFloat64(aTileHeight));
-    nscoord boxPos = nscoord(percent * PRFloat64(aOriginBounds.height));
-    y = boxPos - tilePos;
+  else if (NS_STYLE_BG_Y_POSITION_PERCENT & aColor.mBackgroundFlags) {
+    double percent = aColor.mBackgroundYPosition.mFloat;
+    aAnchorPoint->y = NSToCoordRound(percent*aOriginBounds.height);
+    aTopLeft->y = NSToCoordRound(percent*(aOriginBounds.height - aImageSize.height));
   }
   else {
-    y = 0;
+    aTopLeft->y = aAnchorPoint->y = 0;
   }
-  y += aOriginBounds.y - aClipBounds.y;
-  if (NS_STYLE_BG_REPEAT_Y & aColor.mBackgroundRepeat) {
-    // When we are tiling in the y direction the loop will run from
-    // the top edge of the box to the bottom edge of the box. We need
-    // to adjust the starting coordinate to lie within the band being
-    // rendered.
-    if (y < 0) {
-      y = -y;
-      if (y < 0) {
-        // Some joker gave us max-negative-integer.
-        y = 0;
-      }
-      y %= aTileHeight;
-      y = -y;
-    }
-    else if (y != 0) {
-      y %= aTileHeight;
-      if (y > 0) {
-        y = y - aTileHeight;
-      }
-    }
-    
-    NS_POSTCONDITION((y >= -(aTileHeight - 1)) && (y <= 0), "bad computed anchor value");
-  }
-  aResult.y = y;
 }
 
 const nsStyleBackground*
 nsCSSRendering::FindNonTransparentBackground(nsStyleContext* aContext,
                                              PRBool aStartAtParent /*= PR_FALSE*/)
 {
   NS_ASSERTION(aContext, "Cannot find NonTransparentBackground in a null context" );
   
@@ -1260,94 +1205,16 @@ nsCSSRendering::PaintBackground(nsPresCo
 
   vm->SetDefaultBackgroundColor(canvasColor.mBackgroundColor);
 
   PaintBackgroundWithSC(aPresContext, aRenderingContext, aForFrame,
                         aDirtyRect, aBorderArea, canvasColor,
                         *border, aUsePrintSettings, aBGClipRect);
 }
 
-inline nscoord IntDivFloor(nscoord aDividend, nscoord aDivisor)
-{
-  NS_PRECONDITION(aDivisor > 0,
-                  "this function only works for positive divisors");
-  // ANSI C, ISO 9899:1999 section 6.5.5 defines integer division as
-  // truncation of the result towards zero.  Earlier C standards, as
-  // well as the C++ standards (1998 and 2003) do not, but we depend
-  // on it elsewhere.
-  return (aDividend < 0 ? (aDividend - aDivisor + 1) : aDividend) / aDivisor;
-}
-
-inline nscoord IntDivCeil(nscoord aDividend, nscoord aDivisor)
-{
-  NS_PRECONDITION(aDivisor > 0,
-                  "this function only works for positive divisors");
-  // ANSI C, ISO 9899:1999 section 6.5.5 defines integer division as
-  // truncation of the result towards zero.  Earlier C standards, as
-  // well as the C++ standards (1998 and 2003) do not, but we depend
-  // on it elsewhere.
-  return (aDividend > 0 ? (aDividend + aDivisor - 1) : aDividend) / aDivisor;
-}
-
-/**
- * Return the largest 'v' such that v = aTileOffset + N*aTileSize, for some
- * integer N, and v <= aDirtyStart.
- */
-static nscoord
-FindTileStart(nscoord aDirtyStart, nscoord aTileOffset, nscoord aTileSize)
-{
-  // Find largest integer N such that aTileOffset + N*aTileSize <= aDirtyStart
-  return aTileOffset +
-         IntDivFloor(aDirtyStart - aTileOffset, aTileSize) * aTileSize;
-}
-
-/**
- * Return the smallest 'v' such that v = aTileOffset + N*aTileSize, for some
- * integer N, and v >= aDirtyEnd.
- */
-static nscoord
-FindTileEnd(nscoord aDirtyEnd, nscoord aTileOffset, nscoord aTileSize)
-{
-  // Find smallest integer N such that aTileOffset + N*aTileSize >= aDirtyEnd
-  return aTileOffset +
-         IntDivCeil(aDirtyEnd - aTileOffset, aTileSize) * aTileSize;
-}
-
-static void
-PixelSnapRectangle(gfxContext* aContext, nsIDeviceContext *aDC, nsRect& aRect)
-{
-  gfxRect tmpRect;
-  tmpRect.pos.x = aDC->AppUnitsToGfxUnits(aRect.x);
-  tmpRect.pos.y = aDC->AppUnitsToGfxUnits(aRect.y);
-  tmpRect.size.width = aDC->AppUnitsToGfxUnits(aRect.width);
-  tmpRect.size.height = aDC->AppUnitsToGfxUnits(aRect.height);
-  if (aContext->UserToDevicePixelSnapped(tmpRect)) {
-    tmpRect = aContext->DeviceToUser(tmpRect);
-    aRect.x = aDC->GfxUnitsToAppUnits(tmpRect.pos.x);
-    aRect.y = aDC->GfxUnitsToAppUnits(tmpRect.pos.y);
-    aRect.width = aDC->GfxUnitsToAppUnits(tmpRect.XMost()) - aRect.x;
-    aRect.height = aDC->GfxUnitsToAppUnits(tmpRect.YMost()) - aRect.y;
-  }
-}
-
-static void
-PixelSnapPoint(gfxContext* aContext, nsIDeviceContext *aDC, nsPoint& aPoint)
-{
-  gfxRect tmpRect;
-  tmpRect.pos.x = aDC->AppUnitsToGfxUnits(aPoint.x);
-  tmpRect.pos.y = aDC->AppUnitsToGfxUnits(aPoint.y);
-  tmpRect.size.width = 0;
-  tmpRect.size.height = 0;
-  if (aContext->UserToDevicePixelSnapped(tmpRect)) {
-    tmpRect = aContext->DeviceToUser(tmpRect);
-    aPoint.x = aDC->GfxUnitsToAppUnits(tmpRect.pos.x);
-    aPoint.y = aDC->GfxUnitsToAppUnits(tmpRect.pos.y);
-  }
-}
-
 static PRBool
 IsSolidBorderEdge(const nsStyleBorder& aBorder, PRUint32 aSide)
 {
   if (aBorder.GetActualBorder().side(aSide) == 0)
     return PR_TRUE;
   if (aBorder.GetBorderStyle(aSide) != NS_STYLE_BORDER_STYLE_SOLID)
     return PR_FALSE;
 
@@ -1417,16 +1284,17 @@ nsCSSRendering::PaintBackgroundWithSC(ns
       nsRect dirty;
       dirty.IntersectRect(aDirtyRect, aBorderArea);
       theme->DrawWidgetBackground(&aRenderingContext, aForFrame, 
                                   displayData->mAppearance, aBorderArea, dirty);
       return;
     }
   }
 
+  // Same coordinate space as aBorderArea
   nsRect bgClipArea;
   if (aBGClipRect) {
     bgClipArea = *aBGClipRect;
   }
   else {
     // The background is rendered over the 'background-clip' area.
     bgClipArea = aBorderArea;
     // If the border is solid, then clip the background to the padding-box
@@ -1434,24 +1302,18 @@ nsCSSRendering::PaintBackgroundWithSC(ns
     if (aColor.mBackgroundClip != NS_STYLE_BG_CLIP_BORDER ||
         IsSolidBorder(aBorder)) {
       nsMargin border = aForFrame->GetUsedBorder();
       aForFrame->ApplySkipSides(border);
       bgClipArea.Deflate(border);
     }
   }
 
-  nsIDeviceContext *dc = aPresContext->DeviceContext();
   gfxContext *ctx = aRenderingContext.ThebesContext();
 
-  // Snap bgClipArea to device pixel boundaries.  (We have to snap
-  // bgOriginArea below; if we don't do this as well then we could make
-  // incorrect decisions about various optimizations.)
-  PixelSnapRectangle(ctx, dc, bgClipArea);
-
   // The actual dirty rect is the intersection of the 'background-clip'
   // area and the dirty rect we were given
   nsRect dirtyRect;
   if (!dirtyRect.IntersectRect(bgClipArea, aDirtyRect)) {
     // Nothing to paint
     return;
   }
 
@@ -1485,66 +1347,63 @@ nsCSSRendering::PaintBackgroundWithSC(ns
   image->GetWidth(&imageSize.width);
   image->GetHeight(&imageSize.height);
 
   imageSize.width = nsPresContext::CSSPixelsToAppUnits(imageSize.width);
   imageSize.height = nsPresContext::CSSPixelsToAppUnits(imageSize.height);
 
   req = nsnull;
 
-  nsRect bgOriginArea;
+  // relative to aBorderArea
+  nsRect bgOriginRect;
 
   nsIAtom* frameType = aForFrame->GetType();
+  nsIFrame* geometryFrame = aForFrame;
   if (frameType == nsGkAtoms::inlineFrame ||
       frameType == nsGkAtoms::positionedInlineFrame) {
     switch (aColor.mBackgroundInlinePolicy) {
     case NS_STYLE_BG_INLINE_POLICY_EACH_BOX:
-      bgOriginArea = aBorderArea;
+      bgOriginRect = nsRect(nsPoint(0,0), aBorderArea.Size());
       break;
     case NS_STYLE_BG_INLINE_POLICY_BOUNDING_BOX:
-      bgOriginArea = gInlineBGData->GetBoundingRect(aForFrame) +
-                     aBorderArea.TopLeft();
+      bgOriginRect = gInlineBGData->GetBoundingRect(aForFrame);
       break;
     default:
       NS_ERROR("Unknown background-inline-policy value!  "
                "Please, teach me what to do.");
     case NS_STYLE_BG_INLINE_POLICY_CONTINUOUS:
-      bgOriginArea = gInlineBGData->GetContinuousRect(aForFrame) +
-                     aBorderArea.TopLeft();
+      bgOriginRect = gInlineBGData->GetContinuousRect(aForFrame);
       break;
     }
-  }
-  else {
-    bgOriginArea = aBorderArea;
+  } else if (frameType == nsGkAtoms::canvasFrame) {
+    geometryFrame = aForFrame->GetFirstChild(nsnull);
+    NS_ASSERTION(geometryFrame, "A canvas with a background "
+      "image had no child frame, which is impossible according to CSS. "
+      "Make sure there isn't a background image specified on the "
+      "|:viewport| pseudo-element in |html.css|.");
+    bgOriginRect = geometryFrame->GetRect();
+  } else {
+    bgOriginRect = nsRect(nsPoint(0,0), aBorderArea.Size());
   }
 
   // Background images are tiled over the 'background-clip' area
   // but the origin of the tiling is based on the 'background-origin' area
   if (aColor.mBackgroundOrigin != NS_STYLE_BG_ORIGIN_BORDER) {
-    nsMargin border = aForFrame->GetUsedBorder();
-    aForFrame->ApplySkipSides(border);
-    bgOriginArea.Deflate(border);
+    nsMargin border = geometryFrame->GetUsedBorder();
+    geometryFrame->ApplySkipSides(border);
+    bgOriginRect.Deflate(border);
     if (aColor.mBackgroundOrigin != NS_STYLE_BG_ORIGIN_PADDING) {
-      nsMargin padding = aForFrame->GetUsedPadding();
-      aForFrame->ApplySkipSides(padding);
-      bgOriginArea.Deflate(padding);
+      nsMargin padding = geometryFrame->GetUsedPadding();
+      geometryFrame->ApplySkipSides(padding);
+      bgOriginRect.Deflate(padding);
       NS_ASSERTION(aColor.mBackgroundOrigin == NS_STYLE_BG_ORIGIN_CONTENT,
                    "unknown background-origin value");
     }
   }
 
-  // Snap bgOriginArea to device pixel boundaries to avoid variations in
-  // tiling when the subpixel position of the element changes.
-  PixelSnapRectangle(ctx, dc, bgOriginArea);
-
-  // Based on the repeat setting, compute how many tiles we should
-  // lay down for each axis. The value computed is the maximum based
-  // on the dirty rect before accounting for the background-position.
-  nscoord tileWidth = imageSize.width;
-  nscoord tileHeight = imageSize.height;
   PRBool  needBackgroundColor = NS_GET_A(aColor.mBackgroundColor) > 0;
   PRIntn  repeat = aColor.mBackgroundRepeat;
 
   switch (repeat) {
     case NS_STYLE_BG_REPEAT_X:
       break;
     case NS_STYLE_BG_REPEAT_Y:
       break;
@@ -1577,31 +1436,21 @@ nsCSSRendering::PaintBackgroundWithSC(ns
   }
 
   // The background color is rendered over the 'background-clip' area
   if (needBackgroundColor) {
     PaintBackgroundColor(aPresContext, aRenderingContext, aForFrame, bgClipArea,
                          aColor, aBorder, canDrawBackgroundColor);
   }
 
-  if ((tileWidth == 0) || (tileHeight == 0) || dirtyRect.IsEmpty()) {
-    // Nothing left to paint
-    return;
-  }
-
-  nsPoint borderAreaOriginSnapped = aBorderArea.TopLeft();
-  PixelSnapPoint(ctx, dc, borderAreaOriginSnapped);
-
   // Compute the anchor point.
   //
-  // When tiling, the anchor coordinate values will be negative offsets
-  // from the background-origin area.
-
-  // relative to the origin of aForFrame
-  nsPoint anchor;
+  // relative to aBorderArea.TopLeft() (which is where the top-left
+  // of aForFrame's border-box will be rendered)
+  nsPoint imageTopLeft, anchor;
   if (NS_STYLE_BG_ATTACHMENT_FIXED == aColor.mBackgroundAttachment) {
     // If it's a fixed background attachment, then the image is placed
     // relative to the viewport, which is the area of the root frame
     // in a screen context or the page content frame in a print context.
 
     // Remember that we've drawn position-varying content in this prescontext
     aPresContext->SetRenderedPositionVaryingContent();
 
@@ -1613,275 +1462,82 @@ nsCSSRendering::PaintBackgroundWithSC(ns
       pageContentFrame =
         nsLayoutUtils::GetClosestFrameOfType(aForFrame, nsGkAtoms::pageContentFrame);
       if (pageContentFrame) {
         topFrame = pageContentFrame;
       }
       // else this is an embedded shell and its root frame is what we want
     }
 
-    nsRect viewportArea = topFrame->GetRect();
+    nsRect viewportArea(nsPoint(0, 0), topFrame->GetSize());
 
     if (!pageContentFrame) {
       // Subtract the size of scrollbars.
       nsIScrollableFrame* scrollableFrame =
         aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
       if (scrollableFrame) {
         nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes();
         viewportArea.Deflate(scrollbars);
       }
     }
      
     // Get the anchor point, relative to the viewport.
-    ComputeBackgroundAnchorPoint(aColor, viewportArea, viewportArea, tileWidth, tileHeight, anchor);
+    ComputeBackgroundAnchorPoint(aColor, viewportArea.Size(), imageSize,
+                                 &imageTopLeft, &anchor);
 
     // Convert the anchor point from viewport coordinates to aForFrame
     // coordinates.
-    anchor -= aForFrame->GetOffsetTo(topFrame);
+    nsPoint offset = viewportArea.TopLeft() - aForFrame->GetOffsetTo(topFrame);
+    imageTopLeft += offset;
+    anchor += offset;
   } else {
-    if (frameType == nsGkAtoms::canvasFrame) {
-      // If the frame is the canvas, the image is placed relative to
-      // the root element's (first) frame (see bug 46446)
-      nsRect firstRootElementFrameArea;
-      nsIFrame* firstRootElementFrame = aForFrame->GetFirstChild(nsnull);
-      NS_ASSERTION(firstRootElementFrame, "A canvas with a background "
-        "image had no child frame, which is impossible according to CSS. "
-        "Make sure there isn't a background image specified on the "
-        "|:viewport| pseudo-element in |html.css|.");
-
-      // temporary null check -- see bug 97226
-      if (firstRootElementFrame) {
-        firstRootElementFrameArea = firstRootElementFrame->GetRect();
-
-        // Take the border out of the frame's rect
-        const nsStyleBorder* borderStyle = firstRootElementFrame->GetStyleBorder();
-        firstRootElementFrameArea.Deflate(borderStyle->GetActualBorder());
-
-        // Get the anchor point
-        ComputeBackgroundAnchorPoint(aColor, firstRootElementFrameArea +
-            aBorderArea.TopLeft(), bgClipArea, tileWidth, tileHeight, anchor);
-      } else {
-        ComputeBackgroundAnchorPoint(aColor, bgOriginArea, bgClipArea, tileWidth, tileHeight, anchor);
-      }
-    } else {
-      // Otherwise, it is the normal case, and the background is
-      // simply placed relative to the frame's background-clip area
-      ComputeBackgroundAnchorPoint(aColor, bgOriginArea, bgClipArea, tileWidth, tileHeight, anchor);
-    }
-
-    // For scrolling attachment, the anchor is within the 'background-clip'
-    anchor.x += bgClipArea.x - borderAreaOriginSnapped.x;
-    anchor.y += bgClipArea.y - borderAreaOriginSnapped.y;
+    ComputeBackgroundAnchorPoint(aColor, bgOriginRect.Size(), imageSize,
+                                 &imageTopLeft, &anchor);
+    imageTopLeft += bgOriginRect.TopLeft();
+    anchor += bgOriginRect.TopLeft();
   }
 
-  // Pixel-snap the anchor point so that we don't end up with blurry
-  // images due to subpixel positions.  But round 0.5 down rather than
-  // up, since that's what we've always done.  (And do that by just
-  // snapping the negative of the point.)
-  anchor.x = -anchor.x; anchor.y = -anchor.y;
-  PixelSnapPoint(ctx, dc, anchor);
-  anchor.x = -anchor.x; anchor.y = -anchor.y;
-
   ctx->Save();
 
-  nscoord appUnitsPerPixel = aPresContext->DevPixelsToAppUnits(1);
-
-  ctx->NewPath();
-  ctx->Rectangle(RectToGfxRect(dirtyRect, appUnitsPerPixel), PR_TRUE);
-  ctx->Clip();
-
   nscoord borderRadii[8];
   PRBool haveRadius = GetBorderRadiusTwips(aBorder.mBorderRadius,
                                            aForFrame->GetSize().width,
                                            borderRadii);
-
   if (haveRadius) {
+    nscoord appUnitsPerPixel = aPresContext->DevPixelsToAppUnits(1);
     gfxCornerSizes radii;
     ComputePixelRadii(borderRadii, bgClipArea,
                       aForFrame ? aForFrame->GetSkipSides() : 0,
                       appUnitsPerPixel, &radii);
 
     gfxRect oRect(RectToGfxRect(bgClipArea, appUnitsPerPixel));
     oRect.Round();
     oRect.Condition();
 
     ctx->NewPath();
     ctx->RoundedRectangle(oRect, radii);
     ctx->Clip();
-  }      
-
-  // Compute the x and y starting points and limits for tiling
-
-  /* An Overview Of The Following Logic
-
-          A........ . . . . . . . . . . . . . .
-          :   +---:-------.-------.-------.----  /|\
-          :   |   :       .       .       .       |  nh 
-          :.......: . . . x . . . . . . . . . .  \|/   
-          .   |   .       .       .       .        
-          .   |   .       .  ###########  .        
-          . . . . . . . . . .#. . . . .#. . . .     
-          .   |   .       .  ###########  .      /|\
-          .   |   .       .       .       .       |  h
-          . . | . . . . . . . . . . . . . z . .  \|/
-          .   |   .       .       .       .    
-          |<-----nw------>|       |<--w-->|
-
-       ---- = the background clip area edge. The painting is done within
-              to this area.  If the background is positioned relative to the 
-              viewport ('fixed') then this is the viewport edge.
-
-       .... = the primary tile.
-
-       . .  = the other tiles.
-
-       #### = the dirtyRect. This is the minimum region we want to cover.
-
-          A = The anchor point. This is the point at which the tile should
-              start. Always negative or zero.
-
-          x = x0 and y0 in the code. The point at which tiling must start
-              so that the fewest tiles are laid out while completely
-              covering the dirtyRect area.
-
-          z = x1 and y1 in the code. The point at which tiling must end so
-              that the fewest tiles are laid out while completely covering
-              the dirtyRect area.
-
-          w = the width of the tile (tileWidth).
-
-          h = the height of the tile (tileHeight).
-
-          n = the number of whole tiles that fit between 'A' and 'x'.
-              (the vertical n and the horizontal n are different)
-
-
-       Therefore, 
-
-          x0 = bgClipArea.x + anchor.x + n * tileWidth;
-
-       ...where n is an integer greater or equal to 0 fitting:
-
-          n * tileWidth <= 
-                      dirtyRect.x - (bgClipArea.x + anchor.x) <=
-                                                             (n+1) * tileWidth
-
-       ...i.e.,
+  }
 
-          n <= (dirtyRect.x - (bgClipArea.x + anchor.x)) / tileWidth < n + 1
-
-       ...which, treating the division as an integer divide rounding down, gives:
-
-          n = (dirtyRect.x - (bgClipArea.x + anchor.x)) / tileWidth
-
-       Substituting into the original expression for x0:
-
-          x0 = bgClipArea.x + anchor.x +
-               ((dirtyRect.x - (bgClipArea.x + anchor.x)) / tileWidth) *
-               tileWidth;
-
-       From this x1 is determined,
-
-          x1 = x0 + m * tileWidth;
-
-       ...where m is an integer greater than 0 fitting:
-
-          (m - 1) * tileWidth <
-                            dirtyRect.x + dirtyRect.width - x0 <=
-                                                               m * tileWidth
-
-       ...i.e.,
-
-          m - 1 < (dirtyRect.x + dirtyRect.width - x0) / tileWidth <= m
-
-       ...which, treating the division as an integer divide, and making it
-          round up, gives:
-
-          m = (dirtyRect.x + dirtyRect.width - x0 + tileWidth - 1) / tileWidth
-
-       Substituting into the original expression for x1:
-
-          x1 = x0 + ((dirtyRect.x + dirtyRect.width - x0 + tileWidth - 1) /
-                     tileWidth) * tileWidth
-
-       The vertical case is analogous. If the background is fixed, then 
-       bgClipArea.x and bgClipArea.y are set to zero when finding the parent
-       viewport, above.
-
-  */
-
-  // relative to aBorderArea.TopLeft()
-  // ... but pixel-snapped, so that it comes out correctly relative to
-  // all the other pixel-snapped things
-  nsRect tileRect(anchor, nsSize(tileWidth, tileHeight));
-  // Whether we take the single-image path or the tile path should not
-  // depend on the dirty rect. So decide now which path to take. We
-  // can take the single image path if the anchored image tile
-  // contains the total background area.
-  PRBool useSingleImagePath =
-    tileRect.Contains(bgClipArea - borderAreaOriginSnapped);
-
+  nsRect destArea(imageTopLeft + aBorderArea.TopLeft(), imageSize);
+  nsRect fillArea;
+  fillArea.IntersectRect(destArea, bgClipArea);
   if (repeat & NS_STYLE_BG_REPEAT_X) {
-    // When tiling in the x direction, adjust the starting position of the
-    // tile to account for dirtyRect.x. When tiling in x, the anchor.x value
-    // will be a negative value used to adjust the starting coordinate.
-    nscoord x0 = FindTileStart(dirtyRect.x - borderAreaOriginSnapped.x, anchor.x, tileWidth);
-    nscoord x1 = FindTileEnd(dirtyRect.XMost() - borderAreaOriginSnapped.x, anchor.x, tileWidth);
-    tileRect.x = x0;
-    tileRect.width = x1 - x0;
+    fillArea.x = bgClipArea.x;
+    fillArea.width = bgClipArea.width;
   }
   if (repeat & NS_STYLE_BG_REPEAT_Y) {
-    // When tiling in the y direction, adjust the starting position of the
-    // tile to account for dirtyRect.y. When tiling in y, the anchor.y value
-    // will be a negative value used to adjust the starting coordinate.
-    nscoord y0 = FindTileStart(dirtyRect.y - borderAreaOriginSnapped.y, anchor.y, tileHeight);
-    nscoord y1 = FindTileEnd(dirtyRect.YMost() - borderAreaOriginSnapped.y, anchor.y, tileHeight);
-    tileRect.y = y0;
-    tileRect.height = y1 - y0;
+    fillArea.y = bgClipArea.y;
+    fillArea.height = bgClipArea.height;
   }
 
-  // Take the intersection again to paint only the required area.
-  nsRect absTileRect = tileRect + borderAreaOriginSnapped;
-  
-  nsRect drawRect;
-  if (drawRect.IntersectRect(absTileRect, dirtyRect)) {
-    // Note that due to the way FindTileStart works we're guaranteed
-    // that drawRect overlaps the top-left-most tile when repeating.
-    NS_ASSERTION(drawRect.x >= absTileRect.x && drawRect.y >= absTileRect.y,
-                 "Bogus intersection");
-    NS_ASSERTION(drawRect.x < absTileRect.x + tileWidth,
-                 "Bogus x coord for draw rect");
-    NS_ASSERTION(drawRect.y < absTileRect.y + tileHeight,
-                 "Bogus y coord for draw rect");
-    // Figure out whether we can get away with not tiling at all.
-    nsRect sourceRect = drawRect - absTileRect.TopLeft();
-    // Compute the subimage rectangle that we expect to be sampled.
-    // This is the tile rectangle, clipped to the bgClipArea, and then
-    // passed in relative to the image top-left.
-    nsRect destRect; // The rectangle we would draw ignoring dirty-rect
-    destRect.IntersectRect(absTileRect, bgClipArea);
-    nsRect subimageRect = destRect - borderAreaOriginSnapped - tileRect.TopLeft();
-    if (useSingleImagePath) {
-      NS_ASSERTION(sourceRect.XMost() <= tileWidth && sourceRect.YMost() <= tileHeight,
-                   "We shouldn't need to tile here");
-      // The entire drawRect is contained inside a single tile; just
-      // draw the corresponding part of the image once.
-      nsLayoutUtils::DrawImage(&aRenderingContext, image,
-              destRect, drawRect, &subimageRect);
-    } else {
-      // Note that the subimage is in tile space so it may cover
-      // multiple tiles of the image.
-      subimageRect.ScaleRoundOutInverse(nsIDeviceContext::AppUnitsPerCSSPixel());
-      aRenderingContext.DrawTile(image, absTileRect.x, absTileRect.y,
-              &drawRect, &subimageRect);
-    }
-  }
+  nsLayoutUtils::DrawImage(&aRenderingContext, image,
+      destArea, fillArea, anchor + aBorderArea.TopLeft(), dirtyRect);
 
   ctx->Restore();
-
 }
 
 static void
 DrawBorderImage(nsPresContext* aPresContext,
                 nsIRenderingContext& aRenderingContext,
                 nsIFrame* aForFrame, const nsRect& aBorderArea,
                 const nsStyleBorder& aBorderStyle)
 {
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -2657,160 +2657,195 @@ nsLayoutUtils::GetClosestLayer(nsIFrame*
           layer->GetParent()->GetType() == nsGkAtoms::scrollFrame))
       break;
   }
   if (layer)
     return layer;
   return aFrame->PresContext()->PresShell()->FrameManager()->GetRootFrame();
 }
 
+static gfxPoint
+MapToFloatImagePixels(const nsIntSize& aSize,
+                      const nsRect& aDest, const nsPoint& aPt)
+{
+  return gfxPoint((gfxFloat(aPt.x - aDest.x)*aSize.width)/aDest.width,
+                  (gfxFloat(aPt.y - aDest.y)*aSize.height)/aDest.height);
+}
+
 /* static */ nsresult
 nsLayoutUtils::DrawImage(nsIRenderingContext* aRenderingContext,
-                         imgIContainer* aImage,
-                         const nsRect& aDestRect,
-                         const nsRect& aDirtyRect,
-                         const nsRect* aSourceRect)
+                         imgIContainer*       aImage,
+                         const nsRect&        aDest,
+                         const nsRect&        aFill,
+                         const nsPoint&       aAnchor,
+                         const nsRect&        aDirty)
 {
-  nsRect dirtyRect;
-  dirtyRect.IntersectRect(aDirtyRect, aDestRect);
-  if (dirtyRect.IsEmpty())
+  if (aDest.IsEmpty())
     return NS_OK;
 
+  nsCOMPtr<nsIDeviceContext> dc;
+  aRenderingContext->GetDeviceContext(*getter_AddRefs(dc));
+  gfxFloat appUnitsPerDevPixel = dc->AppUnitsPerDevPixel();
+  gfxContext *ctx = aRenderingContext->ThebesContext();
+
+  // Compute the pixel-snapped area that should be drawn
+  gfxRect fill(aFill.x/appUnitsPerDevPixel,
+               aFill.y/appUnitsPerDevPixel,
+               aFill.width/appUnitsPerDevPixel,
+               aFill.height/appUnitsPerDevPixel);
+  PRBool ignoreScale = PR_FALSE;
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+  ignoreScale = PR_TRUE;
+#endif
+  PRBool didSnap = ctx->UserToDevicePixelSnapped(fill, ignoreScale);
+
+  // Compute dirty rect in gfx space
+  gfxRect dirty(aDirty.x/appUnitsPerDevPixel,
+                aDirty.y/appUnitsPerDevPixel,
+                aDirty.width/appUnitsPerDevPixel,
+                aDirty.height/appUnitsPerDevPixel);
+
   nsCOMPtr<gfxIImageFrame> imgFrame;
   aImage->GetCurrentFrame(getter_AddRefs(imgFrame));
   if (!imgFrame) return NS_ERROR_FAILURE;
 
   nsCOMPtr<nsIImage> img(do_GetInterface(imgFrame));
   if (!img) return NS_ERROR_FAILURE;
 
-  // twSrcRect is always in appunits (twips),
-  // and has nothing to do with the current transform (it's a region
-  // of the image)
-  gfxRect pxSrc;
-  if (aSourceRect) {
-    pxSrc.pos.x = nsIDeviceContext::AppUnitsToGfxCSSPixels(aSourceRect->x);
-    pxSrc.pos.y = nsIDeviceContext::AppUnitsToGfxCSSPixels(aSourceRect->y);
-    pxSrc.size.width = nsIDeviceContext::AppUnitsToGfxCSSPixels(aSourceRect->width);
-    pxSrc.size.height = nsIDeviceContext::AppUnitsToGfxCSSPixels(aSourceRect->height);
-  } else {
-    pxSrc.pos.x = pxSrc.pos.y = 0.0;
-    PRInt32 w = 0, h = 0;
-    aImage->GetWidth(&w);
-    aImage->GetHeight(&h);
-    pxSrc.size.width = gfxFloat(w);
-    pxSrc.size.height = gfxFloat(h);
+  nsIntSize imageSize;
+  aImage->GetWidth(&imageSize.width);
+  aImage->GetHeight(&imageSize.height);
+  if (imageSize.width == 0 || imageSize.height == 0)
+    return NS_OK;
+
+  // Compute the set of pixels that would be sampled by an ideal rendering
+  gfxPoint subimageTopLeft =
+    MapToFloatImagePixels(imageSize, aDest, aFill.TopLeft());
+  gfxPoint subimageBottomRight =
+    MapToFloatImagePixels(imageSize, aDest, aFill.BottomRight());
+  nsIntRect intSubimage;
+  intSubimage.MoveTo(NSToIntFloor(subimageTopLeft.x),
+                     NSToIntFloor(subimageTopLeft.y));
+  intSubimage.SizeTo(NSToIntCeil(subimageBottomRight.x) - intSubimage.x,
+                     NSToIntCeil(subimageBottomRight.y) - intSubimage.y);
+
+  // Compute the anchor point and compute final fill rect.
+  // This code assumes that pixel-based devices have one pixel per
+  // device unit!
+  gfxPoint anchorPoint(aAnchor.x/appUnitsPerDevPixel,
+                       aAnchor.y/appUnitsPerDevPixel);
+  gfxMatrix currentMatrix = ctx->CurrentMatrix();
+  gfxRect finalFillRect = fill;
+  if (didSnap) {
+    ctx->UserToDevicePixelSnapped(anchorPoint, ignoreScale);
+
+    // This form of Transform is safe to call since non-axis-aligned
+    // transforms wouldn't be snapped.
+    dirty = currentMatrix.Transform(dirty);
+    dirty.RoundOut();
+    finalFillRect = fill.Intersect(dirty);
+    if (finalFillRect.IsEmpty())
+      return NS_OK;
+
+    ctx->IdentityMatrix();
   }
-  gfxRect pxSubimage = pxSrc;
-
-  nsCOMPtr<nsIDeviceContext> dc;
-  aRenderingContext->GetDeviceContext(*getter_AddRefs(dc));
-
-  gfxContext *ctx = aRenderingContext->ThebesContext();
-
-  // the dest rect is affected by the current transform; that'll be
-  // handled by Image::Draw(), when we actually set up the rectangle.
-  
-  // Snap the edges of where layout wants the image to the nearest
-  // pixel, but then convert back to gfxFloats for the rest of the math.
-  gfxRect pxDest;
-  {
-    pxDest.pos.x = dc->AppUnitsToGfxUnits(aDestRect.x);
-    pxDest.pos.y = dc->AppUnitsToGfxUnits(aDestRect.y);
-    pxDest.size.width = dc->AppUnitsToGfxUnits(aDestRect.width);
-    pxDest.size.height = dc->AppUnitsToGfxUnits(aDestRect.height);
-    if (ctx->UserToDevicePixelSnapped(pxDest))
-      pxDest = ctx->DeviceToUser(pxDest);
-  }
-
-  // And likewise for the dirty rect.  (Is should be OK to round to
-  // nearest rather than outwards, since any dirty rects coming from the
-  // OS should be on pixel boundaries; the rest is other things it's
-  // been intersected with, and we should be rounding those consistently.)
-  gfxRect pxDirty;
-  {
-    pxDirty.pos.x = dc->AppUnitsToGfxUnits(dirtyRect.x);
-    pxDirty.pos.y = dc->AppUnitsToGfxUnits(dirtyRect.y);
-    pxDirty.size.width = dc->AppUnitsToGfxUnits(dirtyRect.width);
-    pxDirty.size.height = dc->AppUnitsToGfxUnits(dirtyRect.height);
-    if (ctx->UserToDevicePixelSnapped(pxDirty))
-      pxDirty = ctx->DeviceToUser(pxDirty);
-  }
-
-  // Reduce the src rect to what's needed for the dirty rect.
-  if (pxDirty.size.width != pxDest.size.width) {
-    const gfxFloat ratio = pxSrc.size.width / pxDest.size.width;
-    pxSrc.pos.x += (pxDirty.pos.x - pxDest.pos.x) * ratio;
-    pxSrc.size.width = pxDirty.size.width * ratio;
+  // If we're not snapping, then we ignore the dirty rect. It's hard
+  // to correctly use it with arbitrary transforms --- it really *has*
+  // to be aligned perfectly with pixel boundaries or the choice of
+  // dirty rect will affect the values of rendered pixels.
+
+  gfxPoint imageSpaceAnchorPoint =
+    MapToFloatImagePixels(imageSize, aDest, aAnchor);
+  imageSpaceAnchorPoint.Round();
+  gfxFloat scaleX = imageSize.width*appUnitsPerDevPixel/aDest.width;
+  gfxFloat scaleY = imageSize.height*appUnitsPerDevPixel/aDest.height;
+  if (didSnap) {
+    // ctx now has the identity matrix, so we need to adjust our
+    // scales to match
+    scaleX /= currentMatrix.xx;
+    scaleY /= currentMatrix.yy;
   }
-  if (pxDirty.size.height != pxDest.size.height) {
-    const gfxFloat ratio = pxSrc.size.height / pxDest.size.height;
-    pxSrc.pos.y += (pxDirty.pos.y - pxDest.pos.y) * ratio;
-    pxSrc.size.height = pxDirty.size.height * ratio;
-  }
-
-  // If we were asked to draw a 0-width or 0-height image,
-  // as either the src or dst, just bail; we can't do anything
-  // useful with this.
-  if (pxSrc.IsEmpty() || pxDirty.IsEmpty())
-  {
-    return NS_OK;
+  gfxFloat translateX = imageSpaceAnchorPoint.x - anchorPoint.x*scaleX;
+  gfxFloat translateY = imageSpaceAnchorPoint.y - anchorPoint.y*scaleY;
+  gfxMatrix transform(scaleX, 0, 0, scaleY, translateX, translateY);
+
+  nsIntRect innerRect;
+  imgFrame->GetRect(innerRect);
+  nsIntMargin padding(innerRect.x, innerRect.y,
+    imageSize.width - innerRect.XMost(), imageSize.height - innerRect.YMost());
+  img->Draw(ctx, transform, finalFillRect, padding, intSubimage);
+  ctx->SetMatrix(currentMatrix);
+  return NS_OK;
+}
+
+/* static */ nsresult
+nsLayoutUtils::DrawSingleUnscaledImage(nsIRenderingContext* aRenderingContext,
+                                       imgIContainer*       aImage,
+                                       const nsPoint&       aDest,
+                                       const nsRect&        aDirty,
+                                       const nsRect*        aSourceArea)
+{
+  nsIntSize size;
+  aImage->GetWidth(&size.width);
+  aImage->GetHeight(&size.height);
+
+  nscoord appUnitsPerCSSPixel = nsIDeviceContext::AppUnitsPerCSSPixel();
+  nsRect source;
+  if (aSourceArea) {
+    source = *aSourceArea;
+  } else {
+    source.SizeTo(size.width*appUnitsPerCSSPixel, size.height*appUnitsPerCSSPixel);
   }
 
-  // For Bug 87819
-  // imgFrame may want image to start at different position, so adjust
-  nsIntRect pxImgFrameRect;
-  imgFrame->GetRect(pxImgFrameRect);
-
-  if (pxImgFrameRect.x > 0) {
-    gfxFloat fx(pxImgFrameRect.x);
-    pxSubimage.pos.x -= fx;
-    pxSrc.pos.x -= fx;
-
-    gfxFloat scaled_x = pxSrc.pos.x;
-    if (pxDirty.size.width != pxSrc.size.width) {
-      scaled_x = scaled_x * (pxDirty.size.width / pxSrc.size.width);
-    }
-
-    if (pxSrc.pos.x < 0.0) {
-      pxDirty.pos.x -= scaled_x;
-      pxSrc.size.width += pxSrc.pos.x;
-      pxDirty.size.width += scaled_x;
-      if (pxSrc.size.width <= 0.0 || pxDirty.size.width <= 0.0)
-        return NS_OK;
-      pxSrc.pos.x = 0.0;
-    }
-  }
-  if (pxSrc.pos.x > gfxFloat(pxImgFrameRect.width)) {
+  nsRect dest(aDest - source.TopLeft(),
+    nsSize(size.width*appUnitsPerCSSPixel, size.height*appUnitsPerCSSPixel));
+  nsRect fill(aDest, source.Size());
+  return DrawImage(aRenderingContext, aImage, dest, fill, aDest, aDirty);
+}
+
+/* static */ nsresult
+nsLayoutUtils::DrawSingleImage(nsIRenderingContext* aRenderingContext,
+                               imgIContainer*       aImage,
+                               const nsRect&        aDest,
+                               const nsRect&        aDirty,
+                               const nsRect*        aSourceArea)
+{
+  nsIntSize size;
+  aImage->GetWidth(&size.width);
+  aImage->GetHeight(&size.height);
+
+  if (size.width == 0 || size.height == 0)
     return NS_OK;
+  
+  nscoord appUnitsPerCSSPixel = nsIDeviceContext::AppUnitsPerCSSPixel();
+  nsRect source;
+  if (aSourceArea) {
+    source = *aSourceArea;
+  } else {
+    source.SizeTo(size.width*appUnitsPerCSSPixel, size.height*appUnitsPerCSSPixel);
   }
 
-  if (pxImgFrameRect.y > 0) {
-    gfxFloat fy(pxImgFrameRect.y);
-    pxSubimage.pos.y -= fy;
-    pxSrc.pos.y -= fy;
-
-    gfxFloat scaled_y = pxSrc.pos.y;
-    if (pxDirty.size.height != pxSrc.size.height) {
-      scaled_y = scaled_y * (pxDirty.size.height / pxSrc.size.height);
-    }
-
-    if (pxSrc.pos.y < 0.0) {
-      pxDirty.pos.y -= scaled_y;
-      pxSrc.size.height += pxSrc.pos.y;
-      pxDirty.size.height += scaled_y;
-      if (pxSrc.size.height <= 0.0 || pxDirty.size.height <= 0.0)
-        return NS_OK;
-      pxSrc.pos.y = 0.0;
-    }
-  }
-  if (pxSrc.pos.y > gfxFloat(pxImgFrameRect.height)) {
-    return NS_OK;
-  }
-
-  return img->Draw(*aRenderingContext, pxSrc, pxSubimage, pxDirty);
+  nsRect dest = GetWholeImageDestination(size, source, aDest);
+  return DrawImage(aRenderingContext, aImage, dest, aDest, aDest.TopLeft(), aDirty);
+}
+
+/* static */ nsRect
+nsLayoutUtils::GetWholeImageDestination(const nsIntSize& aWholeImageSize,
+                                        const nsRect& aImageSourceArea,
+                                        const nsRect& aDestArea)
+{
+  double scaleX = double(aDestArea.width)/aImageSourceArea.width;
+  double scaleY = double(aDestArea.height)/aImageSourceArea.height;
+  nscoord destOffsetX = NSToCoordRound(aImageSourceArea.x*scaleX);
+  nscoord destOffsetY = NSToCoordRound(aImageSourceArea.y*scaleY);
+  nscoord appUnitsPerCSSPixel = nsIDeviceContext::AppUnitsPerCSSPixel();
+  nscoord wholeSizeX = NSToCoordRound(aWholeImageSize.width*appUnitsPerCSSPixel*scaleX);
+  nscoord wholeSizeY = NSToCoordRound(aWholeImageSize.height*appUnitsPerCSSPixel*scaleY);
+  return nsRect(aDestArea.TopLeft() - nsPoint(destOffsetX, destOffsetY),
+                nsSize(wholeSizeX, wholeSizeY));
 }
 
 void
 nsLayoutUtils::SetFontFromStyle(nsIRenderingContext* aRC, nsStyleContext* aSC) 
 {
   const nsStyleFont* font = aSC->GetStyleFont();
   const nsStyleVisibility* visibility = aSC->GetStyleVisibility();
 
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -789,37 +789,86 @@ public:
    * definition. This is the nearest frame that is either positioned or scrolled
    * (the child of a scroll frame). In Gecko terms, it's approximately
    * equivalent to having a view, at least for simple HTML. However, views are
    * going away, so this is a cleaner definition.
    */
   static nsIFrame* GetClosestLayer(nsIFrame* aFrame);
 
   /**
-   * Draw a single image.
+   * Draw an image.
+   * See https://wiki.mozilla.org/Gecko:Image_Snapping_and_Rendering
+   *   @param aRenderingContext Where to draw the image, set up with an
+   *                            appropriate scale and transform for drawing in
+   *                            app units.
+   *   @param aImage            The image.
+   *   @param aDest             Where one copy of the image should mapped to.
+   *   @param aFill             The area to be filled with copies of the
+   *                            image.
+   *   @param aAnchor           A point in aFill which we will ensure is
+   *                            pixel-aligned in the output.
+   *   @param aDirty            Pixels outside this area may be skipped.
+   */
+  static nsresult DrawImage(nsIRenderingContext* aRenderingContext,
+                            imgIContainer*       aImage,
+                            const nsRect&        aDest,
+                            const nsRect&        aFill,
+                            const nsPoint&       aAnchor,
+                            const nsRect&        aDirty);
+
+  /**
+   * Draw a whole image without scaling or tiling.
+   *
    *   @param aRenderingContext Where to draw the image, set up with an
    *                            appropriate scale and transform for drawing in
-   *                            app units (aDestRect).
+   *                            app units.
+   *   @param aImage            The image.
+   *   @param aDest             The top-left where the image should be drawn
+   *   @param aDirty            Pixels outside this area may be skipped.
+   *   @param aSourceArea       If non-null, this area is extracted from
+   *                            the image and drawn at aDest. It's
+   *                            in appunits. For best results it should
+   *                            be aligned with image pixels.
+   */
+  static nsresult DrawSingleUnscaledImage(nsIRenderingContext* aRenderingContext,
+                                          imgIContainer*       aImage,
+                                          const nsPoint&       aDest,
+                                          const nsRect&        aDirty,
+                                          const nsRect*        aSourceArea = nsnull);
+
+  /**
+   * Draw a whole image without tiling.
+   *
+   *   @param aRenderingContext Where to draw the image, set up with an
+   *                            appropriate scale and transform for drawing in
+   *                            app units.
    *   @param aImage            The image.
-   *   @param aDestRect         Where to draw the image (app units).
-   *   @param aDirtyRect        Draw only within this region (rounded to the
-   *                            nearest pixel); the intersection of
-   *                            invalidation and clipping (this is the
-   *                            destination clip)
-   *   @param aSourceRect       If null, draw the entire image so it fits in
-   *                            aDestRect.  If non-null, the subregion of the
-   *                            image that should be drawn (in app units, such
-   *                            that converting it to CSS pixels yields image
-   *                            pixels).
+   *   @param aDest             The area that the image should fill
+   *   @param aDirty            Pixels outside this area may be skipped.
+   *   @param aSourceArea       If non-null, this area is extracted from
+   *                            the image and drawn in aDest. It's
+   *                            in appunits. For best results it should
+   *                            be aligned with image pixels.
    */
-  static nsresult DrawImage(nsIRenderingContext* aRenderingContext,
-                            imgIContainer* aImage,
-                            const nsRect& aDestRect,
-                            const nsRect& aDirtyRect,
-                            const nsRect* aSourceRect = nsnull);
+  static nsresult DrawSingleImage(nsIRenderingContext* aRenderingContext,
+                                  imgIContainer*       aImage,
+                                  const nsRect&        aDest,
+                                  const nsRect&        aDirty,
+                                  const nsRect*        aSourceArea = nsnull);
+
+  /**
+   * Given a source area of an image (in appunits) and a destination area
+   * that we want to map that source area too, computes the area that
+   * would be covered by the whole image. This is useful for passing to
+   * the aDest parameter of DrawImage, when we want to draw a subimage
+   * of an overall image.
+   */
+  static nsRect GetWholeImageDestination(const nsIntSize& aWholeImageSize,
+                                         const nsRect& aImageSourceArea,
+                                         const nsRect& aDestArea);
 
   /**
    * Set the font on aRC based on the style in aSC
    */
   static void SetFontFromStyle(nsIRenderingContext* aRC, nsStyleContext* aSC);
 
   /**
    * Determine if any corner radius is of nonzero size
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -234,18 +234,18 @@ nsBulletFrame::PaintBullet(nsIRenderingC
     if (status & imgIRequest::STATUS_LOAD_COMPLETE &&
         !(status & imgIRequest::STATUS_ERROR)) {
       nsCOMPtr<imgIContainer> imageCon;
       mImageRequest->GetImage(getter_AddRefs(imageCon));
       if (imageCon) {
         nsRect dest(mPadding.left, mPadding.top,
                     mRect.width - (mPadding.left + mPadding.right),
                     mRect.height - (mPadding.top + mPadding.bottom));
-        nsLayoutUtils::DrawImage(&aRenderingContext, imageCon,
-                                 dest + aPt, aDirtyRect);
+        nsLayoutUtils::DrawSingleImage(&aRenderingContext, imageCon,
+             dest + aPt, aDirtyRect);
         return;
       }
     }
   }
 
   const nsStyleColor* myColor = GetStyleColor();
 
   nsCOMPtr<nsIFontMetrics> fm;
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -1064,17 +1064,17 @@ nsImageFrame::DisplayAltFeedback(nsIRend
       if (aRequest) {
         aRequest->GetImage(getter_AddRefs(imgCon));
       }
       if (imgCon) {
         // draw it
         nsRect dest((vis->mDirection == NS_STYLE_DIRECTION_RTL) ?
                     inner.XMost() - size : inner.x,
                     inner.y, size, size);
-        nsLayoutUtils::DrawImage(&aRenderingContext, imgCon, dest, aDirtyRect);
+        nsLayoutUtils::DrawSingleImage(&aRenderingContext, imgCon, dest, aDirtyRect);
         iconUsed = PR_TRUE;
       }
     }
 
     // if we could not draw the image, then just draw some graffiti
     if (!iconUsed) {
       nscolor oldColor;
       nscoord iconXPos = (vis->mDirection ==   NS_STYLE_DIRECTION_RTL) ?
@@ -1169,23 +1169,20 @@ nsDisplayImage::Paint(nsDisplayListBuild
 void
 nsImageFrame::PaintImage(nsIRenderingContext& aRenderingContext, nsPoint aPt,
                          const nsRect& aDirtyRect, imgIContainer* aImage)
 {
   // Render the image into our content area (the area inside
   // the borders and padding)
   NS_ASSERTION(GetInnerArea().width == mComputedSize.width, "bad width");
   nsRect inner = GetInnerArea() + aPt;
-  nsRect clip;
-  clip.IntersectRect(inner, aDirtyRect);
-
   nsRect dest(inner.TopLeft(), mComputedSize);
   dest.y -= GetContinuationOffset();
 
-  nsLayoutUtils::DrawImage(&aRenderingContext, aImage, dest, clip);
+  nsLayoutUtils::DrawSingleImage(&aRenderingContext, aImage, dest, aDirtyRect);
 
   nsPresContext* presContext = PresContext();
   nsImageMap* map = GetImageMap(presContext);
   if (nsnull != map) {
     aRenderingContext.PushState();
     aRenderingContext.SetColor(NS_RGB(0, 0, 0));
     aRenderingContext.SetLineStyle(nsLineStyle_kDotted);
     aRenderingContext.Translate(inner.x, inner.y);
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/364968-1-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+div { position:absolute; width:7px; height:7px; }
+.r { top:0; left:0; background:red; }
+.g { top:0; left:9px; background:lime; }
+.b { top:9px; left:0; background:blue; }
+.y { top:9px; left:9px; background:yellow; }
+</style>
+</head>
+<body>
+<div style="left:256px; top:0;">
+  <div class="r"></div>
+  <div class="g"></div>
+  <div class="b"></div>
+  <div class="y"></div>
+</div>
+<div style="left:272px; top:0;">
+  <div class="r"></div>
+  <div class="g"></div>
+  <div class="b"></div>
+  <div class="y"></div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/364968-1.xul
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<stack>
+  <hbox flex="1">
+    <spacer width="256"/>
+    <image width="16" height="16" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAACAAAAAAgCAYAAACG5JhhAAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH1wUdBDEnzSKcAAAAAXpJREFUeJzt3MEJwDAMBEE5pP+WnSKCMSwzFdxXsGjtmT0AAABA2nL9AwAAQN5zewAAAAAAAAAA8J8AAAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAIAAAQAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAgAABAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAAAB75p9ewMAAABw2J51ewIAAABwmA8AAAAAAAAAABAgAAAAAAAAAACAAAEAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAIAAAQAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAABAwAdutQVA2w3ITgAAAABJRU5ErkJggg=="/>
+    <image width="16" height="16" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAACAAAAAAgCAYAAACG5JhhAAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH1wUdBDEnzSKcAAAAAXpJREFUeJzt3MEJwDAMBEE5pP+WnSKCMSwzFdxXsGjtmT0AAABA2nL9AwAAQN5zewAAAAAAAAAA8J8AAAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAIAAAQAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAgAABAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAAAB75p9ewMAAABw2J51ewIAAABwmA8AAAAAAAAAABAgAAAAAAAAAACAAAEAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAIAAAQAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAABAwAdutQVA2w3ITgAAAABJRU5ErkJggg=="/>
+    <hbox flex="1"/>
+  </hbox>
+  <!-- overdraw the troublesome junctions between colored boxes -->
+  <spacer left="263" top="0" height="16" width="2" style="background:white;"/>
+  <spacer left="279" top="0" height="16" width="2" style="background:white;"/>
+  <spacer left="256" top="7" height="2" width="32" style="background:white;"/>
+</stack>
+</window>
--- a/layout/reftests/bugs/433640-1-ref.html
+++ b/layout/reftests/bugs/433640-1-ref.html
@@ -14,32 +14,32 @@
   
   body > div { height:100px; }
   
   p { height:20px; }
 </style>
 </head><body>
 
 <div>
-<div class="cell"><p>31    x 32</p><div style="width:31px; background-position:-17px -16px;"  class="image"></div></div>
-<div class="cell"><p>31.1  x 32</p><div style="width:31px; background-position:-17px -16px;"  class="image"></div></div>
+<div class="cell"><p>31    x 32</p><div style="width:31px; background-position:-16px -16px;"  class="image"></div></div>
+<div class="cell"><p>31.1  x 32</p><div style="width:31px; background-position:-16px -16px;"  class="image"></div></div>
 <div class="cell"><p>31.5  x 32</p><div style="width:32px; background-position:-16px -16px;"  class="image"></div></div>
 <div class="cell"><p>31.8  x 32</p><div style="width:32px; background-position:-16px -16px;"  class="image"></div></div>
 </div>
 
 <div>
 <div class="cell"><p>32    x 32</p><div style="width:32px; background-position:-16px -16px;"  class="image"></div></div>
 <div class="cell"><p>32.1  x 32</p><div style="width:32px; background-position:-16px -16px;"  class="image"></div></div>
 <div class="cell"><p>32.5  x 32</p><div style="width:33px; background-position:-16px -16px;"  class="image"></div></div>
 <div class="cell"><p>32.8  x 32</p><div style="width:33px; background-position:-16px -16px;"  class="image"></div></div>
 </div>
 
 <div>
-<div class="cell"><p>32 x 31   </p><div style="height:31px; background-position:-16px -17px;" class="image"></div></div>
-<div class="cell"><p>32 x 31.1 </p><div style="height:31px; background-position:-16px -17px;" class="image"></div></div>
+<div class="cell"><p>32 x 31   </p><div style="height:31px; background-position:-16px -16px;" class="image"></div></div>
+<div class="cell"><p>32 x 31.1 </p><div style="height:31px; background-position:-16px -16px;" class="image"></div></div>
 <div class="cell"><p>32 x 31.5 </p><div style="height:32px; background-position:-16px -16px;" class="image"></div></div>
 <div class="cell"><p>32 x 31.8 </p><div style="height:32px; background-position:-16px -16px;" class="image"></div></div>
 </div>
 
 <div>
 <div class="cell"><p>32 x 32   </p><div style="height:32px; background-position:-16px -16px;" class="image"></div></div>
 <div class="cell"><p>32 x 32.1 </p><div style="height:32px; background-position:-16px -16px;" class="image"></div></div>
 <div class="cell"><p>32 x 32.5 </p><div style="height:33px; background-position:-16px -16px;" class="image"></div></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/446100-1a.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html reftest-zoom="1.1">
+<head>
+<style>
+div { margin:1em; }
+/* A 7x7px image, black with a 5x5 transparent box centered in it */
+div.box { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAFklEQVQImWNgYGD4jwdDCCxgQCWxYgBX8hfpeym4dwAAAABJRU5ErkJggg==); }
+/* A 7x5px image, black with a 5x5 transparent box centered in it */
+div.vstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAFElEQVQImWNgYGD4z4Ad/GcYAEkAw+kJ94z5rSYAAAAASUVORK5CYII=); }
+/* A 5x7px image, black with a 5x5 transparent box centered in it */
+div.hstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAHCAYAAADAp4fuAAAAE0lEQVQImWNgYGD4jwXTD2DYDgDN4Qn3yMcPlwAAAABJRU5ErkJggg==); }
+</style>
+</head>
+<body>
+<div class="box" style="background-position:-1px -1px; width:5px; height:5px;"></div>
+<div class="vstrip" style="background-position:-1px 0px; width:5px; height:22px;"></div>
+<div class="hstrip" style="background-position:0px -1px; width:22px; height:5px;"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/446100-1b.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html reftest-zoom="1.5">
+<head>
+<style>
+div { margin:1em; }
+/* A 7x7px image, black with a 5x5 transparent box centered in it */
+div.box { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAFklEQVQImWNgYGD4jwdDCCxgQCWxYgBX8hfpeym4dwAAAABJRU5ErkJggg==); }
+/* A 7x5px image, black with a 5x5 transparent box centered in it */
+div.vstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAFElEQVQImWNgYGD4z4Ad/GcYAEkAw+kJ94z5rSYAAAAASUVORK5CYII=); }
+/* A 5x7px image, black with a 5x5 transparent box centered in it */
+div.hstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAHCAYAAADAp4fuAAAAE0lEQVQImWNgYGD4jwXTD2DYDgDN4Qn3yMcPlwAAAABJRU5ErkJggg==); }
+</style>
+</head>
+<body>
+<div class="box" style="background-position:-1px -1px; width:5px; height:5px;"></div>
+<div class="vstrip" style="background-position:-1px 0px; width:5px; height:22px;"></div>
+<div class="hstrip" style="background-position:0px -1px; width:22px; height:5px;"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/446100-1c.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html reftest-zoom="1.7">
+<head>
+<style>
+div { margin:1em; }
+/* A 7x7px image, black with a 5x5 transparent box centered in it */
+div.box { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAFklEQVQImWNgYGD4jwdDCCxgQCWxYgBX8hfpeym4dwAAAABJRU5ErkJggg==); }
+/* A 7x5px image, black with a 5x5 transparent box centered in it */
+div.vstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAFElEQVQImWNgYGD4z4Ad/GcYAEkAw+kJ94z5rSYAAAAASUVORK5CYII=); }
+/* A 5x7px image, black with a 5x5 transparent box centered in it */
+div.hstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAHCAYAAADAp4fuAAAAE0lEQVQImWNgYGD4jwXTD2DYDgDN4Qn3yMcPlwAAAABJRU5ErkJggg==); }
+</style>
+</head>
+<body>
+<div class="box" style="background-position:-1px -1px; width:5px; height:5px;"></div>
+<div class="vstrip" style="background-position:-1px 0px; width:5px; height:22px;"></div>
+<div class="hstrip" style="background-position:0px -1px; width:22px; height:5px;"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/446100-1d.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html reftest-zoom="2.0">
+<head>
+<style>
+div { margin:1em; }
+/* A 7x7px image, black with a 5x5 transparent box centered in it */
+div.box { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAFklEQVQImWNgYGD4jwdDCCxgQCWxYgBX8hfpeym4dwAAAABJRU5ErkJggg==); }
+/* A 7x5px image, black with a 5x5 transparent box centered in it */
+div.vstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAFElEQVQImWNgYGD4z4Ad/GcYAEkAw+kJ94z5rSYAAAAASUVORK5CYII=); }
+/* A 5x7px image, black with a 5x5 transparent box centered in it */
+div.hstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAHCAYAAADAp4fuAAAAE0lEQVQImWNgYGD4jwXTD2DYDgDN4Qn3yMcPlwAAAABJRU5ErkJggg==); }
+</style>
+</head>
+<body>
+<div class="box" style="background-position:-1px -1px; width:5px; height:5px;"></div>
+<div class="vstrip" style="background-position:-1px 0px; width:5px; height:22px;"></div>
+<div class="hstrip" style="background-position:0px -1px; width:22px; height:5px;"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/446100-1e.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html reftest-zoom="0.8">
+<head>
+<style>
+div { margin:1em; }
+/* A 7x7px image, black with a 5x5 transparent box centered in it */
+div.box { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAFklEQVQImWNgYGD4jwdDCCxgQCWxYgBX8hfpeym4dwAAAABJRU5ErkJggg==); }
+/* A 7x5px image, black with a 5x5 transparent box centered in it */
+div.vstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAFElEQVQImWNgYGD4z4Ad/GcYAEkAw+kJ94z5rSYAAAAASUVORK5CYII=); }
+/* A 5x7px image, black with a 5x5 transparent box centered in it */
+div.hstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAHCAYAAADAp4fuAAAAE0lEQVQImWNgYGD4jwXTD2DYDgDN4Qn3yMcPlwAAAABJRU5ErkJggg==); }
+</style>
+</head>
+<body>
+<div class="box" style="background-position:-1px -1px; width:5px; height:5px;"></div>
+<div class="vstrip" style="background-position:-1px 0px; width:5px; height:22px;"></div>
+<div class="hstrip" style="background-position:0px -1px; width:22px; height:5px;"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/446100-1f.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html reftest-zoom="0.67">
+<head>
+<style>
+div { margin:1em; }
+/* A 7x7px image, black with a 5x5 transparent box centered in it */
+div.box { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAFklEQVQImWNgYGD4jwdDCCxgQCWxYgBX8hfpeym4dwAAAABJRU5ErkJggg==); }
+/* A 7x5px image, black with a 5x5 transparent box centered in it */
+div.vstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAFElEQVQImWNgYGD4z4Ad/GcYAEkAw+kJ94z5rSYAAAAASUVORK5CYII=); }
+/* A 5x7px image, black with a 5x5 transparent box centered in it */
+div.hstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAHCAYAAADAp4fuAAAAE0lEQVQImWNgYGD4jwXTD2DYDgDN4Qn3yMcPlwAAAABJRU5ErkJggg==); }
+</style>
+</head>
+<body>
+<div class="box" style="background-position:-1px -1px; width:5px; height:5px;"></div>
+<div class="vstrip" style="background-position:-1px 0px; width:5px; height:22px;"></div>
+<div class="hstrip" style="background-position:0px -1px; width:22px; height:5px;"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/446100-1g.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html reftest-zoom="0.5">
+<head>
+<style>
+div { margin:1em; }
+/* A 7x7px image, black with a 5x5 transparent box centered in it */
+div.box { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAFklEQVQImWNgYGD4jwdDCCxgQCWxYgBX8hfpeym4dwAAAABJRU5ErkJggg==); }
+/* A 7x5px image, black with a 5x5 transparent box centered in it */
+div.vstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAFElEQVQImWNgYGD4z4Ad/GcYAEkAw+kJ94z5rSYAAAAASUVORK5CYII=); }
+/* A 5x7px image, black with a 5x5 transparent box centered in it */
+div.hstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAHCAYAAADAp4fuAAAAE0lEQVQImWNgYGD4jwXTD2DYDgDN4Qn3yMcPlwAAAABJRU5ErkJggg==); }
+</style>
+</head>
+<body>
+<div class="box" style="background-position:-1px -1px; width:5px; height:5px;"></div>
+<div class="vstrip" style="background-position:-1px 0px; width:5px; height:22px;"></div>
+<div class="hstrip" style="background-position:0px -1px; width:22px; height:5px;"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/446100-1h.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html reftest-zoom="0.3">
+<head>
+<style>
+div { margin:1em; }
+/* A 7x7px image, black with a 5x5 transparent box centered in it */
+div.box { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAFklEQVQImWNgYGD4jwdDCCxgQCWxYgBX8hfpeym4dwAAAABJRU5ErkJggg==); }
+/* A 7x5px image, black with a 5x5 transparent box centered in it */
+div.vstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAFElEQVQImWNgYGD4z4Ad/GcYAEkAw+kJ94z5rSYAAAAASUVORK5CYII=); }
+/* A 5x7px image, black with a 5x5 transparent box centered in it */
+div.hstrip { background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAHCAYAAADAp4fuAAAAE0lEQVQImWNgYGD4jwXTD2DYDgDN4Qn3yMcPlwAAAABJRU5ErkJggg==); }
+</style>
+</head>
+<body>
+<div class="box" style="background-position:-1px -1px; width:5px; height:5px;"></div>
+<div class="vstrip" style="background-position:-1px 0px; width:5px; height:22px;"></div>
+<div class="hstrip" style="background-position:0px -1px; width:22px; height:5px;"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-1-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div style="position:absolute; top:30px; left:30px;
+            background: url(mozilla-banner.gif) no-repeat; width:600px; height:100px;"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-1a.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<html style="margin:30px; border:30px solid rgba(0,0,0,0);
+             background: url(mozilla-banner.gif) no-repeat;
+              -moz-background-origin:border; height:100px;">
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-1b.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<html style="border:30px solid rgba(0,0,0,0);
+             background: url(mozilla-banner.gif) no-repeat;
+              -moz-background-origin:padding; height:100px;">
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-1c.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<html style="padding:30px; background: url(mozilla-banner.gif) no-repeat;
+              -moz-background-origin:content; height:100px;">
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-1d.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<html style="background: url(mozilla-banner.gif) no-repeat;
+             background-position:100% 30px; height:100px; width:630px;">
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-1e.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<html style="margin:30px; border:30px solid rgba(0,0,0,0); height:100px;">
+<body style="background: url(mozilla-banner.gif) no-repeat; -moz-background-origin:border;">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-1f.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<html style="border:30px solid rgba(0,0,0,0); height:100px;">
+<body style="background: url(mozilla-banner.gif) no-repeat; -moz-background-origin:padding;">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-1g.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<html style="padding:30px; height:100px;">
+<body style="background: url(mozilla-banner.gif) no-repeat; -moz-background-origin:content;">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-1h.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<html style="height:100px; width:630px;">
+<body style="background: url(mozilla-banner.gif) no-repeat; background-position:100% 30px;">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-2-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+div { width:198px; overflow:hidden; background: url(mozilla-banner.gif) no-repeat; height:100px; border:1px solid; }
+</style>
+</head>
+<body>
+<div></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+div { width:198.2px; background: url(mozilla-banner.gif) no-repeat; height:100px; border:1px solid; }
+</style>
+</head>
+<body>
+<div></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-3-iframe.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body style="margin:0;">
+<img src="mozilla-banner.gif">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-3-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<img src="mozilla-banner.gif" style="margin-left:100px;">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-3.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<iframe style="margin-left:100px; border:none; width:650px; height:150px;" src="458487-3-iframe.html"></iframe>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-4-ref.html
@@ -0,0 +1,3 @@
+<!DOCTYPE HTML>
+<html style="background: url(mozilla-banner.gif) no-repeat;">
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-4a.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<html style="margin:30px; border:30px solid rgba(0,0,0,0);
+             background: url(mozilla-banner.gif) no-repeat fixed;
+             -moz-background-origin:border; height:10px;">
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-4b.html
@@ -0,0 +1,5 @@
+<!DOCTYPE HTML>
+<html style="border:30px solid rgba(0,0,0,0);
+             background: url(mozilla-banner.gif) no-repeat fixed;
+             -moz-background-origin:padding; height:10px;">
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-4c.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html style="margin:30px; border:30px solid rgba(0,0,0,0); height:10px;">
+<body style="background: url(mozilla-banner.gif) no-repeat fixed;
+             -moz-background-origin:border;">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-5-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div style="position:absolute; left:20px; top:20px; height:20px; width:600px;
+            background: url(mozilla-banner.gif) no-repeat;
+            background-position:-20px -20px;">
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-5a.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<body style="margin:0">
+<div style="margin:20px; border:10px solid rgba(0,0,0,0);
+            background: url(mozilla-banner.gif) no-repeat fixed;
+            -moz-background-origin:border; height:0px;">
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/458487-5b.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<body style="margin:0">
+<div style="margin:20px; border:10px solid rgba(0,0,0,0);
+            background: url(mozilla-banner.gif) no-repeat fixed;
+            -moz-background-origin:padding; height:0px;">
+</div>
+</body>
+</html>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -442,16 +442,17 @@ random-if(MOZ_WIDGET_TOOLKIT=="gtk2") ==
 == 363858-6b.html 363858-6-ref.html
 == 363874.html 363874-ref.html
 == 363874-max-width.html 363874-max-width-ref.html
 == 364066-1.html 364066-1-ref.html
 == 364318-1.xhtml 364318-1-ref.xhtml
 == 364079-1.html 364079-1-ref.html
 == 364861-1.html 364861-1-ref.html
 == 364862-1.html 364862-1-ref.html
+== 364968-1.xul 364968-1-ref.html
 == 365173-1.html 365173-1-ref.html
 == 366207-1.xul 366207-1-ref.xul 
 == 366616-1.xul 366616-1-ref.xul
 == 367220-1.html 367220-1-ref.html
 == 367247-s-visible.html 367247-s-hidden.html
 == 367247-s-hidden.html 367247-s-auto.html
 != 367247-s-auto.html 367247-s-scroll.html
 != 367247-l-visible.html 367247-l-hidden.html
@@ -912,24 +913,47 @@ random == 429849-1.html 429849-1-ref.htm
 == 439910.html 439910-ref.html
 == 441259-1.html 441259-1-ref.html
 fails == 441259-2.html 441259-2-ref.html # bug 441400
 == 442542-1.html 442542-1-ref.html
 == 444015-1.html 444015-1-ref.html
 == 444928-1.html 444928-1-ref.html
 == 444928-2.html 444928-2-ref.html
 != 444928-3.html 444928-3-notref.html
+== 446100-1a.html about:blank
+== 446100-1b.html about:blank
+== 446100-1c.html about:blank
+== 446100-1d.html about:blank
+== 446100-1e.html about:blank
+== 446100-1f.html about:blank
+== 446100-1g.html about:blank
+== 446100-1h.html about:blank
 # == 448193.html 448193-ref.html  # Fails due to 2 small single-pixel differences
 # == 448987.html 448987-ref.html  # Disabled for now - it needs privileges
 == 449171-1.html 449171-ref.html
 == 449519-1.html 449519-1-ref.html
 # == 449653-1.html 449653-1-ref.html # Disabled for now - it needs privileges
 == 450670-1.html 450670-1-ref.html
 == 451168-1.html 451168-1-ref.html
 == 452964-1.html 452964-1-ref.html
 == 454361.html about:blank
 == 455105-1.html 455105-ref.html
 == 455105-2.html 455105-ref.html
 == 455280-1.xhtml 455280-1-ref.xhtml
 fails-if(MOZ_WIDGET_TOOLKIT=="cocoa") == 456147.xul 456147-ref.html # bug 456147, but not caused by it
 == 456484-1.html 456484-1-ref.html
+== 458487-1a.html 458487-1-ref.html
+== 458487-1b.html 458487-1-ref.html
+== 458487-1c.html 458487-1-ref.html
+== 458487-1d.html 458487-1-ref.html
+== 458487-1e.html 458487-1-ref.html
+== 458487-1f.html 458487-1-ref.html
+== 458487-1g.html 458487-1-ref.html
+== 458487-1h.html 458487-1-ref.html
+== 458487-2.html 458487-2-ref.html
+== 458487-3.html 458487-3-ref.html
+== 458487-4a.html 458487-4-ref.html
+== 458487-4b.html 458487-4-ref.html
+== 458487-4c.html 458487-4-ref.html
+== 458487-5a.html 458487-5-ref.html
+== 458487-5b.html 458487-5-ref.html
 == 461266-1.html 461266-1-ref.html
 fails == 461512-1.html 461512-1-ref.html # Bug 461512
--- a/layout/xul/base/src/nsImageBoxFrame.cpp
+++ b/layout/xul/base/src/nsImageBoxFrame.cpp
@@ -373,18 +373,18 @@ nsImageBoxFrame::PaintImage(nsIRendering
   if (!dirty.IntersectRect(aDirtyRect, rect))
     return;
 
   nsCOMPtr<imgIContainer> imgCon;
   mImageRequest->GetImage(getter_AddRefs(imgCon));
 
   if (imgCon) {
     PRBool hasSubRect = !mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0);
-    nsLayoutUtils::DrawImage(&aRenderingContext, imgCon,
-                             rect, dirty, hasSubRect ? &mSubRect : nsnull);
+    nsLayoutUtils::DrawSingleImage(&aRenderingContext, imgCon,
+        rect, dirty, hasSubRect ? &mSubRect : nsnull);
   }
 }
 
 
 //
 // DidSetStyleContext
 //
 // When the style context changes, make sure that all of our image is up to date.
--- a/layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp
+++ b/layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp
@@ -3366,28 +3366,28 @@ nsTreeBodyFrame::PaintTwisty(PRInt32    
       twistyRect.Deflate(bp);
       imageSize.Deflate(bp);
 
       // Get the image for drawing.
       nsCOMPtr<imgIContainer> image;
       PRBool useImageRegion = PR_TRUE;
       GetImage(aRowIndex, aColumn, PR_TRUE, twistyContext, useImageRegion, getter_AddRefs(image));
       if (image) {
-        nsRect r(twistyRect.x, twistyRect.y, imageSize.width, imageSize.height);
+        nsPoint pt = twistyRect.TopLeft();
 
         // Center the image. XXX Obey vertical-align style prop?
         if (imageSize.height < twistyRect.height) {
-          r.y += (twistyRect.height - imageSize.height)/2;
+          pt.y += (twistyRect.height - imageSize.height)/2;
         }
           
         // Paint the image.
-        nsLayoutUtils::DrawImage(&aRenderingContext, image,
-                                 r, aDirtyRect, &imageSize);
+        nsLayoutUtils::DrawSingleUnscaledImage(&aRenderingContext, image,
+            pt, aDirtyRect, &imageSize);
       }
-    }        
+    }
   }
 }
 
 void
 nsTreeBodyFrame::PaintImage(PRInt32              aRowIndex,
                             nsTreeColumn*        aColumn,
                             const nsRect&        aImageRect,
                             nsPresContext*       aPresContext,
@@ -3485,32 +3485,33 @@ nsTreeBodyFrame::PaintImage(PRInt32     
     // Deflate destRect for the border and padding.
     destRect.Deflate(bp);
 
     // Get the image source rectangle - the rectangle containing the part of
     // the image that we are going to display.
     // sourceRect will be passed as the aSrcRect argument in the DrawImage method.
     nsRect sourceRect = GetImageSourceRect(imageContext, useImageRegion, image);
 
-    // If destRect width/height was adjusted to fit within the cell
-    // width/height, we need to make corresponding adjustments to the
-    // sourceRect width/height.
-    // Here's an explanation. Let's say that the image is 100 pixels tall and
+    // Let's say that the image is 100 pixels tall and
     // that the CSS has specified that the destination height should be 50
     // pixels tall. Let's say that the cell height is only 20 pixels. So, in
     // those 20 visible pixels, we want to see the top 20/50ths of the image.
     // So, the sourceRect.height should be 100 * 20 / 50, which is 40 pixels.
     // Essentially, we are scaling the image as dictated by the CSS destination
     // height and width, and we are then clipping the scaled image by the cell
     // width and height.
-    nsRect clip;
-    clip.IntersectRect(aDirtyRect, destRect);
+    nsIntSize rawImageSize;
+    image->GetWidth(&rawImageSize.width);
+    image->GetHeight(&rawImageSize.height);
+    nsRect wholeImageDest =
+      nsLayoutUtils::GetWholeImageDestination(rawImageSize, sourceRect,
+          nsRect(destRect.TopLeft(), imageDestSize));
+
     nsLayoutUtils::DrawImage(&aRenderingContext, image,
-                             nsRect(destRect.TopLeft(), imageDestSize),
-                             clip, &sourceRect);
+        wholeImageDest, destRect, destRect.TopLeft(), aDirtyRect);
   }
 
   // Update the aRemainingWidth and aCurrX values.
   imageRect.Inflate(imageMargin);
   aRemainingWidth -= imageRect.width;
   aCurrX += imageRect.width;
 }
 
@@ -3648,29 +3649,29 @@ nsTreeBodyFrame::PaintCheckbox(PRInt32  
   GetBorderPadding(checkboxContext, bp);
   checkboxRect.Deflate(bp);
 
   // Get the image for drawing.
   nsCOMPtr<imgIContainer> image;
   PRBool useImageRegion = PR_TRUE;
   GetImage(aRowIndex, aColumn, PR_TRUE, checkboxContext, useImageRegion, getter_AddRefs(image));
   if (image) {
-    nsRect r(checkboxRect.x, checkboxRect.y, imageSize.width, imageSize.height);
+    nsPoint pt = checkboxRect.TopLeft();
           
     if (imageSize.height < checkboxRect.height) {
-      r.y += (checkboxRect.height - imageSize.height)/2;
+      pt.y += (checkboxRect.height - imageSize.height)/2;
     }
 
     if (imageSize.width < checkboxRect.width) {
-      r.x += (checkboxRect.width - imageSize.width)/2;
+      pt.x += (checkboxRect.width - imageSize.width)/2;
     }
 
     // Paint the image.
-    nsLayoutUtils::DrawImage(&aRenderingContext, image,
-                             r, aDirtyRect, &imageSize);
+    nsLayoutUtils::DrawSingleUnscaledImage(&aRenderingContext, image,
+        pt, aDirtyRect, &imageSize);
   }
 }
 
 void
 nsTreeBodyFrame::PaintProgressMeter(PRInt32              aRowIndex,
                                     nsTreeColumn*        aColumn,
                                     const nsRect&        aProgressMeterRect,
                                     nsPresContext*      aPresContext,
@@ -3714,30 +3715,44 @@ nsTreeBodyFrame::PaintProgressMeter(PRIn
       intValue = 0;
     else if (intValue > 100)
       intValue = 100;
 
     meterRect.width = NSToCoordRound((float)intValue / 100 * meterRect.width);
     PRBool useImageRegion = PR_TRUE;
     nsCOMPtr<imgIContainer> image;
     GetImage(aRowIndex, aColumn, PR_TRUE, meterContext, useImageRegion, getter_AddRefs(image));
-    if (image)
-      aRenderingContext.DrawTile(image, 0, 0, &meterRect, nsnull);
-    else
+    if (image) {
+      PRInt32 width, height;
+      image->GetWidth(&width);
+      image->GetHeight(&height);
+      nsSize size(width*nsIDeviceContext::AppUnitsPerCSSPixel(),
+                  height*nsIDeviceContext::AppUnitsPerCSSPixel());
+      nsLayoutUtils::DrawImage(&aRenderingContext, image,
+          nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(), aDirtyRect);
+    } else {
       aRenderingContext.FillRect(meterRect);
+    }
   }
   else if (state == nsITreeView::PROGRESS_UNDETERMINED) {
     // Adjust the rect for its border and padding.
     AdjustForBorderPadding(meterContext, meterRect);
 
     PRBool useImageRegion = PR_TRUE;
     nsCOMPtr<imgIContainer> image;
     GetImage(aRowIndex, aColumn, PR_TRUE, meterContext, useImageRegion, getter_AddRefs(image));
-    if (image)
-      aRenderingContext.DrawTile(image, 0, 0, &meterRect, nsnull);
+    if (image) {
+      PRInt32 width, height;
+      image->GetWidth(&width);
+      image->GetHeight(&height);
+      nsSize size(width*nsIDeviceContext::AppUnitsPerCSSPixel(),
+                  height*nsIDeviceContext::AppUnitsPerCSSPixel());
+      nsLayoutUtils::DrawImage(&aRenderingContext, image,
+          nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(), aDirtyRect);
+    }
   }
 }
 
 
 void
 nsTreeBodyFrame::PaintDropFeedback(const nsRect&        aDropFeedbackRect,
                                    nsPresContext*      aPresContext,
                                    nsIRenderingContext& aRenderingContext,
--- a/widget/src/xpwidgets/nsBaseDragService.cpp
+++ b/widget/src/xpwidgets/nsBaseDragService.cpp
@@ -554,72 +554,67 @@ nsBaseDragService::DrawDragForImage(nsPr
   else {
     NS_ASSERTION(aCanvas, "both image and canvas are null");
     PRUint32 width, height;
     aCanvas->GetSize(&width, &height);
     aScreenDragRect->width = width;
     aScreenDragRect->height = height;
   }
 
-  nsRect srcRect = *aScreenDragRect;
-  srcRect.MoveTo(0, 0);
-  nsRect destRect = srcRect;
+  nsSize srcSize = aScreenDragRect->Size();
+  nsSize destSize = srcSize;
 
-  if (destRect.width == 0 || destRect.height == 0)
+  if (destSize.width == 0 || destSize.height == 0)
     return NS_ERROR_FAILURE;
 
   // if the image is larger than half the screen size, scale it down. This
   // scaling algorithm is the same as is used in nsPresShell::PaintRangePaintInfo
   nsIDeviceContext* deviceContext = aPresContext->DeviceContext();
   nsRect maxSize;
   deviceContext->GetClientRect(maxSize);
   nscoord maxWidth = aPresContext->AppUnitsToDevPixels(maxSize.width >> 1);
   nscoord maxHeight = aPresContext->AppUnitsToDevPixels(maxSize.height >> 1);
-  if (destRect.width > maxWidth || destRect.height > maxHeight) {
+  if (destSize.width > maxWidth || destSize.height > maxHeight) {
     float scale = 1.0;
-    if (destRect.width > maxWidth)
-      scale = PR_MIN(scale, float(maxWidth) / destRect.width);
-    if (destRect.height > maxHeight)
-      scale = PR_MIN(scale, float(maxHeight) / destRect.height);
+    if (destSize.width > maxWidth)
+      scale = PR_MIN(scale, float(maxWidth) / destSize.width);
+    if (destSize.height > maxHeight)
+      scale = PR_MIN(scale, float(maxHeight) / destSize.height);
 
-    destRect.width = NSToIntFloor(float(destRect.width) * scale);
-    destRect.height = NSToIntFloor(float(destRect.height) * scale);
+    destSize.width = NSToIntFloor(float(destSize.width) * scale);
+    destSize.height = NSToIntFloor(float(destSize.height) * scale);
 
     aScreenDragRect->x = NSToIntFloor(aScreenX - float(mImageX) * scale);
     aScreenDragRect->y = NSToIntFloor(aScreenY - float(mImageY) * scale);
-    aScreenDragRect->width = destRect.width;
-    aScreenDragRect->height = destRect.height;
+    aScreenDragRect->width = destSize.width;
+    aScreenDragRect->height = destSize.height;
   }
 
   nsRefPtr<gfxImageSurface> surface =
-    new gfxImageSurface(gfxIntSize(destRect.width, destRect.height),
+    new gfxImageSurface(gfxIntSize(destSize.width, destSize.height),
                         gfxImageSurface::ImageFormatARGB32);
   if (!surface)
     return NS_ERROR_FAILURE;
 
+  nsRefPtr<gfxContext> ctx = new gfxContext(surface);
+  if (!ctx)
+    return NS_ERROR_FAILURE;
+
   *aSurface = surface;
   NS_ADDREF(*aSurface);
 
-  nsCOMPtr<nsIRenderingContext> rc;
-  deviceContext->CreateRenderingContextInstance(*getter_AddRefs(rc));
-  rc->Init(deviceContext, surface);
-
   if (aImageLoader) {
-    // clear the image before drawing
-    gfxContext context(surface);
-    context.SetOperator(gfxContext::OPERATOR_CLEAR);
-    context.Rectangle(gfxRect(0, 0, destRect.width, destRect.height));
-    context.Fill();
-
-    gfxRect inRect = gfxRect(srcRect.x, srcRect.y, srcRect.width, srcRect.height);
-    gfxRect outRect = gfxRect(destRect.x, destRect.y, destRect.width, destRect.height);
-    return img->Draw(*rc, inRect, inRect, outRect);
-  }
-  else {
-    return aCanvas->RenderContexts(rc->ThebesContext());
+    gfxRect outRect(0, 0, destSize.width, destSize.height);
+    gfxMatrix scale =
+      gfxMatrix().Scale(srcSize.width/outRect.Width(), srcSize.height/outRect.Height());
+    img->Draw(ctx, scale, outRect, nsIntMargin(0,0,0,0),
+              nsIntRect(0, 0, srcSize.width, srcSize.height));
+    return NS_OK;
+  } else {
+    return aCanvas->RenderContexts(ctx);
   }
 }
 
 void
 nsBaseDragService::ConvertToUnscaledDevPixels(nsPresContext* aPresContext,
                                               PRInt32* aScreenX, PRInt32* aScreenY)
 {
   PRInt32 adj = aPresContext->DeviceContext()->UnscaledAppUnitsPerDevPixel();