Bug 696569 - Allow for non-premultiplied data for canvases - r=bjacob,joedrew
authorJeff Gilbert <jgilbert@mozilla.com>
Fri, 23 Mar 2012 15:10:50 -0700
changeset 93959 1e64ae9b40096ba2d66ce34e1388f8da770129e6
parent 93958 f53bfab388dec869401c3722874f000889d523fe
child 93960 220f36e7f9f78d656680e400b19f76c70af0eb48
push id160
push userlsblakk@mozilla.com
push dateFri, 13 Jul 2012 18:18:57 +0000
treeherdermozilla-release@228ba1a111fc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbjacob, joedrew
bugs696569
milestone14.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 696569 - Allow for non-premultiplied data for canvases - r=bjacob,joedrew
content/canvas/public/nsICanvasElementExternal.h
content/canvas/public/nsICanvasRenderingContextInternal.h
content/canvas/src/WebGLContext.cpp
content/canvas/src/WebGLContext.h
content/canvas/src/nsCanvasRenderingContext2D.cpp
content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
content/canvas/test/webgl/failing_tests_linux.txt
content/canvas/test/webgl/failing_tests_mac.txt
content/canvas/test/webgl/failing_tests_windows.txt
content/html/content/public/nsHTMLCanvasElement.h
content/html/content/src/nsHTMLCanvasElement.cpp
gfx/thebes/gfxColor.h
gfx/thebes/gfxUtils.cpp
gfx/thebes/gfxUtils.h
image/decoders/nsPNGDecoder.cpp
image/src/Decoder.cpp
image/src/Decoder.h
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/imgFrame.cpp
image/src/imgFrame.h
layout/base/nsLayoutUtils.cpp
--- a/content/canvas/public/nsICanvasElementExternal.h
+++ b/content/canvas/public/nsICanvasElementExternal.h
@@ -62,24 +62,29 @@ struct _cairo_surface;
  * same functionality is needed in both places, two separate methods should be
  * used.
  */
 
 class nsICanvasElementExternal : public nsISupports {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICANVASELEMENTEXTERNAL_IID)
 
+  enum {
+    RenderFlagPremultAlpha = 0x1
+  };
+
   /**
    * Get the size in pixels of this canvas element
    */
   NS_IMETHOD_(nsIntSize) GetSizeExternal() = 0;
 
   /*
    * Ask the canvas element to tell the contexts to render themselves
    * to the given gfxContext at the origin of its coordinate space.
    */
   NS_IMETHOD RenderContextsExternal(gfxContext *ctx,
-                                    gfxPattern::GraphicsFilter aFilter) = 0;
+                                    gfxPattern::GraphicsFilter aFilter,
+                                    PRUint32 aFlags = RenderFlagPremultAlpha) = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsICanvasElementExternal, NS_ICANVASELEMENTEXTERNAL_IID)
 
 #endif /* nsICanvasElementExternal_h___ */
--- a/content/canvas/public/nsICanvasRenderingContextInternal.h
+++ b/content/canvas/public/nsICanvasRenderingContextInternal.h
@@ -69,28 +69,34 @@ class SourceSurface;
 
 class nsICanvasRenderingContextInternal : public nsISupports {
 public:
   typedef mozilla::layers::CanvasLayer CanvasLayer;
   typedef mozilla::layers::LayerManager LayerManager;
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICANVASRENDERINGCONTEXTINTERNAL_IID)
 
+  enum {
+    RenderFlagPremultAlpha = 0x1
+  };
+
   // This method should NOT hold a ref to aParentCanvas; it will be called
   // with nsnull when the element is going away.
   NS_IMETHOD SetCanvasElement(nsHTMLCanvasElement* aParentCanvas) = 0;
 
   // Sets the dimensions of the canvas, in pixels.  Called
   // whenever the size of the element changes.
   NS_IMETHOD SetDimensions(PRInt32 width, PRInt32 height) = 0;
 
   NS_IMETHOD InitializeWithSurface(nsIDocShell *docShell, gfxASurface *surface, PRInt32 width, PRInt32 height) = 0;
 
   // Render the canvas at the origin of the given gfxContext
