Bug 1355763 - Add gfxAlphaType for specifying opaque/premult/non-premult. - r=daoshengmu
authorJeff Gilbert <jgilbert@mozilla.com>
Wed, 12 Apr 2017 02:55:23 -0700
changeset 562194 f0e8fd36b730c776f054d462e0375081288c6ff0
parent 562193 ab34b3ca99013d693ef70fbcc3dba1d5921fdcf2
child 562195 bbd23665a2d1a5c514b4d2f1f93e3ff46aa74dcc
push id53981
push userbmo:jgilbert@mozilla.com
push dateThu, 13 Apr 2017 16:16:08 +0000
reviewersdaoshengmu
bugs1355763
milestone55.0a1
Bug 1355763 - Add gfxAlphaType for specifying opaque/premult/non-premult. - r=daoshengmu MozReview-Commit-ID: Jzr8aPYlEcO
dom/canvas/CanvasRenderingContext2D.h
dom/canvas/ImageBitmap.cpp
dom/canvas/ImageBitmap.h
dom/canvas/ImageBitmapRenderingContext.cpp
dom/canvas/ImageBitmapRenderingContext.h
dom/canvas/OffscreenCanvas.cpp
dom/canvas/OffscreenCanvas.h
dom/canvas/TexUnpackBlob.cpp
dom/canvas/TexUnpackBlob.h
dom/canvas/WebGLContext.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLTextureUpload.cpp
dom/canvas/nsICanvasRenderingContextInternal.h
dom/html/HTMLCanvasElement.cpp
dom/html/HTMLCanvasElement.h
gfx/layers/ImageContainer.h
gfx/thebes/gfxTypes.h
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -404,17 +404,17 @@ public:
   void SetImageSmoothingEnabled(bool aImageSmoothingEnabled) override
   {
     if (aImageSmoothingEnabled != CurrentState().imageSmoothingEnabled) {
       CurrentState().imageSmoothingEnabled = aImageSmoothingEnabled;
     }
   }
 
   void DrawWindow(nsGlobalWindow& aWindow, double aX, double aY,
-		  double aW, double aH,
+                  double aW, double aH,
                   const nsAString& aBgColor, uint32_t aFlags,
                   mozilla::ErrorResult& aError);
 
   enum RenderingMode {
     SoftwareBackendMode,
     OpenGLBackendMode,
     DefaultBackendMode
   };
@@ -446,21 +446,22 @@ public:
   NS_IMETHOD SetDimensions(int32_t aWidth, int32_t aHeight) override;
   NS_IMETHOD InitializeWithDrawTarget(nsIDocShell* aShell,
                                       NotNull<gfx::DrawTarget*> aTarget) override;
 
   NS_IMETHOD GetInputStream(const char* aMimeType,
                             const char16_t* aEncoderOptions,
                             nsIInputStream** aStream) override;
 
-  already_AddRefed<mozilla::gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr) override
+  already_AddRefed<mozilla::gfx::SourceSurface>
+  GetSurfaceSnapshot(gfxAlphaType* aOutAlphaType = nullptr) override
   {
     EnsureTarget();
-    if (aPremultAlpha) {
-      *aPremultAlpha = true;
+    if (aOutAlphaType) {
+      *aOutAlphaType = (mOpaque ? gfxAlphaType::Opaque : gfxAlphaType::Premult);
     }
     return mTarget->Snapshot();
   }
 
   NS_IMETHOD SetIsOpaque(bool aIsOpaque) override;
   bool GetIsOpaque() override { return mOpaque; }
   NS_IMETHOD Reset() override;
   already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -427,23 +427,23 @@ HasRasterImage(HTMLImageElement& aImageE
       return true;
     }
   }
 
   return false;
 }
 
 ImageBitmap::ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData,
-                         bool aIsPremultipliedAlpha /* = true */)
+                         gfxAlphaType aAlphaType)
   : mParent(aGlobal)
   , mData(aData)
   , mSurface(nullptr)
   , mDataWrapper(new ImageUtils(mData))
   , mPictureRect(0, 0, aData->GetSize().width, aData->GetSize().height)
-  , mIsPremultipliedAlpha(aIsPremultipliedAlpha)
+  , mAlphaType(aAlphaType)
   , mIsCroppingAreaOutSideOfSourceImage(false)
 {
   MOZ_ASSERT(aData, "aData is null in ImageBitmap constructor.");
 }
 
 ImageBitmap::~ImageBitmap()
 {
 }
@@ -617,23 +617,20 @@ ImageBitmap::PrepareForDrawTarget(gfx::D
     target->CopySurface(mSurface, surfPortion, dest);
     mSurface = target->Snapshot();
 
     // Make mCropRect match new surface we've cropped to
     mPictureRect.MoveTo(0, 0);
   }
 
   // Pre-multiply alpha here.
-  // Apply pre-multiply alpha only if mIsPremultipliedAlpha is false.
   // Ignore this step if the source surface does not have alpha channel; this
   // kind of source surfaces might come form layers::PlanarYCbCrImage.
-  if (!mIsPremultipliedAlpha &&
-      mSurface->GetFormat() != SurfaceFormat::B8G8R8X8 &&
-      mSurface->GetFormat() != SurfaceFormat::R8G8B8X8 &&
-      mSurface->GetFormat() != SurfaceFormat::X8R8G8B8) {
+  MOZ_ASSERT(IsOpaque(mSurface->GetFormat()) == (mAlphaType == gfxAlphaType::Opaque));
+  if (mAlphaType == gfxAlphaType::NonPremult) {
     MOZ_ASSERT(mSurface->GetFormat() == SurfaceFormat::R8G8B8A8 ||
                mSurface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
                mSurface->GetFormat() == SurfaceFormat::A8R8G8B8);
 
     RefPtr<DataSourceSurface> dstSurface = mSurface->GetDataSurface();
     MOZ_ASSERT(dstSurface);
 
     RefPtr<DataSourceSurface> srcSurface;
@@ -687,33 +684,32 @@ ImageBitmap::TransferAsImage()
   return image.forget();
 }
 
 UniquePtr<ImageBitmapCloneData>
 ImageBitmap::ToCloneData() const
 {
   UniquePtr<ImageBitmapCloneData> result(new ImageBitmapCloneData());
   result->mPictureRect = mPictureRect;
-  result->mIsPremultipliedAlpha = mIsPremultipliedAlpha;
+  result->mAlphaType = mAlphaType;
   result->mIsCroppingAreaOutSideOfSourceImage = mIsCroppingAreaOutSideOfSourceImage;
   RefPtr<SourceSurface> surface = mData->GetAsSourceSurface();
   result->mSurface = surface->GetDataSurface();
   MOZ_ASSERT(result->mSurface);
 
   return Move(result);
 }
 
 /* static */ already_AddRefed<ImageBitmap>
 ImageBitmap::CreateFromCloneData(nsIGlobalObject* aGlobal,
                                  ImageBitmapCloneData* aData)
 {
   RefPtr<layers::Image> data = CreateImageFromSurface(aData->mSurface);
 
-  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data,
-                                            aData->mIsPremultipliedAlpha);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, aData->mAlphaType);
 
   // Report memory allocation.
   RegisterAllocation(aGlobal, aData->mSurface);
 
   ret->mIsCroppingAreaOutSideOfSourceImage =
     aData->mIsCroppingAreaOutSideOfSourceImage;
 
   ErrorResult rv;
