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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Andrew Osmond 2020-03-12 00:37:53 +00:00
Родитель 7b7c684070
Коммит 5c7e1dd283
22 изменённых файлов: 271 добавлений и 259 удалений

Просмотреть файл

@ -48,6 +48,7 @@ Decoder::Decoder(RasterImage* aImage)
mTransform(nullptr),
mImageData(nullptr),
mImageDataLength(0),
mCMSMode(gfxPlatform::GetCMSMode()),
mImage(aImage),
mFrameRecycler(nullptr),
mProgress(NoProgress),
@ -89,6 +90,44 @@ Decoder::~Decoder() {
}
}
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.
*/

Просмотреть файл

@ -328,10 +328,7 @@ class Decoder {
* 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.
@ -456,6 +453,9 @@ class Decoder {
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
@ -567,6 +567,8 @@ class Decoder {
uint8_t* mImageData; // Pointer to image data in BGRA/X
uint32_t mImageDataLength;
uint32_t mCMSMode;
private:
RefPtr<RasterImage> mImage;
Maybe<SourceBufferIterator> mIterator;

Просмотреть файл

@ -19,7 +19,8 @@ namespace image {
*/
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)
@ -40,6 +41,9 @@ inline SurfaceFlags ToSurfaceFlags(uint32_t aFlags) {
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;
}
@ -55,6 +59,9 @@ inline uint32_t FromSurfaceFlags(SurfaceFlags aFlags) {
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;
}

Просмотреть файл

@ -76,8 +76,10 @@ static nsresult moz_icon_to_channel(nsIURI* aURI, const nsACString& aFileExt,
*(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()) {
@ -87,12 +89,6 @@ static nsresult moz_icon_to_channel(nsIURI* aURI, const nsACString& aFileExt,
}
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);

Просмотреть файл

@ -59,14 +59,16 @@ static nsresult moz_gdk_pixbuf_to_channel(GdkPixbuf* aPixbuf, nsIURI* aURI,
*(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,
// 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));

Просмотреть файл

@ -300,7 +300,11 @@ nsresult nsIconChannel::MakeInputStream(nsIInputStream** _retval, bool aNonBlock
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.

Просмотреть файл

@ -717,12 +717,7 @@ LexerTransition<nsBMPDecoder::State> nsBMPDecoder::ReadBitfields(
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);
@ -734,9 +729,9 @@ LexerTransition<nsBMPDecoder::State> nsBMPDecoder::ReadBitfields(
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:
@ -782,15 +777,15 @@ void nsBMPDecoder::PrepareCalibratedColorProfile() {
("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;
}
@ -822,8 +817,8 @@ void nsBMPDecoder::PrepareColorProfileTransform() {
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"));

Просмотреть файл

@ -410,9 +410,8 @@ void nsGIFDecoder2::ConvertColormap(uint32_t* aColormap, uint32_t aColors) {
}
// 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);
}

Просмотреть файл

@ -51,6 +51,20 @@ LexerTransition<nsIconDecoder::State> nsIconDecoder::ReadHeader(
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;
@ -69,7 +83,7 @@ LexerTransition<nsIconDecoder::State> nsIconDecoder::ReadHeader(
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();
}

Просмотреть файл

@ -109,8 +109,6 @@ nsJPEGDecoder::nsJPEGDecoder(RasterImage* aImage,
mBackBuffer = nullptr;
mBackBufferLen = mBackBufferSize = mBackBufferUnreadLen = 0;
mCMSMode = 0;
MOZ_LOG(sJPEGDecoderAccountingLog, LogLevel::Debug,
("nsJPEGDecoder::nsJPEGDecoder: Creating JPEG decoder %p", this));
}
@ -134,11 +132,6 @@ Maybe<Telemetry::HistogramID> nsJPEGDecoder::SpeedHistogram() const {
}
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);
@ -292,7 +285,7 @@ LexerTransition<nsJPEGDecoder::State> nsJPEGDecoder::ReadJPEGData(
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();
@ -329,12 +322,12 @@ LexerTransition<nsJPEGDecoder::State> nsJPEGDecoder::ReadJPEGData(
}
// Create the color management transform.
mTransform = qcms_transform_create(
mInProfile, *inputType, gfxPlatform::GetCMSOutputProfile(),
mTransform = qcms_transform_create(mInProfile, *inputType,
GetCMSOutputProfile(),
outputType, (qcms_intent)intent);
}
} else if (mCMSMode == eCMSMode_All) {
mTransform = gfxPlatform::GetCMSOSRGBATransform();
mTransform = GetCMSsRGBTransform(SurfaceFormat::OS_RGBX);
}
}

Просмотреть файл

@ -104,8 +104,6 @@ class nsJPEGDecoder : public Decoder {
const Decoder::DecodeStyle mDecodeStyle;
uint32_t mCMSMode;
SurfacePipe mPipe;
};

Просмотреть файл

@ -109,7 +109,6 @@ nsPNGDecoder::nsPNGDecoder(RasterImage* aImage)
mCMSLine(nullptr),
interlacebuf(nullptr),
mFormat(SurfaceFormat::UNKNOWN),
mCMSMode(0),
mChannels(0),
mPass(0),
mFrameIsHidden(false),
@ -270,10 +269,6 @@ void nsPNGDecoder::EndImageFrame() {
}
nsresult nsPNGDecoder::InitInternal() {
mCMSMode = gfxPlatform::GetCMSMode();
if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
mCMSMode = eCMSMode_Off;
}
mDisablePremultipliedAlpha =
bool(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
@ -598,7 +593,7 @@ void nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) {
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
@ -659,7 +654,7 @@ void nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) {
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;
@ -687,19 +682,21 @@ void nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) {
}
}
decoder->mTransform = qcms_transform_create(
decoder->mInProfile, inType, gfxPlatform::GetCMSOutputProfile(),
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;
}

Просмотреть файл

@ -97,9 +97,6 @@ class nsPNGDecoder : public Decoder {
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;

Просмотреть файл

@ -289,13 +289,8 @@ 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;
}
@ -304,7 +299,7 @@ void nsWebPDecoder::ApplyColorProfile(const char* aProfile, size_t aLength) {
("[this=%p] nsWebPDecoder::ApplyColorProfile -- not tagged, use "
"sRGB transform\n",
this));
mTransform = gfxPlatform::GetCMSBGRATransform();
mTransform = GetCMSsRGBTransform(SurfaceFormat::OS_RGBA);
return;
}
@ -335,9 +330,8 @@ void nsWebPDecoder::ApplyColorProfile(const char* aProfile, size_t aLength) {
// 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",

Просмотреть файл

@ -217,6 +217,9 @@ interface imgIContainer : nsISupports
* 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;
@ -231,6 +234,7 @@ interface imgIContainer : nsISupports
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
@ -238,6 +242,13 @@ interface imgIContainer : nsISupports
*/
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.
*

Просмотреть файл

@ -36,12 +36,8 @@ AutoInitializeImageLib::AutoInitializeImageLib() {
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.

Просмотреть файл

@ -16,6 +16,7 @@
#include "mozilla/gfx/2D.h"
#include "Decoder.h"
#include "gfxColor.h"
#include "gfxPlatform.h"
#include "nsCOMPtr.h"
#include "SurfaceFlags.h"
#include "SurfacePipe.h"
@ -34,12 +35,13 @@ 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); }
@ -55,6 +57,25 @@ struct BGRAColor {
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),
@ -76,6 +97,7 @@ struct BGRAColor {
uint8_t mRed;
uint8_t mAlpha;
bool mPremultiplied;
bool msRGB;
};
enum TestCaseFlags {
@ -109,6 +131,31 @@ struct ImageTestCase {
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;

Просмотреть файл

@ -65,8 +65,7 @@ class DecodeToSurfaceRunnable : public Runnable {
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:

Просмотреть файл

@ -86,8 +86,7 @@ static void CheckDecoderResults(const ImageTestCase& aTestCase,
}
// 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>
@ -113,7 +112,7 @@ void WithSingleChunkDecode(const ImageTestCase& aTestCase,
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);
@ -150,7 +149,7 @@ void WithDelayedChunkDecode(const ImageTestCase& aTestCase,
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);
@ -194,7 +193,7 @@ static void CheckDecoderMultiChunk(const ImageTestCase& aTestCase,
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);
@ -247,14 +246,17 @@ static void CheckDownscaleDuringDecode(const ImageTestCase& aTestCase) {
// 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));
});
}
@ -279,8 +281,9 @@ static void CheckAnimationDecoderResults(const ImageTestCase& aTestCase,
// 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) {
@ -296,8 +299,7 @@ static void CheckAnimationDecoderResults(const ImageTestCase& aTestCase,
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.
@ -343,7 +345,7 @@ static void WithSingleChunkAnimationDecode(const ImageTestCase& aTestCase,
// 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);
@ -428,9 +430,9 @@ static void CheckDecoderFrameFirst(const ImageTestCase& aTestCase) {
// Ensure that we decoded the static version of the image.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
LookupResult result = SurfaceCache::Lookup(
ImageKey(image.get()),
RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
PlaybackType::eStatic),
/* aMarkUsed = */ false);
ASSERT_EQ(MatchType::EXACT, result.Type());
@ -439,9 +441,9 @@ static void CheckDecoderFrameFirst(const ImageTestCase& aTestCase) {
// Ensure that we didn't decode the animated version of the image.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
LookupResult result = SurfaceCache::Lookup(
ImageKey(image.get()),
RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
PlaybackType::eAnimated),
/* aMarkUsed = */ false);
ASSERT_EQ(MatchType::NOT_FOUND, result.Type());
@ -454,9 +456,9 @@ static void CheckDecoderFrameFirst(const ImageTestCase& aTestCase) {
// Ensure that we decoded both frames of the animated version of the image.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
LookupResult result = SurfaceCache::Lookup(
ImageKey(image.get()),
RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
PlaybackType::eAnimated),
/* aMarkUsed = */ true);
ASSERT_EQ(MatchType::EXACT, result.Type());
@ -470,9 +472,9 @@ static void CheckDecoderFrameFirst(const ImageTestCase& aTestCase) {
// Ensure that the static version is still around.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
LookupResult result = SurfaceCache::Lookup(
ImageKey(image.get()),
RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
PlaybackType::eStatic),
/* aMarkUsed = */ true);
ASSERT_EQ(MatchType::EXACT, result.Type());
@ -536,9 +538,9 @@ static void CheckDecoderFrameCurrent(const ImageTestCase& aTestCase) {
// Ensure that we decoded both frames of the animated version of the image.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
LookupResult result = SurfaceCache::Lookup(
ImageKey(image.get()),
RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
PlaybackType::eAnimated),
/* aMarkUsed = */ true);
ASSERT_EQ(MatchType::EXACT, result.Type());
@ -552,9 +554,9 @@ static void CheckDecoderFrameCurrent(const ImageTestCase& aTestCase) {
// Ensure that we didn't decode the static version of the image.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
LookupResult result = SurfaceCache::Lookup(
ImageKey(image.get()),
RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
PlaybackType::eStatic),
/* aMarkUsed = */ false);
ASSERT_EQ(MatchType::NOT_FOUND, result.Type());
@ -567,9 +569,9 @@ static void CheckDecoderFrameCurrent(const ImageTestCase& aTestCase) {
// Ensure that we decoded the static version of the image.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
LookupResult result = SurfaceCache::Lookup(
ImageKey(image.get()),
RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
PlaybackType::eStatic),
/* aMarkUsed = */ true);
ASSERT_EQ(MatchType::EXACT, result.Type());
@ -578,9 +580,9 @@ static void CheckDecoderFrameCurrent(const ImageTestCase& aTestCase) {
// Ensure that both frames of the animated version are still around.
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(image.get()),
RasterSurfaceKey(imageSize, DefaultSurfaceFlags(),
LookupResult result = SurfaceCache::Lookup(
ImageKey(image.get()),
RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags,
PlaybackType::eAnimated),
/* aMarkUsed = */ true);
ASSERT_EQ(MatchType::EXACT, result.Type());
@ -598,126 +600,44 @@ class ImageDecoders : public ::testing::Test {
AutoInitializeImageLib mInit;
};
TEST_F(ImageDecoders, PNGSingleChunk) {
CheckDecoderSingleChunk(GreenPNGTestCase());
#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, 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());
}
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());
}
@ -864,7 +784,7 @@ TEST_F(ImageDecoders, AnimatedGIFWithExtraImageSubBlocks) {
// 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());

Просмотреть файл

@ -27,7 +27,8 @@ static void CheckFrameAnimatorBlendResults(const ImageTestCase& aTestCase,
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);
@ -36,8 +37,9 @@ static void CheckFrameAnimatorBlendResults(const ImageTestCase& aTestCase,
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>
@ -80,7 +82,7 @@ static void WithFrameAnimatorDecode(const ImageTestCase& aTestCase,
// 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));

Просмотреть файл

@ -100,7 +100,7 @@ static void CheckMetadata(const ImageTestCase& aTestCase,
// 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);
@ -236,7 +236,7 @@ TEST_F(ImageDecoderMetadata, NoFrameDelayGIFFullDecode) {
// 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());

Двоичные данные
image/test/gtest/green.icon

Двоичный файл не отображается.