/* 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 "gtest/gtest.h" #include "Common.h" #include "AnimationSurfaceProvider.h" #include "DecodePool.h" #include "Decoder.h" #include "DecoderFactory.h" #include "decoders/nsBMPDecoder.h" #include "IDecodingTask.h" #include "ImageOps.h" #include "imgIContainer.h" #include "ImageFactory.h" #include "mozilla/ScopeExit.h" #include "mozilla/gfx/2D.h" #include "nsComponentManagerUtils.h" #include "nsCOMPtr.h" #include "nsIInputStream.h" #include "mozilla/RefPtr.h" #include "nsStreamUtils.h" #include "nsString.h" #include "nsThreadUtils.h" #include "ProgressTracker.h" #include "SourceBuffer.h" using namespace mozilla; using namespace mozilla::gfx; using namespace mozilla::image; static already_AddRefed CheckDecoderState( const ImageTestCase& aTestCase, image::Decoder* aDecoder) { // image::Decoder should match what we asked for in the MIME type. EXPECT_NE(aDecoder->GetType(), DecoderType::UNKNOWN); EXPECT_EQ(aDecoder->GetType(), DecoderFactory::GetDecoderType(aTestCase.mMimeType)); EXPECT_TRUE(aDecoder->GetDecodeDone()); EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR), aDecoder->HasError()); // Verify that the decoder made the expected progress. Progress progress = aDecoder->TakeProgress(); EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR), bool(progress & FLAG_HAS_ERROR)); if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) { return nullptr; // That's all we can check for bad images. } EXPECT_TRUE(bool(progress & FLAG_SIZE_AVAILABLE)); EXPECT_TRUE(bool(progress & FLAG_DECODE_COMPLETE)); EXPECT_TRUE(bool(progress & FLAG_FRAME_COMPLETE)); EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT), bool(progress & FLAG_HAS_TRANSPARENCY)); EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED), bool(progress & FLAG_IS_ANIMATED)); // The decoder should get the correct size. IntSize size = aDecoder->Size(); EXPECT_EQ(aTestCase.mSize.width, size.width); EXPECT_EQ(aTestCase.mSize.height, size.height); // Get the current frame, which is always the first frame of the image // because CreateAnonymousDecoder() forces a first-frame-only decode. RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); RefPtr surface = currentFrame->GetSourceSurface(); // Verify that the resulting surfaces matches our expectations. EXPECT_TRUE(surface->IsDataSourceSurface()); EXPECT_TRUE(surface->GetFormat() == SurfaceFormat::OS_RGBX || surface->GetFormat() == SurfaceFormat::OS_RGBA); EXPECT_EQ(aTestCase.mOutputSize, surface->GetSize()); return surface.forget(); } static void CheckDecoderResults(const ImageTestCase& aTestCase, image::Decoder* aDecoder) { RefPtr surface = CheckDecoderState(aTestCase, aDecoder); if (!surface) { return; } if (aTestCase.mFlags & TEST_CASE_IGNORE_OUTPUT) { return; } // Check the output. EXPECT_TRUE(IsSolidColor(surface, aTestCase.Color(), aTestCase.Fuzz())); } template void WithBadBufferDecode(const ImageTestCase& aTestCase, const Maybe& aOutputSize, Func aResultChecker) { // Prepare a SourceBuffer with an error that will immediately move iterators // to COMPLETE. auto sourceBuffer = MakeNotNull>(); sourceBuffer->ExpectLength(SIZE_MAX); // Create a decoder. DecoderType decoderType = DecoderFactory::GetDecoderType(aTestCase.mMimeType); RefPtr decoder = DecoderFactory::CreateAnonymousDecoder( decoderType, sourceBuffer, aOutputSize, DecoderFlags::FIRST_FRAME_ONLY, aTestCase.mSurfaceFlags); ASSERT_TRUE(decoder != nullptr); RefPtr task = new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ false); // Run the full decoder synchronously on the main thread. task->Run(); // Call the lambda to verify the expected results. aResultChecker(decoder); } static void CheckDecoderBadBuffer(const ImageTestCase& aTestCase) { WithBadBufferDecode(aTestCase, Nothing(), [&](image::Decoder* aDecoder) { CheckDecoderResults(aTestCase, aDecoder); }); } template void WithSingleChunkDecode(const ImageTestCase& aTestCase, const Maybe& aOutputSize, bool aUseDecodePool, Func aResultChecker) { nsCOMPtr inputStream = LoadFile(aTestCase.mPath); ASSERT_TRUE(inputStream != nullptr); // Figure out how much data we have. uint64_t length; nsresult rv = inputStream->Available(&length); ASSERT_TRUE(NS_SUCCEEDED(rv)); // Write the data into a SourceBuffer. auto sourceBuffer = MakeNotNull>(); sourceBuffer->ExpectLength(length); rv = sourceBuffer->AppendFromInputStream(inputStream, length); ASSERT_TRUE(NS_SUCCEEDED(rv)); sourceBuffer->Complete(NS_OK); // Create a decoder. DecoderType decoderType = DecoderFactory::GetDecoderType(aTestCase.mMimeType); RefPtr decoder = DecoderFactory::CreateAnonymousDecoder( decoderType, sourceBuffer, aOutputSize, DecoderFlags::FIRST_FRAME_ONLY, aTestCase.mSurfaceFlags); ASSERT_TRUE(decoder != nullptr); RefPtr task = new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ false); if (aUseDecodePool) { DecodePool::Singleton()->AsyncRun(task.get()); while (!decoder->GetDecodeDone()) { task->Resume(); } } else { // Run the full decoder synchronously on the main thread. task->Run(); } // Call the lambda to verify the expected results. aResultChecker(decoder); } static void CheckDecoderSingleChunk(const ImageTestCase& aTestCase, bool aUseDecodePool = false) { WithSingleChunkDecode(aTestCase, Nothing(), aUseDecodePool, [&](image::Decoder* aDecoder) { CheckDecoderResults(aTestCase, aDecoder); }); } template void WithDelayedChunkDecode(const ImageTestCase& aTestCase, const Maybe& aOutputSize, Func aResultChecker) { nsCOMPtr inputStream = LoadFile(aTestCase.mPath); ASSERT_TRUE(inputStream != nullptr); // Figure out how much data we have. uint64_t length; nsresult rv = inputStream->Available(&length); ASSERT_TRUE(NS_SUCCEEDED(rv)); // Prepare an empty SourceBuffer. auto sourceBuffer = MakeNotNull>(); // Create a decoder. DecoderType decoderType = DecoderFactory::GetDecoderType(aTestCase.mMimeType); RefPtr decoder = DecoderFactory::CreateAnonymousDecoder( decoderType, sourceBuffer, aOutputSize, DecoderFlags::FIRST_FRAME_ONLY, aTestCase.mSurfaceFlags); ASSERT_TRUE(decoder != nullptr); RefPtr task = new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ true); // Run the full decoder synchronously. It should now be waiting on // the iterator to yield some data since we haven't written anything yet. task->Run(); // Writing all of the data should wake up the decoder to complete. sourceBuffer->ExpectLength(length); rv = sourceBuffer->AppendFromInputStream(inputStream, length); ASSERT_TRUE(NS_SUCCEEDED(rv)); sourceBuffer->Complete(NS_OK); // It would have gotten posted to the main thread to avoid mutex contention. SpinPendingEvents(); // Call the lambda to verify the expected results. aResultChecker(decoder); } static void CheckDecoderDelayedChunk(const ImageTestCase& aTestCase) { WithDelayedChunkDecode(aTestCase, Nothing(), [&](image::Decoder* aDecoder) { CheckDecoderResults(aTestCase, aDecoder); }); } static void CheckDecoderMultiChunk(const ImageTestCase& aTestCase, uint64_t aChunkSize = 1) { nsCOMPtr inputStream = LoadFile(aTestCase.mPath); ASSERT_TRUE(inputStream != nullptr); // Figure out how much data we have. uint64_t length; nsresult rv = inputStream->Available(&length); ASSERT_TRUE(NS_SUCCEEDED(rv)); // Create a SourceBuffer and a decoder. auto sourceBuffer = MakeNotNull>(); sourceBuffer->ExpectLength(length); DecoderType decoderType = DecoderFactory::GetDecoderType(aTestCase.mMimeType); RefPtr decoder = DecoderFactory::CreateAnonymousDecoder( decoderType, sourceBuffer, Nothing(), DecoderFlags::FIRST_FRAME_ONLY, aTestCase.mSurfaceFlags); ASSERT_TRUE(decoder != nullptr); RefPtr task = new AnonymousDecodingTask(WrapNotNull(decoder), /* aResumable */ true); // Run the full decoder synchronously. It should now be waiting on // the iterator to yield some data since we haven't written anything yet. task->Run(); while (length > 0) { uint64_t read = length > aChunkSize ? aChunkSize : length; length -= read; uint64_t available = 0; rv = inputStream->Available(&available); ASSERT_TRUE(available >= read); ASSERT_TRUE(NS_SUCCEEDED(rv)); // Writing any data should wake up the decoder to complete. rv = sourceBuffer->AppendFromInputStream(inputStream, read); ASSERT_TRUE(NS_SUCCEEDED(rv)); // It would have gotten posted to the main thread to avoid mutex contention. SpinPendingEvents(); } sourceBuffer->Complete(NS_OK); SpinPendingEvents(); CheckDecoderResults(aTestCase, decoder); } static void CheckDownscaleDuringDecode(const ImageTestCase& aTestCase) { // This function expects that |aTestCase| 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. We'll downscale it from 100x100 to 20x20. IntSize outputSize(20, 20); WithSingleChunkDecode( aTestCase, Some(outputSize), /* aUseDecodePool */ false, [&](image::Decoder* aDecoder) { RefPtr surface = CheckDecoderState(aTestCase, aDecoder); // There are no downscale-during-decode tests that have // TEST_CASE_HAS_ERROR set, so we expect to always get a surface here. EXPECT_TRUE(surface != nullptr); if (aTestCase.mFlags & TEST_CASE_IGNORE_OUTPUT) { return; } // Check that the downscaled image is correct. Note that we skip rows // near the transitions between colors, since the downscaler does not // produce a sharp boundary at these points. Even some of the rows we // test need a small amount of fuzz; this is just the nature of Lanczos // downscaling. EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4, aTestCase.ChooseColor(BGRAColor::Green()), /* aFuzz = */ 47)); EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, aTestCase.ChooseColor(BGRAColor::Red()), /* aFuzz = */ 27)); EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(), /* aFuzz = */ 47)); EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, aTestCase.ChooseColor(BGRAColor::Red()), /* aFuzz = */ 27)); }); } static void CheckAnimationDecoderResults(const ImageTestCase& aTestCase, AnimationSurfaceProvider* aProvider, image::Decoder* aDecoder) { EXPECT_TRUE(aDecoder->GetDecodeDone()); EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR), aDecoder->HasError()); if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) { return; // That's all we can check for bad images. } // The decoder should get the correct size. IntSize size = aDecoder->Size(); EXPECT_EQ(aTestCase.mSize.width, size.width); EXPECT_EQ(aTestCase.mSize.height, size.height); if (aTestCase.mFlags & TEST_CASE_IGNORE_OUTPUT) { return; } // Check the output. AutoTArray framePixels; framePixels.AppendElement(aTestCase.ChooseColor(BGRAColor::Green())); framePixels.AppendElement( aTestCase.ChooseColor(BGRAColor(0x7F, 0x7F, 0x7F, 0xFF))); DrawableSurface drawableSurface(WrapNotNull(aProvider)); for (size_t i = 0; i < framePixels.Length(); ++i) { nsresult rv = drawableSurface.Seek(i); EXPECT_TRUE(NS_SUCCEEDED(rv)); // Check the first frame, all green. RawAccessFrameRef rawFrame = drawableSurface->RawAccessRef(); RefPtr surface = rawFrame->GetSourceSurface(); // Verify that the resulting surfaces matches our expectations. EXPECT_TRUE(surface->IsDataSourceSurface()); EXPECT_TRUE(surface->GetFormat() == SurfaceFormat::OS_RGBX || surface->GetFormat() == SurfaceFormat::OS_RGBA); EXPECT_EQ(aTestCase.mOutputSize, surface->GetSize()); EXPECT_TRUE(IsSolidColor(surface, framePixels[i], aTestCase.Fuzz())); } // Should be no more frames. nsresult rv = drawableSurface.Seek(framePixels.Length()); EXPECT_TRUE(NS_FAILED(rv)); } template static void WithSingleChunkAnimationDecode(const ImageTestCase& aTestCase, Func aResultChecker) { // Create an image. RefPtr image = ImageFactory::CreateAnonymousImage( nsDependentCString(aTestCase.mMimeType)); ASSERT_TRUE(!image->HasError()); NotNull> rasterImage = WrapNotNull(static_cast(image.get())); nsCOMPtr inputStream = LoadFile(aTestCase.mPath); ASSERT_TRUE(inputStream != nullptr); // Figure out how much data we have. uint64_t length; nsresult rv = inputStream->Available(&length); ASSERT_TRUE(NS_SUCCEEDED(rv)); // Write the data into a SourceBuffer. NotNull> sourceBuffer = WrapNotNull(new SourceBuffer()); sourceBuffer->ExpectLength(length); rv = sourceBuffer->AppendFromInputStream(inputStream, length); ASSERT_TRUE(NS_SUCCEEDED(rv)); sourceBuffer->Complete(NS_OK); // Create a metadata decoder first, because otherwise RasterImage will get // unhappy about finding out the image is animated during a full decode. DecoderType decoderType = DecoderFactory::GetDecoderType(aTestCase.mMimeType); RefPtr task = DecoderFactory::CreateMetadataDecoder( decoderType, rasterImage, sourceBuffer); ASSERT_TRUE(task != nullptr); // Run the metadata decoder synchronously. task->Run(); // Create a decoder. DecoderFlags decoderFlags = DefaultDecoderFlags(); SurfaceFlags surfaceFlags = aTestCase.mSurfaceFlags; RefPtr decoder = DecoderFactory::CreateAnonymousDecoder( decoderType, sourceBuffer, Nothing(), decoderFlags, surfaceFlags); ASSERT_TRUE(decoder != nullptr); // Create an AnimationSurfaceProvider which will manage the decoding process // and make this decoder's output available in the surface cache. SurfaceKey surfaceKey = RasterSurfaceKey(aTestCase.mOutputSize, surfaceFlags, PlaybackType::eAnimated); RefPtr provider = new AnimationSurfaceProvider( rasterImage, surfaceKey, WrapNotNull(decoder), /* aCurrentFrame */ 0); // Run the full decoder synchronously. provider->Run(); // Call the lambda to verify the expected results. aResultChecker(provider, decoder); } static void CheckAnimationDecoderSingleChunk(const ImageTestCase& aTestCase) { WithSingleChunkAnimationDecode( aTestCase, [&](AnimationSurfaceProvider* aProvider, image::Decoder* aDecoder) { CheckAnimationDecoderResults(aTestCase, aProvider, aDecoder); }); } static void CheckDecoderFrameFirst(const ImageTestCase& aTestCase) { // Verify that we can decode this test case and retrieve the first frame using // imgIContainer::FRAME_FIRST. This ensures that we correctly trigger a // single-frame decode rather than an animated decode when // imgIContainer::FRAME_FIRST is requested. // Create an image. RefPtr image = ImageFactory::CreateAnonymousImage( nsDependentCString(aTestCase.mMimeType)); ASSERT_TRUE(!image->HasError()); nsCOMPtr inputStream = LoadFile(aTestCase.mPath); ASSERT_TRUE(inputStream); // Figure out how much data we have. uint64_t length; nsresult rv = inputStream->Available(&length); ASSERT_TRUE(NS_SUCCEEDED(rv)); // Write the data into the image. rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0, static_cast(length)); ASSERT_TRUE(NS_SUCCEEDED(rv)); // Let the image know we've sent all the data. rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true); ASSERT_TRUE(NS_SUCCEEDED(rv)); RefPtr tracker = image->GetProgressTracker(); tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE); // Lock the image so its surfaces don't disappear during the test. image->LockImage(); auto unlock = mozilla::MakeScopeExit([&] { image->UnlockImage(); }); // Use GetFrame() to force a sync decode of the image, specifying FRAME_FIRST // to ensure that we don't get an animated decode. RefPtr surface = image->GetFrame( imgIContainer::FRAME_FIRST, imgIContainer::FLAG_SYNC_DECODE); // Ensure that the image's metadata meets our expectations. IntSize imageSize(0, 0); rv = image->GetWidth(&imageSize.width); EXPECT_TRUE(NS_SUCCEEDED(rv)); rv = image->GetHeight(&imageSize.height); EXPECT_TRUE(NS_SUCCEEDED(rv)); EXPECT_EQ(aTestCase.mSize.width, imageSize.width); EXPECT_EQ(aTestCase.mSize.height, imageSize.height); Progress imageProgress = tracker->GetProgress(); EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false); EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true); // Ensure that we decoded the static version of the image. { LookupResult result = SurfaceCache::Lookup( ImageKey(image.get()), RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags, PlaybackType::eStatic), /* aMarkUsed = */ false); ASSERT_EQ(MatchType::EXACT, result.Type()); EXPECT_TRUE(bool(result.Surface())); } // Ensure that we didn't decode the animated version of the image. { LookupResult result = SurfaceCache::Lookup( ImageKey(image.get()), RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags, PlaybackType::eAnimated), /* aMarkUsed = */ false); ASSERT_EQ(MatchType::NOT_FOUND, result.Type()); } // Use GetFrame() to force a sync decode of the image, this time specifying // FRAME_CURRENT to ensure that we get an animated decode. RefPtr animatedSurface = image->GetFrame( imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_SYNC_DECODE); // Ensure that we decoded both frames of the animated version of the image. { LookupResult result = SurfaceCache::Lookup( ImageKey(image.get()), RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags, PlaybackType::eAnimated), /* aMarkUsed = */ true); ASSERT_EQ(MatchType::EXACT, result.Type()); EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0))); EXPECT_TRUE(bool(result.Surface())); RefPtr partialFrame = result.Surface().GetFrame(1); EXPECT_TRUE(bool(partialFrame)); } // Ensure that the static version is still around. { LookupResult result = SurfaceCache::Lookup( ImageKey(image.get()), RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags, PlaybackType::eStatic), /* aMarkUsed = */ true); ASSERT_EQ(MatchType::EXACT, result.Type()); EXPECT_TRUE(bool(result.Surface())); } } static void CheckDecoderFrameCurrent(const ImageTestCase& aTestCase) { // Verify that we can decode this test case and retrieve the entire sequence // of frames using imgIContainer::FRAME_CURRENT. This ensures that we // correctly trigger an animated decode rather than a single-frame decode when // imgIContainer::FRAME_CURRENT is requested. // Create an image. RefPtr image = ImageFactory::CreateAnonymousImage( nsDependentCString(aTestCase.mMimeType)); ASSERT_TRUE(!image->HasError()); nsCOMPtr inputStream = LoadFile(aTestCase.mPath); ASSERT_TRUE(inputStream); // Figure out how much data we have. uint64_t length; nsresult rv = inputStream->Available(&length); ASSERT_TRUE(NS_SUCCEEDED(rv)); // Write the data into the image. rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0, static_cast(length)); ASSERT_TRUE(NS_SUCCEEDED(rv)); // Let the image know we've sent all the data. rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true); ASSERT_TRUE(NS_SUCCEEDED(rv)); RefPtr tracker = image->GetProgressTracker(); tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE); // Lock the image so its surfaces don't disappear during the test. image->LockImage(); // Use GetFrame() to force a sync decode of the image, specifying // FRAME_CURRENT to ensure we get an animated decode. RefPtr surface = image->GetFrame( imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_SYNC_DECODE); // Ensure that the image's metadata meets our expectations. IntSize imageSize(0, 0); rv = image->GetWidth(&imageSize.width); EXPECT_TRUE(NS_SUCCEEDED(rv)); rv = image->GetHeight(&imageSize.height); EXPECT_TRUE(NS_SUCCEEDED(rv)); EXPECT_EQ(aTestCase.mSize.width, imageSize.width); EXPECT_EQ(aTestCase.mSize.height, imageSize.height); Progress imageProgress = tracker->GetProgress(); EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false); EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true); // Ensure that we decoded both frames of the animated version of the image. { LookupResult result = SurfaceCache::Lookup( ImageKey(image.get()), RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags, PlaybackType::eAnimated), /* aMarkUsed = */ true); ASSERT_EQ(MatchType::EXACT, result.Type()); EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0))); EXPECT_TRUE(bool(result.Surface())); RefPtr partialFrame = result.Surface().GetFrame(1); EXPECT_TRUE(bool(partialFrame)); } // Ensure that we didn't decode the static version of the image. { LookupResult result = SurfaceCache::Lookup( ImageKey(image.get()), RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags, PlaybackType::eStatic), /* aMarkUsed = */ false); ASSERT_EQ(MatchType::NOT_FOUND, result.Type()); } // Use GetFrame() to force a sync decode of the image, this time specifying // FRAME_FIRST to ensure that we get a single-frame decode. RefPtr animatedSurface = image->GetFrame( imgIContainer::FRAME_FIRST, imgIContainer::FLAG_SYNC_DECODE); // Ensure that we decoded the static version of the image. { LookupResult result = SurfaceCache::Lookup( ImageKey(image.get()), RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags, PlaybackType::eStatic), /* aMarkUsed = */ true); ASSERT_EQ(MatchType::EXACT, result.Type()); EXPECT_TRUE(bool(result.Surface())); } // Ensure that both frames of the animated version are still around. { LookupResult result = SurfaceCache::Lookup( ImageKey(image.get()), RasterSurfaceKey(imageSize, aTestCase.mSurfaceFlags, PlaybackType::eAnimated), /* aMarkUsed = */ true); ASSERT_EQ(MatchType::EXACT, result.Type()); EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0))); EXPECT_TRUE(bool(result.Surface())); RefPtr partialFrame = result.Surface().GetFrame(1); EXPECT_TRUE(bool(partialFrame)); } } class ImageDecoders : public ::testing::Test { protected: AutoInitializeImageLib mInit; }; #define IMAGE_GTEST_DECODER_BASE_F(test_prefix) \ TEST_F(ImageDecoders, test_prefix##SingleChunk) { \ CheckDecoderSingleChunk(Green##test_prefix##TestCase()); \ } \ \ TEST_F(ImageDecoders, test_prefix##DelayedChunk) { \ CheckDecoderDelayedChunk(Green##test_prefix##TestCase()); \ } \ \ TEST_F(ImageDecoders, test_prefix##MultiChunk) { \ CheckDecoderMultiChunk(Green##test_prefix##TestCase()); \ } \ \ TEST_F(ImageDecoders, test_prefix##DownscaleDuringDecode) { \ CheckDownscaleDuringDecode(Downscaled##test_prefix##TestCase()); \ } \ \ TEST_F(ImageDecoders, test_prefix##ForceSRGB) { \ CheckDecoderSingleChunk(Green##test_prefix##TestCase().WithSurfaceFlags( \ SurfaceFlags::TO_SRGB_COLORSPACE)); \ } \ \ TEST_F(ImageDecoders, test_prefix##BadBuffer) { \ CheckDecoderBadBuffer(Green##test_prefix##TestCase().WithFlags( \ TEST_CASE_HAS_ERROR | TEST_CASE_IGNORE_OUTPUT)); \ } IMAGE_GTEST_DECODER_BASE_F(PNG) IMAGE_GTEST_DECODER_BASE_F(GIF) IMAGE_GTEST_DECODER_BASE_F(JPG) IMAGE_GTEST_DECODER_BASE_F(BMP) IMAGE_GTEST_DECODER_BASE_F(ICO) IMAGE_GTEST_DECODER_BASE_F(Icon) IMAGE_GTEST_DECODER_BASE_F(WebP) TEST_F(ImageDecoders, ICOWithANDMaskDownscaleDuringDecode) { CheckDownscaleDuringDecode(DownscaledTransparentICOWithANDMaskTestCase()); } TEST_F(ImageDecoders, WebPLargeMultiChunk) { CheckDecoderMultiChunk(LargeWebPTestCase(), /* aChunkSize */ 64); } TEST_F(ImageDecoders, WebPIccSrgbMultiChunk) { CheckDecoderMultiChunk(GreenWebPIccSrgbTestCase()); } TEST_F(ImageDecoders, WebPTransparentSingleChunk) { CheckDecoderSingleChunk(TransparentWebPTestCase()); } TEST_F(ImageDecoders, WebPTransparentNoAlphaHeaderSingleChunk) { CheckDecoderSingleChunk(TransparentNoAlphaHeaderWebPTestCase()); } TEST_F(ImageDecoders, AVIFSingleChunk) { CheckDecoderSingleChunk(GreenAVIFTestCase()); } TEST_F(ImageDecoders, AVIFMultiLayerSingleChunk) { CheckDecoderSingleChunk(MultiLayerAVIFTestCase()); } // This test must use the decode pool in order to check for regressions // of crashing the dav1d decoder when the ImgDecoder threads have a standard- // sized stack. TEST_F(ImageDecoders, AVIFStackCheck) { CheckDecoderSingleChunk(StackCheckAVIFTestCase(), /* aUseDecodePool */ true); } TEST_F(ImageDecoders, AVIFDelayedChunk) { CheckDecoderDelayedChunk(GreenAVIFTestCase()); } TEST_F(ImageDecoders, AVIFMultiChunk) { CheckDecoderMultiChunk(GreenAVIFTestCase()); } TEST_F(ImageDecoders, AVIFLargeMultiChunk) { CheckDecoderMultiChunk(LargeAVIFTestCase(), /* aChunkSize */ 64); } TEST_F(ImageDecoders, AVIFDownscaleDuringDecode) { CheckDownscaleDuringDecode(DownscaledAVIFTestCase()); } TEST_F(ImageDecoders, AnimatedGIFSingleChunk) { CheckDecoderSingleChunk(GreenFirstFrameAnimatedGIFTestCase()); } TEST_F(ImageDecoders, AnimatedGIFMultiChunk) { CheckDecoderMultiChunk(GreenFirstFrameAnimatedGIFTestCase()); } TEST_F(ImageDecoders, AnimatedGIFWithBlendedFrames) { CheckAnimationDecoderSingleChunk(GreenFirstFrameAnimatedGIFTestCase()); } TEST_F(ImageDecoders, AnimatedPNGSingleChunk) { CheckDecoderSingleChunk(GreenFirstFrameAnimatedPNGTestCase()); } TEST_F(ImageDecoders, AnimatedPNGMultiChunk) { CheckDecoderMultiChunk(GreenFirstFrameAnimatedPNGTestCase()); } TEST_F(ImageDecoders, AnimatedPNGWithBlendedFrames) { CheckAnimationDecoderSingleChunk(GreenFirstFrameAnimatedPNGTestCase()); } TEST_F(ImageDecoders, AnimatedWebPSingleChunk) { CheckDecoderSingleChunk(GreenFirstFrameAnimatedWebPTestCase()); } TEST_F(ImageDecoders, AnimatedWebPMultiChunk) { CheckDecoderMultiChunk(GreenFirstFrameAnimatedWebPTestCase()); } TEST_F(ImageDecoders, AnimatedWebPWithBlendedFrames) { CheckAnimationDecoderSingleChunk(GreenFirstFrameAnimatedWebPTestCase()); } TEST_F(ImageDecoders, CorruptSingleChunk) { CheckDecoderSingleChunk(CorruptTestCase()); } TEST_F(ImageDecoders, CorruptMultiChunk) { CheckDecoderMultiChunk(CorruptTestCase()); } TEST_F(ImageDecoders, CorruptBMPWithTruncatedHeaderSingleChunk) { CheckDecoderSingleChunk(CorruptBMPWithTruncatedHeader()); } TEST_F(ImageDecoders, CorruptBMPWithTruncatedHeaderMultiChunk) { CheckDecoderMultiChunk(CorruptBMPWithTruncatedHeader()); } TEST_F(ImageDecoders, CorruptICOWithBadBMPWidthSingleChunk) { CheckDecoderSingleChunk(CorruptICOWithBadBMPWidthTestCase()); } TEST_F(ImageDecoders, CorruptICOWithBadBMPWidthMultiChunk) { CheckDecoderMultiChunk(CorruptICOWithBadBMPWidthTestCase()); } TEST_F(ImageDecoders, CorruptICOWithBadBMPHeightSingleChunk) { CheckDecoderSingleChunk(CorruptICOWithBadBMPHeightTestCase()); } TEST_F(ImageDecoders, CorruptICOWithBadBMPHeightMultiChunk) { CheckDecoderMultiChunk(CorruptICOWithBadBMPHeightTestCase()); } TEST_F(ImageDecoders, CorruptICOWithBadBppSingleChunk) { CheckDecoderSingleChunk(CorruptICOWithBadBppTestCase()); } // Running this test under emulation for Android 7 on x86_64 seems to result // in the large allocation succeeding, but leaving so little memory left the // system falls over and it kills the test run, so we skip it instead. // See bug 1655846 for more details. #ifndef ANDROID TEST_F(ImageDecoders, CorruptAVIFSingleChunk) { CheckDecoderSingleChunk(CorruptAVIFTestCase()); } #endif TEST_F(ImageDecoders, AnimatedGIFWithFRAME_FIRST) { CheckDecoderFrameFirst(GreenFirstFrameAnimatedGIFTestCase()); } TEST_F(ImageDecoders, AnimatedGIFWithFRAME_CURRENT) { CheckDecoderFrameCurrent(GreenFirstFrameAnimatedGIFTestCase()); } TEST_F(ImageDecoders, AnimatedGIFWithExtraImageSubBlocks) { ImageTestCase testCase = ExtraImageSubBlocksAnimatedGIFTestCase(); // Verify that we can decode this test case and get two frames, even though // there are extra image sub blocks between the first and second frame. The // extra data shouldn't confuse the decoder or cause the decode to fail. // Create an image. RefPtr image = ImageFactory::CreateAnonymousImage( nsDependentCString(testCase.mMimeType)); ASSERT_TRUE(!image->HasError()); nsCOMPtr inputStream = LoadFile(testCase.mPath); ASSERT_TRUE(inputStream); // Figure out how much data we have. uint64_t length; nsresult rv = inputStream->Available(&length); ASSERT_TRUE(NS_SUCCEEDED(rv)); // Write the data into the image. rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0, static_cast(length)); ASSERT_TRUE(NS_SUCCEEDED(rv)); // Let the image know we've sent all the data. rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true); ASSERT_TRUE(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); // Ensure that the image's metadata meets our expectations. IntSize imageSize(0, 0); rv = image->GetWidth(&imageSize.width); EXPECT_TRUE(NS_SUCCEEDED(rv)); rv = image->GetHeight(&imageSize.height); EXPECT_TRUE(NS_SUCCEEDED(rv)); EXPECT_EQ(testCase.mSize.width, imageSize.width); EXPECT_EQ(testCase.mSize.height, imageSize.height); Progress imageProgress = tracker->GetProgress(); EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false); EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true); // Ensure that we decoded both frames of the image. LookupResult result = SurfaceCache::Lookup(ImageKey(image.get()), RasterSurfaceKey(imageSize, testCase.mSurfaceFlags, PlaybackType::eAnimated), /* aMarkUsed = */ true); ASSERT_EQ(MatchType::EXACT, result.Type()); EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0))); EXPECT_TRUE(bool(result.Surface())); RefPtr partialFrame = result.Surface().GetFrame(1); EXPECT_TRUE(bool(partialFrame)); } TEST_F(ImageDecoders, AnimatedWebPWithFRAME_FIRST) { CheckDecoderFrameFirst(GreenFirstFrameAnimatedWebPTestCase()); } TEST_F(ImageDecoders, AnimatedWebPWithFRAME_CURRENT) { CheckDecoderFrameCurrent(GreenFirstFrameAnimatedWebPTestCase()); } TEST_F(ImageDecoders, TruncatedSmallGIFSingleChunk) { CheckDecoderSingleChunk(TruncatedSmallGIFTestCase()); } TEST_F(ImageDecoders, LargeICOWithBMPSingleChunk) { CheckDecoderSingleChunk(LargeICOWithBMPTestCase()); } TEST_F(ImageDecoders, LargeICOWithBMPMultiChunk) { CheckDecoderMultiChunk(LargeICOWithBMPTestCase(), /* aChunkSize */ 64); } TEST_F(ImageDecoders, LargeICOWithPNGSingleChunk) { CheckDecoderSingleChunk(LargeICOWithPNGTestCase()); } TEST_F(ImageDecoders, LargeICOWithPNGMultiChunk) { CheckDecoderMultiChunk(LargeICOWithPNGTestCase()); } TEST_F(ImageDecoders, MultipleSizesICOSingleChunk) { ImageTestCase testCase = GreenMultipleSizesICOTestCase(); // Create an image. RefPtr image = ImageFactory::CreateAnonymousImage( nsDependentCString(testCase.mMimeType)); ASSERT_TRUE(!image->HasError()); nsCOMPtr inputStream = LoadFile(testCase.mPath); ASSERT_TRUE(inputStream); // Figure out how much data we have. uint64_t length; nsresult rv = inputStream->Available(&length); ASSERT_TRUE(NS_SUCCEEDED(rv)); // Write the data into the image. rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0, static_cast(length)); ASSERT_TRUE(NS_SUCCEEDED(rv)); // Let the image know we've sent all the data. rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true); ASSERT_TRUE(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); // Ensure that the image's metadata meets our expectations. IntSize imageSize(0, 0); rv = image->GetWidth(&imageSize.width); EXPECT_TRUE(NS_SUCCEEDED(rv)); rv = image->GetHeight(&imageSize.height); EXPECT_TRUE(NS_SUCCEEDED(rv)); EXPECT_EQ(testCase.mSize.width, imageSize.width); EXPECT_EQ(testCase.mSize.height, imageSize.height); nsTArray nativeSizes; rv = image->GetNativeSizes(nativeSizes); EXPECT_TRUE(NS_SUCCEEDED(rv)); ASSERT_EQ(6u, nativeSizes.Length()); IntSize expectedSizes[] = {IntSize(16, 16), IntSize(32, 32), IntSize(64, 64), IntSize(128, 128), IntSize(256, 256), IntSize(256, 128)}; for (int i = 0; i < 6; ++i) { EXPECT_EQ(expectedSizes[i], nativeSizes[i]); } RefPtr image90 = ImageOps::Orient(image, Orientation(Angle::D90, Flip::Unflipped)); rv = image90->GetNativeSizes(nativeSizes); EXPECT_TRUE(NS_SUCCEEDED(rv)); ASSERT_EQ(6u, nativeSizes.Length()); for (int i = 0; i < 5; ++i) { EXPECT_EQ(expectedSizes[i], nativeSizes[i]); } EXPECT_EQ(IntSize(128, 256), nativeSizes[5]); RefPtr image180 = ImageOps::Orient(image, Orientation(Angle::D180, Flip::Unflipped)); rv = image180->GetNativeSizes(nativeSizes); EXPECT_TRUE(NS_SUCCEEDED(rv)); ASSERT_EQ(6u, nativeSizes.Length()); for (int i = 0; i < 6; ++i) { EXPECT_EQ(expectedSizes[i], nativeSizes[i]); } }