@@ -921,16 +917,18 @@ ImageBitmap::CreateInternal(nsIGlobalObj
 {
   // Copy data into SourceSurface.
   dom::Uint8ClampedArray array;
   DebugOnly<bool> inited = array.Init(aImageData.GetDataObject());
   MOZ_ASSERT(inited);
 
   array.ComputeLengthAndData();
   const SurfaceFormat FORMAT = SurfaceFormat::R8G8B8A8;
+  // ImageData's underlying data is not alpha-premultiplied.
+  const auto alphaType = gfxAlphaType::NonPremult;
   const uint32_t BYTES_PER_PIXEL = BytesPerPixel(FORMAT);
   const uint32_t imageWidth = aImageData.Width();
   const uint32_t imageHeight = aImageData.Height();
   const uint32_t imageStride = imageWidth * BYTES_PER_PIXEL;
   const uint32_t dataLength = array.Length();
   const gfx::IntSize imageSize(imageWidth, imageHeight);
 
   // Check the ImageData is neutered or not.
@@ -959,18 +957,17 @@ ImageBitmap::CreateInternal(nsIGlobalObj
   }
 
   if (NS_WARN_IF(!data)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   // Create an ImageBimtap.
-  // ImageData's underlying data is not alpha-premultiplied.
-  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, false);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, alphaType);
 
   // Report memory allocation.
   RegisterAllocation(aGlobal, data);
 
   // The cropping information has been handled in the CreateImageFromRawData()
   // function.
 
   // Set mIsCroppingAreaOutSideOfSourceImage.
@@ -1030,17 +1027,17 @@ ImageBitmap::CreateInternal(nsIGlobalObj
                             const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
 {
   if (!aImageBitmap.mData) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   RefPtr<layers::Image> data = aImageBitmap.mData;
-  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, aImageBitmap.mIsPremultipliedAlpha);
+  RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, aImageBitmap.mAlphaType);
 
   // Set the picture rectangle.
   if (ret && aCropRect.isSome()) {
     ret->SetPictureRect(aCropRect.ref(), aRv);
   }
 
   // Set mIsCroppingAreaOutSideOfSourceImage.
   if (aImageBitmap.mIsCroppingAreaOutSideOfSourceImage == true) {
@@ -1494,45 +1491,45 @@ ImageBitmap::ReadStructuredClone(JSConte
   MOZ_ASSERT(aCx);
   MOZ_ASSERT(aReader);
   // aParent might be null.
 
   uint32_t picRectX_;
   uint32_t picRectY_;
   uint32_t picRectWidth_;
   uint32_t picRectHeight_;
-  uint32_t isPremultipliedAlpha_;
+  uint32_t alphaType_;
   uint32_t isCroppingAreaOutSideOfSourceImage_;
 
   if (!JS_ReadUint32Pair(aReader, &picRectX_, &picRectY_) ||
       !JS_ReadUint32Pair(aReader, &picRectWidth_, &picRectHeight_) ||
-      !JS_ReadUint32Pair(aReader, &isPremultipliedAlpha_,
+      !JS_ReadUint32Pair(aReader, &alphaType_,
                                   &isCroppingAreaOutSideOfSourceImage_)) {
     return nullptr;
   }
 
   int32_t picRectX = BitwiseCast<int32_t>(picRectX_);
   int32_t picRectY = BitwiseCast<int32_t>(picRectY_);
   int32_t picRectWidth = BitwiseCast<int32_t>(picRectWidth_);
   int32_t picRectHeight = BitwiseCast<int32_t>(picRectHeight_);
+  const auto alphaType = BitwiseCast<gfxAlphaType>(alphaType_);
 
   // Create a new ImageBitmap.
   MOZ_ASSERT(!aClonedSurfaces.IsEmpty());
   MOZ_ASSERT(aIndex < aClonedSurfaces.Length());
 
   // RefPtr<ImageBitmap> needs to go out of scope before toObjectOrNull() is
   // called because the static analysis thinks dereferencing XPCOM objects
   // can GC (because in some cases it can!), and a return statement with a
   // JSObject* type means that JSObject* is on the stack as a raw pointer
   // while destructors are running.
   JS::Rooted<JS::Value> value(aCx);
   {
     RefPtr<layers::Image> img = CreateImageFromSurface(aClonedSurfaces[aIndex]);
-    RefPtr<ImageBitmap> imageBitmap =
-      new ImageBitmap(aParent, img, isPremultipliedAlpha_);
+    RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(aParent, img, alphaType);
 
     imageBitmap->mIsCroppingAreaOutSideOfSourceImage =
       isCroppingAreaOutSideOfSourceImage_;
 
     ErrorResult error;
     imageBitmap->SetPictureRect(IntRect(picRectX, picRectY,
                                         picRectWidth, picRectHeight), error);
     if (NS_WARN_IF(error.Failed())) {
@@ -1558,26 +1555,26 @@ ImageBitmap::WriteStructuredClone(JSStru
 {
   MOZ_ASSERT(aWriter);
   MOZ_ASSERT(aImageBitmap);
 
   const uint32_t picRectX = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.x);
   const uint32_t picRectY = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.y);
   const uint32_t picRectWidth = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.width);
   const uint32_t picRectHeight = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.height);
-  const uint32_t isPremultipliedAlpha = aImageBitmap->mIsPremultipliedAlpha ? 1 : 0;
+  const uint32_t alphaType = BitwiseCast<uint32_t>(aImageBitmap->mAlphaType);
   const uint32_t isCroppingAreaOutSideOfSourceImage = aImageBitmap->mIsCroppingAreaOutSideOfSourceImage ? 1 : 0;
 
   // Indexing the cloned surfaces and send the index to the receiver.
   uint32_t index = aClonedSurfaces.Length();
 
   if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, SCTAG_DOM_IMAGEBITMAP, index)) ||
       NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectX, picRectY)) ||
       NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectWidth, picRectHeight)) ||
-      NS_WARN_IF(!JS_WriteUint32Pair(aWriter, isPremultipliedAlpha,
+      NS_WARN_IF(!JS_WriteUint32Pair(aWriter, alphaType,
                                               isCroppingAreaOutSideOfSourceImage))) {
     return false;
   }
 
   RefPtr<SourceSurface> surface =
     aImageBitmap->mData->GetAsSourceSurface();
   RefPtr<DataSourceSurface> snapshot = surface->GetDataSurface();
   RefPtr<DataSourceSurface> dstDataSurface;
@@ -2130,17 +2127,18 @@ ImageBitmap::Create(nsIGlobalObject* aGl
 
   if (NS_WARN_IF(!data)) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return promise.forget();
   }
 
   // Create an ImageBimtap.
   // Assume the data from an external buffer is not alpha-premultiplied.
-  RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(aGlobal, data, false);
+  RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(aGlobal, data,
+                                                    gfxAlphaType::NonPremult);
 
   // Report memory allocation.
   RegisterAllocation(aGlobal, data);
 
   // We don't need to call SetPictureRect() here because there is no cropping
   // supported and the ImageBitmap's mPictureRect is the size of the source
   // image in default
 
--- a/dom/canvas/ImageBitmap.h
+++ b/dom/canvas/ImageBitmap.h
@@ -58,17 +58,17 @@ class ImageUtils;
 template<typename T> class MapDataIntoBufferSource;
 class Promise;
 class PostMessageEvent; // For StructuredClone between windows.
 
 struct ImageBitmapCloneData final
 {
   RefPtr<gfx::DataSourceSurface> mSurface;
   gfx::IntRect mPictureRect;
-  bool mIsPremultipliedAlpha;
+  gfxAlphaType mAlphaType;
   bool mIsCroppingAreaOutSideOfSourceImage;
 };
 
 /*
  * ImageBitmap is an opaque handler to several kinds of image-like objects from
  * HTMLImageElement, HTMLVideoElement, HTMLCanvasElement, ImageData to
  * CanvasRenderingContext2D and Image Blob.
  *
@@ -195,17 +195,17 @@ protected:
    *    re-decoding if the original decoded data is alpha-premultiplied) and
    * 2) while decoding a blob. But we do not do it in both code path too.
    *
    * ImageData's underlying data is triggered as non-premultipliedAlpha, so set
    * the aIsPremultipliedAlpha to be false in the
    * CreateInternal(from ImageData) method.
    */
   ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData,
-              bool aIsPremultipliedAlpha = true);
+              gfxAlphaType aAlphaType = gfxAlphaType::Premult);
 
   virtual ~ImageBitmap();
 
   void SetPictureRect(const gfx::IntRect& aRect, ErrorResult& aRv);
 
   void SetIsCroppingAreaOutSideOfSourceImage(const gfx::IntSize& aSourceSize,
                                              const Maybe<gfx::IntRect>& aCroppingRect);
 
