Bug 1620600 - Add flags to allow image decoders to produce sRGB output. r=tnikkel
☠☠ backed out by c01bce3a4e0f ☠ ☠
authorAndrew Osmond <aosmond@mozilla.com>
Thu, 12 Mar 2020 00:37:53 +0000
changeset 518284 e6e5816403d958a0bcaf427195ca7b113263d01f
parent 518283 e8984249397a68e0fbfc5ee5ae5fb43413f8f7dd
child 518285 e5cd778db960970ddccbca5e1f016eb18d815125
push id37207
push useropoprus@mozilla.com
push dateThu, 12 Mar 2020 09:33:12 +0000
treeherdermozilla-central@ffd615bf92dd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstnikkel
bugs1620600
milestone76.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 1620600 - Add flags to allow image decoders to produce sRGB output. r=tnikkel Currently we can only use the gfx.color_management.force_srgb pref to force all images to sRGB, or just accept device space. It would be nice to be able to test device space in our tests, as well as sRGB. This patch adds a surface flag which allows us to selectively output sRGB. This will also be useful for clipboard and re-encoding purposes, since they want a neutral output. In an ideal world we would just output the color profile and the pixel data in the original color space, but for now this is a relatively simple approach that works on all platforms and interops well with all applications. Differential Revision: https://phabricator.services.mozilla.com/D65734
image/Decoder.cpp
image/Decoder.h
image/SurfaceFlags.h
image/decoders/icon/android/nsIconChannel.cpp
image/decoders/icon/gtk/nsIconChannel.cpp
image/decoders/icon/mac/nsIconChannelCocoa.mm
image/decoders/nsBMPDecoder.cpp
image/decoders/nsGIFDecoder2.cpp
image/decoders/nsIconDecoder.cpp
image/decoders/nsJPEGDecoder.cpp
image/decoders/nsJPEGDecoder.h
image/decoders/nsPNGDecoder.cpp
image/decoders/nsPNGDecoder.h
image/decoders/nsWebPDecoder.cpp
image/imgIContainer.idl
image/test/gtest/Common.cpp
image/test/gtest/Common.h
image/test/gtest/TestDecodeToSurface.cpp
image/test/gtest/TestDecoders.cpp
image/test/gtest/TestFrameAnimator.cpp
image/test/gtest/TestMetadata.cpp
image/test/gtest/green.icon
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -43,16 +43,17 @@ class MOZ_STACK_CLASS AutoRecordDecoderT
   TimeStamp mStartTime;
 };
 
 Decoder::Decoder(RasterImage* aImage)
     : mInProfile(nullptr),
       mTransform(nullptr),
       mImageData(nullptr),
       mImageDataLength(0),
+      mCMSMode(gfxPlatform::GetCMSMode()),
       mImage(aImage),
       mFrameRecycler(nullptr),
       mProgress(NoProgress),
       mFrameCount(0),
       mLoopLength(FrameTimeout::Zero()),
       mDecoderFlags(DefaultDecoderFlags()),
       mSurfaceFlags(DefaultSurfaceFlags()),
       mInitialized(false),
@@ -84,16 +85,54 @@ Decoder::~Decoder() {
 
   if (mImage && !NS_IsMainThread()) {
     // Dispatch mImage to main thread to prevent it from being destructed by the
     // decode thread.
     NS_ReleaseOnMainThreadSystemGroup(mImage.forget());
   }
 }
 
+void Decoder::SetSurfaceFlags(SurfaceFlags aSurfaceFlags) {
+  MOZ_ASSERT(!mInitialized);
+  mSurfaceFlags = aSurfaceFlags;
+  if (mSurfaceFlags & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
+    mCMSMode = eCMSMode_Off;
+  }
+}
+
+qcms_profile* Decoder::GetCMSOutputProfile() const {
+  if (mSurfaceFlags & SurfaceFlags::TO_SRGB_COLORSPACE) {
+    return gfxPlatform::GetCMSsRGBProfile();
+  }
+  return gfxPlatform::GetCMSOutputProfile();
+}
+
+qcms_transform* Decoder::GetCMSsRGBTransform(SurfaceFormat aFormat) const {
+  if (mSurfaceFlags & SurfaceFlags::TO_SRGB_COLORSPACE) {
+    // We want a transform to convert from sRGB to device space, but we are
+    // already using sRGB as our device space. That means we can skip
+    // color management entirely.
+    return nullptr;
+  }
+
+  switch (aFormat) {
+    case SurfaceFormat::B8G8R8A8:
+    case SurfaceFormat::B8G8R8X8:
+      return gfxPlatform::GetCMSBGRATransform();
+    case SurfaceFormat::R8G8B8A8:
+    case SurfaceFormat::R8G8B8X8:
+      return gfxPlatform::GetCMSRGBATransform();
+    case SurfaceFormat::R8G8B8:
+      return gfxPlatform::GetCMSRGBTransform();
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unsupported surface format!");
+      return nullptr;
+  }
+}
+
 /*
  * Common implementation of the decoder interface.
  */
 
 nsresult Decoder::Init() {
   // No re-initializing
   MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!");
 
--- a/image/Decoder.h
+++ b/image/Decoder.h
@@ -323,20 +323,17 @@ class Decoder {
     mDecoderFlags = aDecoderFlags;
   }
   DecoderFlags GetDecoderFlags() const { return mDecoderFlags; }
 
   /**
    * Get or set the SurfaceFlags that select the kind of output this decoder
    * will produce.
    */
-  void SetSurfaceFlags(SurfaceFlags aSurfaceFlags) {
-    MOZ_ASSERT(!mInitialized);
-    mSurfaceFlags = aSurfaceFlags;
-  }
+  void SetSurfaceFlags(SurfaceFlags aSurfaceFlags);
   SurfaceFlags GetSurfaceFlags() const { return mSurfaceFlags; }
 
   /// @return true if we know the intrinsic size of the image we're decoding.
   bool HasSize() const { return mImageMetadata.HasSize(); }
 
   /**
    * @return the intrinsic size of the image we're decoding.
    *
@@ -451,16 +448,19 @@ class Decoder {
    */
   virtual nsresult InitInternal();
   virtual LexerResult DoDecode(SourceBufferIterator& aIterator,
                                IResumable* aOnResume) = 0;
   virtual nsresult BeforeFinishInternal();
   virtual nsresult FinishInternal();
   virtual nsresult FinishWithErrorInternal();
 
+  qcms_profile* GetCMSOutputProfile() const;
+  qcms_transform* GetCMSsRGBTransform(gfx::SurfaceFormat aFormat) const;
+
   /**
    * @return the per-image-format telemetry ID for recording this decoder's
    * speed, or Nothing() if we don't record speed telemetry for this kind of
    * decoder.
    */
   virtual Maybe<Telemetry::HistogramID> SpeedHistogram() const {
     return Nothing();
   }
@@ -562,16 +562,18 @@ class Decoder {
   qcms_profile* mInProfile;
 
   /// Color management transform to apply to image data.
   qcms_transform* mTransform;
 
   uint8_t* mImageData;  // Pointer to image data in BGRA/X
   uint32_t mImageDataLength;
 
+  uint32_t mCMSMode;
+
  private:
   RefPtr<RasterImage> mImage;
   Maybe<SourceBufferIterator> mIterator;
   IDecoderFrameRecycler* mFrameRecycler;
 
   // The current frame the decoder is producing.
   RawAccessFrameRef mCurrentFrame;
 
--- a/image/SurfaceFlags.h
+++ b/image/SurfaceFlags.h
@@ -14,17 +14,18 @@ namespace image {
 
 /**
  * Flags that change the output a decoder generates. Because different
  * combinations of these flags result in logically different surfaces, these
  * flags must be taken into account in SurfaceCache lookups.
  */
 enum class SurfaceFlags : uint8_t {
   NO_PREMULTIPLY_ALPHA = 1 << 0,
-  NO_COLORSPACE_CONVERSION = 1 << 1
+  NO_COLORSPACE_CONVERSION = 1 << 1,
+  TO_SRGB_COLORSPACE = 2 << 1,
 };
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SurfaceFlags)
 
 /**
  * @return the default set of surface flags.
  */
 inline SurfaceFlags DefaultSurfaceFlags() { return SurfaceFlags(); }
 
@@ -35,30 +36,36 @@ inline SurfaceFlags DefaultSurfaceFlags(
 inline SurfaceFlags ToSurfaceFlags(uint32_t aFlags) {
   SurfaceFlags flags = DefaultSurfaceFlags();
   if (aFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA) {
     flags |= SurfaceFlags::NO_PREMULTIPLY_ALPHA;
   }
   if (aFlags & imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION) {
     flags |= SurfaceFlags::NO_COLORSPACE_CONVERSION;
   }
+  if (aFlags & imgIContainer::FLAG_DECODE_TO_SRGB_COLORSPACE) {
+    flags |= SurfaceFlags::TO_SRGB_COLORSPACE;
+  }
   return flags;
 }
 
 /**
  * Given a set of SurfaceFlags, returns a set of imgIContainer FLAG_* flags with
  * the corresponding flags set.
  */
 inline uint32_t FromSurfaceFlags(SurfaceFlags aFlags) {
   uint32_t flags = imgIContainer::DECODE_FLAGS_DEFAULT;
   if (aFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA) {
     flags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
   }
   if (aFlags & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
     flags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION;
   }
+  if (aFlags & SurfaceFlags::TO_SRGB_COLORSPACE) {
+    flags |= imgIContainer::FLAG_DECODE_TO_SRGB_COLORSPACE;
+  }
   return flags;
 }
 
 }  // namespace image
 }  // namespace mozilla
 
 #endif  // mozilla_image_SurfaceFlags_h
--- a/image/decoders/icon/android/nsIconChannel.cpp
+++ b/image/decoders/icon/android/nsIconChannel.cpp
@@ -71,33 +71,29 @@ static nsresult moz_icon_to_channel(nsIU
   if (!buf_size.isValid()) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size.value());
   uint8_t* out = buf;
 
   *(out++) = width;
   *(out++) = height;
-  *(out++) = uint8_t(mozilla::gfx::SurfaceFormat::OS_RGBA);
-  *(out++) = 0;
+  *(out++) = uint8_t(mozilla::gfx::SurfaceFormat::R8G8B8A8);
+
+  // Set all bits to ensure in nsIconDecoder we color manage and premultiply.
+  *(out++) = 0xFF;
 
   nsresult rv;
   if (XRE_IsParentProcess()) {
     rv = GetIconForExtension(aFileExt, aIconSize, out);
   } else {
     rv = CallRemoteGetIconForExtension(aFileExt, aIconSize, out);
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Encode the RGBA data
-  int32_t stride = 4 * width;
-  gfx::PremultiplyData(out, stride, gfx::SurfaceFormat::R8G8B8A8, out, stride,
-                       gfx::SurfaceFormat::OS_RGBA,
-                       gfx::IntSize(width, height));
-
   nsCOMPtr<nsIStringInputStream> stream =
       do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = stream->AdoptData((char*)buf, buf_size.value());
   NS_ENSURE_SUCCESS(rv, rv);
 
   // nsIconProtocolHandler::NewChannel will provide the correct loadInfo for
--- a/image/decoders/icon/gtk/nsIconChannel.cpp
+++ b/image/decoders/icon/gtk/nsIconChannel.cpp
@@ -54,27 +54,29 @@ static nsresult moz_gdk_pixbuf_to_channe
     return NS_ERROR_OUT_OF_MEMORY;
   }
   uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size.value());
   uint8_t* out = buf;
 
   *(out++) = width;
   *(out++) = height;
   *(out++) = uint8_t(mozilla::gfx::SurfaceFormat::OS_RGBA);
