RasterImage::WillDrawOpaqueNow() { if (!IsOpaque()) { return false; } if (mAnimationState) { // We never discard frames of animated images. return true; } // If we are not locked our decoded data could get discard at any time (ie // between the call to this function and when we are asked to draw), so we // have to return false if we are unlocked. if (IsUnlocked()) { return false; } LookupResult result = SurfaceCache::LookupBestMatch(ImageKey(this), RasterSurfaceKey(mSize, DefaultSurfaceFlags(), PlaybackType::eStatic)); MatchType matchType = result.Type(); if (matchType == MatchType::NOT_FOUND || matchType == MatchType::PENDING || !result.Surface()->IsFinished()) { return false; } return true; }
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> image = ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType)); ASSERT_TRUE(!image->HasError()); nsCOMPtr<nsIInputStream> 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<uint32_t>(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<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); // 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 firstFrameLookupResult = SurfaceCache::Lookup(ImageKey(image.get()), RasterSurfaceKey(imageSize, DefaultSurfaceFlags(), /* aFrameNum = */ 0)); EXPECT_EQ(MatchType::EXACT, firstFrameLookupResult.Type()); LookupResult secondFrameLookupResult = SurfaceCache::Lookup(ImageKey(image.get()), RasterSurfaceKey(imageSize, DefaultSurfaceFlags(), /* aFrameNum = */ 1)); EXPECT_EQ(MatchType::EXACT, secondFrameLookupResult.Type()); }
TEST(ImageMetadata, NoFrameDelayGIFFullDecode) { ImageTestCase testCase = NoFrameDelayGIFTestCase(); // The previous test (NoFrameDelayGIF) verifies that we *don't* detect that // this test case is animated, because it has a zero frame delay for the first // frame. This test verifies that when we do a full decode, we detect the // animation at that point and successfully decode all the frames. // Create an image. RefPtr<Image> image = ImageFactory::CreateAnonymousImage(nsAutoCString(testCase.mMimeType)); ASSERT_TRUE(!image->HasError()); nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.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 the image. rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0, static_cast<uint32_t>(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<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); // 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 firstFrameLookupResult = SurfaceCache::Lookup(ImageKey(image.get()), RasterSurfaceKey(imageSize, DefaultSurfaceFlags(), /* aFrameNum = */ 0)); EXPECT_EQ(MatchType::EXACT, firstFrameLookupResult.Type()); LookupResult secondFrameLookupResult = SurfaceCache::Lookup(ImageKey(image.get()), RasterSurfaceKey(imageSize, DefaultSurfaceFlags(), /* aFrameNum = */ 1)); EXPECT_EQ(MatchType::EXACT, secondFrameLookupResult.Type()); }
DrawableSurface RasterImage::LookupFrame(const IntSize& aSize, uint32_t aFlags, PlaybackType aPlaybackType) { MOZ_ASSERT(NS_IsMainThread()); // If we're opaque, we don't need to care about premultiplied alpha, because // that can only matter for frames with transparency. if (IsOpaque()) { aFlags &= ~FLAG_DECODE_NO_PREMULTIPLY_ALPHA; } IntSize requestedSize = CanDownscaleDuringDecode(aSize, aFlags) ? aSize : mSize; if (requestedSize.IsEmpty()) { return DrawableSurface(); // Can't decode to a surface of zero size. } LookupResult result = LookupFrameInternal(requestedSize, aFlags, aPlaybackType); if (!result && !mHasSize) { // We can't request a decode without knowing our intrinsic size. Give up. return DrawableSurface(); } if (result.Type() == MatchType::NOT_FOUND || result.Type() == MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND || ((aFlags & FLAG_SYNC_DECODE) && !result)) { // We don't have a copy of this frame, and there's no decoder working on // one. (Or we're sync decoding and the existing decoder hasn't even started // yet.) Trigger decoding so it'll be available next time. MOZ_ASSERT(aPlaybackType != PlaybackType::eAnimated || !mAnimationState || mAnimationState->KnownFrameCount() < 1, "Animated frames should be locked"); Decode(requestedSize, aFlags, aPlaybackType); // If we can sync decode, we should already have the frame. if (aFlags & FLAG_SYNC_DECODE) { result = LookupFrameInternal(requestedSize, aFlags, aPlaybackType); } } if (!result) { // We still weren't able to get a frame. Give up. return DrawableSurface(); } if (result.Surface()->GetCompositingFailed()) { return DrawableSurface(); } MOZ_ASSERT(!result.Surface()->GetIsPaletted(), "Should not have a paletted frame"); // Sync decoding guarantees that we got the frame, but if it's owned by an // async decoder that's currently running, the contents of the frame may not // be available yet. Make sure we get everything. if (mHasSourceData && (aFlags & FLAG_SYNC_DECODE)) { result.Surface()->WaitUntilFinished(); } // If we could have done some decoding in this function we need to check if // that decoding encountered an error and hence aborted the surface. We want // to avoid calling IsAborted if we weren't passed any sync decode flag because // IsAborted acquires the monitor for the imgFrame. if (aFlags & (FLAG_SYNC_DECODE | FLAG_SYNC_DECODE_IF_FAST) && result.Surface()->IsAborted()) { return DrawableSurface(); } return Move(result.Surface()); }
TEST_F(ImageDecoders, AnimatedGIFWithFRAME_CURRENT) { ImageTestCase testCase = GreenFirstFrameAnimatedGIFTestCase(); // 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> image = ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType)); ASSERT_TRUE(!image->HasError()); nsCOMPtr<nsIInputStream> 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<uint32_t>(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<ProgressTracker> 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<SourceSurface> 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 animated version of the image. { LookupResult result = SurfaceCache::Lookup(ImageKey(image.get()), RasterSurfaceKey(imageSize, DefaultSurfaceFlags(), PlaybackType::eAnimated)); ASSERT_EQ(MatchType::EXACT, result.Type()); EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0))); EXPECT_TRUE(bool(result.Surface())); EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1))); EXPECT_TRUE(bool(result.Surface())); } // Ensure that we didn't decode the static version of the image. { LookupResult result = SurfaceCache::Lookup(ImageKey(image.get()), RasterSurfaceKey(imageSize, DefaultSurfaceFlags(), PlaybackType::eStatic)); 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<SourceSurface> 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, DefaultSurfaceFlags(), PlaybackType::eStatic)); 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, DefaultSurfaceFlags(), PlaybackType::eAnimated)); ASSERT_EQ(MatchType::EXACT, result.Type()); EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0))); EXPECT_TRUE(bool(result.Surface())); EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1))); EXPECT_TRUE(bool(result.Surface())); } }