diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /image/test/gtest/Common.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'image/test/gtest/Common.cpp')
-rw-r--r-- | image/test/gtest/Common.cpp | 1076 |
1 files changed, 1076 insertions, 0 deletions
diff --git a/image/test/gtest/Common.cpp b/image/test/gtest/Common.cpp new file mode 100644 index 0000000000..d0ddbecedf --- /dev/null +++ b/image/test/gtest/Common.cpp @@ -0,0 +1,1076 @@ +/* -*- 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); + rv = Preferences::SetBool("image.avif.sequence.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(expectedPixel, gotPixel) + << "Color mismatch for rectangle from " << aRect.TopLeft() + << " to " << aRect.BottomRight() << ": " + << "got rgba(" << static_cast<int>(gotColor.mRed) << ", " + << static_cast<int>(gotColor.mGreen) << ", " + << static_cast<int>(gotColor.mBlue) << ", " + << static_cast<int>(gotColor.mAlpha) << "), " + << "expected rgba(" << static_cast<int>(pmColor.mRed) << ", " + << static_cast<int>(pmColor.mGreen) << ", " + << static_cast<int>(pmColor.mBlue) << ", " + << static_cast<int>(pmColor.mAlpha) << ")"; + return 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 GreenFirstFrameAnimatedAVIFTestCase() { + return ImageTestCase("first-frame-green.avif", "image/avif", + 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 BlendAnimatedAVIFTestCase() { + return ImageTestCase("blend.avif", "image/avif", 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 |