-  NS_IMETHOD Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter) = 0;
+  NS_IMETHOD Render(gfxContext *ctx,
+                    gfxPattern::GraphicsFilter aFilter,
+                    PRUint32 aFlags = RenderFlagPremultAlpha) = 0;
 
   // Gives you a stream containing the image represented by this context.
   // The format is given in aMimeTime, for example "image/png".
   //
   // If the image format does not support transparency or aIncludeTransparency
   // is false, alpha will be discarded and the result will be the image
   // composited on black.
   NS_IMETHOD GetInputStream(const char *aMimeType,
--- a/content/canvas/src/WebGLContext.cpp
+++ b/content/canvas/src/WebGLContext.cpp
@@ -556,28 +556,36 @@ WebGLContext::SetDimensions(PRInt32 widt
 
     gl->ClearSafely();
 
     reporter.SetSuccessful();
     return NS_OK;
 }
 
 NS_IMETHODIMP
-WebGLContext::Render(gfxContext *ctx, gfxPattern::GraphicsFilter f)
+WebGLContext::Render(gfxContext *ctx, gfxPattern::GraphicsFilter f, PRUint32 aFlags)
 {
     if (!gl)
         return NS_OK;
 
     nsRefPtr<gfxImageSurface> surf = new gfxImageSurface(gfxIntSize(mWidth, mHeight),
                                                          gfxASurface::ImageFormatARGB32);
     if (surf->CairoStatus() != 0)
         return NS_ERROR_FAILURE;
 
     gl->ReadPixelsIntoImageSurface(0, 0, mWidth, mHeight, surf);
-    gfxUtils::PremultiplyImageSurface(surf);
+
+    bool srcPremultAlpha = mOptions.premultipliedAlpha;
+    bool dstPremultAlpha = aFlags & RenderFlagPremultAlpha;
+
+    if (!srcPremultAlpha && dstPremultAlpha) {
+        gfxUtils::PremultiplyImageSurface(surf);
+    } else if (srcPremultAlpha && !dstPremultAlpha) {
+        gfxUtils::UnpremultiplyImageSurface(surf);
+    }
 
     nsRefPtr<gfxPattern> pat = new gfxPattern(surf);
     pat->SetFilter(f);
 
     // Pixels from ReadPixels will be "upside down" compared to
     // what cairo wants, so draw with a y-flip and a translte to
     // flip them.
     gfxMatrix m;
@@ -603,38 +611,50 @@ WebGLContext::GetInputStream(const char*
 
     nsRefPtr<gfxImageSurface> surf = new gfxImageSurface(gfxIntSize(mWidth, mHeight),
                                                          gfxASurface::ImageFormatARGB32);
     if (surf->CairoStatus() != 0)
         return NS_ERROR_FAILURE;
 
     nsRefPtr<gfxContext> tmpcx = new gfxContext(surf);
     // Use Render() to make sure that appropriate y-flip gets applied
-    nsresult rv = Render(tmpcx, gfxPattern::FILTER_NEAREST);
+    PRUint32 flags = mOptions.premultipliedAlpha ? RenderFlagPremultAlpha : 0;
+    nsresult rv = Render(tmpcx, gfxPattern::FILTER_NEAREST, flags);
     if (NS_FAILED(rv))
         return rv;
 
     const char encoderPrefix[] = "@mozilla.org/image/encoder;2?type=";
     nsAutoArrayPtr<char> conid(new char[strlen(encoderPrefix) + strlen(aMimeType) + 1]);
 
     if (!conid)
         return NS_ERROR_OUT_OF_MEMORY;
 
     strcpy(conid, encoderPrefix);
     strcat(conid, aMimeType);
 
     nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(conid);
     if (!encoder)
         return NS_ERROR_FAILURE;
 
+    int format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
+    if (!mOptions.premultipliedAlpha) {
+        // We need to convert to INPUT_FORMAT_RGBA, otherwise
+        // we are automatically considered premult, and unpremult'd.
+        // Yes, it is THAT silly.
+        // Except for different lossy conversions by color,
+        // we could probably just change the label, and not change the data.
+        gfxUtils::ConvertBGRAtoRGBA(surf);
+        format = imgIEncoder::INPUT_FORMAT_RGBA;
+    }
+
     rv = encoder->InitFromData(surf->Data(),
                                mWidth * mHeight * 4,
                                mWidth, mHeight,
                                surf->Stride(),
-                               imgIEncoder::INPUT_FORMAT_HOSTARGB,
+                               format,
                                nsDependentString(aEncoderOptions));
     NS_ENSURE_SUCCESS(rv, rv);
 
     return CallQueryInterface(encoder, aStream);
 }
 
 NS_IMETHODIMP
 WebGLContext::GetThebesSurface(gfxASurface **surface)
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -537,17 +537,19 @@ public:
 
     // nsICanvasRenderingContextInternal
     NS_IMETHOD SetCanvasElement(nsHTMLCanvasElement* aParentCanvas);
     NS_IMETHOD SetDimensions(PRInt32 width, PRInt32 height);
     NS_IMETHOD InitializeWithSurface(nsIDocShell *docShell, gfxASurface *surface, PRInt32 width, PRInt32 height)
         { return NS_ERROR_NOT_IMPLEMENTED; }
     NS_IMETHOD Reset()
         { /* (InitializeWithSurface) */ return NS_ERROR_NOT_IMPLEMENTED; }
-    NS_IMETHOD Render(gfxContext *ctx, gfxPattern::GraphicsFilter f);
+    NS_IMETHOD Render(gfxContext *ctx,
+                      gfxPattern::GraphicsFilter f,
+                      PRUint32 aFlags = RenderFlagPremultAlpha);
     NS_IMETHOD GetInputStream(const char* aMimeType,
                               const PRUnichar* aEncoderOptions,
                               nsIInputStream **aStream);
     NS_IMETHOD GetThebesSurface(gfxASurface **surface);
     mozilla::TemporaryRef<mozilla::gfx::SourceSurface> GetSurfaceSnapshot()
         { return nsnull; }
 
     NS_IMETHOD SetIsOpaque(bool b) { return NS_OK; };
--- a/content/canvas/src/nsCanvasRenderingContext2D.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp
@@ -346,17 +346,19 @@ public:
     nsresult Redraw();
 
     // nsICanvasRenderingContextInternal
     NS_IMETHOD SetCanvasElement(nsHTMLCanvasElement* aParentCanvas);
     NS_IMETHOD SetDimensions(PRInt32 width, PRInt32 height);
     void Initialize(nsIDocShell *shell, PRInt32 width, PRInt32 height);
     NS_IMETHOD InitializeWithSurface(nsIDocShell *shell, gfxASurface *surface, PRInt32 width, PRInt32 height);
     bool EnsureSurface();
-    NS_IMETHOD Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter);
+    NS_IMETHOD Render(gfxContext *ctx,
+                      gfxPattern::GraphicsFilter aFilter,
+                      PRUint32 aFlags = RenderFlagPremultAlpha);
     NS_IMETHOD GetInputStream(const char* aMimeType,
                               const PRUnichar* aEncoderOptions,
                               nsIInputStream **aStream);
     NS_IMETHOD GetThebesSurface(gfxASurface **surface);
     mozilla::TemporaryRef<mozilla::gfx::SourceSurface> GetSurfaceSnapshot()
         { return nsnull; }
 
     NS_IMETHOD SetIsOpaque(bool isOpaque);
@@ -1273,17 +1275,17 @@ nsCanvasRenderingContext2D::SetIsIPC(boo
          */
         return SetDimensions(mWidth, mHeight);
     }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsCanvasRenderingContext2D::Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter)
