summaryrefslogtreecommitdiffstats
path: root/image/test/gtest/Common.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'image/test/gtest/Common.cpp')
-rw-r--r--image/test/gtest/Common.cpp1057
1 files changed, 1057 insertions, 0 deletions
diff --git a/image/test/gtest/Common.cpp b/image/test/gtest/Common.cpp
new file mode 100644
index 0000000000..5e12d2f601
--- /dev/null
+++ b/image/test/gtest/Common.cpp
@@ -0,0 +1,1057 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Common.h"
+
+#include <cstdlib>
+
+#include "gfxPlatform.h"
+
+#include "ImageFactory.h"
+#include "imgITools.h"
+#include "mozilla/Preferences.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsIProperties.h"
+#include "nsNetUtil.h"
+#include "mozilla/RefPtr.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace image {
+
+using namespace gfx;
+
+using std::vector;
+
+static bool sImageLibInitialized = false;
+
+AutoInitializeImageLib::AutoInitializeImageLib() {
+ if (MOZ_LIKELY(sImageLibInitialized)) {
+ return;
+ }
+
+ EXPECT_TRUE(NS_IsMainThread());
+ sImageLibInitialized = true;
+
+ // Ensure WebP is enabled to run decoder tests.
+ nsresult rv = Preferences::SetBool("image.webp.enabled", true);
+ EXPECT_TRUE(rv == NS_OK);
+
+ // Ensure AVIF is enabled to run decoder tests.
+ rv = Preferences::SetBool("image.avif.enabled", true);
+ EXPECT_TRUE(rv == NS_OK);
+
+#ifdef MOZ_JXL
+ // Ensure JXL is enabled to run decoder tests.
+ rv = Preferences::SetBool("image.jxl.enabled", true);
+ EXPECT_TRUE(rv == NS_OK);
+#endif
+
+ // Ensure that ImageLib services are initialized.
+ nsCOMPtr<imgITools> imgTools =
+ do_CreateInstance("@mozilla.org/image/tools;1");
+ EXPECT_TRUE(imgTools != nullptr);
+
+ // Ensure gfxPlatform is initialized.
+ gfxPlatform::GetPlatform();
+
+ // Ensure we always color manage images with gtests.
+ gfxPlatform::SetCMSModeOverride(CMSMode::All);
+
+ // Depending on initialization order, it is possible that our pref changes
+ // have not taken effect yet because there are pending gfx-related events on
+ // the main thread.
+ SpinPendingEvents();
+}
+
+void ImageBenchmarkBase::SetUp() {
+ nsCOMPtr<nsIInputStream> inputStream = LoadFile(mTestCase.mPath);
+ ASSERT_TRUE(inputStream != nullptr);
+
+ // Figure out how much data we have.
+ uint64_t length;
+ nsresult rv = inputStream->Available(&length);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ // Write the data into a SourceBuffer.
+ mSourceBuffer = new SourceBuffer();
+ mSourceBuffer->ExpectLength(length);
+ rv = mSourceBuffer->AppendFromInputStream(inputStream, length);
+ ASSERT_NS_SUCCEEDED(rv);
+ mSourceBuffer->Complete(NS_OK);
+}
+
+void ImageBenchmarkBase::TearDown() {}
+
+///////////////////////////////////////////////////////////////////////////////
+// General Helpers
+///////////////////////////////////////////////////////////////////////////////
+
+// These macros work like gtest's ASSERT_* macros, except that they can be used
+// in functions that return values.
+#define ASSERT_TRUE_OR_RETURN(e, rv) \
+ EXPECT_TRUE(e); \
+ if (!(e)) { \
+ return rv; \
+ }
+
+#define ASSERT_EQ_OR_RETURN(a, b, rv) \
+ EXPECT_EQ(a, b); \
+ if ((a) != (b)) { \
+ return rv; \
+ }
+
+#define ASSERT_GE_OR_RETURN(a, b, rv) \
+ EXPECT_GE(a, b); \
+ if (!((a) >= (b))) { \
+ return rv; \
+ }
+
+#define ASSERT_LE_OR_RETURN(a, b, rv) \
+ EXPECT_LE(a, b); \
+ if (!((a) <= (b))) { \
+ return rv; \
+ }
+
+#define ASSERT_LT_OR_RETURN(a, b, rv) \
+ EXPECT_LT(a, b); \
+ if (!((a) < (b))) { \
+ return rv; \
+ }
+
+void SpinPendingEvents() {
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ EXPECT_TRUE(mainThread != nullptr);
+
+ bool processed;
+ do {
+ processed = false;
+ nsresult rv = mainThread->ProcessNextEvent(false, &processed);
+ EXPECT_NS_SUCCEEDED(rv);
+ } while (processed);
+}
+
+already_AddRefed<nsIInputStream> LoadFile(const char* aRelativePath) {
+ nsresult rv;
+
+ nsCOMPtr<nsIProperties> dirService =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
+ ASSERT_TRUE_OR_RETURN(dirService != nullptr, nullptr);
+
+ // Retrieve the current working directory.
+ nsCOMPtr<nsIFile> file;
+ rv = dirService->Get(NS_OS_CURRENT_WORKING_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(file));
+ ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
+ // Construct the final path by appending the working path to the current
+ // working directory.
+ file->AppendNative(nsDependentCString(aRelativePath));
+
+ // Construct an input stream for the requested file.
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), file);
+ ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
+
+ // Ensure the resulting input stream is buffered.
+ if (!NS_InputStreamIsBuffered(inputStream)) {
+ nsCOMPtr<nsIInputStream> bufStream;
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream),
+ inputStream.forget(), 1024);
+ ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
+ inputStream = bufStream;
+ }
+
+ return inputStream.forget();
+}
+
+bool IsSolidColor(SourceSurface* aSurface, BGRAColor aColor,
+ uint8_t aFuzz /* = 0 */) {
+ IntSize size = aSurface->GetSize();
+ return RectIsSolidColor(aSurface, IntRect(0, 0, size.width, size.height),
+ aColor, aFuzz);
+}
+
+bool RowsAreSolidColor(SourceSurface* aSurface, int32_t aStartRow,
+ int32_t aRowCount, BGRAColor aColor,
+ uint8_t aFuzz /* = 0 */) {
+ IntSize size = aSurface->GetSize();
+ return RectIsSolidColor(
+ aSurface, IntRect(0, aStartRow, size.width, aRowCount), aColor, aFuzz);
+}
+
+bool RectIsSolidColor(SourceSurface* aSurface, const IntRect& aRect,
+ BGRAColor aColor, uint8_t aFuzz /* = 0 */) {
+ IntSize surfaceSize = aSurface->GetSize();
+ IntRect rect =
+ aRect.Intersect(IntRect(0, 0, surfaceSize.width, surfaceSize.height));
+
+ RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
+ ASSERT_TRUE_OR_RETURN(dataSurface != nullptr, false);
+
+ DataSourceSurface::ScopedMap mapping(dataSurface,
+ DataSourceSurface::MapType::READ);
+ ASSERT_TRUE_OR_RETURN(mapping.IsMapped(), false);
+ ASSERT_EQ_OR_RETURN(mapping.GetStride(), surfaceSize.width * 4, false);
+
+ uint8_t* data = mapping.GetData();
+ ASSERT_TRUE_OR_RETURN(data != nullptr, false);
+
+ BGRAColor pmColor = aColor.Premultiply();
+ uint32_t expectedPixel = pmColor.AsPixel();
+
+ int32_t rowLength = mapping.GetStride();
+ for (int32_t row = rect.Y(); row < rect.YMost(); ++row) {
+ for (int32_t col = rect.X(); col < rect.XMost(); ++col) {
+ int32_t i = row * rowLength + col * 4;
+ uint32_t gotPixel = *reinterpret_cast<uint32_t*>(data + i);
+ if (expectedPixel != gotPixel) {
+ BGRAColor gotColor = BGRAColor::FromPixel(gotPixel);
+ if (abs(pmColor.mBlue - gotColor.mBlue) > aFuzz ||
+ abs(pmColor.mGreen - gotColor.mGreen) > aFuzz ||
+ abs(pmColor.mRed - gotColor.mRed) > aFuzz ||
+ abs(pmColor.mAlpha - gotColor.mAlpha) > aFuzz) {
+ EXPECT_EQ(pmColor.mBlue, gotColor.mBlue);
+ EXPECT_EQ(pmColor.mGreen, gotColor.mGreen);
+ EXPECT_EQ(pmColor.mRed, gotColor.mRed);
+ EXPECT_EQ(pmColor.mAlpha, gotColor.mAlpha);
+ ASSERT_EQ_OR_RETURN(expectedPixel, gotPixel, false);
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+bool RowHasPixels(SourceSurface* aSurface, int32_t aRow,
+ const vector<BGRAColor>& aPixels) {
+ ASSERT_GE_OR_RETURN(aRow, 0, false);
+
+ IntSize surfaceSize = aSurface->GetSize();
+ ASSERT_EQ_OR_RETURN(aPixels.size(), size_t(surfaceSize.width), false);
+ ASSERT_LT_OR_RETURN(aRow, surfaceSize.height, false);
+
+ RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
+ ASSERT_TRUE_OR_RETURN(dataSurface, false);
+
+ DataSourceSurface::ScopedMap mapping(dataSurface,
+ DataSourceSurface::MapType::READ);
+ ASSERT_TRUE_OR_RETURN(mapping.IsMapped(), false);
+ ASSERT_EQ_OR_RETURN(mapping.GetStride(), surfaceSize.width * 4, false);
+
+ uint8_t* data = mapping.GetData();
+ ASSERT_TRUE_OR_RETURN(data != nullptr, false);
+
+ int32_t rowLength = mapping.GetStride();
+ for (int32_t col = 0; col < surfaceSize.width; ++col) {
+ int32_t i = aRow * rowLength + col * 4;
+ uint32_t gotPixelData = *reinterpret_cast<uint32_t*>(data + i);
+ BGRAColor gotPixel = BGRAColor::FromPixel(gotPixelData);
+ EXPECT_EQ(aPixels[col].mBlue, gotPixel.mBlue);
+ EXPECT_EQ(aPixels[col].mGreen, gotPixel.mGreen);
+ EXPECT_EQ(aPixels[col].mRed, gotPixel.mRed);
+ EXPECT_EQ(aPixels[col].mAlpha, gotPixel.mAlpha);
+ ASSERT_EQ_OR_RETURN(aPixels[col].AsPixel(), gotPixelData, false);
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// SurfacePipe Helpers
+///////////////////////////////////////////////////////////////////////////////
+
+already_AddRefed<Decoder> CreateTrivialDecoder() {
+ DecoderType decoderType = DecoderFactory::GetDecoderType("image/gif");
+ auto sourceBuffer = MakeNotNull<RefPtr<SourceBuffer>>();
+ RefPtr<Decoder> decoder = DecoderFactory::CreateAnonymousDecoder(
+ decoderType, sourceBuffer, Nothing(), DefaultDecoderFlags(),
+ DefaultSurfaceFlags());
+ return decoder.forget();
+}
+
+void AssertCorrectPipelineFinalState(SurfaceFilter* aFilter,
+ const IntRect& aInputSpaceRect,
+ const IntRect& aOutputSpaceRect) {
+ EXPECT_TRUE(aFilter->IsSurfaceFinished());
+ Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
+ EXPECT_TRUE(invalidRect.isSome());
+ EXPECT_EQ(aInputSpaceRect, invalidRect->mInputSpaceRect.ToUnknownRect());
+ EXPECT_EQ(aOutputSpaceRect, invalidRect->mOutputSpaceRect.ToUnknownRect());
+}
+
+void CheckGeneratedImage(Decoder* aDecoder, const IntRect& aRect,
+ uint8_t aFuzz /* = 0 */) {
+ RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
+ RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
+ CheckGeneratedSurface(surface, aRect, BGRAColor::Green(),
+ BGRAColor::Transparent(), aFuzz);
+}
+
+void CheckGeneratedSurface(SourceSurface* aSurface, const IntRect& aRect,
+ const BGRAColor& aInnerColor,
+ const BGRAColor& aOuterColor,
+ uint8_t aFuzz /* = 0 */) {
+ const IntSize surfaceSize = aSurface->GetSize();
+
+ // This diagram shows how the surface is divided into regions that the code
+ // below tests for the correct content. The output rect is the bounds of the
+ // region labeled 'C'.
+ //
+ // +---------------------------+
+ // | A |
+ // +---------+--------+--------+
+ // | B | C | D |
+ // +---------+--------+--------+
+ // | E |
+ // +---------------------------+
+
+ // Check that the output rect itself is the inner color. (Region 'C'.)
+ EXPECT_TRUE(RectIsSolidColor(aSurface, aRect, aInnerColor, aFuzz));
+
+ // Check that the area above the output rect is the outer color. (Region 'A'.)
+ EXPECT_TRUE(RectIsSolidColor(aSurface,
+ IntRect(0, 0, surfaceSize.width, aRect.Y()),
+ aOuterColor, aFuzz));
+
+ // Check that the area to the left of the output rect is the outer color.
+ // (Region 'B'.)
+ EXPECT_TRUE(RectIsSolidColor(aSurface,
+ IntRect(0, aRect.Y(), aRect.X(), aRect.YMost()),
+ aOuterColor, aFuzz));
+
+ // Check that the area to the right of the output rect is the outer color.
+ // (Region 'D'.)
+ const int32_t widthOnRight = surfaceSize.width - aRect.XMost();
+ EXPECT_TRUE(RectIsSolidColor(
+ aSurface, IntRect(aRect.XMost(), aRect.Y(), widthOnRight, aRect.YMost()),
+ aOuterColor, aFuzz));
+
+ // Check that the area below the output rect is the outer color. (Region 'E'.)
+ const int32_t heightBelow = surfaceSize.height - aRect.YMost();
+ EXPECT_TRUE(RectIsSolidColor(
+ aSurface, IntRect(0, aRect.YMost(), surfaceSize.width, heightBelow),
+ aOuterColor, aFuzz));
+}
+
+void CheckWritePixels(Decoder* aDecoder, SurfaceFilter* aFilter,
+ const Maybe<IntRect>& aOutputRect /* = Nothing() */,
+ const Maybe<IntRect>& aInputRect /* = Nothing() */,
+ const Maybe<IntRect>& aInputWriteRect /* = Nothing() */,
+ const Maybe<IntRect>& aOutputWriteRect /* = Nothing() */,
+ uint8_t aFuzz /* = 0 */) {
+ CheckTransformedWritePixels(aDecoder, aFilter, BGRAColor::Green(),
+ BGRAColor::Green(), aOutputRect, aInputRect,
+ aInputWriteRect, aOutputWriteRect, aFuzz);
+}
+
+void CheckTransformedWritePixels(
+ Decoder* aDecoder, SurfaceFilter* aFilter, const BGRAColor& aInputColor,
+ const BGRAColor& aOutputColor,
+ const Maybe<IntRect>& aOutputRect /* = Nothing() */,
+ const Maybe<IntRect>& aInputRect /* = Nothing() */,
+ const Maybe<IntRect>& aInputWriteRect /* = Nothing() */,
+ const Maybe<IntRect>& aOutputWriteRect /* = Nothing() */,
+ uint8_t aFuzz /* = 0 */) {
+ IntRect outputRect = aOutputRect.valueOr(IntRect(0, 0, 100, 100));
+ IntRect inputRect = aInputRect.valueOr(IntRect(0, 0, 100, 100));
+ IntRect inputWriteRect = aInputWriteRect.valueOr(inputRect);
+ IntRect outputWriteRect = aOutputWriteRect.valueOr(outputRect);
+
+ // Fill the image.
+ int32_t count = 0;
+ auto result = aFilter->WritePixels<uint32_t>([&] {
+ ++count;
+ return AsVariant(aInputColor.AsPixel());
+ });
+ EXPECT_EQ(WriteState::FINISHED, result);
+ EXPECT_EQ(inputWriteRect.Width() * inputWriteRect.Height(), count);
+
+ AssertCorrectPipelineFinalState(aFilter, inputRect, outputRect);
+
+ // Attempt to write more data and make sure nothing changes.
+ const int32_t oldCount = count;
+ result = aFilter->WritePixels<uint32_t>([&] {
+ ++count;
+ return AsVariant(aInputColor.AsPixel());
+ });
+ EXPECT_EQ(oldCount, count);
+ EXPECT_EQ(WriteState::FINISHED, result);
+ EXPECT_TRUE(aFilter->IsSurfaceFinished());
+ Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
+ EXPECT_TRUE(invalidRect.isNothing());
+
+ // Attempt to advance to the next row and make sure nothing changes.
+ aFilter->AdvanceRow();
+ EXPECT_TRUE(aFilter->IsSurfaceFinished());
+ invalidRect = aFilter->TakeInvalidRect();
+ EXPECT_TRUE(invalidRect.isNothing());
+
+ // Check that the generated image is correct.
+ RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
+ RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
+ CheckGeneratedSurface(surface, outputWriteRect, aOutputColor,
+ BGRAColor::Transparent(), aFuzz);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Test Data
+///////////////////////////////////////////////////////////////////////////////
+
+ImageTestCase GreenPNGTestCase() {
+ return ImageTestCase("green.png", "image/png", IntSize(100, 100));
+}
+
+ImageTestCase GreenGIFTestCase() {
+ return ImageTestCase("green.gif", "image/gif", IntSize(100, 100));
+}
+
+ImageTestCase GreenJPGTestCase() {
+ return ImageTestCase("green.jpg", "image/jpeg", IntSize(100, 100),
+ TEST_CASE_IS_FUZZY);
+}
+
+ImageTestCase GreenBMPTestCase() {
+ return ImageTestCase("green.bmp", "image/bmp", IntSize(100, 100));
+}
+
+ImageTestCase GreenICOTestCase() {
+ // This ICO contains a 32-bit BMP, and we use a BMP's alpha data by default
+ // when the BMP is embedded in an ICO, so it's transparent.
+ return ImageTestCase("green.ico", "image/x-icon", IntSize(100, 100),
+ TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase GreenIconTestCase() {
+ return ImageTestCase("green.icon", "image/icon", IntSize(100, 100),
+ TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase GreenWebPTestCase() {
+ return ImageTestCase("green.webp", "image/webp", IntSize(100, 100));
+}
+
+ImageTestCase GreenAVIFTestCase() {
+ return ImageTestCase("green.avif", "image/avif", IntSize(100, 100));
+}
+
+ImageTestCase NonzeroReservedAVIFTestCase() {
+ auto testCase = ImageTestCase("hdlr-nonzero-reserved-bug-1727033.avif",
+ "image/avif", IntSize(1, 1));
+ testCase.mColor = BGRAColor(0x00, 0x00, 0x00, 0xFF);
+ return testCase;
+}
+
+ImageTestCase MultipleColrAVIFTestCase() {
+ auto testCase = ImageTestCase("valid-avif-colr-nclx-and-prof.avif",
+ "image/avif", IntSize(1, 1));
+ testCase.mColor = BGRAColor(0x00, 0x00, 0x00, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Transparent10bit420AVIFTestCase() {
+ auto testCase =
+ ImageTestCase("transparent-green-50pct-10bit-yuv420.avif", "image/avif",
+ IntSize(100, 100), TEST_CASE_IS_TRANSPARENT);
+ testCase.mColor = BGRAColor(0x00, 0xFF, 0x00, 0x80);
+ return testCase;
+}
+
+ImageTestCase Transparent10bit422AVIFTestCase() {
+ auto testCase =
+ ImageTestCase("transparent-green-50pct-10bit-yuv422.avif", "image/avif",
+ IntSize(100, 100), TEST_CASE_IS_TRANSPARENT);
+ testCase.mColor = BGRAColor(0x00, 0xFF, 0x00, 0x80);
+ return testCase;
+}
+
+ImageTestCase Transparent10bit444AVIFTestCase() {
+ auto testCase =
+ ImageTestCase("transparent-green-50pct-10bit-yuv444.avif", "image/avif",
+ IntSize(100, 100), TEST_CASE_IS_TRANSPARENT);
+ testCase.mColor = BGRAColor(0x00, 0xFF, 0x00, 0x80);
+ return testCase;
+}
+
+ImageTestCase Transparent12bit420AVIFTestCase() {
+ auto testCase =
+ ImageTestCase("transparent-green-50pct-12bit-yuv420.avif", "image/avif",
+ IntSize(100, 100), TEST_CASE_IS_TRANSPARENT);
+ testCase.mColor = BGRAColor(0x00, 0xFF, 0x00, 0x80);
+ return testCase;
+}
+
+ImageTestCase Transparent12bit422AVIFTestCase() {
+ auto testCase =
+ ImageTestCase("transparent-green-50pct-12bit-yuv422.avif", "image/avif",
+ IntSize(100, 100), TEST_CASE_IS_TRANSPARENT);
+ testCase.mColor = BGRAColor(0x00, 0xFF, 0x00, 0x80);
+ return testCase;
+}
+
+ImageTestCase Transparent12bit444AVIFTestCase() {
+ auto testCase =
+ ImageTestCase("transparent-green-50pct-12bit-yuv444.avif", "image/avif",
+ IntSize(100, 100), TEST_CASE_IS_TRANSPARENT);
+ testCase.mColor = BGRAColor(0x00, 0xFF, 0x00, 0x80);
+ return testCase;
+}
+
+ImageTestCase Transparent8bit420AVIFTestCase() {
+ auto testCase =
+ ImageTestCase("transparent-green-50pct-8bit-yuv420.avif", "image/avif",
+ IntSize(100, 100), TEST_CASE_IS_TRANSPARENT);
+ // Small error is expected
+ testCase.mColor = BGRAColor(0x02, 0xFF, 0x00, 0x80);
+ return testCase;
+}
+
+ImageTestCase Transparent8bit422AVIFTestCase() {
+ auto testCase =
+ ImageTestCase("transparent-green-50pct-8bit-yuv422.avif", "image/avif",
+ IntSize(100, 100), TEST_CASE_IS_TRANSPARENT);
+ // Small error is expected
+ testCase.mColor = BGRAColor(0x02, 0xFF, 0x00, 0x80);
+ return testCase;
+}
+
+ImageTestCase Transparent8bit444AVIFTestCase() {
+ auto testCase =
+ ImageTestCase("transparent-green-50pct-8bit-yuv444.avif", "image/avif",
+ IntSize(100, 100), TEST_CASE_IS_TRANSPARENT);
+ // Small error is expected
+ testCase.mColor = BGRAColor(0x02, 0xFF, 0x00, 0x80);
+ return testCase;
+}
+
+ImageTestCase Gray8bitLimitedRangeBT601AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-8bit-limited-range-bt601.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray8bitLimitedRangeBT709AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-8bit-limited-range-bt709.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray8bitLimitedRangeBT2020AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-8bit-limited-range-bt2020.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray8bitFullRangeBT601AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-8bit-full-range-bt601.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray8bitFullRangeBT709AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-8bit-full-range-bt709.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray8bitFullRangeBT2020AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-8bit-full-range-bt2020.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray10bitLimitedRangeBT601AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-10bit-limited-range-bt601.avif",
+ "image/avif", IntSize(100, 100));
+ // Small error is expected
+ testCase.mColor = BGRAColor(0xEA, 0xEA, 0xEA, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray10bitLimitedRangeBT709AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-10bit-limited-range-bt709.avif",
+ "image/avif", IntSize(100, 100));
+ // Small error is expected
+ testCase.mColor = BGRAColor(0xEA, 0xEA, 0xEA, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray10bitLimitedRangeBT2020AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-10bit-limited-range-bt2020.avif",
+ "image/avif", IntSize(100, 100));
+ // Small error is expected
+ testCase.mColor = BGRAColor(0xEA, 0xEA, 0xEA, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray10bitFullRangeBT601AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-10bit-full-range-bt601.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray10bitFullRangeBT709AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-10bit-full-range-bt709.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray10bitFullRangeBT2020AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-10bit-full-range-bt2020.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray12bitLimitedRangeBT601AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-12bit-limited-range-bt601.avif",
+ "image/avif", IntSize(100, 100));
+ // Small error is expected
+ testCase.mColor = BGRAColor(0xEA, 0xEA, 0xEA, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray12bitLimitedRangeBT709AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-12bit-limited-range-bt709.avif",
+ "image/avif", IntSize(100, 100));
+ // Small error is expected
+ testCase.mColor = BGRAColor(0xEA, 0xEA, 0xEA, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray12bitLimitedRangeBT2020AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-12bit-limited-range-bt2020.avif",
+ "image/avif", IntSize(100, 100));
+ // Small error is expected
+ testCase.mColor = BGRAColor(0xEA, 0xEA, 0xEA, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray12bitFullRangeBT601AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-12bit-full-range-bt601.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray12bitFullRangeBT709AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-12bit-full-range-bt709.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray12bitFullRangeBT2020AVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-12bit-full-range-bt2020.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray8bitLimitedRangeGrayscaleAVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-8bit-limited-range-grayscale.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray8bitFullRangeGrayscaleAVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-8bit-full-range-grayscale.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray10bitLimitedRangeGrayscaleAVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-10bit-limited-range-grayscale.avif",
+ "image/avif", IntSize(100, 100));
+ // Small error is expected
+ testCase.mColor = BGRAColor(0xEA, 0xEA, 0xEA, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray10bitFullRangeGrayscaleAVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-10bit-full-range-grayscale.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray12bitLimitedRangeGrayscaleAVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-12bit-limited-range-grayscale.avif",
+ "image/avif", IntSize(100, 100));
+ // Small error is expected
+ testCase.mColor = BGRAColor(0xEA, 0xEA, 0xEA, 0xFF);
+ return testCase;
+}
+
+ImageTestCase Gray12bitFullRangeGrayscaleAVIFTestCase() {
+ auto testCase = ImageTestCase("gray-235-12bit-full-range-grayscale.avif",
+ "image/avif", IntSize(100, 100));
+ testCase.mColor = BGRAColor(0xEB, 0xEB, 0xEB, 0xFF);
+ return testCase;
+}
+
+ImageTestCase StackCheckAVIFTestCase() {
+ return ImageTestCase("stackcheck.avif", "image/avif", IntSize(4096, 2924),
+ TEST_CASE_IGNORE_OUTPUT);
+}
+
+// Add TEST_CASE_IGNORE_OUTPUT since this isn't a solid green image and we just
+// want to test that it decodes correctly.
+ImageTestCase MultiLayerAVIFTestCase() {
+ return ImageTestCase("multilayer.avif", "image/avif", IntSize(1280, 720),
+ TEST_CASE_IGNORE_OUTPUT);
+}
+
+ImageTestCase LargeWebPTestCase() {
+ return ImageTestCase("large.webp", "image/webp", IntSize(1200, 660),
+ TEST_CASE_IGNORE_OUTPUT);
+}
+
+ImageTestCase LargeAVIFTestCase() {
+ return ImageTestCase("large.avif", "image/avif", IntSize(1200, 660),
+ TEST_CASE_IGNORE_OUTPUT);
+}
+
+ImageTestCase GreenWebPIccSrgbTestCase() {
+ return ImageTestCase("green.icc_srgb.webp", "image/webp", IntSize(100, 100));
+}
+
+ImageTestCase GreenFirstFrameAnimatedGIFTestCase() {
+ return ImageTestCase("first-frame-green.gif", "image/gif", IntSize(100, 100),
+ TEST_CASE_IS_ANIMATED);
+}
+
+ImageTestCase GreenFirstFrameAnimatedPNGTestCase() {
+ return ImageTestCase("first-frame-green.png", "image/png", IntSize(100, 100),
+ TEST_CASE_IS_TRANSPARENT | TEST_CASE_IS_ANIMATED);
+}
+
+ImageTestCase GreenFirstFrameAnimatedWebPTestCase() {
+ return ImageTestCase("first-frame-green.webp", "image/webp",
+ IntSize(100, 100), TEST_CASE_IS_ANIMATED);
+}
+
+ImageTestCase BlendAnimatedGIFTestCase() {
+ return ImageTestCase("blend.gif", "image/gif", IntSize(100, 100),
+ TEST_CASE_IS_ANIMATED);
+}
+
+ImageTestCase BlendAnimatedPNGTestCase() {
+ return ImageTestCase("blend.png", "image/png", IntSize(100, 100),
+ TEST_CASE_IS_TRANSPARENT | TEST_CASE_IS_ANIMATED);
+}
+
+ImageTestCase BlendAnimatedWebPTestCase() {
+ return ImageTestCase("blend.webp", "image/webp", IntSize(100, 100),
+ TEST_CASE_IS_TRANSPARENT | TEST_CASE_IS_ANIMATED);
+}
+
+ImageTestCase CorruptTestCase() {
+ return ImageTestCase("corrupt.jpg", "image/jpeg", IntSize(100, 100),
+ TEST_CASE_HAS_ERROR);
+}
+
+ImageTestCase CorruptBMPWithTruncatedHeader() {
+ // This BMP has a header which is truncated right between the BIH and the
+ // bitfields, which is a particularly error-prone place w.r.t. the BMP decoder
+ // state machine.
+ return ImageTestCase("invalid-truncated-metadata.bmp", "image/bmp",
+ IntSize(100, 100), TEST_CASE_HAS_ERROR);
+}
+
+ImageTestCase CorruptICOWithBadBMPWidthTestCase() {
+ // This ICO contains a BMP icon which has a width that doesn't match the size
+ // listed in the corresponding ICO directory entry.
+ return ImageTestCase("corrupt-with-bad-bmp-width.ico", "image/x-icon",
+ IntSize(100, 100), TEST_CASE_HAS_ERROR);
+}
+
+ImageTestCase CorruptICOWithBadBMPHeightTestCase() {
+ // This ICO contains a BMP icon which has a height that doesn't match the size
+ // listed in the corresponding ICO directory entry.
+ return ImageTestCase("corrupt-with-bad-bmp-height.ico", "image/x-icon",
+ IntSize(100, 100), TEST_CASE_HAS_ERROR);
+}
+
+ImageTestCase CorruptICOWithBadBppTestCase() {
+ // This test case is an ICO with a BPP (15) in the ICO header which differs
+ // from that in the BMP header itself (1). It should ignore the ICO BPP when
+ // the BMP BPP is available and thus correctly decode the image.
+ return ImageTestCase("corrupt-with-bad-ico-bpp.ico", "image/x-icon",
+ IntSize(100, 100), TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase CorruptAVIFTestCase() {
+ return ImageTestCase("bug-1655846.avif", "image/avif", IntSize(100, 100),
+ TEST_CASE_HAS_ERROR);
+}
+
+ImageTestCase TransparentAVIFTestCase() {
+ return ImageTestCase("transparent.avif", "image/avif", IntSize(1200, 1200),
+ TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase TransparentPNGTestCase() {
+ return ImageTestCase("transparent.png", "image/png", IntSize(32, 32),
+ TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase TransparentGIFTestCase() {
+ return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16),
+ TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase TransparentWebPTestCase() {
+ ImageTestCase test("transparent.webp", "image/webp", IntSize(100, 100),
+ TEST_CASE_IS_TRANSPARENT);
+ test.mColor = BGRAColor::Transparent();
+ return test;
+}
+
+ImageTestCase TransparentNoAlphaHeaderWebPTestCase() {
+ ImageTestCase test("transparent-no-alpha-header.webp", "image/webp",
+ IntSize(100, 100), TEST_CASE_IS_FUZZY);
+ test.mColor = BGRAColor(0x00, 0x00, 0x00, 0xFF); // black
+ return test;
+}
+
+ImageTestCase FirstFramePaddingGIFTestCase() {
+ return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16),
+ TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase TransparentIfWithinICOBMPTestCase(TestCaseFlags aFlags) {
+ // This is a BMP that is only transparent when decoded as if it is within an
+ // ICO file. (Note: aFlags needs to be set to TEST_CASE_DEFAULT_FLAGS or
+ // TEST_CASE_IS_TRANSPARENT accordingly.)
+ return ImageTestCase("transparent-if-within-ico.bmp", "image/bmp",
+ IntSize(32, 32), aFlags);
+}
+
+ImageTestCase RLE4BMPTestCase() {
+ return ImageTestCase("rle4.bmp", "image/bmp", IntSize(320, 240),
+ TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase RLE8BMPTestCase() {
+ return ImageTestCase("rle8.bmp", "image/bmp", IntSize(32, 32),
+ TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase NoFrameDelayGIFTestCase() {
+ // This is an invalid (or at least, questionably valid) GIF that's animated
+ // even though it specifies a frame delay of zero. It's animated, but it's not
+ // marked TEST_CASE_IS_ANIMATED because the metadata decoder can't detect that
+ // it's animated.
+ return ImageTestCase("no-frame-delay.gif", "image/gif", IntSize(100, 100));
+}
+
+ImageTestCase ExtraImageSubBlocksAnimatedGIFTestCase() {
+ // This is a corrupt GIF that has extra image sub blocks between the first and
+ // second frame.
+ return ImageTestCase("animated-with-extra-image-sub-blocks.gif", "image/gif",
+ IntSize(100, 100));
+}
+
+ImageTestCase DownscaledPNGTestCase() {
+ // This testcase (and all the other "downscaled") testcases) consists of 25
+ // lines of green, followed by 25 lines of red, followed by 25 lines of green,
+ // followed by 25 more lines of red. It's intended that tests downscale it
+ // from 100x100 to 20x20, so we specify a 20x20 output size.
+ return ImageTestCase("downscaled.png", "image/png", IntSize(100, 100),
+ IntSize(20, 20));
+}
+
+ImageTestCase DownscaledGIFTestCase() {
+ return ImageTestCase("downscaled.gif", "image/gif", IntSize(100, 100),
+ IntSize(20, 20));
+}
+
+ImageTestCase DownscaledJPGTestCase() {
+ return ImageTestCase("downscaled.jpg", "image/jpeg", IntSize(100, 100),
+ IntSize(20, 20));
+}
+
+ImageTestCase DownscaledBMPTestCase() {
+ return ImageTestCase("downscaled.bmp", "image/bmp", IntSize(100, 100),
+ IntSize(20, 20));
+}
+
+ImageTestCase DownscaledICOTestCase() {
+ return ImageTestCase("downscaled.ico", "image/x-icon", IntSize(100, 100),
+ IntSize(20, 20), TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase DownscaledIconTestCase() {
+ return ImageTestCase("downscaled.icon", "image/icon", IntSize(100, 100),
+ IntSize(20, 20), TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase DownscaledWebPTestCase() {
+ return ImageTestCase("downscaled.webp", "image/webp", IntSize(100, 100),
+ IntSize(20, 20));
+}
+
+ImageTestCase DownscaledAVIFTestCase() {
+ return ImageTestCase("downscaled.avif", "image/avif", IntSize(100, 100),
+ IntSize(20, 20));
+}
+
+ImageTestCase DownscaledTransparentICOWithANDMaskTestCase() {
+ // This test case is an ICO with AND mask transparency. We want to ensure that
+ // we can downscale it without crashing or triggering ASAN failures, but its
+ // content isn't simple to verify, so for now we don't check the output.
+ return ImageTestCase("transparent-ico-with-and-mask.ico", "image/x-icon",
+ IntSize(32, 32), IntSize(20, 20),
+ TEST_CASE_IS_TRANSPARENT | TEST_CASE_IGNORE_OUTPUT);
+}
+
+ImageTestCase TruncatedSmallGIFTestCase() {
+ return ImageTestCase("green-1x1-truncated.gif", "image/gif", IntSize(1, 1),
+ TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase LargeICOWithBMPTestCase() {
+ return ImageTestCase("green-large-bmp.ico", "image/x-icon", IntSize(256, 256),
+ TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase LargeICOWithPNGTestCase() {
+ return ImageTestCase("green-large-png.ico", "image/x-icon", IntSize(512, 512),
+ TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase GreenMultipleSizesICOTestCase() {
+ return ImageTestCase("green-multiple-sizes.ico", "image/x-icon",
+ IntSize(256, 256));
+}
+
+ImageTestCase PerfGrayJPGTestCase() {
+ return ImageTestCase("perf_gray.jpg", "image/jpeg", IntSize(1000, 1000));
+}
+
+ImageTestCase PerfCmykJPGTestCase() {
+ return ImageTestCase("perf_cmyk.jpg", "image/jpeg", IntSize(1000, 1000));
+}
+
+ImageTestCase PerfYCbCrJPGTestCase() {
+ return ImageTestCase("perf_ycbcr.jpg", "image/jpeg", IntSize(1000, 1000));
+}
+
+ImageTestCase PerfRgbPNGTestCase() {
+ return ImageTestCase("perf_srgb.png", "image/png", IntSize(1000, 1000));
+}
+
+ImageTestCase PerfRgbAlphaPNGTestCase() {
+ return ImageTestCase("perf_srgb_alpha.png", "image/png", IntSize(1000, 1000),
+ TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase PerfGrayPNGTestCase() {
+ return ImageTestCase("perf_gray.png", "image/png", IntSize(1000, 1000));
+}
+
+ImageTestCase PerfGrayAlphaPNGTestCase() {
+ return ImageTestCase("perf_gray_alpha.png", "image/png", IntSize(1000, 1000),
+ TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase PerfRgbLosslessWebPTestCase() {
+ return ImageTestCase("perf_srgb_lossless.webp", "image/webp",
+ IntSize(1000, 1000));
+}
+
+ImageTestCase PerfRgbAlphaLosslessWebPTestCase() {
+ return ImageTestCase("perf_srgb_alpha_lossless.webp", "image/webp",
+ IntSize(1000, 1000), TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase PerfRgbLossyWebPTestCase() {
+ return ImageTestCase("perf_srgb_lossy.webp", "image/webp",
+ IntSize(1000, 1000));
+}
+
+ImageTestCase PerfRgbAlphaLossyWebPTestCase() {
+ return ImageTestCase("perf_srgb_alpha_lossy.webp", "image/webp",
+ IntSize(1000, 1000), TEST_CASE_IS_TRANSPARENT);
+}
+
+ImageTestCase PerfRgbGIFTestCase() {
+ return ImageTestCase("perf_srgb.gif", "image/gif", IntSize(1000, 1000));
+}
+
+#ifdef MOZ_JXL
+ImageTestCase GreenJXLTestCase() {
+ return ImageTestCase("green.jxl", "image/jxl", IntSize(100, 100));
+}
+
+ImageTestCase DownscaledJXLTestCase() {
+ return ImageTestCase("downscaled.jxl", "image/jxl", IntSize(100, 100),
+ IntSize(20, 20));
+}
+
+ImageTestCase LargeJXLTestCase() {
+ return ImageTestCase("large.jxl", "image/jxl", IntSize(1200, 660),
+ TEST_CASE_IGNORE_OUTPUT);
+}
+
+ImageTestCase TransparentJXLTestCase() {
+ return ImageTestCase("transparent.jxl", "image/jxl", IntSize(1200, 1200),
+ TEST_CASE_IS_TRANSPARENT);
+}
+#endif
+
+ImageTestCase ExifResolutionTestCase() {
+ return ImageTestCase("exif_resolution.jpg", "image/jpeg", IntSize(100, 50));
+}
+
+RefPtr<Image> TestCaseToDecodedImage(const ImageTestCase& aTestCase) {
+ RefPtr<Image> image = ImageFactory::CreateAnonymousImage(
+ nsDependentCString(aTestCase.mMimeType));
+ MOZ_RELEASE_ASSERT(!image->HasError());
+
+ nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
+ MOZ_RELEASE_ASSERT(inputStream);
+
+ // Figure out how much data we have.
+ uint64_t length;
+ nsresult rv = inputStream->Available(&length);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+
+ // Write the data into the image.
+ rv = image->OnImageDataAvailable(nullptr, inputStream, 0,
+ static_cast<uint32_t>(length));
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+
+ // Let the image know we've sent all the data.
+ rv = image->OnImageDataComplete(nullptr, NS_OK, true);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+
+ RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
+ tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
+
+ // Use GetFrame() to force a sync decode of the image.
+ RefPtr<SourceSurface> surface = image->GetFrame(
+ imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_SYNC_DECODE);
+ Unused << surface;
+ return image;
+}
+
+} // namespace image
+} // namespace mozilla