-  *(out++) = 0;
+
+  // Set all bits to ensure in nsIconDecoder we color manage and premultiply.
+  *(out++) = 0xFF;
 
   const guchar* const pixels = gdk_pixbuf_get_pixels(aPixbuf);
   int instride = gdk_pixbuf_get_rowstride(aPixbuf);
   int outstride = width * n_channels;
 
-  // encode the RGB data and the A data
-  mozilla::gfx::PremultiplyData(pixels, instride,
-                                mozilla::gfx::SurfaceFormat::R8G8B8A8, out,
-                                outstride, mozilla::gfx::SurfaceFormat::OS_RGBA,
-                                mozilla::gfx::IntSize(width, height));
+  // encode the RGB data and the A data and adjust the stride as necessary.
+  mozilla::gfx::SwizzleData(pixels, instride,
+                            mozilla::gfx::SurfaceFormat::R8G8B8A8, out,
+                            outstride, mozilla::gfx::SurfaceFormat::OS_RGBA,
+                            mozilla::gfx::IntSize(width, height));
 
   nsresult rv;
   nsCOMPtr<nsIStringInputStream> stream =
       do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
 
   // Prevent the leaking of buf
   if (NS_WARN_IF(NS_FAILED(rv))) {
     free(buf);
--- a/image/decoders/icon/mac/nsIconChannelCocoa.mm
+++ b/image/decoders/icon/mac/nsIconChannelCocoa.mm
@@ -295,17 +295,21 @@ nsresult nsIconChannel::MakeInputStream(
   //  - 1 byte for the image width, as u8
   //  - 1 byte for the image height, as u8
   //  - the raw image data as BGRA, width * height * 4 bytes.
   size_t bufferCapacity = 4 + width * height * 4;
   UniquePtr<uint8_t[]> fileBuf = MakeUnique<uint8_t[]>(bufferCapacity);
   fileBuf[0] = uint8_t(width);
   fileBuf[1] = uint8_t(height);
   fileBuf[2] = uint8_t(mozilla::gfx::SurfaceFormat::B8G8R8A8);
+
+  // Clear all bits to ensure in nsIconDecoder we assume we are already color
+  // managed and premultiplied.
   fileBuf[3] = 0;
+
   uint8_t* imageBuf = &fileBuf[4];
 
   // Create a CGBitmapContext around imageBuf and draw iconImage to it.
   // This gives us the image data in the format we want: BGRA, four bytes per
   // pixel, in host endianness, with premultiplied alpha.
   CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
   CGContextRef ctx =
       CGBitmapContextCreate(imageBuf, width, height, 8 /* bitsPerComponent */, width * 4, cs,
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -712,36 +712,31 @@ LexerTransition<nsBMPDecoder::State> nsB
     // smaller, because the file might erroneously index past mNumColors.
     mColors = MakeUnique<ColorTableEntry[]>(256);
     memset(mColors.get(), 0, 256 * sizeof(ColorTableEntry));
 
     // OS/2 Bitmaps have no padding byte.
     mBytesPerColor = (mH.mBIHSize == InfoHeaderLength::WIN_V2) ? 3 : 4;
   }
 
-  auto cmsMode = gfxPlatform::GetCMSMode();
-  if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
-    cmsMode = eCMSMode_Off;
-  }
-
-  if (cmsMode != eCMSMode_Off) {
+  if (mCMSMode != eCMSMode_Off) {
     switch (mH.mCsType) {
       case InfoColorSpace::EMBEDDED:
         return SeekColorProfile(aLength);
       case InfoColorSpace::CALIBRATED_RGB:
         PrepareCalibratedColorProfile();
         break;
       case InfoColorSpace::SRGB:
       case InfoColorSpace::WINDOWS:
         MOZ_LOG(sBMPLog, LogLevel::Debug, ("using sRGB color profile\n"));
         if (mColors) {
           // We will transform the color table instead of the output pixels.
-          mTransform = gfxPlatform::GetCMSRGBTransform();
+          mTransform = GetCMSsRGBTransform(SurfaceFormat::R8G8B8);
         } else {
-          mTransform = gfxPlatform::GetCMSOSRGBATransform();
+          mTransform = GetCMSsRGBTransform(SurfaceFormat::OS_RGBA);
         }
         break;
       case InfoColorSpace::LINKED:
       default:
         // Not supported, no color management.
         MOZ_LOG(sBMPLog, LogLevel::Debug, ("color space type not provided\n"));
         break;
     }
@@ -777,25 +772,25 @@ void nsBMPDecoder::PrepareCalibratedColo
   if (mInProfile) {
     MOZ_LOG(sBMPLog, LogLevel::Debug, ("using calibrated RGB color profile\n"));
     PrepareColorProfileTransform();
   } else {
     MOZ_LOG(sBMPLog, LogLevel::Debug,
             ("failed to create calibrated RGB color profile, using sRGB\n"));
     if (mColors) {
       // We will transform the color table instead of the output pixels.
-      mTransform = gfxPlatform::GetCMSRGBTransform();
+      mTransform = GetCMSsRGBTransform(SurfaceFormat::R8G8B8);
     } else {
-      mTransform = gfxPlatform::GetCMSOSRGBATransform();
+      mTransform = GetCMSsRGBTransform(SurfaceFormat::OS_RGBA);
     }
   }
 }
 
 void nsBMPDecoder::PrepareColorProfileTransform() {
-  if (!mInProfile || !gfxPlatform::GetCMSOutputProfile()) {
+  if (!mInProfile || !GetCMSOutputProfile()) {
     return;
   }
 
   qcms_data_type inType;
   qcms_data_type outType;
   if (mColors) {
     // We will transform the color table instead of the output pixels.
     inType = QCMS_DATA_RGB_8;
@@ -817,18 +812,18 @@ void nsBMPDecoder::PrepareColorProfileTr
       intent = QCMS_INTENT_ABSOLUTE_COLORIMETRIC;
       break;
     case InfoColorIntent::IMAGES:
     default:
       intent = QCMS_INTENT_PERCEPTUAL;
       break;
   }
 
-  mTransform = qcms_transform_create(
-      mInProfile, inType, gfxPlatform::GetCMSOutputProfile(), outType, intent);
+  mTransform = qcms_transform_create(mInProfile, inType, GetCMSOutputProfile(),
+                                     outType, intent);
   if (!mTransform) {
     MOZ_LOG(sBMPLog, LogLevel::Debug,
             ("failed to create color profile transform\n"));
   }
 }
 
 LexerTransition<nsBMPDecoder::State> nsBMPDecoder::SeekColorProfile(
     size_t aLength) {
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -405,19 +405,18 @@ Tuple<int32_t, Maybe<WriteState>> nsGIFD
 /// Expand the colormap from RGB to Packed ARGB as needed by Cairo.
 /// And apply any LCMS transformation.
 void nsGIFDecoder2::ConvertColormap(uint32_t* aColormap, uint32_t aColors) {
   if (!aColors) {
     return;
   }
 
   // Apply CMS transformation if enabled and available
-  if (!(GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) &&
-      gfxPlatform::GetCMSMode() == eCMSMode_All) {
-    qcms_transform* transform = gfxPlatform::GetCMSRGBTransform();
+  if (mCMSMode == eCMSMode_All) {
+    qcms_transform* transform = GetCMSsRGBTransform(SurfaceFormat::R8G8B8);
     if (transform) {
       qcms_transform_data(transform, aColormap, aColormap, aColors);
     }
   }
 
   // Expand color table from RGB to BGRA.
   MOZ_ASSERT(mSwizzleFn);
   uint8_t* data = reinterpret_cast<uint8_t*>(aColormap);
--- a/image/decoders/nsIconDecoder.cpp
+++ b/image/decoders/nsIconDecoder.cpp
@@ -46,16 +46,30 @@ LexerResult nsIconDecoder::DoDecode(Sour
 }
 
 LexerTransition<nsIconDecoder::State> nsIconDecoder::ReadHeader(
     const char* aData) {
   // Grab the width and height.
   uint8_t width = uint8_t(aData[0]);
   uint8_t height = uint8_t(aData[1]);
   SurfaceFormat format = SurfaceFormat(aData[2]);
+  bool transform = bool(aData[3]);
+
+  // FIXME(aosmond): On OSX we get the icon in device space and already
+  // premultiplied, so we can't support the surface flags with icons right now.
+  SurfacePipeFlags pipeFlags = SurfacePipeFlags();
+  if (transform) {
+    if (mCMSMode == eCMSMode_All) {
+      mTransform = GetCMSsRGBTransform(format);
+    }
+
+    if (!(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA)) {
+      pipeFlags |= SurfacePipeFlags::PREMULTIPLY_ALPHA;
+    }
+  }
 
   // The input is 32bpp, so we expect 4 bytes of data per pixel.
   mBytesPerRow = width * 4;
 
   // Post our size to the superclass.
   PostSize(width, height);
 
   // Icons have alpha.
@@ -64,17 +78,17 @@ LexerTransition<nsIconDecoder::State> ns
   // If we're doing a metadata decode, we're done.
   if (IsMetadataDecode()) {
     return Transition::TerminateSuccess();
   }
 
   MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
   Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(
       this, Size(), OutputSize(), FullFrame(), format, SurfaceFormat::OS_RGBA,
-      /* aAnimParams */ Nothing(), mTransform, SurfacePipeFlags());
+      /* aAnimParams */ Nothing(), mTransform, pipeFlags);
   if (!pipe) {
     return Transition::TerminateFailure();
   }
 
   mPipe = std::move(*pipe);
 
   MOZ_ASSERT(mImageData, "Should have a buffer now");
 
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -104,18 +104,16 @@ nsJPEGDecoder::nsJPEGDecoder(RasterImage
   mInfo.client_data = (void*)this;
 
   mSegment = nullptr;
   mSegmentLen = 0;
 
   mBackBuffer = nullptr;
   mBackBufferLen = mBackBufferSize = mBackBufferUnreadLen = 0;
 
-  mCMSMode = 0;
-
   MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
           ("nsJPEGDecoder::nsJPEGDecoder: Creating JPEG decoder %p", this));
 }
 
 nsJPEGDecoder::~nsJPEGDecoder() {
   // Step 8: Release JPEG decompression object
   mInfo.src = nullptr;
   jpeg_destroy_decompress(&mInfo);
@@ -129,21 +127,16 @@ nsJPEGDecoder::~nsJPEGDecoder() {
           ("nsJPEGDecoder::~nsJPEGDecoder: Destroying JPEG decoder %p", this));
 }
 
 Maybe<Telemetry::HistogramID> nsJPEGDecoder::SpeedHistogram() const {
   return Some(Telemetry::IMAGE_DECODE_SPEED_JPEG);
 }
 
 nsresult nsJPEGDecoder::InitInternal() {
-  mCMSMode = gfxPlatform::GetCMSMode();
-  if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
-    mCMSMode = eCMSMode_Off;
-  }
-
   // We set up the normal JPEG error routines, then override error_exit.
   mInfo.err = jpeg_std_error(&mErr.pub);
   //   mInfo.err = jpeg_std_error(&mErr.pub);
   mErr.pub.error_exit = my_error_exit;
   // Establish the setjmp return context for my_error_exit to use.
   if (setjmp(mErr.setjmp_buffer)) {
     // If we get here, the JPEG code has signaled an error, and initialization
     // has failed.
@@ -287,17 +280,17 @@ LexerTransition<nsJPEGDecoder::State> ns
           mState = JPEG_ERROR;
           MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
                   ("} (unknown colorspace (3))"));
           return Transition::TerminateFailure();
       }
 
       if (mCMSMode != eCMSMode_Off) {
         if ((mInProfile = GetICCProfile(mInfo)) != nullptr &&
-            gfxPlatform::GetCMSOutputProfile()) {
+            GetCMSOutputProfile()) {
           uint32_t profileSpace = qcms_profile_get_color_space(mInProfile);
 
           qcms_data_type outputType = gfxPlatform::GetCMSOSRGBAType();
           Maybe<qcms_data_type> inputType;
           if (profileSpace == icSigRgbData) {
             // We can always color manage RGB profiles since it happens at the
             // end of the pipeline.
             inputType.emplace(outputType);
@@ -324,22 +317,22 @@ LexerTransition<nsJPEGDecoder::State> ns
           if (inputType) {
             // Calculate rendering intent.
             int intent = gfxPlatform::GetRenderingIntent();
             if (intent == -1) {
               intent = qcms_profile_get_rendering_intent(mInProfile);
             }
 
             // Create the color management transform.
-            mTransform = qcms_transform_create(
-                mInProfile, *inputType, gfxPlatform::GetCMSOutputProfile(),
-                outputType, (qcms_intent)intent);
+            mTransform = qcms_transform_create(mInProfile, *inputType,
+                                               GetCMSOutputProfile(),
+                                               outputType, (qcms_intent)intent);
           }
         } else if (mCMSMode == eCMSMode_All) {
-          mTransform = gfxPlatform::GetCMSOSRGBATransform();
+          mTransform = GetCMSsRGBTransform(SurfaceFormat::OS_RGBX);
         }
       }
 
       // We don't want to use the pipe buffers directly because we don't want
       // any reads on non-BGRA formatted data.
       if (mInfo.out_color_space == JCS_GRAYSCALE ||
           mInfo.out_color_space == JCS_CMYK) {
         mCMSLine = new (std::nothrow) uint32_t[mInfo.image_width];
--- a/image/decoders/nsJPEGDecoder.h
+++ b/image/decoders/nsJPEGDecoder.h
@@ -99,17 +99,15 @@ class nsJPEGDecoder : public Decoder {
   uint32_t mProfileLength;
 
   uint32_t* mCMSLine;
 
   bool mReading;
 
   const Decoder::DecodeStyle mDecodeStyle;
 
-  uint32_t mCMSMode;
-
   SurfacePipe mPipe;
 };
 
 }  // namespace image
 }  // namespace mozilla
 
 #endif  // mozilla_image_decoders_nsJPEGDecoder_h
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -104,17 +104,16 @@ nsPNGDecoder::nsPNGDecoder(RasterImage* 
              Transition::TerminateSuccess()),
       mNextTransition(Transition::ContinueUnbuffered(State::PNG_DATA)),
       mLastChunkLength(0),
       mPNG(nullptr),
       mInfo(nullptr),
       mCMSLine(nullptr),
       interlacebuf(nullptr),
       mFormat(SurfaceFormat::UNKNOWN),
-      mCMSMode(0),
       mChannels(0),
       mPass(0),
       mFrameIsHidden(false),
       mDisablePremultipliedAlpha(false),
       mGotInfoCallback(false),
       mUsePipeTransform(false),
       mNumFrames(0) {}
 
@@ -265,20 +264,16 @@ void nsPNGDecoder::EndImageFrame() {
   Opacity opacity = mFormat == SurfaceFormat::OS_RGBX
                         ? Opacity::FULLY_OPAQUE
                         : Opacity::SOME_TRANSPARENCY;
 
   PostFrameStop(opacity);
 }
 
 nsresult nsPNGDecoder::InitInternal() {
-  mCMSMode = gfxPlatform::GetCMSMode();
-  if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
-    mCMSMode = eCMSMode_Off;
-  }
   mDisablePremultipliedAlpha =
       bool(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
 
 #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
   static png_byte color_chunks[] = {99,  72, 82, 77, '\0',     // cHRM
                                     105, 67, 67, 80, '\0'};    // iCCP
   static png_byte unused_chunks[] = {98,  75, 71, 68,  '\0',   // bKGD
                                      101, 88, 73, 102, '\0',   // eXIf
@@ -593,17 +588,17 @@ void nsPNGDecoder::info_callback(png_str
       intent = gfxPlatform::GetRenderingIntent();
       uint32_t pIntent =
           decoder->ReadColorProfile(png_ptr, info_ptr, color_type, &sRGBTag);
       // If we're not mandating an intent, use the one from the image.
       if (intent == uint32_t(-1)) {
         intent = pIntent;
       }
     }
-    if (!decoder->mInProfile || !gfxPlatform::GetCMSOutputProfile()) {
+    if (!decoder->mInProfile || !decoder->GetCMSOutputProfile()) {
       png_set_gray_to_rgb(png_ptr);
 
       // only do gamma correction if CMS isn't entirely disabled
       if (decoder->mCMSMode != eCMSMode_Off) {
         PNGDoGammaCorrection(png_ptr, info_ptr);
       }
     }
   }
@@ -654,17 +649,17 @@ void nsPNGDecoder::info_callback(png_str
     // they will call with the proper first frame rect in the full decode.
     decoder->PostHasTransparencyIfNeeded(transparency);
 
     // We have the metadata we're looking for, so stop here, before we allocate
     // buffers below.
     return decoder->DoTerminate(png_ptr, TerminalState::SUCCESS);
   }
 
-  if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) {
+  if (decoder->mInProfile && decoder->GetCMSOutputProfile()) {
     qcms_data_type inType;
     qcms_data_type outType;
 
     uint32_t profileSpace = qcms_profile_get_color_space(decoder->mInProfile);
     decoder->mUsePipeTransform = profileSpace != icSigGrayData;
     if (decoder->mUsePipeTransform) {
       // If the transform happens with SurfacePipe, it will be in RGBA if we
       // have an alpha channel, because the swizzle and premultiplication
@@ -682,29 +677,31 @@ void nsPNGDecoder::info_callback(png_str
         inType = QCMS_DATA_GRAYA_8;
         outType = gfxPlatform::GetCMSOSRGBAType();
       } else {
         inType = QCMS_DATA_GRAY_8;
         outType = gfxPlatform::GetCMSOSRGBAType();
       }
     }
 
-    decoder->mTransform = qcms_transform_create(
-        decoder->mInProfile, inType, gfxPlatform::GetCMSOutputProfile(),
-        outType, (qcms_intent)intent);
+    decoder->mTransform = qcms_transform_create(decoder->mInProfile, inType,
+                                                decoder->GetCMSOutputProfile(),
+                                                outType, (qcms_intent)intent);
   } else if ((sRGBTag && decoder->mCMSMode == eCMSMode_TaggedOnly) ||
              decoder->mCMSMode == eCMSMode_All) {
     // If the transform happens with SurfacePipe, it will be in RGBA if we
     // have an alpha channel, because the swizzle and premultiplication
-    // happens after color management. Otherwise it will be in BGRA because
+    // happens after color management. Otherwise it will be in OS_RGBA because
     // the swizzle happens at the start.
     if (transparency == TransparencyType::eAlpha) {
-      decoder->mTransform = gfxPlatform::GetCMSRGBATransform();
+      decoder->mTransform =
+          decoder->GetCMSsRGBTransform(SurfaceFormat::R8G8B8A8);
     } else {
-      decoder->mTransform = gfxPlatform::GetCMSBGRATransform();
+      decoder->mTransform =
+          decoder->GetCMSsRGBTransform(SurfaceFormat::OS_RGBA);
     }
     decoder->mUsePipeTransform = true;
   }
 
 #ifdef PNG_APNG_SUPPORTED
   if (isAnimated) {
     png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback,
                                  nullptr);
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -92,19 +92,16 @@ class nsPNGDecoder : public Decoder {
  public:
   png_structp mPNG;
   png_infop mInfo;
   nsIntRect mFrameRect;
   uint8_t* mCMSLine;
   uint8_t* interlacebuf;
   gfx::SurfaceFormat mFormat;
 
-  // whether CMS or premultiplied alpha are forced off
-  uint32_t mCMSMode;
-
   uint8_t mChannels;
   uint8_t mPass;
   bool mFrameIsHidden;
   bool mDisablePremultipliedAlpha;
   bool mGotInfoCallback;
   bool mUsePipeTransform;
 
   struct AnimFrameInfo {
--- a/image/decoders/nsWebPDecoder.cpp
+++ b/image/decoders/nsWebPDecoder.cpp
@@ -284,32 +284,27 @@ void nsWebPDecoder::EndFrame() {
   mLastRow = 0;
   ++mCurrentFrame;
 }
 
 void nsWebPDecoder::ApplyColorProfile(const char* aProfile, size_t aLength) {
   MOZ_ASSERT(!mGotColorProfile);
   mGotColorProfile = true;
 
-  if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
-    return;
-  }
-
-  auto mode = gfxPlatform::GetCMSMode();
-  if (mode == eCMSMode_Off || (mode == eCMSMode_TaggedOnly && !aProfile) ||
-      !gfxPlatform::GetCMSOutputProfile()) {
+  if (mCMSMode == eCMSMode_Off || !GetCMSOutputProfile() ||
+      (mCMSMode == eCMSMode_TaggedOnly && !aProfile)) {
     return;
   }
 
   if (!aProfile) {
     MOZ_LOG(sWebPLog, LogLevel::Debug,
             ("[this=%p] nsWebPDecoder::ApplyColorProfile -- not tagged, use "
              "sRGB transform\n",
              this));
-    mTransform = gfxPlatform::GetCMSBGRATransform();
+    mTransform = GetCMSsRGBTransform(SurfaceFormat::OS_RGBA);
     return;
   }
 
   mInProfile = qcms_profile_from_memory(aProfile, aLength);
   if (!mInProfile) {
     MOZ_LOG(
         sWebPLog, LogLevel::Error,
         ("[this=%p] nsWebPDecoder::ApplyColorProfile -- bad color profile\n",
@@ -330,19 +325,18 @@ void nsWebPDecoder::ApplyColorProfile(co
   // Calculate rendering intent.
   int intent = gfxPlatform::GetRenderingIntent();
   if (intent == -1) {
     intent = qcms_profile_get_rendering_intent(mInProfile);
   }
 
   // Create the color management transform.
   qcms_data_type type = gfxPlatform::GetCMSOSRGBAType();
-  mTransform = qcms_transform_create(mInProfile, type,
-                                     gfxPlatform::GetCMSOutputProfile(), type,
-                                     (qcms_intent)intent);
+  mTransform = qcms_transform_create(mInProfile, type, GetCMSOutputProfile(),
+                                     type, (qcms_intent)intent);
   MOZ_LOG(sWebPLog, LogLevel::Debug,
           ("[this=%p] nsWebPDecoder::ApplyColorProfile -- use tagged "
            "transform\n",
            this));
 }
 
 LexerResult nsWebPDecoder::ReadHeader(WebPDemuxer* aDemuxer, bool aIsComplete) {
   MOZ_ASSERT(aDemuxer);
--- a/image/imgIContainer.idl
+++ b/image/imgIContainer.idl
@@ -212,38 +212,49 @@ interface imgIContainer : nsISupports
    * intrinsic size. In such a case, we synthesize a viewport for the SVG image
    * (a "window into SVG space") based on the border image area, and we need to
    * be sure we don't subsequently scale that viewport in a way that distorts
    * its contents by stretching them more in one dimension than the other.
    *
    * FLAG_AVOID_REDECODE_FOR_SIZE: If there is already a raster surface
    * available for this image, but it is not the same size as requested, skip
    * starting a new decode for said size.
+   *
+   * FLAG_DECODE_TO_SRGB_COLORSPACE: Instead of converting the colorspace to
+   * the display's colorspace, use sRGB.
    */
   const unsigned long FLAG_NONE                            = 0x0;
   const unsigned long FLAG_SYNC_DECODE                     = 0x1;
   const unsigned long FLAG_SYNC_DECODE_IF_FAST             = 0x2;
   const unsigned long FLAG_ASYNC_NOTIFY                    = 0x4;
   const unsigned long FLAG_DECODE_NO_PREMULTIPLY_ALPHA     = 0x8;
   const unsigned long FLAG_DECODE_NO_COLORSPACE_CONVERSION = 0x10;
   const unsigned long FLAG_CLAMP                           = 0x20;
   const unsigned long FLAG_HIGH_QUALITY_SCALING            = 0x40;
   const unsigned long FLAG_WANT_DATA_SURFACE               = 0x80;
   const unsigned long FLAG_BYPASS_SURFACE_CACHE            = 0x100;
   const unsigned long FLAG_FORCE_PRESERVEASPECTRATIO_NONE  = 0x200;
   const unsigned long FLAG_FORCE_UNIFORM_SCALING           = 0x400;
   const unsigned long FLAG_AVOID_REDECODE_FOR_SIZE         = 0x800;
+  const unsigned long FLAG_DECODE_TO_SRGB_COLORSPACE       = 0x1000;
 
   /**
    * A constant specifying the default set of decode flags (i.e., the default
    * values for FLAG_DECODE_*).
    */
   const unsigned long DECODE_FLAGS_DEFAULT = 0;
 
   /**
+   * A constant specifying the decode flags recommended to be used when
+   * re-encoding an image, or with the clipboard.
+   */
+  const unsigned long DECODE_FLAGS_FOR_REENCODE =
+      FLAG_DECODE_NO_PREMULTIPLY_ALPHA | FLAG_DECODE_TO_SRGB_COLORSPACE;
+
+  /**
     * Constants for specifying various "special" frames.
     *
     * FRAME_FIRST: The first frame
     * FRAME_CURRENT: The current frame
     *
     * FRAME_MAX_VALUE should be set to the value of the maximum constant above,
     * as it is used for ensuring that a valid value was passed in.
     */
--- a/image/test/gtest/Common.cpp
+++ b/image/test/gtest/Common.cpp
@@ -31,22 +31,18 @@ static bool sImageLibInitialized = false
 AutoInitializeImageLib::AutoInitializeImageLib() {
   if (MOZ_LIKELY(sImageLibInitialized)) {
     return;
   }
 
   EXPECT_TRUE(NS_IsMainThread());
   sImageLibInitialized = true;
 
-  // Force sRGB to be consistent with reftests.
-  nsresult rv = Preferences::SetBool("gfx.color_management.force_srgb", true);
-  EXPECT_TRUE(rv == NS_OK);
-
   // Ensure WebP is enabled to run decoder tests.
-  rv = Preferences::SetBool("image.webp.enabled", true);
+  nsresult rv = Preferences::SetBool("image.webp.enabled", true);
   EXPECT_TRUE(rv == NS_OK);
 
   // Ensure that ImageLib services are initialized.
   nsCOMPtr<imgITools> imgTools =
       do_CreateInstance("@mozilla.org/image/tools;1");
   EXPECT_TRUE(imgTools != nullptr);
 
   // Ensure gfxPlatform is initialized.
--- a/image/test/gtest/Common.h
+++ b/image/test/gtest/Common.h
@@ -11,16 +11,17 @@
 #include "gtest/gtest.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/gfx/2D.h"
 #include "Decoder.h"
 #include "gfxColor.h"
+#include "gfxPlatform.h"
 #include "nsCOMPtr.h"
 #include "SurfaceFlags.h"
 #include "SurfacePipe.h"
 #include "SurfacePipeFactory.h"
 
 class nsIInputStream;
 
 namespace mozilla {
@@ -29,37 +30,57 @@ namespace image {
 ///////////////////////////////////////////////////////////////////////////////
 // Types
 ///////////////////////////////////////////////////////////////////////////////
 
 struct BGRAColor {
   BGRAColor() : BGRAColor(0, 0, 0, 0) {}
 
   BGRAColor(uint8_t aBlue, uint8_t aGreen, uint8_t aRed, uint8_t aAlpha,
-            bool aPremultiplied = false)
+            bool aPremultiplied = false, bool asRGB = true)
       : mBlue(aBlue),
         mGreen(aGreen),
         mRed(aRed),
         mAlpha(aAlpha),
-        mPremultiplied(aPremultiplied) {}
+        mPremultiplied(aPremultiplied),
+        msRGB(asRGB) {}
 
   static BGRAColor Green() { return BGRAColor(0x00, 0xFF, 0x00, 0xFF); }
   static BGRAColor Red() { return BGRAColor(0x00, 0x00, 0xFF, 0xFF); }
   static BGRAColor Blue() { return BGRAColor(0xFF, 0x00, 0x00, 0xFF); }
   static BGRAColor Transparent() { return BGRAColor(0x00, 0x00, 0x00, 0x00); }
 
   static BGRAColor FromPixel(uint32_t aPixel) {
     uint8_t r, g, b, a;
     r = (aPixel >> gfx::SurfaceFormatBit::OS_R) & 0xFF;
     g = (aPixel >> gfx::SurfaceFormatBit::OS_G) & 0xFF;
     b = (aPixel >> gfx::SurfaceFormatBit::OS_B) & 0xFF;
     a = (aPixel >> gfx::SurfaceFormatBit::OS_A) & 0xFF;
     return BGRAColor(b, g, r, a, true);
   }
 
+  BGRAColor DeviceColor() const {
+    MOZ_ASSERT(!mPremultiplied);
+    if (msRGB) {
+      gfx::DeviceColor color = gfx::ToDeviceColor(
+          gfx::sRGBColor(float(mRed) / 255.0f, float(mGreen) / 255.0f,
+                         float(mBlue) / 255.0f, 1.0));
+      return BGRAColor(uint8_t(color.b * 255.0f), uint8_t(color.g * 255.0f),
+                       uint8_t(color.r * 255.0f), mAlpha, mPremultiplied,
+                       /* asRGB */ false);
+    }
+    return *this;
+  }
+
+  BGRAColor sRGBColor() const {
+    MOZ_ASSERT(msRGB);
+    MOZ_ASSERT(!mPremultiplied);
+    return *this;
+  }
+
   BGRAColor Premultiply() const {
     if (!mPremultiplied) {
       return BGRAColor(gfxPreMultiply(mBlue, mAlpha),
                        gfxPreMultiply(mGreen, mAlpha),
                        gfxPreMultiply(mRed, mAlpha), mAlpha, true);
     }
     return *this;
   }
@@ -71,16 +92,17 @@ struct BGRAColor {
     return gfxPackedPixelNoPreMultiply(mAlpha, mRed, mGreen, mBlue);
   }
 
   uint8_t mBlue;
   uint8_t mGreen;
   uint8_t mRed;
   uint8_t mAlpha;
   bool mPremultiplied;
+  bool msRGB;
 };
 
 enum TestCaseFlags {
   TEST_CASE_DEFAULT_FLAGS = 0,
   TEST_CASE_IS_FUZZY = 1 << 0,
   TEST_CASE_HAS_ERROR = 1 << 1,
   TEST_CASE_IS_TRANSPARENT = 1 << 2,
   TEST_CASE_IS_ANIMATED = 1 << 3,
@@ -104,16 +126,41 @@ struct ImageTestCase {
       : mPath(aPath),
         mMimeType(aMimeType),
         mSize(aSize),
         mOutputSize(aOutputSize),
         mFlags(aFlags),
         mSurfaceFlags(DefaultSurfaceFlags()),
         mColor(BGRAColor::Green()) {}
 
+  ImageTestCase WithSurfaceFlags(SurfaceFlags aSurfaceFlags) const {
+    ImageTestCase self = *this;
+    self.mSurfaceFlags = aSurfaceFlags;
+    return self;
+  }
+
+  BGRAColor ChooseColor(const BGRAColor& aColor) const {
+    if (mSurfaceFlags & SurfaceFlags::TO_SRGB_COLORSPACE) {
+      return aColor.sRGBColor();
+    }
+    return aColor.DeviceColor();
+  }
+
+  BGRAColor Color() const { return ChooseColor(mColor); }
+
+  uint8_t Fuzz() const {
+    // If we are using device space, there can easily be off by 1 channel errors
+    // depending on the color profile and how the rounding went.
+    if (mFlags & TEST_CASE_IS_FUZZY ||
+        !(mSurfaceFlags & SurfaceFlags::TO_SRGB_COLORSPACE)) {
+      return 1;
+    }
+    return 0;
+  }
+
   const char* mPath;
   const char* mMimeType;
   gfx::IntSize mSize;
   gfx::IntSize mOutputSize;
   uint32_t mFlags;
   SurfaceFlags mSurfaceFlags;
   BGRAColor mColor;
 };
--- a/image/test/gtest/TestDecodeToSurface.cpp
+++ b/image/test/gtest/TestDecodeToSurface.cpp
@@ -60,18 +60,17 @@ class DecodeToSurfaceRunnable : public R
                 mSurface->GetFormat() == SurfaceFormat::OS_RGBA);
 
     if (outputSize) {
       EXPECT_EQ(*outputSize, mSurface->GetSize());
     } else {
       EXPECT_EQ(mTestCase.mSize, mSurface->GetSize());
     }
 
-    EXPECT_TRUE(IsSolidColor(mSurface, BGRAColor::Green(),
-                             mTestCase.mFlags & TEST_CASE_IS_FUZZY ? 1 : 0));
+    EXPECT_TRUE(IsSolidColor(mSurface, mTestCase.Color(), mTestCase.Fuzz()));
   }
 
  private:
   RefPtr<SourceSurface>& mSurface;
   nsCOMPtr<nsIInputStream> mInputStream;
   RefPtr<ImageOps::ImageBuffer> mImageBuffer;
   ImageTestCase mTestCase;
 };
--- a/image/test/gtest/TestDecoders.cpp
+++ b/image/test/gtest/TestDecoders.cpp
@@ -81,18 +81,17 @@ static void CheckDecoderResults(const Im
     return;
   }
 
   if (aTestCase.mFlags & TEST_CASE_IGNORE_OUTPUT) {
     return;
   }
 
   // Check the output.
-  EXPECT_TRUE(IsSolidColor(surface, aTestCase.mColor,
-                           aTestCase.mFlags & TEST_CASE_IS_FUZZY ? 1 : 0));
+  EXPECT_TRUE(IsSolidColor(surface, aTestCase.Color(), aTestCase.Fuzz()));
 }
 
 template <typename Func>
 void WithSingleChunkDecode(const ImageTestCase& aTestCase,
                            const Maybe<IntSize>& aOutputSize,
                            Func aResultChecker) {
   nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
   ASSERT_TRUE(inputStream != nullptr);
@@ -108,17 +107,17 @@ void WithSingleChunkDecode(const ImageTe
   rv = sourceBuffer->AppendFromInputStream(inputStream, length);
   ASSERT_TRUE(NS_SUCCEEDED(rv));
   sourceBuffer->Complete(NS_OK);
 
   // Create a decoder.
   DecoderType decoderType = DecoderFactory::GetDecoderType(aTestCase.mMimeType);
   RefPtr<Decoder> decoder = DecoderFactory::CreateAnonymousDecoder(
       decoderType, sourceBuffer, aOutputSize, DecoderFlags::FIRST_FRAME_ONLY,
-      DefaultSurfaceFlags());
+      aTestCase.mSurfaceFlags);
   ASSERT_TRUE(decoder != nullptr);
   RefPtr<IDecodingTask> task =
       new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ false);
 
   // Run the full decoder synchronously.
   task->Run();
 
   // Call the lambda to verify the expected results.
@@ -145,17 +144,17 @@ void WithDelayedChunkDecode(const ImageT
 
   // Prepare an empty SourceBuffer.
   auto sourceBuffer = MakeNotNull<RefPtr<SourceBuffer>>();
 
   // Create a decoder.
   DecoderType decoderType = DecoderFactory::GetDecoderType(aTestCase.mMimeType);
   RefPtr<Decoder> decoder = DecoderFactory::CreateAnonymousDecoder(
       decoderType, sourceBuffer, aOutputSize, DecoderFlags::FIRST_FRAME_ONLY,
-      DefaultSurfaceFlags());
+      aTestCase.mSurfaceFlags);
   ASSERT_TRUE(decoder != nullptr);
   RefPtr<IDecodingTask> task =
       new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ true);
 
   // Run the full decoder synchronously. It should now be waiting on
   // the iterator to yield some data since we haven't written anything yet.
   task->Run();
 
@@ -189,17 +188,17 @@ static void CheckDecoderMultiChunk(const
   ASSERT_TRUE(NS_SUCCEEDED(rv));
 
   // Create a SourceBuffer and a decoder.
   auto sourceBuffer = MakeNotNull<RefPtr<SourceBuffer>>();
   sourceBuffer->ExpectLength(length);
   DecoderType decoderType = DecoderFactory::GetDecoderType(aTestCase.mMimeType);
   RefPtr<Decoder> decoder = DecoderFactory::CreateAnonymousDecoder(
       decoderType, sourceBuffer, Nothing(), DecoderFlags::FIRST_FRAME_ONLY,
-      DefaultSurfaceFlags());
+      aTestCase.mSurfaceFlags);
   ASSERT_TRUE(decoder != nullptr);
   RefPtr<IDecodingTask> task =
       new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ true);
 
   // Run the full decoder synchronously. It should now be waiting on
   // the iterator to yield some data since we haven't written anything yet.
   task->Run();
 
@@ -242,24 +241,27 @@ static void CheckDownscaleDuringDecode(c
     if (aTestCase.mFlags & TEST_CASE_IGNORE_OUTPUT) {
       return;
     }
 
     // Check that the downscaled image is correct. Note that we skip rows near
     // the transitions between colors, since the downscaler does not produce a
     // sharp boundary at these points. Even some of the rows we test need a
     // small amount of fuzz; this is just the nature of Lanczos downscaling.
-    EXPECT_TRUE(
-        RowsAreSolidColor(surface, 0, 4, BGRAColor::Green(), /* aFuzz = */ 47));
-    EXPECT_TRUE(
-        RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(), /* aFuzz = */ 27));
+    EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4,
+                                  aTestCase.ChooseColor(BGRAColor::Green()),
+                                  /* aFuzz = */ 47));
+    EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3,
+                                  aTestCase.ChooseColor(BGRAColor::Red()),
+                                  /* aFuzz = */ 27));
     EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(),
                                   /* aFuzz = */ 47));
-    EXPECT_TRUE(
-        RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(), /* aFuzz = */ 27));
+    EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4,
+                                  aTestCase.ChooseColor(BGRAColor::Red()),
+                                  /* aFuzz = */ 27));
   });
 }
 
 static void CheckAnimationDecoderResults(const ImageTestCase& aTestCase,
                                          AnimationSurfaceProvider* aProvider,
                                          Decoder* aDecoder) {
   EXPECT_TRUE(aDecoder->GetDecodeDone());
   EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR), aDecoder->HasError());
