LookupResult FrameAnimator::GetCompositedFrame(AnimationState& aState) { // If we have a composited version of this frame, return that. if (mLastCompositedFrameIndex >= 0 && (uint32_t(mLastCompositedFrameIndex) == aState.mCurrentAnimationFrameIndex)) { return LookupResult(DrawableSurface(mCompositingFrame->DrawableRef()), MatchType::EXACT); } // Otherwise return the raw frame. DoBlend is required to ensure that we only // hit this case if the frame is not paletted and doesn't require compositing. LookupResult result = SurfaceCache::Lookup(ImageKey(mImage), RasterSurfaceKey(mSize, DefaultSurfaceFlags(), PlaybackType::eAnimated)); if (!result) { return result; } // Seek to the appropriate frame. If seeking fails, it means that we couldn't // get the frame we're looking for; treat this as if the lookup failed. if (NS_FAILED(result.Surface().Seek(aState.mCurrentAnimationFrameIndex))) { return LookupResult(MatchType::NOT_FOUND); } MOZ_ASSERT(!result.Surface()->GetIsPaletted(), "About to return a paletted frame"); return result; }
already_AddRefed<gfxDrawable> VectorImage::LookupCachedSurface(const SVGDrawingParameters& aParams) { // If we're not allowed to use a cached surface, don't attempt a lookup. if (aParams.flags & FLAG_BYPASS_SURFACE_CACHE) { return nullptr; } // We don't do any caching if we have animation, so don't bother with a lookup // in this case either. if (mHaveAnimations) { return nullptr; } LookupResult result = SurfaceCache::Lookup(ImageKey(this), VectorSurfaceKey(aParams.size, aParams.svgContext)); if (!result) { return nullptr; // No matching surface, or the OS freed the volatile buffer. } RefPtr<SourceSurface> sourceSurface = result.Surface()->GetSourceSurface(); if (!sourceSurface) { // Something went wrong. (Probably a GPU driver crash or device reset.) // Attempt to recover. RecoverFromLossOfSurfaces(); return nullptr; } RefPtr<gfxDrawable> svgDrawable = new gfxSurfaceDrawable(sourceSurface, result.Surface()->GetSize()); return svgDrawable.forget(); }
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; }
RawAccessFrameRef FrameAnimator::GetRawFrame(uint32_t aFrameNum) const { LookupResult result = SurfaceCache::Lookup(ImageKey(mImage), RasterSurfaceKey(mSize, DefaultSurfaceFlags(), PlaybackType::eAnimated)); if (!result) { return RawAccessFrameRef(); } // Seek to the frame we want. If seeking fails, it means we couldn't get the // frame we're looking for, so we bail here to avoid returning the wrong frame // to the caller. if (NS_FAILED(result.Surface().Seek(aFrameNum))) { return RawAccessFrameRef(); // Not available yet. } return result.Surface()->RawAccessRef(); }
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, 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 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())); }
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())); } }