+nsCanvasRenderingContext2D::Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter, PRUint32 aFlags)
 {
     nsresult rv = NS_OK;
 
     if (!EnsureSurface())
         return NS_ERROR_FAILURE;
 
     nsRefPtr<gfxPattern> pat = new gfxPattern(mSurface);
 
@@ -1298,16 +1300,24 @@ nsCanvasRenderingContext2D::Render(gfxCo
     // pixel alignment for this stuff!
     ctx->NewPath();
     ctx->PixelSnappedRectangleAndSetPattern(gfxRect(0, 0, mWidth, mHeight), pat);
     ctx->Fill();
 
     if (mOpaque)
         ctx->SetOperator(op);
 
+    if (!(aFlags & RenderFlagPremultAlpha)) {
+        nsRefPtr<gfxASurface> curSurface = ctx->CurrentSurface();
+        nsRefPtr<gfxImageSurface> gis = curSurface->GetAsImageSurface();
+        NS_ABORT_IF_FALSE(gis, "If non-premult alpha, must be able to get image surface!");
+
+        gfxUtils::UnpremultiplyImageSurface(gis);
+    }
+
     return rv;
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2D::GetInputStream(const char *aMimeType,
                                            const PRUnichar *aEncoderOptions,
                                            nsIInputStream **aStream)
 {
--- a/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
@@ -401,17 +401,19 @@ public:
   nsresult Redraw();
 
   // nsICanvasRenderingContextInternal
   NS_IMETHOD SetCanvasElement(nsHTMLCanvasElement* aParentCanvas);
   NS_IMETHOD SetDimensions(PRInt32 width, PRInt32 height);
   NS_IMETHOD InitializeWithSurface(nsIDocShell *shell, gfxASurface *surface, PRInt32 width, PRInt32 height)
   { return NS_ERROR_NOT_IMPLEMENTED; }
 
-  NS_IMETHOD Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter);
+  NS_IMETHOD Render(gfxContext *ctx,
+                    gfxPattern::GraphicsFilter aFilter,
+                    PRUint32 aFlags = RenderFlagPremultAlpha);
   NS_IMETHOD GetInputStream(const char* aMimeType,
                             const PRUnichar* aEncoderOptions,
                             nsIInputStream **aStream);
   NS_IMETHOD GetThebesSurface(gfxASurface **surface);
 
   TemporaryRef<SourceSurface> GetSurfaceSnapshot()
   { return mTarget ? mTarget->Snapshot() : nsnull; }
 
@@ -1377,17 +1379,17 @@ nsCanvasRenderingContext2DAzure::SetIsIP
       */
     return SetDimensions(mWidth, mHeight);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsCanvasRenderingContext2DAzure::Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter)
+nsCanvasRenderingContext2DAzure::Render(gfxContext *ctx, gfxPattern::GraphicsFilter aFilter, PRUint32 aFlags)
 {
   nsresult rv = NS_OK;
 
   if (!mValid || !mTarget) {
     return NS_ERROR_FAILURE;
   }
 
   nsRefPtr<gfxASurface> surface;
@@ -1409,16 +1411,24 @@ nsCanvasRenderingContext2DAzure::Render(
   // pixel alignment for this stuff!
   ctx->NewPath();
   ctx->PixelSnappedRectangleAndSetPattern(gfxRect(0, 0, mWidth, mHeight), pat);
   ctx->Fill();
 
   if (mOpaque)
       ctx->SetOperator(op);
 
+  if (!(aFlags & RenderFlagPremultAlpha)) {
+      nsRefPtr<gfxASurface> curSurface = ctx->CurrentSurface();
+      nsRefPtr<gfxImageSurface> gis = curSurface->GetAsImageSurface();
+      NS_ABORT_IF_FALSE(gis, "If non-premult alpha, must be able to get image surface!");
+
+      gfxUtils::UnpremultiplyImageSurface(gis);
+  }
+
   return rv;
 }
 
 NS_IMETHODIMP
 nsCanvasRenderingContext2DAzure::GetInputStream(const char *aMimeType,
                                                 const PRUnichar *aEncoderOptions,
                                                 nsIInputStream **aStream)
 {
--- a/content/canvas/test/webgl/failing_tests_linux.txt
+++ b/content/canvas/test/webgl/failing_tests_linux.txt
@@ -1,7 +1,6 @@
-conformance/context/premultiplyalpha-test.html
 conformance/misc/uninitialized-test.html
 conformance/programs/gl-get-active-attribute.html
 conformance/textures/texture-mips.html
 conformance/uniforms/gl-uniform-bool.html
 conformance/more/conformance/quickCheckAPI-S_V.html
 conformance/renderbuffers/framebuffer-object-attachment.html
\ No newline at end of file
--- a/content/canvas/test/webgl/failing_tests_mac.txt
+++ b/content/canvas/test/webgl/failing_tests_mac.txt
@@ -1,6 +1,5 @@
-conformance/context/premultiplyalpha-test.html
 conformance/glsl/misc/glsl-function-nodes.html
 conformance/more/conformance/quickCheckAPI-S_V.html
 conformance/programs/program-test.html
 conformance/textures/texture-mips.html
 conformance/textures/texture-npot.html
--- a/content/canvas/test/webgl/failing_tests_windows.txt
+++ b/content/canvas/test/webgl/failing_tests_windows.txt
@@ -1,6 +1,5 @@
-conformance/context/premultiplyalpha-test.html
 conformance/glsl/functions/glsl-function-atan.html
 conformance/glsl/functions/glsl-function-atan-xy.html
 conformance/more/conformance/quickCheckAPI-S_V.html
 conformance/glsl/misc/struct-nesting-under-maximum.html
 conformance/more/functions/uniformfArrayLen1.html
--- a/content/html/content/public/nsHTMLCanvasElement.h
+++ b/content/html/content/public/nsHTMLCanvasElement.h
@@ -137,17 +137,19 @@ public:
    * across its entire area.
    */
   bool GetIsOpaque();
 
   /*
    * nsICanvasElementExternal -- for use outside of content/layout
    */
   NS_IMETHOD_(nsIntSize) GetSizeExternal();
-  NS_IMETHOD RenderContextsExternal(gfxContext *aContext, gfxPattern::GraphicsFilter aFilter);
+  NS_IMETHOD RenderContextsExternal(gfxContext *aContext,
+                                    gfxPattern::GraphicsFilter aFilter,
+                                    PRUint32 aFlags = RenderFlagPremultAlpha);
 
   virtual bool ParseAttribute(PRInt32 aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult);
   nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute, PRInt32 aModType) const;
 
   // SetAttr override.  C++ is stupid, so have to override both
--- a/content/html/content/src/nsHTMLCanvasElement.cpp
+++ b/content/html/content/src/nsHTMLCanvasElement.cpp
@@ -789,22 +789,22 @@ nsHTMLCanvasElement::MarkContextClean()
 
 NS_IMETHODIMP_(nsIntSize)
 nsHTMLCanvasElement::GetSizeExternal()
 {
   return GetWidthHeight();
 }
 
 NS_IMETHODIMP
-nsHTMLCanvasElement::RenderContextsExternal(gfxContext *aContext, gfxPattern::GraphicsFilter aFilter)
+nsHTMLCanvasElement::RenderContextsExternal(gfxContext *aContext, gfxPattern::GraphicsFilter aFilter, PRUint32 aFlags)
 {
   if (!mCurrentContext)
     return NS_OK;
 
-  return mCurrentContext->Render(aContext, aFilter);
+  return mCurrentContext->Render(aContext, aFilter, aFlags);
 }
 
 nsresult NS_NewCanvasRenderingContext2DThebes(nsIDOMCanvasRenderingContext2D** aResult);
 nsresult NS_NewCanvasRenderingContext2DAzure(nsIDOMCanvasRenderingContext2D** aResult);
 
 nsresult
 NS_NewCanvasRenderingContext2D(nsIDOMCanvasRenderingContext2D** aResult)
 {
--- a/gfx/thebes/gfxColor.h
+++ b/gfx/thebes/gfxColor.h
@@ -179,16 +179,18 @@ struct THEBES_API gfxRGBA {
      */
     gfxRGBA(gfxFloat _r, gfxFloat _g, gfxFloat _b, gfxFloat _a=1.0) : r(_r), g(_g), b(_b), a(_a) {}
 
     /**
      * Initialize this color from a packed 32-bit color.
      * The color value is interpreted based on colorType;
      * all values use the native platform endianness.
      *
+     * Resulting gfxRGBA stores non-premultiplied data.
+     *
      * @see gfxRGBA::Packed
      */
     gfxRGBA(PRUint32 c, PackedColorType colorType = PACKED_ABGR) {
         if (colorType == PACKED_ABGR ||
             colorType == PACKED_ABGR_PREMULTIPLIED)
         {
             r = ((c >> 0) & 0xff) * (1.0 / 255.0);
             g = ((c >> 8) & 0xff) * (1.0 / 255.0);
--- a/gfx/thebes/gfxUtils.cpp
+++ b/gfx/thebes/gfxUtils.cpp
@@ -205,16 +205,61 @@ gfxUtils::UnpremultiplyImageSurface(gfxI
         *dst++ = a;
         *dst++ = UnpremultiplyValue(a, r);
         *dst++ = UnpremultiplyValue(a, g);
         *dst++ = UnpremultiplyValue(a, b);
 #endif
     }
 }
 
+void
+gfxUtils::ConvertBGRAtoRGBA(gfxImageSurface *aSourceSurface,
+                            gfxImageSurface *aDestSurface) {
+    if (!aDestSurface)
+        aDestSurface = aSourceSurface;
+
+    NS_ABORT_IF_FALSE(aSourceSurface->Format() == aDestSurface->Format() &&
+                      aSourceSurface->Width() == aDestSurface->Width() &&
+                      aSourceSurface->Height() == aDestSurface->Height() &&
+                      aSourceSurface->Stride() == aDestSurface->Stride(),
+                      "Source and destination surfaces don't have identical characteristics");
+
+    NS_ABORT_IF_FALSE(aSourceSurface->Stride() == aSourceSurface->Width() * 4,
+                      "Source surface stride isn't tightly packed");
+
+    NS_ABORT_IF_FALSE(aSourceSurface->Format() == gfxASurface::ImageFormatARGB32,
+                      "Surfaces must be ARGB32");
+
+    PRUint8 *src = aSourceSurface->Data();
+    PRUint8 *dst = aDestSurface->Data();
+
+    PRUint32 dim = aSourceSurface->Width() * aSourceSurface->Height();
+    PRUint8 *srcEnd = src + 4*dim;
+
+    if (src == dst) {
+        PRUint8 buffer[4];
+        for (; src != srcEnd; src += 4) {
+            buffer[0] = src[2];
+            buffer[1] = src[1];
+            buffer[2] = src[0];
+
+            src[0] = buffer[0];
+            src[1] = buffer[1];
+            src[2] = buffer[2];
+        }
+    } else {
+        for (; src != srcEnd; src += 4, dst += 4) {
+            dst[0] = src[2];
+            dst[1] = src[1];
+            dst[2] = src[0];
+            dst[3] = src[3];
+        }
+    }
+}
+
 static bool
 IsSafeImageTransformComponent(gfxFloat aValue)
 {
   return aValue >= -32768 && aValue <= 32767;
 }
 
 /**
  * This returns the fastest operator to use for solid surfaces which have no
--- a/gfx/thebes/gfxUtils.h
+++ b/gfx/thebes/gfxUtils.h
@@ -60,16 +60,19 @@ public:
      * If the source is not ImageFormatARGB32, no operation is performed.  If
      * aDestSurface is given, the data is copied over.
      */
     static void PremultiplyImageSurface(gfxImageSurface *aSourceSurface,
                                         gfxImageSurface *aDestSurface = nsnull);
     static void UnpremultiplyImageSurface(gfxImageSurface *aSurface,
                                           gfxImageSurface *aDestSurface = nsnull);
 
+    static void ConvertBGRAtoRGBA(gfxImageSurface *aSourceSurface,
+                                  gfxImageSurface *aDestSurface = nsnull);
+
     /**
      * Draw something drawable while working around limitations like bad support
      * for EXTEND_PAD, lack of source-clipping, or cairo / pixman bugs with
      * extreme user-space-to-image-space transforms.
      *
      * The input parameters here usually come from the output of our image
      * snapping algorithm in nsLayoutUtils.cpp.
      * This method is split from nsLayoutUtils::DrawPixelSnapped to allow for
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -204,16 +204,19 @@ void nsPNGDecoder::EndImageFrame()
   numFrames = mImage.GetNumFrames();
 
   // We can't use mPNG->num_frames_read as it may be one ahead.
   if (numFrames > 1) {
     // Tell the image renderer that the frame is complete
     if (mFrameHasNoAlpha)
       mImage.SetFrameHasNoAlpha(numFrames - 1);
 
+    // PNG is always non-premult
+    mImage.SetFrameAsNonPremult(numFrames - 1, true);
+
     PostInvalidation(mFrameRect);
   }
 #endif
 
   PostFrameStop();
 }
 
 void
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -295,16 +295,23 @@ Decoder::PostInvalidation(nsIntRect& aRe
 void
 Decoder::PostDecodeDone()
 {
   NS_ABORT_IF_FALSE(!IsSizeDecode(), "Can't be done with decoding with size decode!");
   NS_ABORT_IF_FALSE(!mInFrame, "Can't be done decoding if we're mid-frame!");
   NS_ABORT_IF_FALSE(!mDecodeDone, "Decode already done!");
   mDecodeDone = true;
 
+  // Set premult before DecodingComplete(), since DecodingComplete() calls Optimize()
+  int frames = GetFrameCount();
+  bool isNonPremult = GetDecodeFlags() & DECODER_NO_PREMULTIPLY_ALPHA;
+  for (int i = 0; i < frames; i++) {
+    mImage.SetFrameAsNonPremult(i, isNonPremult);
+  }
+
   // Notify
   mImage.DecodingComplete();
   if (mObserver) {
     mObserver->OnStopContainer(nsnull, &mImage);
     mObserver->OnStopDecode(nsnull, NS_OK, nsnull);
   }
 }
 
--- a/image/src/Decoder.h
+++ b/image/src/Decoder.h
@@ -145,18 +145,18 @@ public:
   bool GetDecodeDone() const {
     return mDecodeDone;
   }
 
   // flags.  Keep these in sync with imgIContainer.idl.
   // SetDecodeFlags must be called before Init(), otherwise
   // default flags are assumed.
   enum {
-    DECODER_NO_PREMULTIPLY_ALPHA = 0x2,
-    DECODER_NO_COLORSPACE_CONVERSION = 0x4
+    DECODER_NO_PREMULTIPLY_ALPHA = 0x2,     // imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA
+    DECODER_NO_COLORSPACE_CONVERSION = 0x4  // imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION
   };
   void SetDecodeFlags(PRUint32 aFlags) { mDecodeFlags = aFlags; }
   PRUint32 GetDecodeFlags() { return mDecodeFlags; }
 
   // Use HistogramCount as an invalid Histogram ID
   virtual Telemetry::ID SpeedHistogram() { return Telemetry::HistogramCount; }
 
 protected:
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -1320,16 +1320,35 @@ RasterImage::SetFrameHasNoAlpha(PRUint32
   NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
 
   frame->SetHasNoAlpha();
 
   return NS_OK;
 }
 
 nsresult
+RasterImage::SetFrameAsNonPremult(PRUint32 aFrameNum, bool aIsNonPremult)
+{
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  NS_ABORT_IF_FALSE(aFrameNum < mFrames.Length(), "Invalid frame index!");
+  if (aFrameNum >= mFrames.Length())
+    return NS_ERROR_INVALID_ARG;
+
+  imgFrame* frame = GetImgFrame(aFrameNum);
+  NS_ABORT_IF_FALSE(frame, "Calling SetFrameAsNonPremult on frame that doesn't exist!");
+  NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+
+  frame->SetAsNonPremult(aIsNonPremult);
+
+  return NS_OK;
+}
+
+nsresult
 RasterImage::DecodingComplete()
 {
   if (mError)
     return NS_ERROR_FAILURE;
 
   // Flag that we're done decoding.
   // XXX - these should probably be combined when we fix animated image
   // discarding with bug 500402.
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -239,16 +239,17 @@ public:
   void ForceDiscard() { Discard(/* force = */ true); }
 
   /* Callbacks for decoders */
   nsresult SetFrameDisposalMethod(PRUint32 aFrameNum,
                                   PRInt32 aDisposalMethod);
   nsresult SetFrameTimeout(PRUint32 aFrameNum, PRInt32 aTimeout);
   nsresult SetFrameBlendMethod(PRUint32 aFrameNum, PRInt32 aBlendMethod);
   nsresult SetFrameHasNoAlpha(PRUint32 aFrameNum);
+  nsresult SetFrameAsNonPremult(PRUint32 aFrameNum, bool aIsNonPremult);
 
   /**
    * Sets the size of the container. This should only be called by the
    * decoder. This function may be called multiple times, but will throw an
    * error if subsequent calls do not match the first.
    */
   nsresult SetSize(PRInt32 aWidth, PRInt32 aHeight);
 
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -140,22 +140,23 @@ imgFrame::imgFrame() :
   mPalettedImageData(nsnull),
   mSinglePixelColor(0),
   mTimeout(100),
   mDisposalMethod(0), /* imgIContainer::kDisposeNotSpecified */
   mBlendMethod(1), /* imgIContainer::kBlendOver */
   mSinglePixel(false),
   mNeverUseDeviceSurface(false),
   mFormatChanged(false),
-  mCompositingFailed(false)
+  mCompositingFailed(false),
+  mNonPremult(false),
 #ifdef USE_WIN_SURFACE
-  , mIsDDBSurface(false)
+  mIsDDBSurface(false),
 #endif
-  , mLocked(false)
-  , mInformedDiscardTracker(false)
+  mLocked(false),
+  mInformedDiscardTracker(false)
 {
   static bool hasCheckedOptimize = false;
   if (!hasCheckedOptimize) {
     if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) {
       gDisableOptimize = true;
     }
     hasCheckedOptimize = true;
   }
@@ -249,37 +250,43 @@ nsresult imgFrame::Init(PRInt32 aX, PRIn
 nsresult imgFrame::Optimize()
 {
   if (gDisableOptimize)
     return NS_OK;
 
   if (mPalettedImageData || mOptSurface || mSinglePixel)
     return NS_OK;
 
+  // Don't do single-color opts on non-premult data.
+  // Cairo doesn't support non-premult single-colors.
+  if (mNonPremult)
+    return NS_OK;
+
   /* Figure out if the entire image is a constant color */
 
   // this should always be true
   if (mImageSurface->Stride() == mSize.width * 4) {
     PRUint32 *imgData = (PRUint32*) mImageSurface->Data();
     PRUint32 firstPixel = * (PRUint32*) imgData;
     PRUint32 pixelCount = mSize.width * mSize.height + 1;
 
     while (--pixelCount && *imgData++ == firstPixel)
       ;
 
     if (pixelCount == 0) {
       // all pixels were the same
       if (mFormat == gfxASurface::ImageFormatARGB32 ||
           mFormat == gfxASurface::ImageFormatRGB24)
       {
-        mSinglePixelColor = gfxRGBA
-          (firstPixel,
-           (mFormat == gfxImageSurface::ImageFormatRGB24 ?
-            gfxRGBA::PACKED_XRGB :
-            gfxRGBA::PACKED_ARGB_PREMULTIPLIED));
+        // Should already be premult if desired.
+        gfxRGBA::PackedColorType inputType = gfxRGBA::PACKED_XRGB;
+        if (mFormat == gfxASurface::ImageFormatARGB32)
+          inputType = gfxRGBA::PACKED_ARGB_PREMULTIPLIED;
+
+        mSinglePixelColor = gfxRGBA(firstPixel, inputType);
 
         mSinglePixel = true;
 
         // blow away the older surfaces (if they exist), to release their memory
         mImageSurface = nsnull;
         mOptSurface = nsnull;
 #ifdef USE_WIN_SURFACE
         mWinSurface = nsnull;
@@ -429,16 +436,17 @@ imgFrame::SurfaceForDrawing(bool        
     tmpCtx.SetOperator(gfxContext::OPERATOR_SOURCE);
     if (mSinglePixel) {
       tmpCtx.SetDeviceColor(mSinglePixelColor);
     } else {
       tmpCtx.SetSource(ThebesSurface(), gfxPoint(aPadding.left, aPadding.top));
     }
     tmpCtx.Rectangle(available);
     tmpCtx.Fill();
+
     return SurfaceWithFormat(new gfxSurfaceDrawable(surface, size), format);
   }
 
   // Not tiling, and we have a surface, so we can account for
   // padding and/or a partial decode just by twiddling parameters.
   // First, update our user-space fill rect.
   aSourceRect = aSourceRect.Intersect(available);
   gfxMatrix imageSpaceToUserSpace = aUserSpaceToImageSpace;
@@ -512,16 +520,18 @@ nsresult imgFrame::Extract(const nsIntRe
   // surface for the sub-image; this ensures that the correct
   // (albeit slower) Cairo fallback scaler will be used.
   subImage->mNeverUseDeviceSurface = true;
 
   nsresult rv = subImage->Init(0, 0, aRegion.width, aRegion.height, 
                                mFormat, mPaletteDepth);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  subImage->SetAsNonPremult(mNonPremult);
+
   // scope to destroy ctx
   {
     gfxContext ctx(subImage->ThebesSurface());
     ctx.SetOperator(gfxContext::OPERATOR_SOURCE);
     if (mSinglePixel) {
       ctx.SetDeviceColor(mSinglePixelColor);
     } else {
       // SetSource() places point (0,0) of its first argument at
@@ -767,16 +777,21 @@ bool imgFrame::ImageComplete() const
 void imgFrame::SetHasNoAlpha()
 {
   if (mFormat == gfxASurface::ImageFormatARGB32) {
       mFormat = gfxASurface::ImageFormatRGB24;
       mFormatChanged = true;
   }
 }
 
+void imgFrame::SetAsNonPremult(bool aIsNonPremult)
+{
+  mNonPremult = aIsNonPremult;
+}
+
 bool imgFrame::GetCompositingFailed() const
 {
   return mCompositingFailed;
 }
 
 void imgFrame::SetCompositingFailed(bool val)
 {
   mCompositingFailed = val;
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -87,16 +87,17 @@ public:
 
   PRInt32 GetFrameDisposalMethod() const;
   void SetFrameDisposalMethod(PRInt32 aFrameDisposalMethod);
   PRInt32 GetBlendMethod() const;
   void SetBlendMethod(PRInt32 aBlendMethod);
   bool ImageComplete() const;
 
   void SetHasNoAlpha();
+  void SetAsNonPremult(bool aIsNonPremult);
 
   bool GetCompositingFailed() const;
   void SetCompositingFailed(bool val);
 
   nsresult LockImageData();
   nsresult UnlockImageData();
 
   nsresult GetSurface(gfxASurface **aSurface) const
@@ -175,28 +176,30 @@ private: // data
   nsIntRect    mDecoded;
 
   // The palette and image data for images that are paletted, since Cairo
   // doesn't support these images.
   // The paletted data comes first, then the image data itself.
   // Total length is PaletteDataLength() + GetImageDataLength().
   PRUint8*     mPalettedImageData;
 
+  // Note that the data stored in gfxRGBA is *non-alpha-premultiplied*.
   gfxRGBA      mSinglePixelColor;
 
   PRInt32      mTimeout; // -1 means display forever
   PRInt32      mDisposalMethod;
 
   gfxASurface::gfxImageFormat mFormat;
   PRUint8      mPaletteDepth;
   PRInt8       mBlendMethod;
   bool mSinglePixel;
   bool mNeverUseDeviceSurface;
   bool mFormatChanged;
   bool mCompositingFailed;
+  bool mNonPremult;
   /** Indicates if the image data is currently locked */
   bool mLocked;
 
   /** Have we called DiscardTracker::InformAllocation()? */
   bool mInformedDiscardTracker;
 
 #ifdef XP_WIN
   bool mIsDDBSurface;
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -4045,18 +4045,19 @@ nsLayoutUtils::SurfaceFromElementResult
 nsLayoutUtils::SurfaceFromElement(dom::Element* aElement,
                                   PRUint32 aSurfaceFlags)
 {
   SurfaceFromElementResult result;
   nsresult rv;
 
   bool forceCopy = (aSurfaceFlags & SFE_WANT_NEW_SURFACE) != 0;
   bool wantImageSurface = (aSurfaceFlags & SFE_WANT_IMAGE_SURFACE) != 0;
-
-  if (aSurfaceFlags & SFE_NO_PREMULTIPLY_ALPHA) {
+  bool premultAlpha = (aSurfaceFlags & SFE_NO_PREMULTIPLY_ALPHA) == 0;
+
+  if (!premultAlpha) {
     forceCopy = true;
     wantImageSurface = true;
   }
 
   // If it's a <canvas>, we may be able to just grab its internal surface
   if (nsHTMLCanvasElement* canvas = nsHTMLCanvasElement::FromContent(aElement)) {
     gfxIntSize size = canvas->GetSize();
 
@@ -4077,31 +4078,26 @@ nsLayoutUtils::SurfaceFromElement(dom::E
       if (wantImageSurface) {
         surf = new gfxImageSurface(size, gfxASurface::ImageFormatARGB32);
       } else {
         surf = gfxPlatform::GetPlatform()->CreateOffscreenSurface(size, gfxASurface::CONTENT_COLOR_ALPHA);
       }
 
       nsRefPtr<gfxContext> ctx = new gfxContext(surf);
       // XXX shouldn't use the external interface, but maybe we can layerify this
-      rv = canvas->RenderContextsExternal(ctx, gfxPattern::FILTER_NEAREST);
+      PRUint32 flags = premultAlpha ? nsHTMLCanvasElement::RenderFlagPremultAlpha : 0;
+      rv = canvas->RenderContextsExternal(ctx, gfxPattern::FILTER_NEAREST, flags);
       if (NS_FAILED(rv))
         return result;
     }
 
     // Ensure that any future changes to the canvas trigger proper invalidation,
     // in case this is being used by -moz-element()
     canvas->MarkContextClean();
 
-    if (aSurfaceFlags & SFE_NO_PREMULTIPLY_ALPHA) {
-      // we can modify this surface since we force a copy above when
-      // when NO_PREMULTIPLY_ALPHA is set
-      gfxUtils::UnpremultiplyImageSurface(static_cast<gfxImageSurface*>(surf.get()));
-    }
-
     result.mSurface = surf;
     result.mSize = size;
     result.mPrincipal = aElement->NodePrincipal();
     result.mIsWriteOnly = canvas->IsWriteOnly();
 
     return result;
   }