@@ -274,35 +276,35 @@ static void CheckAnimationDecoderResults
   EXPECT_EQ(aTestCase.mSize.height, size.height);
 
   if (aTestCase.mFlags & TEST_CASE_IGNORE_OUTPUT) {
     return;
   }
 
   // Check the output.
   AutoTArray<BGRAColor, 2> framePixels;
-  framePixels.AppendElement(BGRAColor::Green());
-  framePixels.AppendElement(BGRAColor(0x7F, 0x7F, 0x7F, 0xFF));
+  framePixels.AppendElement(aTestCase.ChooseColor(BGRAColor::Green()));
+  framePixels.AppendElement(
+      aTestCase.ChooseColor(BGRAColor(0x7F, 0x7F, 0x7F, 0xFF)));
 
   DrawableSurface drawableSurface(WrapNotNull(aProvider));
   for (size_t i = 0; i < framePixels.Length(); ++i) {
     nsresult rv = drawableSurface.Seek(i);
     EXPECT_TRUE(NS_SUCCEEDED(rv));
 
     // Check the first frame, all green.
     RawAccessFrameRef rawFrame = drawableSurface->RawAccessRef();
     RefPtr<SourceSurface> surface = rawFrame->GetSourceSurface();
 
     // Verify that the resulting surfaces matches our expectations.
     EXPECT_TRUE(surface->IsDataSourceSurface());
     EXPECT_TRUE(surface->GetFormat() == SurfaceFormat::OS_RGBX ||
                 surface->GetFormat() == SurfaceFormat::OS_RGBA);
     EXPECT_EQ(aTestCase.mOutputSize, surface->GetSize());
-    EXPECT_TRUE(IsSolidColor(surface, framePixels[i],
-                             aTestCase.mFlags & TEST_CASE_IS_FUZZY ? 1 : 0));
+    EXPECT_TRUE(IsSolidColor(surface, framePixels[i], aTestCase.Fuzz()));
   }
 
   // Should be no more frames.
   nsresult rv = drawableSurface.Seek(framePixels.Length());
   EXPECT_TRUE(NS_FAILED(rv));
 }
 
 template <typename Func>
