/* -*- 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 #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 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 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 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 LoadFile(const char* aRelativePath) { nsresult rv; nsCOMPtr dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); ASSERT_TRUE_OR_RETURN(dirService != nullptr, nullptr); // Retrieve the current working directory. nsCOMPtr 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 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 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 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(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(gotColor.mRed) << ", " << static_cast(gotColor.mGreen) << ", " << static_cast(gotColor.mBlue) << ", " << static_cast(gotColor.mAlpha) << "), " << "expected rgba(" << static_cast(pmColor.mRed) << ", " << static_cast(pmColor.mGreen) << ", " << static_cast(pmColor.mBlue) << ", " << static_cast(pmColor.mAlpha) << ")"; return false; } } } } return true; } bool RowHasPixels(SourceSurface* aSurface, int32_t aRow, const vector& 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 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(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 CreateTrivialDecoder() { DecoderType decoderType = DecoderFactory::GetDecoderType("image/gif"); auto sourceBuffer = MakeNotNull>(); RefPtr 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 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 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& aOutputRect /* = Nothing() */, const Maybe& aInputRect /* = Nothing() */, const Maybe& aInputWriteRect /* = Nothing() */, const Maybe& 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& aOutputRect /* = Nothing() */, const Maybe& aInputRect /* = Nothing() */, const Maybe& aInputWriteRect /* = Nothing() */, const Maybe& 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([&] { ++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([&] { ++count; return AsVariant(aInputColor.AsPixel()); }); EXPECT_EQ(oldCount, count); EXPECT_EQ(WriteState::FINISHED, result); EXPECT_TRUE(aFilter->IsSurfaceFinished()); Maybe 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 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 TestCaseToDecodedImage(const ImageTestCase& aTestCase) { RefPtr image = ImageFactory::CreateAnonymousImage( nsDependentCString(aTestCase.mMimeType)); MOZ_RELEASE_ASSERT(!image->HasError()); nsCOMPtr 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(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 tracker = image->GetProgressTracker(); tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE); // Use GetFrame() to force a sync decode of the image. RefPtr surface = image->GetFrame( imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_SYNC_DECODE); Unused << surface; return image; } } // namespace image } // namespace mozilla