1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "FrameAnimator.h"
8 #include "mozilla/MemoryReporting.h"
9 #include "mozilla/Move.h"
10 #include "mozilla/CheckedInt.h"
11 #include "imgIContainer.h"
12 #include "LookupResult.h"
13 #include "MainThreadUtils.h"
14 #include "RasterImage.h"
26 ///////////////////////////////////////////////////////////////////////////////
27 // AnimationState implementation.
28 ///////////////////////////////////////////////////////////////////////////////
31 AnimationState::UpdateState(bool aAnimationFinished
,
33 const gfx::IntSize
& aSize
,
34 bool aAllowInvalidation
/* = true */)
37 SurfaceCache::Lookup(ImageKey(aImage
),
38 RasterSurfaceKey(aSize
,
39 DefaultSurfaceFlags(),
40 PlaybackType::eAnimated
));
42 return UpdateStateInternal(result
, aAnimationFinished
, aSize
, aAllowInvalidation
);
46 AnimationState::UpdateStateInternal(LookupResult
& aResult
,
47 bool aAnimationFinished
,
48 const gfx::IntSize
& aSize
,
49 bool aAllowInvalidation
/* = true */)
51 // Update mDiscarded and mIsCurrentlyDecoded.
52 if (aResult
.Type() == MatchType::NOT_FOUND
) {
53 // no frames, we've either been discarded, or never been decoded before.
54 mDiscarded
= mHasBeenDecoded
;
55 mIsCurrentlyDecoded
= false;
56 } else if (aResult
.Type() == MatchType::PENDING
) {
57 // no frames yet, but a decoder is or will be working on it.
59 mIsCurrentlyDecoded
= false;
60 mHasRequestedDecode
= true;
62 MOZ_ASSERT(aResult
.Type() == MatchType::EXACT
);
64 mHasRequestedDecode
= true;
66 // If mHasBeenDecoded is true then we know the true total frame count and
67 // we can use it to determine if we have all the frames now so we know if
68 // we are currently fully decoded.
69 // If mHasBeenDecoded is false then we'll get another UpdateState call
70 // when the decode finishes.
71 if (mHasBeenDecoded
) {
72 Maybe
<uint32_t> frameCount
= FrameCount();
73 MOZ_ASSERT(frameCount
.isSome());
74 mIsCurrentlyDecoded
= aResult
.Surface().IsFullyDecoded();
80 if (aAllowInvalidation
) {
81 // Update the value of mCompositedFrameInvalid.
82 if (mIsCurrentlyDecoded
|| aAnimationFinished
) {
83 // Animated images that have finished their animation (ie because it is a
84 // finite length animation) don't have RequestRefresh called on them, and so
85 // mCompositedFrameInvalid would never get cleared. We clear it here (and
86 // also in RasterImage::Decode when we create a decoder for an image that
87 // has finished animated so it can display sooner than waiting until the
88 // decode completes). We also do it if we are fully decoded. This is safe
89 // to do for images that aren't finished animating because before we paint
90 // the refresh driver will call into us to advance to the correct frame,
91 // and that will succeed because we have all the frames.
92 if (mCompositedFrameInvalid
) {
93 // Invalidate if we are marking the composited frame valid.
96 mCompositedFrameInvalid
= false;
97 } else if (aResult
.Type() == MatchType::NOT_FOUND
||
98 aResult
.Type() == MatchType::PENDING
) {
99 if (mHasRequestedDecode
) {
100 MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
101 mCompositedFrameInvalid
= true;
104 // Otherwise don't change the value of mCompositedFrameInvalid, it will be
105 // updated by RequestRefresh.
112 AnimationState::NotifyDecodeComplete()
114 mHasBeenDecoded
= true;
118 AnimationState::ResetAnimation()
120 mCurrentAnimationFrameIndex
= 0;
124 AnimationState::SetAnimationMode(uint16_t aAnimationMode
)
126 mAnimationMode
= aAnimationMode
;
130 AnimationState::UpdateKnownFrameCount(uint32_t aFrameCount
)
132 if (aFrameCount
<= mFrameCount
) {
133 // Nothing to do. Since we can redecode animated images, we may see the same
134 // sequence of updates replayed again, so seeing a smaller frame count than
135 // what we already know about doesn't indicate an error.
139 MOZ_ASSERT(!mHasBeenDecoded
, "Adding new frames after decoding is finished?");
140 MOZ_ASSERT(aFrameCount
<= mFrameCount
+ 1, "Skipped a frame?");
142 mFrameCount
= aFrameCount
;
146 AnimationState::FrameCount() const
148 return mHasBeenDecoded
? Some(mFrameCount
) : Nothing();
152 AnimationState::SetFirstFrameRefreshArea(const IntRect
& aRefreshArea
)
154 mFirstFrameRefreshArea
= aRefreshArea
;
158 AnimationState::InitAnimationFrameTimeIfNecessary()
160 if (mCurrentAnimationFrameTime
.IsNull()) {
161 mCurrentAnimationFrameTime
= TimeStamp::Now();
166 AnimationState::SetAnimationFrameTime(const TimeStamp
& aTime
)
168 mCurrentAnimationFrameTime
= aTime
;
172 AnimationState::GetCurrentAnimationFrameIndex() const
174 return mCurrentAnimationFrameIndex
;
178 AnimationState::LoopLength() const
180 // If we don't know the loop length yet, we have to treat it as infinite.
182 return FrameTimeout::Forever();
185 MOZ_ASSERT(mHasBeenDecoded
, "We know the loop length but decoding isn't done?");
187 // If we're not looping, a single loop time has no meaning.
188 if (mAnimationMode
!= imgIContainer::kNormalAnimMode
) {
189 return FrameTimeout::Forever();
196 ///////////////////////////////////////////////////////////////////////////////
197 // FrameAnimator implementation.
198 ///////////////////////////////////////////////////////////////////////////////
201 FrameAnimator::GetCurrentImgFrameEndTime(AnimationState
& aState
,
202 DrawableSurface
& aFrames
) const
204 TimeStamp currentFrameTime
= aState
.mCurrentAnimationFrameTime
;
205 Maybe
<FrameTimeout
> timeout
=
206 GetTimeoutForFrame(aState
, aFrames
, aState
.mCurrentAnimationFrameIndex
);
208 if (timeout
.isNothing()) {
209 MOZ_ASSERT(aState
.GetHasRequestedDecode() && !aState
.GetIsCurrentlyDecoded());
213 if (*timeout
== FrameTimeout::Forever()) {
214 // We need to return a sentinel value in this case, because our logic
215 // doesn't work correctly if we have an infinitely long timeout. We use one
216 // year in the future as the sentinel because it works with the loop in
217 // RequestRefresh() below.
218 // XXX(seth): It'd be preferable to make our logic work correctly with
219 // infinitely long timeouts.
220 return Some(TimeStamp::NowLoRes() +
221 TimeDuration::FromMilliseconds(31536000.0));
224 TimeDuration durationOfTimeout
=
225 TimeDuration::FromMilliseconds(double(timeout
->AsMilliseconds()));
226 TimeStamp currentFrameEndTime
= currentFrameTime
+ durationOfTimeout
;
228 return Some(currentFrameEndTime
);
232 FrameAnimator::AdvanceFrame(AnimationState
& aState
,
233 DrawableSurface
& aFrames
,
236 NS_ASSERTION(aTime
<= TimeStamp::Now(),
237 "Given time appears to be in the future");
238 AUTO_PROFILER_LABEL("FrameAnimator::AdvanceFrame", GRAPHICS
);
242 // Determine what the next frame is, taking into account looping.
243 uint32_t currentFrameIndex
= aState
.mCurrentAnimationFrameIndex
;
244 uint32_t nextFrameIndex
= currentFrameIndex
+ 1;
246 // Check if we're at the end of the loop. (FrameCount() returns Nothing() if
247 // we don't know the total count yet.)
248 if (aState
.FrameCount() == Some(nextFrameIndex
)) {
249 // If we are not looping forever, initialize the loop counter
250 if (aState
.mLoopRemainingCount
< 0 && aState
.LoopCount() >= 0) {
251 aState
.mLoopRemainingCount
= aState
.LoopCount();
254 // If animation mode is "loop once", or we're at end of loop counter,
255 // it's time to stop animating.
256 if (aState
.mAnimationMode
== imgIContainer::kLoopOnceAnimMode
||
257 aState
.mLoopRemainingCount
== 0) {
258 ret
.mAnimationFinished
= true;
263 if (aState
.mLoopRemainingCount
> 0) {
264 aState
.mLoopRemainingCount
--;
267 // If we're done, exit early.
268 if (ret
.mAnimationFinished
) {
273 if (nextFrameIndex
>= aState
.KnownFrameCount()) {
274 // We've already advanced to the last decoded frame, nothing more we can do.
275 // We're blocked by network/decoding from displaying the animation at the
276 // rate specified, so that means the frame we are displaying (the latest
277 // available) is the frame we want to be displaying at this time. So we
278 // update the current animation time. If we didn't update the current
279 // animation time then it could lag behind, which would indicate that we are
280 // behind in the animation and should try to catch up. When we are done
281 // decoding (and thus can loop around back to the start of the animation) we
282 // would then jump to a random point in the animation to try to catch up.
283 // But we were never behind in the animation.
284 aState
.mCurrentAnimationFrameTime
= aTime
;
288 // There can be frames in the surface cache with index >= KnownFrameCount()
289 // which GetRawFrame() can access because an async decoder has decoded them,
290 // but which AnimationState doesn't know about yet because we haven't received
291 // the appropriate notification on the main thread. Make sure we stay in sync
292 // with AnimationState.
293 MOZ_ASSERT(nextFrameIndex
< aState
.KnownFrameCount());
294 RawAccessFrameRef nextFrame
= GetRawFrame(aFrames
, nextFrameIndex
);
296 // We should always check to see if we have the next frame even if we have
297 // previously finished decoding. If we needed to redecode (e.g. due to a draw
298 // failure) we would have discarded all the old frames and may not yet have
300 if (!nextFrame
|| !nextFrame
->IsFinished()) {
301 // Uh oh, the frame we want to show is currently being decoded (partial).
302 // Similar to the above case, we could be blocked by network or decoding,
303 // and so we should advance our current time rather than risk jumping
304 // through the animation. We will wait until the next refresh driver tick
306 aState
.mCurrentAnimationFrameTime
= aTime
;
310 Maybe
<FrameTimeout
> nextFrameTimeout
= GetTimeoutForFrame(aState
, aFrames
, nextFrameIndex
);
311 // GetTimeoutForFrame can only return none if frame doesn't exist,
312 // but we just got it above.
313 MOZ_ASSERT(nextFrameTimeout
.isSome());
314 if (*nextFrameTimeout
== FrameTimeout::Forever()) {
315 ret
.mAnimationFinished
= true;
318 if (nextFrameIndex
== 0) {
319 ret
.mDirtyRect
= aState
.FirstFrameRefreshArea();
321 MOZ_ASSERT(nextFrameIndex
== currentFrameIndex
+ 1);
324 if (!DoBlend(aFrames
, &ret
.mDirtyRect
, currentFrameIndex
, nextFrameIndex
)) {
325 // something went wrong, move on to next
326 NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
327 nextFrame
->SetCompositingFailed(true);
328 Maybe
<TimeStamp
> currentFrameEndTime
= GetCurrentImgFrameEndTime(aState
, aFrames
);
329 MOZ_ASSERT(currentFrameEndTime
.isSome());
330 aState
.mCurrentAnimationFrameTime
= *currentFrameEndTime
;
331 aState
.mCurrentAnimationFrameIndex
= nextFrameIndex
;
332 aFrames
.Advance(nextFrameIndex
);
337 nextFrame
->SetCompositingFailed(false);
340 Maybe
<TimeStamp
> currentFrameEndTime
= GetCurrentImgFrameEndTime(aState
, aFrames
);
341 MOZ_ASSERT(currentFrameEndTime
.isSome());
342 aState
.mCurrentAnimationFrameTime
= *currentFrameEndTime
;
344 // If we can get closer to the current time by a multiple of the image's loop
345 // time, we should. We can only do this if we're done decoding; otherwise, we
346 // don't know the full loop length, and LoopLength() will have to return
347 // FrameTimeout::Forever(). We also skip this for images with a finite loop
348 // count if we have initialized mLoopRemainingCount (it only gets initialized
349 // after one full loop).
350 FrameTimeout loopTime
= aState
.LoopLength();
351 if (loopTime
!= FrameTimeout::Forever() &&
352 (aState
.LoopCount() < 0 || aState
.mLoopRemainingCount
>= 0)) {
353 TimeDuration delay
= aTime
- aState
.mCurrentAnimationFrameTime
;
354 if (delay
.ToMilliseconds() > loopTime
.AsMilliseconds()) {
355 // Explicitly use integer division to get the floor of the number of
357 uint64_t loops
= static_cast<uint64_t>(delay
.ToMilliseconds())
358 / loopTime
.AsMilliseconds();
360 // If we have a finite loop count limit the number of loops we advance.
361 if (aState
.mLoopRemainingCount
>= 0) {
362 MOZ_ASSERT(aState
.LoopCount() >= 0);
363 loops
= std::min(loops
, CheckedUint64(aState
.mLoopRemainingCount
).value());
366 aState
.mCurrentAnimationFrameTime
+=
367 TimeDuration::FromMilliseconds(loops
* loopTime
.AsMilliseconds());
369 if (aState
.mLoopRemainingCount
>= 0) {
370 MOZ_ASSERT(loops
<= CheckedUint64(aState
.mLoopRemainingCount
).value());
371 aState
.mLoopRemainingCount
-= CheckedInt32(loops
).value();
376 // Set currentAnimationFrameIndex at the last possible moment
377 aState
.mCurrentAnimationFrameIndex
= nextFrameIndex
;
378 aFrames
.Advance(nextFrameIndex
);
380 // If we're here, we successfully advanced the frame.
381 ret
.mFrameAdvanced
= true;
387 FrameAnimator::ResetAnimation(AnimationState
& aState
)
389 aState
.ResetAnimation();
391 // Our surface provider is synchronized to our state, so we need to reset its
392 // state as well, if we still have one.
393 LookupResult result
=
394 SurfaceCache::Lookup(ImageKey(mImage
),
395 RasterSurfaceKey(mSize
,
396 DefaultSurfaceFlags(),
397 PlaybackType::eAnimated
));
402 result
.Surface().Reset();
406 FrameAnimator::RequestRefresh(AnimationState
& aState
,
407 const TimeStamp
& aTime
,
408 bool aAnimationFinished
)
410 // By default, an empty RefreshResult.
413 if (aState
.IsDiscarded()) {
417 // Get the animation frames once now, and pass them down to callees because
418 // the surface could be discarded at anytime on a different thread. This is
419 // must easier to reason about then trying to write code that is safe to
420 // having the surface disappear at anytime.
421 LookupResult result
=
422 SurfaceCache::Lookup(ImageKey(mImage
),
423 RasterSurfaceKey(mSize
,
424 DefaultSurfaceFlags(),
425 PlaybackType::eAnimated
));
427 ret
.mDirtyRect
= aState
.UpdateStateInternal(result
, aAnimationFinished
, mSize
);
428 if (aState
.IsDiscarded() || !result
) {
429 if (!ret
.mDirtyRect
.IsEmpty()) {
430 ret
.mFrameAdvanced
= true;
435 // only advance the frame if the current time is greater than or
436 // equal to the current frame's end time.
437 Maybe
<TimeStamp
> currentFrameEndTime
=
438 GetCurrentImgFrameEndTime(aState
, result
.Surface());
439 if (currentFrameEndTime
.isNothing()) {
440 MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
441 MOZ_ASSERT(aState
.GetHasRequestedDecode() && !aState
.GetIsCurrentlyDecoded());
442 MOZ_ASSERT(aState
.mCompositedFrameInvalid
);
443 // Nothing we can do but wait for our previous current frame to be decoded
444 // again so we can determine what to do next.
448 while (*currentFrameEndTime
<= aTime
) {
449 TimeStamp oldFrameEndTime
= *currentFrameEndTime
;
451 RefreshResult frameRes
= AdvanceFrame(aState
, result
.Surface(), aTime
);
453 // Accumulate our result for returning to callers.
454 ret
.Accumulate(frameRes
);
456 currentFrameEndTime
= GetCurrentImgFrameEndTime(aState
, result
.Surface());
457 // AdvanceFrame can't advance to a frame that doesn't exist yet.
458 MOZ_ASSERT(currentFrameEndTime
.isSome());
460 // If we didn't advance a frame, and our frame end time didn't change,
461 // then we need to break out of this loop & wait for the frame(s)
462 // to finish downloading.
463 if (!frameRes
.mFrameAdvanced
&& (*currentFrameEndTime
== oldFrameEndTime
)) {
468 // Advanced to the correct frame, the composited frame is now valid to be drawn.
469 if (*currentFrameEndTime
> aTime
) {
470 aState
.mCompositedFrameInvalid
= false;
471 ret
.mDirtyRect
= IntRect(IntPoint(0,0), mSize
);
474 MOZ_ASSERT(!aState
.mIsCurrentlyDecoded
|| !aState
.mCompositedFrameInvalid
);
480 FrameAnimator::GetCompositedFrame(AnimationState
& aState
)
482 LookupResult result
=
483 SurfaceCache::Lookup(ImageKey(mImage
),
484 RasterSurfaceKey(mSize
,
485 DefaultSurfaceFlags(),
486 PlaybackType::eAnimated
));
488 if (aState
.mCompositedFrameInvalid
) {
489 MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
490 MOZ_ASSERT(aState
.GetHasRequestedDecode());
491 MOZ_ASSERT(!aState
.GetIsCurrentlyDecoded());
492 if (result
.Type() == MatchType::NOT_FOUND
) {
495 return LookupResult(MatchType::PENDING
);
498 // If we have a composited version of this frame, return that.
499 if (mLastCompositedFrameIndex
>= 0 &&
500 (uint32_t(mLastCompositedFrameIndex
) == aState
.mCurrentAnimationFrameIndex
)) {
501 return LookupResult(DrawableSurface(mCompositingFrame
->DrawableRef()),
505 // Otherwise return the raw frame. DoBlend is required to ensure that we only
506 // hit this case if the frame is not paletted and doesn't require compositing.
511 // Seek to the appropriate frame. If seeking fails, it means that we couldn't
512 // get the frame we're looking for; treat this as if the lookup failed.
513 if (NS_FAILED(result
.Surface().Seek(aState
.mCurrentAnimationFrameIndex
))) {
514 if (result
.Type() == MatchType::NOT_FOUND
) {
517 return LookupResult(MatchType::PENDING
);
520 MOZ_ASSERT(!result
.Surface()->GetIsPaletted(),
521 "About to return a paletted frame");
527 FrameAnimator::GetTimeoutForFrame(AnimationState
& aState
,
528 DrawableSurface
& aFrames
,
529 uint32_t aFrameNum
) const
531 RawAccessFrameRef frame
= GetRawFrame(aFrames
, aFrameNum
);
533 AnimationData data
= frame
->GetAnimationData();
534 return Some(data
.mTimeout
);
537 MOZ_ASSERT(aState
.mHasRequestedDecode
&& !aState
.mIsCurrentlyDecoded
);
542 DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef
& aSurface
,
543 SurfaceMemoryCounterType aType
,
544 nsTArray
<SurfaceMemoryCounter
>& aCounters
,
545 MallocSizeOf aMallocSizeOf
)
547 // Concoct a SurfaceKey for this surface.
548 SurfaceKey key
= RasterSurfaceKey(aSurface
->GetImageSize(),
549 DefaultSurfaceFlags(),
550 PlaybackType::eStatic
);
552 // Create a counter for this surface.
553 SurfaceMemoryCounter
counter(key
, /* aIsLocked = */ true,
554 /* aCannotSubstitute */ false,
555 /* aIsFactor2 */ false, aType
);
557 // Extract the surface's memory usage information.
558 size_t heap
= 0, nonHeap
= 0, handles
= 0;
559 aSurface
->AddSizeOfExcludingThis(aMallocSizeOf
, heap
, nonHeap
, handles
);
560 counter
.Values().SetDecodedHeap(heap
);
561 counter
.Values().SetDecodedNonHeap(nonHeap
);
562 counter
.Values().SetExternalHandles(handles
);
565 aCounters
.AppendElement(counter
);
569 FrameAnimator::CollectSizeOfCompositingSurfaces(
570 nsTArray
<SurfaceMemoryCounter
>& aCounters
,
571 MallocSizeOf aMallocSizeOf
) const
573 if (mCompositingFrame
) {
574 DoCollectSizeOfCompositingSurfaces(mCompositingFrame
,
575 SurfaceMemoryCounterType::COMPOSITING
,
580 if (mCompositingPrevFrame
) {
581 DoCollectSizeOfCompositingSurfaces(mCompositingPrevFrame
,
582 SurfaceMemoryCounterType::COMPOSITING_PREV
,
589 FrameAnimator::GetRawFrame(DrawableSurface
& aFrames
, uint32_t aFrameNum
) const
591 // Seek to the frame we want. If seeking fails, it means we couldn't get the
592 // frame we're looking for, so we bail here to avoid returning the wrong frame
594 if (NS_FAILED(aFrames
.Seek(aFrameNum
))) {
595 return RawAccessFrameRef(); // Not available yet.
598 return aFrames
->RawAccessRef();
601 //******************************************************************************
602 // DoBlend gets called when the timer for animation get fired and we have to
603 // update the composited frame of the animation.
605 FrameAnimator::DoBlend(DrawableSurface
& aFrames
,
607 uint32_t aPrevFrameIndex
,
608 uint32_t aNextFrameIndex
)
610 RawAccessFrameRef prevFrame
= GetRawFrame(aFrames
, aPrevFrameIndex
);
611 RawAccessFrameRef nextFrame
= GetRawFrame(aFrames
, aNextFrameIndex
);
613 MOZ_ASSERT(prevFrame
&& nextFrame
, "Should have frames here");
615 AnimationData prevFrameData
= prevFrame
->GetAnimationData();
616 if (prevFrameData
.mDisposalMethod
== DisposalMethod::RESTORE_PREVIOUS
&&
617 !mCompositingPrevFrame
) {
618 prevFrameData
.mDisposalMethod
= DisposalMethod::CLEAR
;
621 IntRect prevRect
= prevFrameData
.mBlendRect
622 ? prevFrameData
.mRect
.Intersect(*prevFrameData
.mBlendRect
)
623 : prevFrameData
.mRect
;
625 bool isFullPrevFrame
= prevRect
.IsEqualRect(0, 0, mSize
.width
, mSize
.height
);
627 // Optimization: DisposeClearAll if the previous frame is the same size as
628 // container and it's clearing itself
629 if (isFullPrevFrame
&&
630 (prevFrameData
.mDisposalMethod
== DisposalMethod::CLEAR
)) {
631 prevFrameData
.mDisposalMethod
= DisposalMethod::CLEAR_ALL
;
634 AnimationData nextFrameData
= nextFrame
->GetAnimationData();
636 IntRect nextRect
= nextFrameData
.mBlendRect
637 ? nextFrameData
.mRect
.Intersect(*nextFrameData
.mBlendRect
)
638 : nextFrameData
.mRect
;
640 bool isFullNextFrame
= nextRect
.IsEqualRect(0, 0, mSize
.width
, mSize
.height
);
642 if (!nextFrame
->GetIsPaletted()) {
643 // Optimization: Skip compositing if the previous frame wants to clear the
645 if (prevFrameData
.mDisposalMethod
== DisposalMethod::CLEAR_ALL
) {
646 aDirtyRect
->SetRect(0, 0, mSize
.width
, mSize
.height
);
650 // Optimization: Skip compositing if this frame is the same size as the
651 // container and it's fully drawing over prev frame (no alpha)
652 if (isFullNextFrame
&&
653 (nextFrameData
.mDisposalMethod
!= DisposalMethod::RESTORE_PREVIOUS
) &&
654 !nextFrameData
.mHasAlpha
) {
655 aDirtyRect
->SetRect(0, 0, mSize
.width
, mSize
.height
);
660 // Calculate area that needs updating
661 switch (prevFrameData
.mDisposalMethod
) {
663 MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
664 case DisposalMethod::NOT_SPECIFIED
:
665 case DisposalMethod::KEEP
:
666 *aDirtyRect
= nextRect
;
669 case DisposalMethod::CLEAR_ALL
:
670 // Whole image container is cleared
671 aDirtyRect
->SetRect(0, 0, mSize
.width
, mSize
.height
);
674 case DisposalMethod::CLEAR
:
675 // Calc area that needs to be redrawn (the combination of previous and
677 // XXX - This could be done with multiple framechanged calls
678 // Having prevFrame way at the top of the image, and nextFrame
679 // way at the bottom, and both frames being small, we'd be
680 // telling framechanged to refresh the whole image when only two
681 // small areas are needed.
682 aDirtyRect
->UnionRect(nextRect
, prevRect
);
685 case DisposalMethod::RESTORE_PREVIOUS
:
686 aDirtyRect
->SetRect(0, 0, mSize
.width
, mSize
.height
);
691 // Skip compositing if the last composited frame is this frame
692 // (Only one composited frame was made for this animation. Example:
693 // Only Frame 3 of a 10 frame image required us to build a composite frame
694 // On the second loop, we do not need to rebuild the frame
695 // since it's still sitting in compositingFrame)
696 if (mLastCompositedFrameIndex
== int32_t(aNextFrameIndex
)) {
700 bool needToBlankComposite
= false;
702 // Create the Compositing Frame
703 if (!mCompositingFrame
) {
704 RefPtr
<imgFrame
> newFrame
= new imgFrame
;
705 nsresult rv
= newFrame
->InitForAnimator(mSize
,
706 SurfaceFormat::B8G8R8A8
);
708 mCompositingFrame
.reset();
711 mCompositingFrame
= newFrame
->RawAccessRef();
712 needToBlankComposite
= true;
713 } else if (int32_t(aNextFrameIndex
) != mLastCompositedFrameIndex
+1) {
715 // If we are not drawing on top of last composited frame,
716 // then we are building a new composite frame, so let's clear it first.
717 needToBlankComposite
= true;
720 AnimationData compositingFrameData
= mCompositingFrame
->GetAnimationData();
722 // More optimizations possible when next frame is not transparent
723 // But if the next frame has DisposalMethod::RESTORE_PREVIOUS,
724 // this "no disposal" optimization is not possible,
725 // because the frame in "after disposal operation" state
726 // needs to be stored in compositingFrame, so it can be
727 // copied into compositingPrevFrame later.
728 bool doDisposal
= true;
729 if (!nextFrameData
.mHasAlpha
&&
730 nextFrameData
.mDisposalMethod
!= DisposalMethod::RESTORE_PREVIOUS
) {
731 if (isFullNextFrame
) {
732 // Optimization: No need to dispose prev.frame when
733 // next frame is full frame and not transparent.
735 // No need to blank the composite frame
736 needToBlankComposite
= false;
738 if ((prevRect
.X() >= nextRect
.X()) && (prevRect
.Y() >= nextRect
.Y()) &&
739 (prevRect
.XMost() <= nextRect
.XMost()) &&
740 (prevRect
.YMost() <= nextRect
.YMost())) {
741 // Optimization: No need to dispose prev.frame when
742 // next frame fully overlaps previous frame.
749 // Dispose of previous: clear, restore, or keep (copy)
750 switch (prevFrameData
.mDisposalMethod
) {
751 case DisposalMethod::CLEAR
:
752 if (needToBlankComposite
) {
753 // If we just created the composite, it could have anything in its
754 // buffer. Clear whole frame
755 ClearFrame(compositingFrameData
.mRawData
,
756 compositingFrameData
.mRect
);
758 // Only blank out previous frame area (both color & Mask/Alpha)
759 ClearFrame(compositingFrameData
.mRawData
,
760 compositingFrameData
.mRect
,
765 case DisposalMethod::CLEAR_ALL
:
766 ClearFrame(compositingFrameData
.mRawData
,
767 compositingFrameData
.mRect
);
770 case DisposalMethod::RESTORE_PREVIOUS
:
771 // It would be better to copy only the area changed back to
773 if (mCompositingPrevFrame
) {
774 AnimationData compositingPrevFrameData
=
775 mCompositingPrevFrame
->GetAnimationData();
777 CopyFrameImage(compositingPrevFrameData
.mRawData
,
778 compositingPrevFrameData
.mRect
,
779 compositingFrameData
.mRawData
,
780 compositingFrameData
.mRect
);
782 // destroy only if we don't need it for this frame's disposal
783 if (nextFrameData
.mDisposalMethod
!=
784 DisposalMethod::RESTORE_PREVIOUS
) {
785 mCompositingPrevFrame
.reset();
788 ClearFrame(compositingFrameData
.mRawData
,
789 compositingFrameData
.mRect
);
794 MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
795 case DisposalMethod::NOT_SPECIFIED
:
796 case DisposalMethod::KEEP
:
797 // Copy previous frame into compositingFrame before we put the new
799 // Assumes that the previous frame represents a full frame (it could be
800 // smaller in size than the container, as long as the frame before it
802 // Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1)
803 // will always be a valid frame number.
804 if (mLastCompositedFrameIndex
!= int32_t(aNextFrameIndex
- 1)) {
805 if (isFullPrevFrame
&& !prevFrame
->GetIsPaletted()) {
806 // Just copy the bits
807 CopyFrameImage(prevFrameData
.mRawData
,
809 compositingFrameData
.mRawData
,
810 compositingFrameData
.mRect
);
812 if (needToBlankComposite
) {
813 // Only blank composite when prev is transparent or not full.
814 if (prevFrameData
.mHasAlpha
|| !isFullPrevFrame
) {
815 ClearFrame(compositingFrameData
.mRawData
,
816 compositingFrameData
.mRect
);
819 DrawFrameTo(prevFrameData
.mRawData
, prevFrameData
.mRect
,
820 prevFrameData
.mPaletteDataLength
,
821 prevFrameData
.mHasAlpha
,
822 compositingFrameData
.mRawData
,
823 compositingFrameData
.mRect
,
824 prevFrameData
.mBlendMethod
,
825 prevFrameData
.mBlendRect
);
829 } else if (needToBlankComposite
) {
830 // If we just created the composite, it could have anything in its
831 // buffers. Clear them
832 ClearFrame(compositingFrameData
.mRawData
,
833 compositingFrameData
.mRect
);
836 // Check if the frame we are composing wants the previous image restored after
837 // it is done. Don't store it (again) if last frame wanted its image restored
839 if ((nextFrameData
.mDisposalMethod
== DisposalMethod::RESTORE_PREVIOUS
) &&
840 (prevFrameData
.mDisposalMethod
!= DisposalMethod::RESTORE_PREVIOUS
)) {
841 // We are storing the whole image.
842 // It would be better if we just stored the area that nextFrame is going to
844 if (!mCompositingPrevFrame
) {
845 RefPtr
<imgFrame
> newFrame
= new imgFrame
;
846 nsresult rv
= newFrame
->InitForAnimator(mSize
,
847 SurfaceFormat::B8G8R8A8
);
849 mCompositingPrevFrame
.reset();
853 mCompositingPrevFrame
= newFrame
->RawAccessRef();
856 AnimationData compositingPrevFrameData
=
857 mCompositingPrevFrame
->GetAnimationData();
859 CopyFrameImage(compositingFrameData
.mRawData
,
860 compositingFrameData
.mRect
,
861 compositingPrevFrameData
.mRawData
,
862 compositingPrevFrameData
.mRect
);
864 mCompositingPrevFrame
->Finish();
867 // blit next frame into it's correct spot
868 DrawFrameTo(nextFrameData
.mRawData
, nextFrameData
.mRect
,
869 nextFrameData
.mPaletteDataLength
,
870 nextFrameData
.mHasAlpha
,
871 compositingFrameData
.mRawData
,
872 compositingFrameData
.mRect
,
873 nextFrameData
.mBlendMethod
,
874 nextFrameData
.mBlendRect
);
876 // Tell the image that it is fully 'downloaded'.
877 mCompositingFrame
->Finish();
879 mLastCompositedFrameIndex
= int32_t(aNextFrameIndex
);
884 //******************************************************************************
885 // Fill aFrame with black. Does also clears the mask.
887 FrameAnimator::ClearFrame(uint8_t* aFrameData
, const IntRect
& aFrameRect
)
893 memset(aFrameData
, 0, aFrameRect
.Width() * aFrameRect
.Height() * 4);
896 //******************************************************************************
898 FrameAnimator::ClearFrame(uint8_t* aFrameData
, const IntRect
& aFrameRect
,
899 const IntRect
& aRectToClear
)
901 if (!aFrameData
|| aFrameRect
.Width() <= 0 || aFrameRect
.Height() <= 0 ||
902 aRectToClear
.Width() <= 0 || aRectToClear
.Height() <= 0) {
906 IntRect toClear
= aFrameRect
.Intersect(aRectToClear
);
907 if (toClear
.IsEmpty()) {
911 uint32_t bytesPerRow
= aFrameRect
.Width() * 4;
912 for (int row
= toClear
.Y(); row
< toClear
.YMost(); ++row
) {
913 memset(aFrameData
+ toClear
.X() * 4 + row
* bytesPerRow
, 0,
914 toClear
.Width() * 4);
918 //******************************************************************************
919 // Whether we succeed or fail will not cause a crash, and there's not much
920 // we can do about a failure, so there we don't return a nsresult
922 FrameAnimator::CopyFrameImage(const uint8_t* aDataSrc
,
923 const IntRect
& aRectSrc
,
925 const IntRect
& aRectDest
)
927 uint32_t dataLengthSrc
= aRectSrc
.Width() * aRectSrc
.Height() * 4;
928 uint32_t dataLengthDest
= aRectDest
.Width() * aRectDest
.Height() * 4;
930 if (!aDataDest
|| !aDataSrc
|| dataLengthSrc
!= dataLengthDest
) {
934 memcpy(aDataDest
, aDataSrc
, dataLengthDest
);
940 FrameAnimator::DrawFrameTo(const uint8_t* aSrcData
, const IntRect
& aSrcRect
,
941 uint32_t aSrcPaletteLength
, bool aSrcHasAlpha
,
942 uint8_t* aDstPixels
, const IntRect
& aDstRect
,
943 BlendMethod aBlendMethod
, const Maybe
<IntRect
>& aBlendRect
)
945 NS_ENSURE_ARG_POINTER(aSrcData
);
946 NS_ENSURE_ARG_POINTER(aDstPixels
);
948 // According to both AGIF and APNG specs, offsets are unsigned
949 if (aSrcRect
.X() < 0 || aSrcRect
.Y() < 0) {
950 NS_WARNING("FrameAnimator::DrawFrameTo: negative offsets not allowed");
951 return NS_ERROR_FAILURE
;
954 // Outside the destination frame, skip it
955 if ((aSrcRect
.X() > aDstRect
.Width()) || (aSrcRect
.Y() > aDstRect
.Height())) {
959 if (aSrcPaletteLength
) {
960 // Larger than the destination frame, clip it
961 int32_t width
= std::min(aSrcRect
.Width(), aDstRect
.Width() - aSrcRect
.X());
962 int32_t height
= std::min(aSrcRect
.Height(), aDstRect
.Height() - aSrcRect
.Y());
964 // The clipped image must now fully fit within destination image frame
965 NS_ASSERTION((aSrcRect
.X() >= 0) && (aSrcRect
.Y() >= 0) &&
966 (aSrcRect
.X() + width
<= aDstRect
.Width()) &&
967 (aSrcRect
.Y() + height
<= aDstRect
.Height()),
968 "FrameAnimator::DrawFrameTo: Invalid aSrcRect");
970 // clipped image size may be smaller than source, but not larger
971 NS_ASSERTION((width
<= aSrcRect
.Width()) && (height
<= aSrcRect
.Height()),
972 "FrameAnimator::DrawFrameTo: source must be smaller than dest");
974 // Get pointers to image data
975 const uint8_t* srcPixels
= aSrcData
+ aSrcPaletteLength
;
976 uint32_t* dstPixels
= reinterpret_cast<uint32_t*>(aDstPixels
);
977 const uint32_t* colormap
= reinterpret_cast<const uint32_t*>(aSrcData
);
979 // Skip to the right offset
980 dstPixels
+= aSrcRect
.X() + (aSrcRect
.Y() * aDstRect
.Width());
982 for (int32_t r
= height
; r
> 0; --r
) {
983 for (int32_t c
= 0; c
< width
; c
++) {
984 dstPixels
[c
] = colormap
[srcPixels
[c
]];
986 // Go to the next row in the source resp. destination image
987 srcPixels
+= aSrcRect
.Width();
988 dstPixels
+= aDstRect
.Width();
991 for (int32_t r
= height
; r
> 0; --r
) {
992 for (int32_t c
= 0; c
< width
; c
++) {
993 const uint32_t color
= colormap
[srcPixels
[c
]];
995 dstPixels
[c
] = color
;
998 // Go to the next row in the source resp. destination image
999 srcPixels
+= aSrcRect
.Width();
1000 dstPixels
+= aDstRect
.Width();
1004 pixman_image_t
* src
=
1005 pixman_image_create_bits(
1006 aSrcHasAlpha
? PIXMAN_a8r8g8b8
: PIXMAN_x8r8g8b8
,
1007 aSrcRect
.Width(), aSrcRect
.Height(),
1008 reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(aSrcData
)),
1009 aSrcRect
.Width() * 4);
1011 return NS_ERROR_OUT_OF_MEMORY
;
1013 pixman_image_t
* dst
=
1014 pixman_image_create_bits(PIXMAN_a8r8g8b8
,
1017 reinterpret_cast<uint32_t*>(aDstPixels
),
1018 aDstRect
.Width() * 4);
1020 pixman_image_unref(src
);
1021 return NS_ERROR_OUT_OF_MEMORY
;
1024 // XXX(seth): This is inefficient but we'll remove it quite soon when we
1025 // move frame compositing into SurfacePipe. For now we need this because
1026 // RemoveFrameRectFilter has transformed PNG frames with frame rects into
1027 // imgFrame's with no frame rects, but with a region of 0 alpha where the
1028 // frame rect should be. This works really nicely if we're using
1029 // BlendMethod::OVER, but BlendMethod::SOURCE will result in that frame rect
1030 // area overwriting the previous frame, which makes the animation look
1031 // wrong. This quick hack fixes that by first compositing the whle new frame
1032 // with BlendMethod::OVER, and then recopying the area that uses
1033 // BlendMethod::SOURCE if needed. To make this work, the decoder has to
1034 // provide a "blend rect" that tells us where to do this. This is just the
1035 // frame rect, but hidden in a way that makes it invisible to most of the
1036 // system, so we can keep eliminating dependencies on it.
1037 auto op
= aBlendMethod
== BlendMethod::SOURCE
? PIXMAN_OP_SRC
1040 if (aBlendMethod
== BlendMethod::OVER
|| !aBlendRect
||
1041 (aBlendMethod
== BlendMethod::SOURCE
&& aSrcRect
.IsEqualEdges(*aBlendRect
))) {
1042 // We don't need to do anything clever. (Or, in the case where no blend
1043 // rect was specified, we can't.)
1044 pixman_image_composite32(op
,
1050 aSrcRect
.X(), aSrcRect
.Y(),
1051 aSrcRect
.Width(), aSrcRect
.Height());
1053 // We need to do the OVER followed by SOURCE trick above.
1054 pixman_image_composite32(PIXMAN_OP_OVER
,
1060 aSrcRect
.X(), aSrcRect
.Y(),
1061 aSrcRect
.Width(), aSrcRect
.Height());
1062 pixman_image_composite32(PIXMAN_OP_SRC
,
1066 aBlendRect
->X(), aBlendRect
->Y(),
1068 aBlendRect
->X(), aBlendRect
->Y(),
1069 aBlendRect
->Width(), aBlendRect
->Height());
1072 pixman_image_unref(src
);
1073 pixman_image_unref(dst
);
1079 } // namespace image
1080 } // namespace mozilla