@@ -267,17 +267,17 @@ protected:
    * Note that if the CreateInternal() copies and crops data from the source
    * image, then this mPictureRect is just the size of the final mData.
    *
    * The mPictureRect will be used at PrepareForDrawTarget() while user is going
    * to draw this ImageBitmap into a HTMLCanvasElement.
    */
   gfx::IntRect mPictureRect;
 
-  const bool mIsPremultipliedAlpha;
+  const gfxAlphaType mAlphaType;
 
   /*
    * Set mIsCroppingAreaOutSideOfSourceImage if image bitmap was cropped to the
    * source rectangle so that it contains any transparent black pixels (cropping
    * area is outside of the source image).
    * This is used in mapDataInto() to check if we should reject promise with
    * IndexSizeError.
    */
--- a/dom/canvas/ImageBitmapRenderingContext.cpp
+++ b/dom/canvas/ImageBitmapRenderingContext.cpp
@@ -177,24 +177,24 @@ ImageBitmapRenderingContext::GetInputStr
     return NS_ERROR_FAILURE;
   }
 
   return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer.get(), format,
                                       encoder, aEncoderOptions, aStream);
 }
 
 already_AddRefed<mozilla::gfx::SourceSurface>
-ImageBitmapRenderingContext::GetSurfaceSnapshot(bool* aPremultAlpha)
+ImageBitmapRenderingContext::GetSurfaceSnapshot(gfxAlphaType* const aOutAlphaType)
 {
   if (!mImage) {
     return nullptr;
   }
 
-  if (aPremultAlpha) {
-    *aPremultAlpha = true;
+  if (aOutAlphaType) {
+    *aOutAlphaType = (GetIsOpaque() ? gfxAlphaType::Opaque : gfxAlphaType::Premult);
   }
 
   RefPtr<SourceSurface> surface = mImage->GetAsSourceSurface();
   if (surface->GetSize() != IntSize(mWidth, mHeight)) {
     return MatchWithIntrinsicSize();
   }
 
   return surface.forget();
--- a/dom/canvas/ImageBitmapRenderingContext.h
+++ b/dom/canvas/ImageBitmapRenderingContext.h
@@ -59,17 +59,17 @@ public:
                                       NotNull<gfx::DrawTarget*> aTarget) override;
 
   virtual mozilla::UniquePtr<uint8_t[]> GetImageBuffer(int32_t* aFormat) override;
   NS_IMETHOD GetInputStream(const char* aMimeType,
                             const char16_t* aEncoderOptions,
                             nsIInputStream** aStream) override;
 
   virtual already_AddRefed<mozilla::gfx::SourceSurface>
-  GetSurfaceSnapshot(bool* aPremultAlpha = nullptr) override;
+  GetSurfaceSnapshot(gfxAlphaType* aOutAlphaType) override;
 
   NS_IMETHOD SetIsOpaque(bool aIsOpaque) override;
   virtual bool GetIsOpaque() override;
   NS_IMETHOD Reset() override;
   virtual already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                                  Layer* aOldLayer,
                                                  LayerManager* aManager,
                                                  bool aMirror = false) override;
--- a/dom/canvas/OffscreenCanvas.cpp
+++ b/dom/canvas/OffscreenCanvas.cpp
@@ -297,23 +297,23 @@ OffscreenCanvas::ToBlob(JSContext* aCx,
 
   CanvasRenderingContextHelper::ToBlob(aCx, global,
                                        callback, aType, aParams, aRv);
 
   return promise.forget();
 }
 
 already_AddRefed<gfx::SourceSurface>
-OffscreenCanvas::GetSurfaceSnapshot(bool* aPremultAlpha)
+OffscreenCanvas::GetSurfaceSnapshot(gfxAlphaType* const aOutAlphaType)
 {
   if (!mCurrentContext) {
     return nullptr;
   }
 
-  return mCurrentContext->GetSurfaceSnapshot(aPremultAlpha);
+  return mCurrentContext->GetSurfaceSnapshot(aOutAlphaType);
 }
 
 nsCOMPtr<nsIGlobalObject>
 OffscreenCanvas::GetGlobalObject()
 {
   if (NS_IsMainThread()) {
     return GetParentObject();
   }
--- a/dom/canvas/OffscreenCanvas.h
+++ b/dom/canvas/OffscreenCanvas.h
@@ -119,17 +119,17 @@ public:
          JS::Handle<JS::Value> aParams,
          ErrorResult& aRv);
 
   nsICanvasRenderingContextInternal* GetContext() const
   {
     return mCurrentContext;
   }
 
-  already_AddRefed<gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr);
+  already_AddRefed<gfx::SourceSurface> GetSurfaceSnapshot(gfxAlphaType* aOutAlphaType = nullptr);
 
   static already_AddRefed<OffscreenCanvas>
   CreateFromCloneData(nsIGlobalObject* aGlobal, OffscreenCanvasCloneData* aData);
 
   static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
 
   // Return true on main-thread, and return gfx.offscreencanvas.enabled
   // on worker thread.
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -265,31 +265,31 @@ ZeroOn2D(TexImageTarget target, uint32_t
 static uint32_t
 FallbackOnZero(uint32_t val, uint32_t fallback)
 {
     return (val ? val : fallback);
 }
 
 TexUnpackBlob::TexUnpackBlob(const WebGLContext* webgl, TexImageTarget target,
                              uint32_t rowLength, uint32_t width, uint32_t height,
-                             uint32_t depth, bool srcIsPremult)
+                             uint32_t depth, gfxAlphaType srcAlphaType)
     : mAlignment(webgl->mPixelStore_UnpackAlignment)
     , mRowLength(rowLength)
     , mImageHeight(FallbackOnZero(ZeroOn2D(target, webgl->mPixelStore_UnpackImageHeight),
                                   height))
 
     , mSkipPixels(webgl->mPixelStore_UnpackSkipPixels)
     , mSkipRows(webgl->mPixelStore_UnpackSkipRows)
     , mSkipImages(ZeroOn2D(target, webgl->mPixelStore_UnpackSkipImages))
 
     , mWidth(width)
     , mHeight(height)
     , mDepth(depth)
 
-    , mSrcIsPremult(srcIsPremult)
+    , mSrcAlphaType(srcAlphaType)
 
     , mNeedsExactUpload(false)
 {
     MOZ_ASSERT_IF(!IsTarget3D(target), mDepth == 1);
 }
 
 static bool
 HasColorAndAlpha(const WebGLTexelFormat format)