@@ -338,17 +340,17 @@ static void WithSingleChunkAnimationDeco
       decoderType, rasterImage, sourceBuffer);
   ASSERT_TRUE(task != nullptr);
 
   // Run the metadata decoder synchronously.
   task->Run();
 
   // Create a decoder.
   DecoderFlags decoderFlags = DefaultDecoderFlags();
-  SurfaceFlags surfaceFlags = DefaultSurfaceFlags();
+  SurfaceFlags surfaceFlags = aTestCase.mSurfaceFlags;
   RefPtr<Decoder> decoder = DecoderFactory::CreateAnonymousDecoder(
       decoderType, sourceBuffer, Nothing(), decoderFlags, surfaceFlags);
   ASSERT_TRUE(decoder != nullptr);
 
   // Create an AnimationSurfaceProvider which will manage the decoding process
   // and make this decoder's output available in the surface cache.
   SurfaceKey surfaceKey = RasterSurfaceKey(aTestCase.mOutputSize, surfaceFlags,
                                            PlaybackType::eAnimated);
@@ -423,63 +425,63 @@ static void CheckDecoderFrameFirst(const
 
   Progress imageProgress = tracker->GetProgress();
 
   EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
   EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
 
   // Ensure that we decoded the static version of the image.
   {
-    LookupResult result =
-        SurfaceCache::Lookup(ImageKey(image.get()),
-                             RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
-                                              PlaybackType::eStatic),
-                             /* aMarkUsed = */ false);
+    LookupResult result = SurfaceCache::Lookup(
+        ImageKey(image.get()),
+        RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
+                         PlaybackType::eStatic),
+        /* aMarkUsed = */ false);
     ASSERT_EQ(MatchType::EXACT, result.Type());
     EXPECT_TRUE(bool(result.Surface()));
   }
 
   // Ensure that we didn't decode the animated version of the image.
   {
-    LookupResult result =
-        SurfaceCache::Lookup(ImageKey(image.get()),
-                             RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
-                                              PlaybackType::eAnimated),
-                             /* aMarkUsed = */ false);
+    LookupResult result = SurfaceCache::Lookup(
+        ImageKey(image.get()),
+        RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
+                         PlaybackType::eAnimated),
+        /* aMarkUsed = */ false);
     ASSERT_EQ(MatchType::NOT_FOUND, result.Type());
   }
 
   // Use GetFrame() to force a sync decode of the image, this time specifying
   // FRAME_CURRENT to ensure that we get an animated decode.
   RefPtr<SourceSurface> animatedSurface = image->GetFrame(
       imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_SYNC_DECODE);
 
   // Ensure that we decoded both frames of the animated version of the image.
   {
-    LookupResult result =
-        SurfaceCache::Lookup(ImageKey(image.get()),
-                             RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
-                                              PlaybackType::eAnimated),
-                             /* aMarkUsed = */ true);
+    LookupResult result = SurfaceCache::Lookup(
+        ImageKey(image.get()),
+        RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
+                         PlaybackType::eAnimated),
+        /* aMarkUsed = */ true);
     ASSERT_EQ(MatchType::EXACT, result.Type());
 
     EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
     EXPECT_TRUE(bool(result.Surface()));
 
     RefPtr<imgFrame> partialFrame = result.Surface().GetFrame(1);
     EXPECT_TRUE(bool(partialFrame));
   }
 
   // Ensure that the static version is still around.
   {
-    LookupResult result =
-        SurfaceCache::Lookup(ImageKey(image.get()),
-                             RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
-                                              PlaybackType::eStatic),
-                             /* aMarkUsed = */ true);
+    LookupResult result = SurfaceCache::Lookup(
+        ImageKey(image.get()),
+        RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
+                         PlaybackType::eStatic),
+        /* aMarkUsed = */ true);
     ASSERT_EQ(MatchType::EXACT, result.Type());
     EXPECT_TRUE(bool(result.Surface()));
   }
 }
 
 static void CheckDecoderFrameCurrent(const ImageTestCase& aTestCase) {
   // Verify that we can decode this test case and retrieve the entire sequence
   // of frames using imgIContainer::FRAME_CURRENT. This ensures that we
@@ -531,198 +533,116 @@ static void CheckDecoderFrameCurrent(con
 
   Progress imageProgress = tracker->GetProgress();
 
   EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
   EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
 
   // Ensure that we decoded both frames of the animated version of the image.
   {
-    LookupResult result =
-        SurfaceCache::Lookup(ImageKey(image.get()),
-                             RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
-                                              PlaybackType::eAnimated),
-                             /* aMarkUsed = */ true);
+    LookupResult result = SurfaceCache::Lookup(
+        ImageKey(image.get()),
+        RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
+                         PlaybackType::eAnimated),
+        /* aMarkUsed = */ true);
     ASSERT_EQ(MatchType::EXACT, result.Type());
 
     EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
     EXPECT_TRUE(bool(result.Surface()));
 
     RefPtr<imgFrame> partialFrame = result.Surface().GetFrame(1);
     EXPECT_TRUE(bool(partialFrame));
   }
 
   // Ensure that we didn't decode the static version of the image.
   {
-    LookupResult result =
-        SurfaceCache::Lookup(ImageKey(image.get()),
-                             RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
-                                              PlaybackType::eStatic),
-                             /* aMarkUsed = */ false);
+    LookupResult result = SurfaceCache::Lookup(
+        ImageKey(image.get()),
+        RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
+                         PlaybackType::eStatic),
+        /* aMarkUsed = */ false);
     ASSERT_EQ(MatchType::NOT_FOUND, result.Type());
   }
 
   // Use GetFrame() to force a sync decode of the image, this time specifying
   // FRAME_FIRST to ensure that we get a single-frame decode.
   RefPtr<SourceSurface> animatedSurface = image->GetFrame(
       imgIContainer::FRAME_FIRST, imgIContainer::FLAG_SYNC_DECODE);
 
   // Ensure that we decoded the static version of the image.
   {
-    LookupResult result =
-        SurfaceCache::Lookup(ImageKey(image.get()),
-                             RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
-                                              PlaybackType::eStatic),
-                             /* aMarkUsed = */ true);
+    LookupResult result = SurfaceCache::Lookup(
+        ImageKey(image.get()),
+        RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
+                         PlaybackType::eStatic),
+        /* aMarkUsed = */ true);
     ASSERT_EQ(MatchType::EXACT, result.Type());
     EXPECT_TRUE(bool(result.Surface()));
   }
 
   // Ensure that both frames of the animated version are still around.
   {
-    LookupResult result =
-        SurfaceCache::Lookup(ImageKey(image.get()),
-                             RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
-                                              PlaybackType::eAnimated),
-                             /* aMarkUsed = */ true);
+    LookupResult result = SurfaceCache::Lookup(
+        ImageKey(image.get()),
+        RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
+                         PlaybackType::eAnimated),
+        /* aMarkUsed = */ true);
     ASSERT_EQ(MatchType::EXACT, result.Type());
 
     EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
     EXPECT_TRUE(bool(result.Surface()));
 
     RefPtr<imgFrame> partialFrame = result.Surface().GetFrame(1);
     EXPECT_TRUE(bool(partialFrame));
   }
 }
 
 class ImageDecoders : public ::testing::Test {
  protected:
   AutoInitializeImageLib mInit;
 };
 