@@ -322,26 +322,37 @@ TexUnpackBlob::ConvertIfNeeded(WebGLCont
     MOZ_ASSERT(srcFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
     MOZ_ASSERT(dstFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
 
     *out_begin = srcBegin;
 
     if (!rowLength || !rowCount)
         return true;
 
+    const auto srcIsPremult = (mSrcAlphaType == gfxAlphaType::Premult);
     const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
+    const auto fnHasPremultMismatch = [&]() {
+        if (mSrcAlphaType == gfxAlphaType::Opaque)
+            return false;
+
+        if (!HasColorAndAlpha(srcFormat))
+            return false;
+
+        return srcIsPremult != dstIsPremult;
+    };
+
     const auto srcOrigin = (webgl->mPixelStore_FlipY ? gl::OriginPos::TopLeft
                                                      : gl::OriginPos::BottomLeft);
     const auto dstOrigin = gl::OriginPos::BottomLeft;
 
     if (srcFormat != dstFormat) {
         webgl->GeneratePerfWarning("%s: Conversion requires pixel reformatting. (%u->%u)",
                                    funcName, uint32_t(srcFormat),
                                    uint32_t(dstFormat));
-    } else if (mSrcIsPremult != dstIsPremult && HasColorAndAlpha(srcFormat)) {
+    } else if (fnHasPremultMismatch()) {
         webgl->GeneratePerfWarning("%s: Conversion requires change in"
                                    " alpha-premultiplication.",
                                    funcName);
     } else if (srcOrigin != dstOrigin) {
         webgl->GeneratePerfWarning("%s: Conversion requires y-flip.", funcName);
     } else if (srcStride != dstStride) {
         webgl->GeneratePerfWarning("%s: Conversion requires change in stride. (%u->%u)",
                                    funcName, uint32_t(srcStride), uint32_t(dstStride));
@@ -364,17 +375,17 @@ TexUnpackBlob::ConvertIfNeeded(WebGLCont
     }
     const auto dstBegin = static_cast<uint8_t*>(dstBuffer.get());
 
     ////
 
     // And go!:
     bool wasTrivial;
     if (!ConvertImage(rowLength, rowCount,
-                      srcBegin, srcStride, srcOrigin, srcFormat, mSrcIsPremult,
+                      srcBegin, srcStride, srcOrigin, srcFormat, srcIsPremult,
                       dstBegin, dstStride, dstOrigin, dstFormat, dstIsPremult,
                       &wasTrivial))
     {
         webgl->ErrorImplementationBug("%s: ConvertImage failed.", funcName);
         return false;
     }
 
     *out_begin = dstBegin;
@@ -398,17 +409,17 @@ DoTexOrSubImage(bool isSubImage, gl::GLC
 //////////////////////////////////////////////////////////////////////////////////////////
 // TexUnpackBytes
 
 TexUnpackBytes::TexUnpackBytes(const WebGLContext* webgl, TexImageTarget target,
                                uint32_t width, uint32_t height, uint32_t depth,
                                bool isClientData, const uint8_t* ptr, size_t availBytes)
     : TexUnpackBlob(webgl, target,
                     FallbackOnZero(webgl->mPixelStore_UnpackRowLength, width),
-                    width, height, depth, false)
+                    width, height, depth, gfxAlphaType::NonPremult)
     , mIsClientData(isClientData)
     , mPtr(ptr)
     , mAvailBytes(availBytes)
 { }
 
 bool
 TexUnpackBytes::Validate(WebGLContext* webgl, const char* funcName,
                          const webgl::PackingInfo& pi)
@@ -573,19 +584,19 @@ TexUnpackBytes::TexOrSubImage(bool isSub
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // TexUnpackImage
 
 TexUnpackImage::TexUnpackImage(const WebGLContext* webgl, TexImageTarget target,
                                uint32_t width, uint32_t height, uint32_t depth,
-                               layers::Image* image, bool isAlphaPremult)
+                               layers::Image* image, gfxAlphaType srcAlphaType)
     : TexUnpackBlob(webgl, target, image->GetSize().width, width, height, depth,
-                    isAlphaPremult)
+                    srcAlphaType)
     , mImage(image)
 { }
 
 TexUnpackImage::~TexUnpackImage()
 { }
 
 bool
 TexUnpackImage::Validate(WebGLContext* webgl, const char* funcName,
@@ -621,25 +632,34 @@ TexUnpackImage::TexOrSubImage(bool isSub
 
     const char* fallbackReason;
     do {
         if (mDepth != 1) {
             fallbackReason = "depth is not 1";
             break;
         }
 
-        const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
-        if (mSrcIsPremult != dstIsPremult) {
+        const auto fnHasPremultMismatch = [&]() {
+            if (mSrcAlphaType == gfxAlphaType::Opaque)
+                return false;
+
+            const bool srcIsPremult = (mSrcAlphaType == gfxAlphaType::Premult);
+            const auto& dstIsPremult = webgl->mPixelStore_PremultiplyAlpha;
+            if (srcIsPremult == dstIsPremult)
+                return false;
+
             if (dstIsPremult) {
                 fallbackReason = "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not true";
             } else {
                 fallbackReason = "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not false";
             }
+            return true;
+        };
+        if (fnHasPremultMismatch())
             break;
-        }
 
         if (dui->unpackFormat != LOCAL_GL_RGB && dui->unpackFormat != LOCAL_GL_RGBA) {
             fallbackReason = "`format` is not RGB or RGBA";
             break;
         }
 
         if (dui->unpackType != LOCAL_GL_UNSIGNED_BYTE) {
             fallbackReason = "`type` is not UNSIGNED_BYTE";
@@ -696,31 +716,32 @@ TexUnpackImage::TexOrSubImage(bool isSub
     if (!dataSurf) {
         webgl->ErrorOutOfMemory("%s: GetAsSourceSurface or GetDataSurface failed after"
                                 " blit failed for TexUnpackImage.",
                                 funcName);
         return false;
     }
 
     const TexUnpackSurface surfBlob(webgl, target, mWidth, mHeight, mDepth, dataSurf,
-                                    mSrcIsPremult);
+                                    mSrcAlphaType);
 
     return surfBlob.TexOrSubImage(isSubImage, needsRespec, funcName, tex, target, level,
                                   dui, xOffset, yOffset, zOffset, out_error);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // TexUnpackSurface
 
 TexUnpackSurface::TexUnpackSurface(const WebGLContext* webgl, TexImageTarget target,
                                    uint32_t width, uint32_t height, uint32_t depth,
-                                   gfx::DataSourceSurface* surf, bool isAlphaPremult)
+                                   gfx::DataSourceSurface* surf,
+                                   gfxAlphaType srcAlphaType)
     : TexUnpackBlob(webgl, target, surf->GetSize().width, width, height, depth,
-                    isAlphaPremult)
+                    srcAlphaType)
     , mSurf(surf)
 { }
 
 //////////
 
 static bool
 GetFormatForSurf(gfx::SourceSurface* surf, WebGLTexelFormat* const out_texelFormat,
                  uint8_t* const out_bpp)
--- a/dom/canvas/TexUnpackBlob.h
+++ b/dom/canvas/TexUnpackBlob.h
@@ -46,23 +46,24 @@ public:
     const uint32_t mImageHeight;
     const uint32_t mSkipPixels;
     const uint32_t mSkipRows;
     const uint32_t mSkipImages;
     const uint32_t mWidth;
     const uint32_t mHeight;
     const uint32_t mDepth;
 
-    const bool mSrcIsPremult;
+    const gfxAlphaType mSrcAlphaType;
 
     bool mNeedsExactUpload;
 
 protected:
     TexUnpackBlob(const WebGLContext* webgl, TexImageTarget target, uint32_t rowLength,
-                  uint32_t width, uint32_t height, uint32_t depth, bool isSrcPremult);
+                  uint32_t width, uint32_t height, uint32_t depth,
+                  gfxAlphaType srcAlphaType);
 
 public:
     virtual ~TexUnpackBlob() { }
 
 protected:
     bool ConvertIfNeeded(WebGLContext* webgl, const char* funcName,
                          const uint32_t rowLength, const uint32_t rowCount,
                          WebGLTexelFormat srcFormat,
@@ -112,17 +113,17 @@ public:
 
 class TexUnpackImage final : public TexUnpackBlob
 {
 public:
     const RefPtr<layers::Image> mImage;
 
     TexUnpackImage(const WebGLContext* webgl, TexImageTarget target, uint32_t width,
                    uint32_t height, uint32_t depth, layers::Image* image,
-                   bool isAlphaPremult);
+                   gfxAlphaType srcAlphaType);
 
     ~TexUnpackImage(); // Prevent needing to define layers::Image in the header.
 
     virtual bool Validate(WebGLContext* webgl, const char* funcName,
                           const webgl::PackingInfo& pi) override;
     virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                                WebGLTexture* tex, TexImageTarget target, GLint level,
                                const webgl::DriverUnpackInfo* dui, GLint xOffset,
@@ -132,17 +133,17 @@ public:
 
 class TexUnpackSurface final : public TexUnpackBlob
 {
 public:
     const RefPtr<gfx::DataSourceSurface> mSurf;
 
     TexUnpackSurface(const WebGLContext* webgl, TexImageTarget target, uint32_t width,
                      uint32_t height, uint32_t depth, gfx::DataSourceSurface* surf,
-                     bool isAlphaPremult);
+                     gfxAlphaType srcAlphaType);
 
     virtual bool Validate(WebGLContext* webgl, const char* funcName,
                           const webgl::PackingInfo& pi) override;
     virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                                WebGLTexture* tex, TexImageTarget target, GLint level,
                                const webgl::DriverUnpackInfo* dui, GLint xOffset,
                                GLint yOffset, GLint zOffset,
                                GLenum* const out_error) const override;
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -1242,24 +1242,20 @@ WebGLContext::LoseOldestWebGLContextIfLi
 }
 
 UniquePtr<uint8_t[]>
 WebGLContext::GetImageBuffer(int32_t* out_format)
 {
     *out_format = 0;
 
     // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
-    bool premult;
-    RefPtr<SourceSurface> snapshot =
-      GetSurfaceSnapshot(mOptions.premultipliedAlpha ? nullptr : &premult);
-    if (!snapshot) {
+    gfxAlphaType any;
+    RefPtr<SourceSurface> snapshot = GetSurfaceSnapshot(&any);
+    if (!snapshot)
         return nullptr;
-    }
-
-    MOZ_ASSERT(mOptions.premultipliedAlpha || !premult, "We must get unpremult when we ask for it!");
 
     RefPtr<DataSourceSurface> dataSurface = snapshot->GetDataSurface();
 
     return gfxUtils::GetImageBuffer(dataSurface, mOptions.premultipliedAlpha,
                                     out_format);
 }
 
 NS_IMETHODIMP
@@ -1267,24 +1263,21 @@ WebGLContext::GetInputStream(const char*
                              const char16_t* encoderOptions,
                              nsIInputStream** out_stream)
 {
     NS_ASSERTION(gl, "GetInputStream on invalid context?");
     if (!gl)
         return NS_ERROR_FAILURE;
 
     // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
-    bool premult;
-    RefPtr<SourceSurface> snapshot =
-      GetSurfaceSnapshot(mOptions.premultipliedAlpha ? nullptr : &premult);
+    gfxAlphaType any;
+    RefPtr<SourceSurface> snapshot = GetSurfaceSnapshot(&any);
     if (!snapshot)
         return NS_ERROR_FAILURE;
 
-    MOZ_ASSERT(mOptions.premultipliedAlpha || !premult, "We must get unpremult when we ask for it!");
-
     RefPtr<DataSourceSurface> dataSurface = snapshot->GetDataSurface();
     return gfxUtils::GetInputStream(dataSurface, mOptions.premultipliedAlpha, mimeType,
                                     encoderOptions, out_stream);
 }
 
 void
 WebGLContext::UpdateLastUseIndex()
 {
@@ -1929,31 +1922,29 @@ WebGLContext::ForceRestoreContext()
 
 void
 WebGLContext::MakeContextCurrent() const
 {
     gl->MakeCurrent();
 }
 
 already_AddRefed<mozilla::gfx::SourceSurface>
-WebGLContext::GetSurfaceSnapshot(bool* out_premultAlpha)
+WebGLContext::GetSurfaceSnapshot(gfxAlphaType* const out_alphaType)
 {
     if (!gl)
         return nullptr;
 
-    bool hasAlpha = mOptions.alpha;
-    SurfaceFormat surfFormat = hasAlpha ? SurfaceFormat::B8G8R8A8
-                                        : SurfaceFormat::B8G8R8X8;
+    const auto surfFormat = mOptions.alpha ? SurfaceFormat::B8G8R8A8
+                                           : SurfaceFormat::B8G8R8X8;
     RefPtr<DataSourceSurface> surf;
     surf = Factory::CreateDataSourceSurfaceWithStride(IntSize(mWidth, mHeight),
                                                       surfFormat,
                                                       mWidth * 4);
-    if (NS_WARN_IF(!surf)) {
+    if (NS_WARN_IF(!surf))
         return nullptr;
-    }
 
     gl->MakeCurrent();
     {
         ScopedBindFramebuffer autoFB(gl, 0);
         ClearBackbufferIfNeeded();
 
         // Save, override, then restore glReadBuffer.
         const GLenum readBufferMode = gl->Screen()->GetReadBufferMode();
@@ -1963,36 +1954,41 @@ WebGLContext::GetSurfaceSnapshot(bool* o
         }
         ReadPixelsIntoDataSurface(gl, surf);
 
         if (readBufferMode != LOCAL_GL_BACK) {
             gl->Screen()->SetReadBuffer(readBufferMode);
         }
     }
 
-    if (out_premultAlpha) {
-        *out_premultAlpha = true;
+    gfxAlphaType alphaType;
+    if (!mOptions.alpha) {
+        alphaType = gfxAlphaType::Opaque;
+    } else if (mOptions.premultipliedAlpha) {
+        alphaType = gfxAlphaType::Premult;
+    } else {
+        alphaType = gfxAlphaType::NonPremult;
     }
-    bool srcPremultAlpha = mOptions.premultipliedAlpha;
-    if (!srcPremultAlpha) {
-        if (out_premultAlpha) {
-            *out_premultAlpha = false;
-        } else if(hasAlpha) {
+
+    if (out_alphaType) {
+        *out_alphaType = alphaType;
+    } else {
+        // Expects Opaque or Premult
+        if (alphaType == gfxAlphaType::NonPremult) {
             gfxUtils::PremultiplyDataSurface(surf, surf);
+            alphaType = gfxAlphaType::Premult;
         }
     }
 
     RefPtr<DrawTarget> dt =
         Factory::CreateDrawTarget(gfxPlatform::GetPlatform()->GetSoftwareBackend(),
                                   IntSize(mWidth, mHeight),
                                   SurfaceFormat::B8G8R8A8);
-
-    if (!dt) {
+    if (!dt)
         return nullptr;
-    }
 
     dt->SetTransform(Matrix::Translation(0.0, mHeight).PreScale(1.0, -1.0));
 
     dt->DrawSurface(surf,
                     Rect(0, 0, mWidth, mHeight),
                     Rect(0, 0, mWidth, mHeight),
                     DrawSurfaceOptions(),
                     DrawOptions(1.0f, CompositionOp::OP_SOURCE));
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -370,18 +370,18 @@ public:
         return NS_ERROR_NOT_IMPLEMENTED;
     }
 
     virtual UniquePtr<uint8_t[]> GetImageBuffer(int32_t* out_format) override;
     NS_IMETHOD GetInputStream(const char* mimeType,
                               const char16_t* encoderOptions,
                               nsIInputStream** out_stream) override;
 
-    already_AddRefed<mozilla::gfx::SourceSurface>
-    GetSurfaceSnapshot(bool* out_premultAlpha) override;
+    virtual already_AddRefed<mozilla::gfx::SourceSurface>
+    GetSurfaceSnapshot(gfxAlphaType* out_alphaType) override;
 
     NS_IMETHOD SetIsOpaque(bool) override { return NS_OK; };
     bool GetIsOpaque() override { return false; }
     NS_IMETHOD SetContextOptions(JSContext* cx,
                                  JS::Handle<JS::Value> options,
                                  ErrorResult& aRvForDictionaryInit) override;
 
     NS_IMETHOD SetIsIPC(bool) override {
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -215,35 +215,29 @@ FromPboOffset(WebGLContext* webgl, const
 static UniquePtr<webgl::TexUnpackBlob>
 FromImageBitmap(WebGLContext* webgl, const char* funcName, TexImageTarget target,
               uint32_t width, uint32_t height, uint32_t depth,
               const dom::ImageBitmap& imageBitmap)
 {
     UniquePtr<dom::ImageBitmapCloneData> cloneData = Move(imageBitmap.ToCloneData());
     const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface;
 
-    ////
-
     if (!width) {
         width = surf->GetSize().width;
     }
 
     if (!height) {
         height = surf->GetSize().height;
     }
 
-    ////
-
-
     // WhatWG "HTML Living Standard" (30 October 2015):
     // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
     //  non-premultiplied alpha values."
-    const bool isAlphaPremult = cloneData->mIsPremultipliedAlpha;
     return MakeUnique<webgl::TexUnpackSurface>(webgl, target, width, height, depth, surf,
-                                               isAlphaPremult);
+                                               cloneData->mAlphaType);
 }
 
 static UniquePtr<webgl::TexUnpackBlob>
 FromImageData(WebGLContext* webgl, const char* funcName, TexImageTarget target,
               uint32_t width, uint32_t height, uint32_t depth,
               const dom::ImageData& imageData, dom::Uint8ClampedArray* scopedArr)
 {
     DebugOnly<bool> inited = scopedArr->Init(imageData.GetDataObject());
@@ -252,16 +246,21 @@ FromImageData(WebGLContext* webgl, const
     scopedArr->ComputeLengthAndData();
     const DebugOnly<size_t> dataSize = scopedArr->Length();
     const void* const data = scopedArr->Data();
 
     const gfx::IntSize size(imageData.Width(), imageData.Height());
     const size_t stride = size.width * 4;
     const gfx::SurfaceFormat surfFormat = gfx::SurfaceFormat::R8G8B8A8;
 
+    // WhatWG "HTML Living Standard" (30 October 2015):
+    // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
+    //  non-premultiplied alpha values."
+    const auto alphaType = gfxAlphaType::NonPremult;
+
     MOZ_ASSERT(dataSize == stride * size.height);
 
     uint8_t* wrappableData = (uint8_t*)data;
 
     const RefPtr<gfx::DataSourceSurface> surf =
         gfx::Factory::CreateWrappingDataSourceSurface(wrappableData, stride, size,
                                                       surfFormat);
     if (!surf) {
@@ -276,22 +275,18 @@ FromImageData(WebGLContext* webgl, const
     }
 
     if (!height) {
         height = imageData.Height();
     }
 
     ////
 
-    // WhatWG "HTML Living Standard" (30 October 2015):
-    // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
-    //  non-premultiplied alpha values."
-    const bool isAlphaPremult = false;
     return MakeUnique<webgl::TexUnpackSurface>(webgl, target, width, height, depth, surf,
-                                               isAlphaPremult);
+                                               alphaType);
 }
 
 UniquePtr<webgl::TexUnpackBlob>
 WebGLContext::FromDomElem(const char* funcName, TexImageTarget target, uint32_t width,
                           uint32_t height, uint32_t depth, const dom::Element& elem,
                           ErrorResult* const out_error)
 {
     // The canvas spec says that drawImage should draw the first frame of
@@ -374,26 +369,24 @@ WebGLContext::FromDomElem(const char* fu
         GenerateWarning("%s: Element is write-only, thus cannot be uploaded.", funcName);
         out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
         return nullptr;
     }
 
     //////
     // Ok, we're good!
 
-    const bool isAlphaPremult = sfer.mIsPremultiplied;
-
     if (layersImage) {
         return MakeUnique<webgl::TexUnpackImage>(this, target, width, height, depth,
-                                                 layersImage,  isAlphaPremult);
+                                                 layersImage, sfer.mAlphaType);
     }
 
     MOZ_ASSERT(dataSurf);
     return MakeUnique<webgl::TexUnpackSurface>(this, target, width, height, depth,
-                                               dataSurf, isAlphaPremult);
+                                               dataSurf, sfer.mAlphaType);
 }
 
 ////////////////////////////////////////
 
 UniquePtr<webgl::TexUnpackBlob>
 WebGLContext::From(const char* funcName, TexImageTarget target, GLsizei rawWidth,
                    GLsizei rawHeight, GLsizei rawDepth, GLint border,
                    const TexImageSource& src, dom::Uint8ClampedArray* const scopedArr)
--- a/dom/canvas/nsICanvasRenderingContextInternal.h
+++ b/dom/canvas/nsICanvasRenderingContextInternal.h
@@ -114,17 +114,18 @@ public:
                             const char16_t *encoderOptions,
                             nsIInputStream **stream) = 0;
 
   // This gets an Azure SourceSurface for the canvas, this will be a snapshot
   // of the canvas at the time it was called.
   // If premultAlpha is provided, then it assumed the callee can handle
   // un-premultiplied surfaces, and *premultAlpha will be set to false
   // if one is returned.
-  virtual already_AddRefed<mozilla::gfx::SourceSurface> GetSurfaceSnapshot(bool* premultAlpha = nullptr) = 0;
+  virtual already_AddRefed<mozilla::gfx::SourceSurface>
+  GetSurfaceSnapshot(gfxAlphaType* out_alphaType = nullptr) = 0;
 
   // If this context is opaque, the backing store of the canvas should
   // be created as opaque; all compositing operators should assume the
   // dst alpha is always 1.0.  If this is never called, the context
   // defaults to false (not opaque).
   NS_IMETHOD SetIsOpaque(bool isOpaque) = 0;
   virtual bool GetIsOpaque() = 0;
 
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -1303,22 +1303,22 @@ HTMLCanvasElement::SetFrameCapture(alrea
     }
 
     RefPtr<Image> imageRefCopy = image.get();
     listener->NewFrame(imageRefCopy.forget(), aTime);
   }
 }
 
 already_AddRefed<SourceSurface>
-HTMLCanvasElement::GetSurfaceSnapshot(bool* aPremultAlpha)
+HTMLCanvasElement::GetSurfaceSnapshot(gfxAlphaType* const aOutAlphaType)
 {
   if (!mCurrentContext)
     return nullptr;
 
-  return mCurrentContext->GetSurfaceSnapshot(aPremultAlpha);
+  return mCurrentContext->GetSurfaceSnapshot(aOutAlphaType);
 }
 
 AsyncCanvasRenderer*
 HTMLCanvasElement::GetAsyncCanvasRenderer()
 {
   if (!mAsyncCanvasRenderer) {
     mAsyncCanvasRenderer = new AsyncCanvasRenderer();
     mAsyncCanvasRenderer->mHTMLCanvasElement = this;
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -249,17 +249,18 @@ public:
 
   /*
    * Returns true if the canvas context content is guaranteed to be opaque
    * across its entire area.
    */
   bool GetIsOpaque();
   virtual bool GetOpaqueAttr() override;
 
-  virtual already_AddRefed<gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr);
+  virtual already_AddRefed<gfx::SourceSurface>
+  GetSurfaceSnapshot(gfxAlphaType* aOutAlphaType = nullptr);
 
   /*
    * Register a FrameCaptureListener with this canvas.
    * The canvas hooks into the RefreshDriver while there are
    * FrameCaptureListeners registered.
    * The registered FrameCaptureListeners are stored as WeakPtrs, thus it's the
    * caller's responsibility to keep them alive. Once a registered
    * FrameCaptureListener is destroyed it will be automatically deregistered.
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -169,24 +169,24 @@ class SharedRGBImage;
 class SurfaceTextureImage;
 #elif defined(XP_MACOSX)
 class MacIOSurfaceImage;
 #endif
 
 /**
  * A class representing a buffer of pixel data. The data can be in one
  * of various formats including YCbCr.
- * 
+ *
  * Create an image using an ImageContainer. Fill the image with data, and
  * then call ImageContainer::SetImage to display it. An image must not be
  * modified after calling SetImage. Image implementations do not need to
  * perform locking; when filling an Image, the Image client is responsible
  * for ensuring only one thread accesses the Image at a time, and after
  * SetImage the image is immutable.
- * 
+ *
  * When resampling an Image, only pixels within the buffer should be
  * sampled. For example, cairo images should be sampled in EXTEND_PAD mode.
  */
 class Image {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Image)
 
 public:
   ImageFormat GetFormat() { return mFormat; }
@@ -253,17 +253,17 @@ protected:
   int32_t mSerial;
   ImageFormat mFormat;
 
   static mozilla::Atomic<int32_t> sSerialCounter;
 };
 
 /**
  * A RecycleBin is owned by an ImageContainer. We store buffers in it that we
- * want to recycle from one image to the next.It's a separate object from 
+ * want to recycle from one image to the next.It's a separate object from
  * ImageContainer because images need to store a strong ref to their RecycleBin
  * and we must avoid creating a reference loop between an ImageContainer and
  * its active image.
  */
 class BufferRecycleBin final {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BufferRecycleBin)
 
   //typedef mozilla::gl::GLContext GLContext;
@@ -336,17 +336,17 @@ public:
 private:
   typedef mozilla::Mutex Mutex;
 
   ~ImageContainerListener();
 
   Mutex mLock;
   ImageContainer* mImageContainer;
 };
- 
+
 /**
  * A class that manages Images for an ImageLayer. The only reason
  * we need a separate class here is that ImageLayers aren't threadsafe
  * (because layers can only be used on the main thread) and we want to
  * be able to set the current Image from any thread, to facilitate
  * video playback without involving the main thread, for example.
  *
  * An ImageContainer can operate in one of these modes:
@@ -416,17 +416,17 @@ public:
    * Every element of aImages must have non-null mImage.
    * mFrameID can be zero, in which case you won't get meaningful
    * painted/dropped frame counts. Otherwise you should use a unique and
    * increasing ID for each decoded and submitted frame (but it's OK to
    * pass the same frame to SetCurrentImages).
    * mProducerID is a unique ID for the stream of images. A change in the
    * mProducerID means changing to a new mFrameID namespace. All frames in
    * aImages must have the same mProducerID.
-   * 
+   *
    * The Image data must not be modified after this method is called!
    * Note that this must not be called if ENABLE_ASYNC has not been set.
    *
    * The implementation calls CurrentImageChanged() while holding
    * mReentrantMonitor.
    *
    * If this ImageContainer has an ImageClient for async video:
    * Schedule a task to send the image to the compositor using the
@@ -452,21 +452,21 @@ public:
    * See Bug 901224.
    */
   void ClearImagesFromImageBridge();
 
   /**
    * Set an Image as the current image to display. The Image must have
    * been created by this ImageContainer.
    * Must be called on the main thread, within a layers transaction.
-   * 
+   *
    * This method takes mReentrantMonitor
    * when accessing thread-shared state.
    * aImage can be null. While it's null, nothing will be painted.
-   * 
+   *
    * The Image data must not be modified after this method is called!
    * Note that this must not be called if ENABLE_ASYNC been set.
    *
    * You won't get meaningful painted/dropped counts when using this method.
    */
   void SetCurrentImageInTransaction(Image* aImage);
   void SetCurrentImagesInTransaction(const nsTArray<NonOwningImage>& aImages);
 
@@ -721,17 +721,17 @@ struct PlanarYCbCrData {
  * The YCbCr format can be:
  *
  * 4:4:4 - CbCr width/height are the same as Y.
  * 4:2:2 - CbCr width is half that of Y. Height is the same.
  * 4:2:0 - CbCr width and height is half that of Y.
  *
  * The color format is detected based on the height/width ratios
  * defined above.
- * 
+ *
  * The Image that is rendered is the picture region defined by
  * mPicX, mPicY and mPicSize. The size of the rendered image is
  * mPicSize, not mYSize or mCbCrSize.
  *
  * mYSkip, mCbSkip, mCrSkip are added to support various output
  * formats from hardware decoder. They are per-pixel skips in the
  * source image.
  *
--- a/gfx/thebes/gfxTypes.h
+++ b/gfx/thebes/gfxTypes.h
@@ -74,9 +74,15 @@ enum class gfxSurfaceType {
 
 enum class gfxContentType {
   COLOR       = 0x1000,
   ALPHA       = 0x2000,
   COLOR_ALPHA = 0x3000,
   SENTINEL    = 0xffff
 };
 
+enum class gfxAlphaType {
+  Opaque,
+  Premult,
+  NonPremult,
+};
+
 #endif /* GFX_TYPES_H */
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -7179,30 +7179,26 @@ nsLayoutUtils::IsReallyFixedPos(nsIFrame
 
 nsLayoutUtils::SurfaceFromElementResult
 nsLayoutUtils::SurfaceFromOffscreenCanvas(OffscreenCanvas* aOffscreenCanvas,
                                           uint32_t aSurfaceFlags,
                                           RefPtr<DrawTarget>& aTarget)
 {
   SurfaceFromElementResult result;
 
-  bool* isPremultiplied = nullptr;
-  if (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) {
-    isPremultiplied = &result.mIsPremultiplied;
-  }
-
   nsIntSize size = aOffscreenCanvas->GetWidthHeight();
 
-  result.mSourceSurface = aOffscreenCanvas->GetSurfaceSnapshot(isPremultiplied);
-    if (!result.mSourceSurface) {
-      // If the element doesn't have a context then we won't get a snapshot. The canvas spec wants us to not error and just
-      // draw nothing, so return an empty surface.
-      DrawTarget *ref = aTarget ? aTarget.get() : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
-      RefPtr<DrawTarget> dt = ref->CreateSimilarDrawTarget(IntSize(size.width, size.height),
-                                                           SurfaceFormat::B8G8R8A8);
+  result.mSourceSurface = aOffscreenCanvas->GetSurfaceSnapshot(&result.mAlphaType);
+  if (!result.mSourceSurface) {
+    // If the element doesn't have a context then we won't get a snapshot. The canvas spec wants us to not error and just
+    // draw nothing, so return an empty surface.
+    result.mAlphaType = gfxAlphaType::Opaque;
+    DrawTarget *ref = aTarget ? aTarget.get() : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+    RefPtr<DrawTarget> dt = ref->CreateSimilarDrawTarget(IntSize(size.width, size.height),
+                                                         SurfaceFormat::B8G8R8A8);
     if (dt) {
       result.mSourceSurface = dt->Snapshot();
     }
   } else if (aTarget) {
     RefPtr<SourceSurface> opt = aTarget->OptimizeSourceSurface(result.mSourceSurface);
     if (opt) {
       result.mSourceSurface = opt;
     }
@@ -7271,17 +7267,16 @@ nsLayoutUtils::SurfaceFromElement(nsIIma
                         ? (uint32_t) imgIContainer::FRAME_FIRST
                         : (uint32_t) imgIContainer::FRAME_CURRENT;
   uint32_t frameFlags = imgIContainer::FLAG_SYNC_DECODE
                       | imgIContainer::FLAG_ASYNC_NOTIFY;
   if (aSurfaceFlags & SFE_NO_COLORSPACE_CONVERSION)
     frameFlags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION;
   if (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) {
     frameFlags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
-    result.mIsPremultiplied = false;
   }
 
   int32_t imgWidth, imgHeight;
   nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
   HTMLImageElement* element = HTMLImageElement::FromContentOrNull(content);
   if (aSurfaceFlags & SFE_USE_ELEMENT_SIZE_IF_VECTOR &&
       element &&
       imgContainer->GetType() == imgIContainer::TYPE_VECTOR) {
@@ -7309,16 +7304,25 @@ nsLayoutUtils::SurfaceFromElement(nsIIma
     // upfront if aTarget is specified.
     if (aTarget) {
       RefPtr<SourceSurface> optSurface =
         aTarget->OptimizeSourceSurface(result.mSourceSurface);
       if (optSurface) {
         result.mSourceSurface = optSurface;
       }
     }
+
+    const auto& format = result.mSourceSurface->GetFormat();
+    if (IsOpaque(format)) {
+      result.mAlphaType = gfxAlphaType::Opaque;
+    } else if (frameFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA) {
+      result.mAlphaType = gfxAlphaType::NonPremult;
+    } else {
+      result.mAlphaType = gfxAlphaType::Premult;
+    }
   } else {
     result.mDrawInfo.mImgContainer = imgContainer;
     result.mDrawInfo.mWhichFrame = whichFrame;
     result.mDrawInfo.mDrawingFlags = frameFlags;
   }
 
   int32_t corsmode;
   if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) {
@@ -7343,33 +7347,29 @@ nsLayoutUtils::SurfaceFromElement(HTMLIm
 
 nsLayoutUtils::SurfaceFromElementResult
 nsLayoutUtils::SurfaceFromElement(HTMLCanvasElement* aElement,
                                   uint32_t aSurfaceFlags,
                                   RefPtr<DrawTarget>& aTarget)
 {
   SurfaceFromElementResult result;
 
-  bool* isPremultiplied = nullptr;
-  if (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) {
-    isPremultiplied = &result.mIsPremultiplied;
-  }
-
   IntSize size = aElement->GetSize();
 
-  result.mSourceSurface = aElement->GetSurfaceSnapshot(isPremultiplied);
+  result.mSourceSurface = aElement->GetSurfaceSnapshot(&result.mAlphaType);
   if (!result.mSourceSurface) {
-     // If the element doesn't have a context then we won't get a snapshot. The canvas spec wants us to not error and just
-     // draw nothing, so return an empty surface.
-     DrawTarget *ref = aTarget ? aTarget.get() : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
-     RefPtr<DrawTarget> dt = ref->CreateSimilarDrawTarget(IntSize(size.width, size.height),
-                                                          SurfaceFormat::B8G8R8A8);
-     if (dt) {
-       result.mSourceSurface = dt->Snapshot();
-     }
+    // If the element doesn't have a context then we won't get a snapshot. The canvas spec wants us to not error and just
+    // draw nothing, so return an empty surface.
+    result.mAlphaType = gfxAlphaType::Opaque;
+    DrawTarget *ref = aTarget ? aTarget.get() : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+    RefPtr<DrawTarget> dt = ref->CreateSimilarDrawTarget(IntSize(size.width, size.height),
+                                                        SurfaceFormat::B8G8R8A8);
+    if (dt) {
+      result.mSourceSurface = dt->Snapshot();
+    }
   } else if (aTarget) {
     RefPtr<SourceSurface> opt = aTarget->OptimizeSourceSurface(result.mSourceSurface);
     if (opt) {
       result.mSourceSurface = opt;
     }
   }
 
   // Ensure that any future changes to the canvas trigger proper invalidation,
@@ -7385,16 +7385,17 @@ nsLayoutUtils::SurfaceFromElement(HTMLCa
 }
 
 nsLayoutUtils::SurfaceFromElementResult
 nsLayoutUtils::SurfaceFromElement(HTMLVideoElement* aElement,
                                   uint32_t aSurfaceFlags,
                                   RefPtr<DrawTarget>& aTarget)
 {
   SurfaceFromElementResult result;
+  result.mAlphaType = gfxAlphaType::Opaque; // Assume opaque.
 
   if (aElement->ContainsRestrictedContent()) {
     return result;
   }
 
   uint16_t readyState;
   if (NS_SUCCEEDED(aElement->GetReadyState(&readyState)) &&
       (readyState == nsIDOMHTMLMediaElement::HAVE_NOTHING ||
@@ -7404,17 +7405,16 @@ nsLayoutUtils::SurfaceFromElement(HTMLVi
   }
 
   // If it doesn't have a principal, just bail
   nsCOMPtr<nsIPrincipal> principal = aElement->GetCurrentVideoPrincipal();
   if (!principal)
     return result;
 
   result.mLayersImage = aElement->GetCurrentImage();
-
   if (!result.mLayersImage)
     return result;
 
   if (aTarget) {
     // They gave us a DrawTarget to optimize for, so even though we have a layers::Image,
     // we should unconditionally grab a SourceSurface and try to optimize it.
     result.mSourceSurface = result.mLayersImage->GetAsSourceSurface();
     if (!result.mSourceSurface)
@@ -8440,17 +8440,17 @@ nsLayoutUtils::IsAPZTestLoggingEnabled()
 // SurfaceFromElementResult
 
 nsLayoutUtils::SurfaceFromElementResult::SurfaceFromElementResult()
   // Use safe default values here
   : mIsWriteOnly(true)
   , mIsStillLoading(false)
   , mHasSize(false)
   , mCORSUsed(false)
-  , mIsPremultiplied(true)
+  , mAlphaType(gfxAlphaType::Opaque)
 {
 }
 
 const RefPtr<mozilla::gfx::SourceSurface>&
 nsLayoutUtils::SurfaceFromElementResult::GetSourceSurface()
 {
   if (!mSourceSurface && mLayersImage) {
     mSourceSurface = mLayersImage->GetAsSourceSurface();
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -2077,19 +2077,18 @@ public:
   enum {
     /* When creating a new surface, create an image surface */
     SFE_WANT_IMAGE_SURFACE = 1 << 0,
     /* Whether to extract the first frame (as opposed to the
        current frame) in the case that the element is an image. */
     SFE_WANT_FIRST_FRAME_IF_IMAGE = 1 << 1,
     /* Whether we should skip colorspace/gamma conversion */
     SFE_NO_COLORSPACE_CONVERSION = 1 << 2,
-    /* Specifies that the caller wants unpremultiplied pixel data.
-       If this is can be done efficiently, the result will be a
-       DataSourceSurface and mIsPremultiplied with be set to false. */
+    /* Specifies that the caller wants either OPAQUE or NON_PREMULT mAlphaType,
+       if this is can be done efficiently. */
     SFE_PREFER_NO_PREMULTIPLY_ALPHA = 1 << 3,
     /* Whether we should skip getting a surface for vector images and
        return a DirectDrawInfo containing an imgIContainer instead. */
     SFE_NO_RASTERIZING_VECTORS = 1 << 4,
     /* If image type is vector, the return surface size will same as
        element size, not image's intrinsic size. */
     SFE_USE_ELEMENT_SIZE_IF_VECTOR = 1 << 5
   };
@@ -2137,18 +2136,18 @@ public:
     bool mIsWriteOnly;
     /* Whether the element was still loading.  Some consumers need to handle
        this case specially. */
     bool mIsStillLoading;
     /* Whether the element has a valid size. */
     bool mHasSize;
     /* Whether the element used CORS when loading. */
     bool mCORSUsed;
-    /* Whether the returned image contains premultiplied pixel data */
-    bool mIsPremultiplied;
+
+    gfxAlphaType mAlphaType;
 
     // Methods:
 
     SurfaceFromElementResult();
 
     // Gets mSourceSurface, or makes a SourceSurface from mLayersImage.
     const RefPtr<mozilla::gfx::SourceSurface>& GetSourceSurface();
   };