-TEST_F(ImageDecoders, PNGSingleChunk) {
-  CheckDecoderSingleChunk(GreenPNGTestCase());
-}
-
-TEST_F(ImageDecoders, PNGDelayedChunk) {
-  CheckDecoderDelayedChunk(GreenPNGTestCase());
-}
-
-TEST_F(ImageDecoders, PNGMultiChunk) {
-  CheckDecoderMultiChunk(GreenPNGTestCase());
-}
-
-TEST_F(ImageDecoders, PNGDownscaleDuringDecode) {
-  CheckDownscaleDuringDecode(DownscaledPNGTestCase());
-}
-
-TEST_F(ImageDecoders, GIFSingleChunk) {
-  CheckDecoderSingleChunk(GreenGIFTestCase());
-}
-
-TEST_F(ImageDecoders, GIFDelayedChunk) {
-  CheckDecoderDelayedChunk(GreenGIFTestCase());
-}
-
-TEST_F(ImageDecoders, GIFMultiChunk) {
-  CheckDecoderMultiChunk(GreenGIFTestCase());
-}
-
-TEST_F(ImageDecoders, GIFDownscaleDuringDecode) {
-  CheckDownscaleDuringDecode(DownscaledGIFTestCase());
-}
-
-TEST_F(ImageDecoders, JPGSingleChunk) {
-  CheckDecoderSingleChunk(GreenJPGTestCase());
-}
-
-TEST_F(ImageDecoders, JPGDelayedChunk) {
-  CheckDecoderDelayedChunk(GreenJPGTestCase());
-}
+#define IMAGE_GTEST_DECODER_BASE_F(test_prefix)                              \
+  TEST_F(ImageDecoders, test_prefix##SingleChunk) {                          \
+    CheckDecoderSingleChunk(Green##test_prefix##TestCase());                 \
+  }                                                                          \
+                                                                             \
+  TEST_F(ImageDecoders, test_prefix##DelayedChunk) {                         \
+    CheckDecoderDelayedChunk(Green##test_prefix##TestCase());                \
+  }                                                                          \
+                                                                             \
+  TEST_F(ImageDecoders, test_prefix##MultiChunk) {                           \
+    CheckDecoderMultiChunk(Green##test_prefix##TestCase());                  \
+  }                                                                          \
+                                                                             \
+  TEST_F(ImageDecoders, test_prefix##DownscaleDuringDecode) {                \
+    CheckDownscaleDuringDecode(Downscaled##test_prefix##TestCase());         \
+  }                                                                          \
+                                                                             \
+  TEST_F(ImageDecoders, test_prefix##ForceSRGB) {                            \
+    CheckDecoderSingleChunk(Green##test_prefix##TestCase().WithSurfaceFlags( \
+        SurfaceFlags::TO_SRGB_COLORSPACE));                                  \
+  }
 
-TEST_F(ImageDecoders, JPGMultiChunk) {
-  CheckDecoderMultiChunk(GreenJPGTestCase());
-}
-
-TEST_F(ImageDecoders, JPGDownscaleDuringDecode) {
-  CheckDownscaleDuringDecode(DownscaledJPGTestCase());
-}
-
-TEST_F(ImageDecoders, BMPSingleChunk) {
-  CheckDecoderSingleChunk(GreenBMPTestCase());
-}
-
-TEST_F(ImageDecoders, BMPDelayedChunk) {
-  CheckDecoderDelayedChunk(GreenBMPTestCase());
-}
-
-TEST_F(ImageDecoders, BMPMultiChunk) {
-  CheckDecoderMultiChunk(GreenBMPTestCase());
-}
-
-TEST_F(ImageDecoders, BMPDownscaleDuringDecode) {
-  CheckDownscaleDuringDecode(DownscaledBMPTestCase());
-}
-
-TEST_F(ImageDecoders, ICOSingleChunk) {
-  CheckDecoderSingleChunk(GreenICOTestCase());
-}
-
-TEST_F(ImageDecoders, ICODelayedChunk) {
-  CheckDecoderDelayedChunk(GreenICOTestCase());
-}
-
-TEST_F(ImageDecoders, ICOMultiChunk) {
-  CheckDecoderMultiChunk(GreenICOTestCase());
-}
-
-TEST_F(ImageDecoders, ICODownscaleDuringDecode) {
-  CheckDownscaleDuringDecode(DownscaledICOTestCase());
-}
+IMAGE_GTEST_DECODER_BASE_F(PNG)
+IMAGE_GTEST_DECODER_BASE_F(GIF)
+IMAGE_GTEST_DECODER_BASE_F(JPG)
+IMAGE_GTEST_DECODER_BASE_F(BMP)
+IMAGE_GTEST_DECODER_BASE_F(ICO)
+IMAGE_GTEST_DECODER_BASE_F(Icon)
+IMAGE_GTEST_DECODER_BASE_F(WebP)
 
 TEST_F(ImageDecoders, ICOWithANDMaskDownscaleDuringDecode) {
   CheckDownscaleDuringDecode(DownscaledTransparentICOWithANDMaskTestCase());
 }
 
-TEST_F(ImageDecoders, IconSingleChunk) {
-  CheckDecoderSingleChunk(GreenIconTestCase());
-}
-
-TEST_F(ImageDecoders, IconDelayedChunk) {
-  CheckDecoderDelayedChunk(GreenIconTestCase());
-}
-
-TEST_F(ImageDecoders, IconMultiChunk) {
-  CheckDecoderMultiChunk(GreenIconTestCase());
-}
-
-TEST_F(ImageDecoders, IconDownscaleDuringDecode) {
-  CheckDownscaleDuringDecode(DownscaledIconTestCase());
-}
-
-TEST_F(ImageDecoders, WebPSingleChunk) {
-  CheckDecoderSingleChunk(GreenWebPTestCase());
-}
-
-TEST_F(ImageDecoders, WebPDelayedChunk) {
-  CheckDecoderDelayedChunk(GreenWebPTestCase());
-}
-
-TEST_F(ImageDecoders, WebPMultiChunk) {
-  CheckDecoderMultiChunk(GreenWebPTestCase());
-}
-
 TEST_F(ImageDecoders, WebPLargeMultiChunk) {
   CheckDecoderMultiChunk(LargeWebPTestCase(), /* aChunkSize */ 64);
 }
 
-TEST_F(ImageDecoders, WebPDownscaleDuringDecode) {
-  CheckDownscaleDuringDecode(DownscaledWebPTestCase());
-}
-
 TEST_F(ImageDecoders, WebPIccSrgbMultiChunk) {
   CheckDecoderMultiChunk(GreenWebPIccSrgbTestCase());
 }
 
 TEST_F(ImageDecoders, WebPTransparentSingleChunk) {
   CheckDecoderSingleChunk(TransparentWebPTestCase());
 }
 
@@ -859,17 +779,17 @@ TEST_F(ImageDecoders, AnimatedGIFWithExt
   Progress imageProgress = tracker->GetProgress();
 
   EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
   EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
 
   // Ensure that we decoded both frames of the image.
   LookupResult result =
       SurfaceCache::Lookup(ImageKey(image.get()),
-                           RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
+                           RasterSurfaceKey(imageSize, testCase.mSurfaceFlags,
                                             PlaybackType::eAnimated),
                            /* aMarkUsed = */ true);
   ASSERT_EQ(MatchType::EXACT, result.Type());
 
   EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
   EXPECT_TRUE(bool(result.Surface()));
 
   RefPtr<imgFrame> partialFrame = result.Surface().GetFrame(1);
--- a/image/test/gtest/TestFrameAnimator.cpp
+++ b/image/test/gtest/TestFrameAnimator.cpp
@@ -22,27 +22,29 @@ static void CheckFrameAnimatorBlendResul
   TimeStamp now = TimeStamp::Now();
   aImage->RequestRefresh(now);
 
   RefPtr<SourceSurface> surface =
       aImage->GetFrame(imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_NONE);
   ASSERT_TRUE(surface != nullptr);
 
   CheckGeneratedSurface(surface, IntRect(0, 0, 50, 50),
-                        BGRAColor::Transparent(), BGRAColor::Red());
+                        BGRAColor::Transparent(),
+                        aTestCase.ChooseColor(BGRAColor::Red()));
 
   // Advance to the next/final frame.
   now = TimeStamp::Now() + TimeDuration::FromMilliseconds(500);
   aImage->RequestRefresh(now);
 
   surface =
       aImage->GetFrame(imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_NONE);
   ASSERT_TRUE(surface != nullptr);
-  CheckGeneratedSurface(surface, IntRect(0, 0, 50, 50), BGRAColor::Green(),
-                        BGRAColor::Red());
+  CheckGeneratedSurface(surface, IntRect(0, 0, 50, 50),
+                        aTestCase.ChooseColor(BGRAColor::Green()),
+                        aTestCase.ChooseColor(BGRAColor::Red()));
 }
 
 template <typename Func>
 static void WithFrameAnimatorDecode(const ImageTestCase& aTestCase,
                                     Func aResultChecker) {
   // Create an image.
   RefPtr<Image> image = ImageFactory::CreateAnonymousImage(
       nsDependentCString(aTestCase.mMimeType));
@@ -75,17 +77,17 @@ static void WithFrameAnimatorDecode(cons
 
   // Run the metadata decoder synchronously.
   task->Run();
   task = nullptr;
 
   // Create an AnimationSurfaceProvider which will manage the decoding process
   // and make this decoder's output available in the surface cache.
   DecoderFlags decoderFlags = DefaultDecoderFlags();
-  SurfaceFlags surfaceFlags = DefaultSurfaceFlags();
+  SurfaceFlags surfaceFlags = aTestCase.mSurfaceFlags;
   rv = DecoderFactory::CreateAnimationDecoder(
       decoderType, rasterImage, sourceBuffer, aTestCase.mSize, decoderFlags,
       surfaceFlags, 0, getter_AddRefs(task));
   EXPECT_EQ(rv, NS_OK);
   ASSERT_TRUE(task != nullptr);
 
   // Run the full decoder synchronously.
   task->Run();
--- a/image/test/gtest/TestMetadata.cpp
+++ b/image/test/gtest/TestMetadata.cpp
@@ -95,17 +95,17 @@ static void CheckMetadata(const ImageTes
   EXPECT_EQ(expectTransparency, bool(metadataProgress & FLAG_HAS_TRANSPARENCY));
 
   EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED),
             bool(metadataProgress & FLAG_IS_ANIMATED));
 
   // Create a full decoder, so we can compare the result.
   decoder = DecoderFactory::CreateAnonymousDecoder(
       decoderType, sourceBuffer, Nothing(), DecoderFlags::FIRST_FRAME_ONLY,
-      DefaultSurfaceFlags());
+      aTestCase.mSurfaceFlags);
   ASSERT_TRUE(decoder != nullptr);
   task =
       new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ false);
 
   if (aBMPWithinICO == BMPWithinICO::YES) {
     static_cast<nsBMPDecoder*>(decoder.get())->SetIsWithinICO();
   }
 
@@ -231,17 +231,17 @@ TEST_F(ImageDecoderMetadata, NoFrameDela
   Progress imageProgress = tracker->GetProgress();
 
   EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
   EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
 
   // Ensure that we decoded both frames of the image.
   LookupResult result =
       SurfaceCache::Lookup(ImageKey(image.get()),
-                           RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
+                           RasterSurfaceKey(imageSize, testCase.mSurfaceFlags,
                                             PlaybackType::eAnimated),
                            /* aMarkUsed = */ true);
   ASSERT_EQ(MatchType::EXACT, result.Type());
 
   EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
   EXPECT_TRUE(bool(result.Surface()));
 
   RefPtr<imgFrame> partialFrame = result.Surface().GetFrame(1);
index 99772bada06081066c64fb962af0d1a8872d89af..1de4eeb783e1b484bf7ea4ec16492cc228538a29
GIT binary patch
literal 40004
zc%1FRF%19!2m>&J*7>(EK2$VqvK;^b00000000000000000000000000000000000
N0000000000;2mn4*ew76