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/. */
5 
6 #include "FrameAnimator.h"
7 
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"
15 #include "gfxPrefs.h"
16 
17 #include "pixman.h"
18 #include <algorithm>
19 
20 namespace mozilla {
21 
22 using namespace gfx;
23 
24 namespace image {
25 
26 ///////////////////////////////////////////////////////////////////////////////
27 // AnimationState implementation.
28 ///////////////////////////////////////////////////////////////////////////////
29 
UpdateState(bool aAnimationFinished,RasterImage * aImage,const gfx::IntSize & aSize,bool aAllowInvalidation)30 const gfx::IntRect AnimationState::UpdateState(
31     bool aAnimationFinished, RasterImage* aImage, const gfx::IntSize& aSize,
32     bool aAllowInvalidation /* = true */) {
33   LookupResult result = SurfaceCache::Lookup(
34       ImageKey(aImage),
35       RasterSurfaceKey(aSize, DefaultSurfaceFlags(), PlaybackType::eAnimated));
36 
37   return UpdateStateInternal(result, aAnimationFinished, aSize,
38                              aAllowInvalidation);
39 }
40 
UpdateStateInternal(LookupResult & aResult,bool aAnimationFinished,const gfx::IntSize & aSize,bool aAllowInvalidation)41 const gfx::IntRect AnimationState::UpdateStateInternal(
42     LookupResult& aResult, bool aAnimationFinished, const gfx::IntSize& aSize,
43     bool aAllowInvalidation /* = true */) {
44   // Update mDiscarded and mIsCurrentlyDecoded.
45   if (aResult.Type() == MatchType::NOT_FOUND) {
46     // no frames, we've either been discarded, or never been decoded before.
47     mDiscarded = mHasBeenDecoded;
48     mIsCurrentlyDecoded = false;
49   } else if (aResult.Type() == MatchType::PENDING) {
50     // no frames yet, but a decoder is or will be working on it.
51     mDiscarded = false;
52     mIsCurrentlyDecoded = false;
53     mHasRequestedDecode = true;
54   } else {
55     MOZ_ASSERT(aResult.Type() == MatchType::EXACT);
56     mDiscarded = false;
57     mHasRequestedDecode = true;
58 
59     // If mHasBeenDecoded is true then we know the true total frame count and
60     // we can use it to determine if we have all the frames now so we know if
61     // we are currently fully decoded.
62     // If mHasBeenDecoded is false then we'll get another UpdateState call
63     // when the decode finishes.
64     if (mHasBeenDecoded) {
65       Maybe<uint32_t> frameCount = FrameCount();
66       MOZ_ASSERT(frameCount.isSome());
67       mIsCurrentlyDecoded = aResult.Surface().IsFullyDecoded();
68     }
69   }
70 
71   gfx::IntRect ret;
72 
73   if (aAllowInvalidation) {
74     // Update the value of mCompositedFrameInvalid.
75     if (mIsCurrentlyDecoded || aAnimationFinished) {
76       // Animated images that have finished their animation (ie because it is a
77       // finite length animation) don't have RequestRefresh called on them, and
78       // so mCompositedFrameInvalid would never get cleared. We clear it here
79       // (and also in RasterImage::Decode when we create a decoder for an image
80       // that has finished animated so it can display sooner than waiting until
81       // the decode completes). We also do it if we are fully decoded. This is
82       // safe to do for images that aren't finished animating because before we
83       // paint the refresh driver will call into us to advance to the correct
84       // frame, and that will succeed because we have all the frames.
85       if (mCompositedFrameInvalid) {
86         // Invalidate if we are marking the composited frame valid.
87         ret.SizeTo(aSize);
88       }
89       mCompositedFrameInvalid = false;
90     } else if (aResult.Type() == MatchType::NOT_FOUND ||
91                aResult.Type() == MatchType::PENDING) {
92       if (mHasRequestedDecode) {
93         MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
94         mCompositedFrameInvalid = true;
95       }
96     }
97     // Otherwise don't change the value of mCompositedFrameInvalid, it will be
98     // updated by RequestRefresh.
99   }
100 
101   return ret;
102 }
103 
NotifyDecodeComplete()104 void AnimationState::NotifyDecodeComplete() { mHasBeenDecoded = true; }
105 
ResetAnimation()106 void AnimationState::ResetAnimation() { mCurrentAnimationFrameIndex = 0; }
107 
SetAnimationMode(uint16_t aAnimationMode)108 void AnimationState::SetAnimationMode(uint16_t aAnimationMode) {
109   mAnimationMode = aAnimationMode;
110 }
111 
UpdateKnownFrameCount(uint32_t aFrameCount)112 void AnimationState::UpdateKnownFrameCount(uint32_t aFrameCount) {
113   if (aFrameCount <= mFrameCount) {
114     // Nothing to do. Since we can redecode animated images, we may see the same
115     // sequence of updates replayed again, so seeing a smaller frame count than
116     // what we already know about doesn't indicate an error.
117     return;
118   }
119 
120   MOZ_ASSERT(!mHasBeenDecoded, "Adding new frames after decoding is finished?");
121   MOZ_ASSERT(aFrameCount <= mFrameCount + 1, "Skipped a frame?");
122 
123   mFrameCount = aFrameCount;
124 }
125 
FrameCount() const126 Maybe<uint32_t> AnimationState::FrameCount() const {
127   return mHasBeenDecoded ? Some(mFrameCount) : Nothing();
128 }
129 
SetFirstFrameRefreshArea(const IntRect & aRefreshArea)130 void AnimationState::SetFirstFrameRefreshArea(const IntRect& aRefreshArea) {
131   mFirstFrameRefreshArea = aRefreshArea;
132 }
133 
InitAnimationFrameTimeIfNecessary()134 void AnimationState::InitAnimationFrameTimeIfNecessary() {
135   if (mCurrentAnimationFrameTime.IsNull()) {
136     mCurrentAnimationFrameTime = TimeStamp::Now();
137   }
138 }
139 
SetAnimationFrameTime(const TimeStamp & aTime)140 void AnimationState::SetAnimationFrameTime(const TimeStamp& aTime) {
141   mCurrentAnimationFrameTime = aTime;
142 }
143 
GetCurrentAnimationFrameIndex() const144 uint32_t AnimationState::GetCurrentAnimationFrameIndex() const {
145   return mCurrentAnimationFrameIndex;
146 }
147 
LoopLength() const148 FrameTimeout AnimationState::LoopLength() const {
149   // If we don't know the loop length yet, we have to treat it as infinite.
150   if (!mLoopLength) {
151     return FrameTimeout::Forever();
152   }
153 
154   MOZ_ASSERT(mHasBeenDecoded,
155              "We know the loop length but decoding isn't done?");
156 
157   // If we're not looping, a single loop time has no meaning.
158   if (mAnimationMode != imgIContainer::kNormalAnimMode) {
159     return FrameTimeout::Forever();
160   }
161 
162   return *mLoopLength;
163 }
164 
165 ///////////////////////////////////////////////////////////////////////////////
166 // FrameAnimator implementation.
167 ///////////////////////////////////////////////////////////////////////////////
168 
GetCurrentImgFrameEndTime(AnimationState & aState,DrawableSurface & aFrames) const169 Maybe<TimeStamp> FrameAnimator::GetCurrentImgFrameEndTime(
170     AnimationState& aState, DrawableSurface& aFrames) const {
171   TimeStamp currentFrameTime = aState.mCurrentAnimationFrameTime;
172   Maybe<FrameTimeout> timeout =
173       GetTimeoutForFrame(aState, aFrames, aState.mCurrentAnimationFrameIndex);
174 
175   if (timeout.isNothing()) {
176     MOZ_ASSERT(aState.GetHasRequestedDecode() &&
177                !aState.GetIsCurrentlyDecoded());
178     return Nothing();
179   }
180 
181   if (*timeout == FrameTimeout::Forever()) {
182     // We need to return a sentinel value in this case, because our logic
183     // doesn't work correctly if we have an infinitely long timeout. We use one
184     // year in the future as the sentinel because it works with the loop in
185     // RequestRefresh() below.
186     // XXX(seth): It'd be preferable to make our logic work correctly with
187     // infinitely long timeouts.
188     return Some(TimeStamp::NowLoRes() +
189                 TimeDuration::FromMilliseconds(31536000.0));
190   }
191 
192   TimeDuration durationOfTimeout =
193       TimeDuration::FromMilliseconds(double(timeout->AsMilliseconds()));
194   TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
195 
196   return Some(currentFrameEndTime);
197 }
198 
AdvanceFrame(AnimationState & aState,DrawableSurface & aFrames,TimeStamp aTime)199 RefreshResult FrameAnimator::AdvanceFrame(AnimationState& aState,
200                                           DrawableSurface& aFrames,
201                                           TimeStamp aTime) {
202   NS_ASSERTION(aTime <= TimeStamp::Now(),
203                "Given time appears to be in the future");
204   AUTO_PROFILER_LABEL("FrameAnimator::AdvanceFrame", GRAPHICS);
205 
206   RefreshResult ret;
207 
208   // Determine what the next frame is, taking into account looping.
209   uint32_t currentFrameIndex = aState.mCurrentAnimationFrameIndex;
210   uint32_t nextFrameIndex = currentFrameIndex + 1;
211 
212   // Check if we're at the end of the loop. (FrameCount() returns Nothing() if
213   // we don't know the total count yet.)
214   if (aState.FrameCount() == Some(nextFrameIndex)) {
215     // If we are not looping forever, initialize the loop counter
216     if (aState.mLoopRemainingCount < 0 && aState.LoopCount() >= 0) {
217       aState.mLoopRemainingCount = aState.LoopCount();
218     }
219 
220     // If animation mode is "loop once", or we're at end of loop counter,
221     // it's time to stop animating.
222     if (aState.mAnimationMode == imgIContainer::kLoopOnceAnimMode ||
223         aState.mLoopRemainingCount == 0) {
224       ret.mAnimationFinished = true;
225     }
226 
227     nextFrameIndex = 0;
228 
229     if (aState.mLoopRemainingCount > 0) {
230       aState.mLoopRemainingCount--;
231     }
232 
233     // If we're done, exit early.
234     if (ret.mAnimationFinished) {
235       return ret;
236     }
237   }
238 
239   if (nextFrameIndex >= aState.KnownFrameCount()) {
240     // We've already advanced to the last decoded frame, nothing more we can do.
241     // We're blocked by network/decoding from displaying the animation at the
242     // rate specified, so that means the frame we are displaying (the latest
243     // available) is the frame we want to be displaying at this time. So we
244     // update the current animation time. If we didn't update the current
245     // animation time then it could lag behind, which would indicate that we are
246     // behind in the animation and should try to catch up. When we are done
247     // decoding (and thus can loop around back to the start of the animation) we
248     // would then jump to a random point in the animation to try to catch up.
249     // But we were never behind in the animation.
250     aState.mCurrentAnimationFrameTime = aTime;
251     return ret;
252   }
253 
254   // There can be frames in the surface cache with index >= KnownFrameCount()
255   // which GetRawFrame() can access because an async decoder has decoded them,
256   // but which AnimationState doesn't know about yet because we haven't received
257   // the appropriate notification on the main thread. Make sure we stay in sync
258   // with AnimationState.
259   MOZ_ASSERT(nextFrameIndex < aState.KnownFrameCount());
260   RawAccessFrameRef nextFrame = GetRawFrame(aFrames, nextFrameIndex);
261 
262   // We should always check to see if we have the next frame even if we have
263   // previously finished decoding. If we needed to redecode (e.g. due to a draw
264   // failure) we would have discarded all the old frames and may not yet have
265   // the new ones.
266   if (!nextFrame || !nextFrame->IsFinished()) {
267     // Uh oh, the frame we want to show is currently being decoded (partial).
268     // Similar to the above case, we could be blocked by network or decoding,
269     // and so we should advance our current time rather than risk jumping
270     // through the animation. We will wait until the next refresh driver tick
271     // and try again.
272     aState.mCurrentAnimationFrameTime = aTime;
273     return ret;
274   }
275 
276   Maybe<FrameTimeout> nextFrameTimeout =
277       GetTimeoutForFrame(aState, aFrames, nextFrameIndex);
278   // GetTimeoutForFrame can only return none if frame doesn't exist,
279   // but we just got it above.
280   MOZ_ASSERT(nextFrameTimeout.isSome());
281   if (*nextFrameTimeout == FrameTimeout::Forever()) {
282     ret.mAnimationFinished = true;
283   }
284 
285   if (nextFrameIndex == 0) {
286     ret.mDirtyRect = aState.FirstFrameRefreshArea();
287   } else {
288     MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1);
289 
290     // Change frame
291     if (!DoBlend(aFrames, &ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) {
292       // something went wrong, move on to next
293       NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
294       nextFrame->SetCompositingFailed(true);
295       Maybe<TimeStamp> currentFrameEndTime =
296           GetCurrentImgFrameEndTime(aState, aFrames);
297       MOZ_ASSERT(currentFrameEndTime.isSome());
298       aState.mCurrentAnimationFrameTime = *currentFrameEndTime;
299       aState.mCurrentAnimationFrameIndex = nextFrameIndex;
300       aFrames.Advance(nextFrameIndex);
301 
302       return ret;
303     }
304 
305     nextFrame->SetCompositingFailed(false);
306   }
307 
308   Maybe<TimeStamp> currentFrameEndTime =
309       GetCurrentImgFrameEndTime(aState, aFrames);
310   MOZ_ASSERT(currentFrameEndTime.isSome());
311   aState.mCurrentAnimationFrameTime = *currentFrameEndTime;
312 
313   // If we can get closer to the current time by a multiple of the image's loop
314   // time, we should. We can only do this if we're done decoding; otherwise, we
315   // don't know the full loop length, and LoopLength() will have to return
316   // FrameTimeout::Forever(). We also skip this for images with a finite loop
317   // count if we have initialized mLoopRemainingCount (it only gets initialized
318   // after one full loop).
319   FrameTimeout loopTime = aState.LoopLength();
320   if (loopTime != FrameTimeout::Forever() &&
321       (aState.LoopCount() < 0 || aState.mLoopRemainingCount >= 0)) {
322     TimeDuration delay = aTime - aState.mCurrentAnimationFrameTime;
323     if (delay.ToMilliseconds() > loopTime.AsMilliseconds()) {
324       // Explicitly use integer division to get the floor of the number of
325       // loops.
326       uint64_t loops = static_cast<uint64_t>(delay.ToMilliseconds()) /
327                        loopTime.AsMilliseconds();
328 
329       // If we have a finite loop count limit the number of loops we advance.
330       if (aState.mLoopRemainingCount >= 0) {
331         MOZ_ASSERT(aState.LoopCount() >= 0);
332         loops =
333             std::min(loops, CheckedUint64(aState.mLoopRemainingCount).value());
334       }
335 
336       aState.mCurrentAnimationFrameTime +=
337           TimeDuration::FromMilliseconds(loops * loopTime.AsMilliseconds());
338 
339       if (aState.mLoopRemainingCount >= 0) {
340         MOZ_ASSERT(loops <= CheckedUint64(aState.mLoopRemainingCount).value());
341         aState.mLoopRemainingCount -= CheckedInt32(loops).value();
342       }
343     }
344   }
345 
346   // Set currentAnimationFrameIndex at the last possible moment
347   aState.mCurrentAnimationFrameIndex = nextFrameIndex;
348   aFrames.Advance(nextFrameIndex);
349 
350   // If we're here, we successfully advanced the frame.
351   ret.mFrameAdvanced = true;
352 
353   return ret;
354 }
355 
ResetAnimation(AnimationState & aState)356 void FrameAnimator::ResetAnimation(AnimationState& aState) {
357   aState.ResetAnimation();
358 
359   // Our surface provider is synchronized to our state, so we need to reset its
360   // state as well, if we still have one.
361   LookupResult result = SurfaceCache::Lookup(
362       ImageKey(mImage),
363       RasterSurfaceKey(mSize, DefaultSurfaceFlags(), PlaybackType::eAnimated));
364   if (!result) {
365     return;
366   }
367 
368   result.Surface().Reset();
369 }
370 
RequestRefresh(AnimationState & aState,const TimeStamp & aTime,bool aAnimationFinished)371 RefreshResult FrameAnimator::RequestRefresh(AnimationState& aState,
372                                             const TimeStamp& aTime,
373                                             bool aAnimationFinished) {
374   // By default, an empty RefreshResult.
375   RefreshResult ret;
376 
377   if (aState.IsDiscarded()) {
378     return ret;
379   }
380 
381   // Get the animation frames once now, and pass them down to callees because
382   // the surface could be discarded at anytime on a different thread. This is
383   // must easier to reason about then trying to write code that is safe to
384   // having the surface disappear at anytime.
385   LookupResult result = SurfaceCache::Lookup(
386       ImageKey(mImage),
387       RasterSurfaceKey(mSize, DefaultSurfaceFlags(), PlaybackType::eAnimated));
388 
389   ret.mDirtyRect =
390       aState.UpdateStateInternal(result, aAnimationFinished, mSize);
391   if (aState.IsDiscarded() || !result) {
392     if (!ret.mDirtyRect.IsEmpty()) {
393       ret.mFrameAdvanced = true;
394     }
395     return ret;
396   }
397 
398   // only advance the frame if the current time is greater than or
399   // equal to the current frame's end time.
400   Maybe<TimeStamp> currentFrameEndTime =
401       GetCurrentImgFrameEndTime(aState, result.Surface());
402   if (currentFrameEndTime.isNothing()) {
403     MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
404     MOZ_ASSERT(aState.GetHasRequestedDecode() &&
405                !aState.GetIsCurrentlyDecoded());
406     MOZ_ASSERT(aState.mCompositedFrameInvalid);
407     // Nothing we can do but wait for our previous current frame to be decoded
408     // again so we can determine what to do next.
409     return ret;
410   }
411 
412   while (*currentFrameEndTime <= aTime) {
413     TimeStamp oldFrameEndTime = *currentFrameEndTime;
414 
415     RefreshResult frameRes = AdvanceFrame(aState, result.Surface(), aTime);
416 
417     // Accumulate our result for returning to callers.
418     ret.Accumulate(frameRes);
419 
420     currentFrameEndTime = GetCurrentImgFrameEndTime(aState, result.Surface());
421     // AdvanceFrame can't advance to a frame that doesn't exist yet.
422     MOZ_ASSERT(currentFrameEndTime.isSome());
423 
424     // If we didn't advance a frame, and our frame end time didn't change,
425     // then we need to break out of this loop & wait for the frame(s)
426     // to finish downloading.
427     if (!frameRes.mFrameAdvanced && (*currentFrameEndTime == oldFrameEndTime)) {
428       break;
429     }
430   }
431 
432   // Advanced to the correct frame, the composited frame is now valid to be
433   // drawn.
434   if (*currentFrameEndTime > aTime) {
435     aState.mCompositedFrameInvalid = false;
436     ret.mDirtyRect = IntRect(IntPoint(0, 0), mSize);
437   }
438 
439   MOZ_ASSERT(!aState.mIsCurrentlyDecoded || !aState.mCompositedFrameInvalid);
440 
441   return ret;
442 }
443 
GetCompositedFrame(AnimationState & aState)444 LookupResult FrameAnimator::GetCompositedFrame(AnimationState& aState) {
445   LookupResult result = SurfaceCache::Lookup(
446       ImageKey(mImage),
447       RasterSurfaceKey(mSize, DefaultSurfaceFlags(), PlaybackType::eAnimated));
448 
449   if (aState.mCompositedFrameInvalid) {
450     MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
451     MOZ_ASSERT(aState.GetHasRequestedDecode());
452     MOZ_ASSERT(!aState.GetIsCurrentlyDecoded());
453     if (result.Type() == MatchType::NOT_FOUND) {
454       return result;
455     }
456     return LookupResult(MatchType::PENDING);
457   }
458 
459   // If we have a composited version of this frame, return that.
460   if (mLastCompositedFrameIndex >= 0 && (uint32_t(mLastCompositedFrameIndex) ==
461                                          aState.mCurrentAnimationFrameIndex)) {
462     return LookupResult(DrawableSurface(mCompositingFrame->DrawableRef()),
463                         MatchType::EXACT);
464   }
465 
466   // Otherwise return the raw frame. DoBlend is required to ensure that we only
467   // hit this case if the frame is not paletted and doesn't require compositing.
468   if (!result) {
469     return result;
470   }
471 
472   // Seek to the appropriate frame. If seeking fails, it means that we couldn't
473   // get the frame we're looking for; treat this as if the lookup failed.
474   if (NS_FAILED(result.Surface().Seek(aState.mCurrentAnimationFrameIndex))) {
475     if (result.Type() == MatchType::NOT_FOUND) {
476       return result;
477     }
478     return LookupResult(MatchType::PENDING);
479   }
480 
481   MOZ_ASSERT(!result.Surface()->GetIsPaletted(),
482              "About to return a paletted frame");
483 
484   return result;
485 }
486 
GetTimeoutForFrame(AnimationState & aState,DrawableSurface & aFrames,uint32_t aFrameNum) const487 Maybe<FrameTimeout> FrameAnimator::GetTimeoutForFrame(
488     AnimationState& aState, DrawableSurface& aFrames,
489     uint32_t aFrameNum) const {
490   RawAccessFrameRef frame = GetRawFrame(aFrames, aFrameNum);
491   if (frame) {
492     AnimationData data = frame->GetAnimationData();
493     return Some(data.mTimeout);
494   }
495 
496   MOZ_ASSERT(aState.mHasRequestedDecode && !aState.mIsCurrentlyDecoded);
497   return Nothing();
498 }
499 
DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef & aSurface,SurfaceMemoryCounterType aType,nsTArray<SurfaceMemoryCounter> & aCounters,MallocSizeOf aMallocSizeOf)500 static void DoCollectSizeOfCompositingSurfaces(
501     const RawAccessFrameRef& aSurface, SurfaceMemoryCounterType aType,
502     nsTArray<SurfaceMemoryCounter>& aCounters, MallocSizeOf aMallocSizeOf) {
503   // Concoct a SurfaceKey for this surface.
504   SurfaceKey key = RasterSurfaceKey(
505       aSurface->GetImageSize(), DefaultSurfaceFlags(), PlaybackType::eStatic);
506 
507   // Create a counter for this surface.
508   SurfaceMemoryCounter counter(key, /* aIsLocked = */ true,
509                                /* aCannotSubstitute */ false,
510                                /* aIsFactor2 */ false, aType);
511 
512   // Extract the surface's memory usage information.
513   size_t heap = 0, nonHeap = 0, handles = 0;
514   aSurface->AddSizeOfExcludingThis(aMallocSizeOf, heap, nonHeap, handles);
515   counter.Values().SetDecodedHeap(heap);
516   counter.Values().SetDecodedNonHeap(nonHeap);
517   counter.Values().SetExternalHandles(handles);
518 
519   // Record it.
520   aCounters.AppendElement(counter);
521 }
522 
CollectSizeOfCompositingSurfaces(nsTArray<SurfaceMemoryCounter> & aCounters,MallocSizeOf aMallocSizeOf) const523 void FrameAnimator::CollectSizeOfCompositingSurfaces(
524     nsTArray<SurfaceMemoryCounter>& aCounters,
525     MallocSizeOf aMallocSizeOf) const {
526   if (mCompositingFrame) {
527     DoCollectSizeOfCompositingSurfaces(mCompositingFrame,
528                                        SurfaceMemoryCounterType::COMPOSITING,
529                                        aCounters, aMallocSizeOf);
530   }
531 
532   if (mCompositingPrevFrame) {
533     DoCollectSizeOfCompositingSurfaces(
534         mCompositingPrevFrame, SurfaceMemoryCounterType::COMPOSITING_PREV,
535         aCounters, aMallocSizeOf);
536   }
537 }
538 
GetRawFrame(DrawableSurface & aFrames,uint32_t aFrameNum) const539 RawAccessFrameRef FrameAnimator::GetRawFrame(DrawableSurface& aFrames,
540                                              uint32_t aFrameNum) const {
541   // Seek to the frame we want. If seeking fails, it means we couldn't get the
542   // frame we're looking for, so we bail here to avoid returning the wrong frame
543   // to the caller.
544   if (NS_FAILED(aFrames.Seek(aFrameNum))) {
545     return RawAccessFrameRef();  // Not available yet.
546   }
547 
548   return aFrames->RawAccessRef();
549 }
550 
551 //******************************************************************************
552 // DoBlend gets called when the timer for animation get fired and we have to
553 // update the composited frame of the animation.
DoBlend(DrawableSurface & aFrames,IntRect * aDirtyRect,uint32_t aPrevFrameIndex,uint32_t aNextFrameIndex)554 bool FrameAnimator::DoBlend(DrawableSurface& aFrames, IntRect* aDirtyRect,
555                             uint32_t aPrevFrameIndex,
556                             uint32_t aNextFrameIndex) {
557   RawAccessFrameRef prevFrame = GetRawFrame(aFrames, aPrevFrameIndex);
558   RawAccessFrameRef nextFrame = GetRawFrame(aFrames, aNextFrameIndex);
559 
560   MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");
561 
562   AnimationData prevFrameData = prevFrame->GetAnimationData();
563   if (prevFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS &&
564       !mCompositingPrevFrame) {
565     prevFrameData.mDisposalMethod = DisposalMethod::CLEAR;
566   }
567 
568   IntRect prevRect =
569       prevFrameData.mBlendRect
570           ? prevFrameData.mRect.Intersect(*prevFrameData.mBlendRect)
571           : prevFrameData.mRect;
572 
573   bool isFullPrevFrame = prevRect.IsEqualRect(0, 0, mSize.width, mSize.height);
574 
575   // Optimization: DisposeClearAll if the previous frame is the same size as
576   //               container and it's clearing itself
577   if (isFullPrevFrame &&
578       (prevFrameData.mDisposalMethod == DisposalMethod::CLEAR)) {
579     prevFrameData.mDisposalMethod = DisposalMethod::CLEAR_ALL;
580   }
581 
582   AnimationData nextFrameData = nextFrame->GetAnimationData();
583 
584   IntRect nextRect =
585       nextFrameData.mBlendRect
586           ? nextFrameData.mRect.Intersect(*nextFrameData.mBlendRect)
587           : nextFrameData.mRect;
588 
589   bool isFullNextFrame = nextRect.IsEqualRect(0, 0, mSize.width, mSize.height);
590 
591   if (!nextFrame->GetIsPaletted()) {
592     // Optimization: Skip compositing if the previous frame wants to clear the
593     //               whole image
594     if (prevFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL) {
595       aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
596       return true;
597     }
598 
599     // Optimization: Skip compositing if this frame is the same size as the
600     //               container and it's fully drawing over prev frame (no alpha)
601     if (isFullNextFrame &&
602         (nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) &&
603         !nextFrameData.mHasAlpha) {
604       aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
605       return true;
606     }
607   }
608 
609   // Calculate area that needs updating
610   switch (prevFrameData.mDisposalMethod) {
611     default:
612       MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
613     case DisposalMethod::NOT_SPECIFIED:
614     case DisposalMethod::KEEP:
615       *aDirtyRect = nextRect;
616       break;
617 
618     case DisposalMethod::CLEAR_ALL:
619       // Whole image container is cleared
620       aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
621       break;
622 
623     case DisposalMethod::CLEAR:
624       // Calc area that needs to be redrawn (the combination of previous and
625       // this frame)
626       // XXX - This could be done with multiple framechanged calls
627       //       Having prevFrame way at the top of the image, and nextFrame
628       //       way at the bottom, and both frames being small, we'd be
629       //       telling framechanged to refresh the whole image when only two
630       //       small areas are needed.
631       aDirtyRect->UnionRect(nextRect, prevRect);
632       break;
633 
634     case DisposalMethod::RESTORE_PREVIOUS:
635       aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
636       break;
637   }
638 
639   // Optimization:
640   //   Skip compositing if the last composited frame is this frame
641   //   (Only one composited frame was made for this animation.  Example:
642   //    Only Frame 3 of a 10 frame image required us to build a composite frame
643   //    On the second loop, we do not need to rebuild the frame
644   //    since it's still sitting in compositingFrame)
645   if (mLastCompositedFrameIndex == int32_t(aNextFrameIndex)) {
646     return true;
647   }
648 
649   bool needToBlankComposite = false;
650 
651   // Create the Compositing Frame
652   if (!mCompositingFrame) {
653     RefPtr<imgFrame> newFrame = new imgFrame;
654     nsresult rv = newFrame->InitForAnimator(mSize, SurfaceFormat::B8G8R8A8);
655     if (NS_FAILED(rv)) {
656       mCompositingFrame.reset();
657       return false;
658     }
659     mCompositingFrame = newFrame->RawAccessRef();
660     needToBlankComposite = true;
661   } else if (int32_t(aNextFrameIndex) != mLastCompositedFrameIndex + 1) {
662     // If we are not drawing on top of last composited frame,
663     // then we are building a new composite frame, so let's clear it first.
664     needToBlankComposite = true;
665   }
666 
667   AnimationData compositingFrameData = mCompositingFrame->GetAnimationData();
668 
669   // More optimizations possible when next frame is not transparent
670   // But if the next frame has DisposalMethod::RESTORE_PREVIOUS,
671   // this "no disposal" optimization is not possible,
672   // because the frame in "after disposal operation" state
673   // needs to be stored in compositingFrame, so it can be
674   // copied into compositingPrevFrame later.
675   bool doDisposal = true;
676   if (!nextFrameData.mHasAlpha &&
677       nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
678     if (isFullNextFrame) {
679       // Optimization: No need to dispose prev.frame when
680       // next frame is full frame and not transparent.
681       doDisposal = false;
682       // No need to blank the composite frame
683       needToBlankComposite = false;
684     } else {
685       if ((prevRect.X() >= nextRect.X()) && (prevRect.Y() >= nextRect.Y()) &&
686           (prevRect.XMost() <= nextRect.XMost()) &&
687           (prevRect.YMost() <= nextRect.YMost())) {
688         // Optimization: No need to dispose prev.frame when
689         // next frame fully overlaps previous frame.
690         doDisposal = false;
691       }
692     }
693   }
694 
695   if (doDisposal) {
696     // Dispose of previous: clear, restore, or keep (copy)
697     switch (prevFrameData.mDisposalMethod) {
698       case DisposalMethod::CLEAR:
699         if (needToBlankComposite) {
700           // If we just created the composite, it could have anything in its
701           // buffer. Clear whole frame
702           ClearFrame(compositingFrameData.mRawData, compositingFrameData.mRect);
703         } else {
704           // Only blank out previous frame area (both color & Mask/Alpha)
705           ClearFrame(compositingFrameData.mRawData, compositingFrameData.mRect,
706                      prevRect);
707         }
708         break;
709 
710       case DisposalMethod::CLEAR_ALL:
711         ClearFrame(compositingFrameData.mRawData, compositingFrameData.mRect);
712         break;
713 
714       case DisposalMethod::RESTORE_PREVIOUS:
715         // It would be better to copy only the area changed back to
716         // compositingFrame.
717         if (mCompositingPrevFrame) {
718           AnimationData compositingPrevFrameData =
719               mCompositingPrevFrame->GetAnimationData();
720 
721           CopyFrameImage(
722               compositingPrevFrameData.mRawData, compositingPrevFrameData.mRect,
723               compositingFrameData.mRawData, compositingFrameData.mRect);
724 
725           // destroy only if we don't need it for this frame's disposal
726           if (nextFrameData.mDisposalMethod !=
727               DisposalMethod::RESTORE_PREVIOUS) {
728             mCompositingPrevFrame.reset();
729           }
730         } else {
731           ClearFrame(compositingFrameData.mRawData, compositingFrameData.mRect);
732         }
733         break;
734 
735       default:
736         MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
737       case DisposalMethod::NOT_SPECIFIED:
738       case DisposalMethod::KEEP:
739         // Copy previous frame into compositingFrame before we put the new
740         // frame on top
741         // Assumes that the previous frame represents a full frame (it could be
742         // smaller in size than the container, as long as the frame before it
743         // erased itself)
744         // Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1)
745         // will always be a valid frame number.
746         if (mLastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
747           if (isFullPrevFrame && !prevFrame->GetIsPaletted()) {
748             // Just copy the bits
749             CopyFrameImage(prevFrameData.mRawData, prevRect,
750                            compositingFrameData.mRawData,
751                            compositingFrameData.mRect);
752           } else {
753             if (needToBlankComposite) {
754               // Only blank composite when prev is transparent or not full.
755               if (prevFrameData.mHasAlpha || !isFullPrevFrame) {
756                 ClearFrame(compositingFrameData.mRawData,
757                            compositingFrameData.mRect);
758               }
759             }
760             DrawFrameTo(prevFrameData.mRawData, prevFrameData.mRect,
761                         prevFrameData.mPaletteDataLength,
762                         prevFrameData.mHasAlpha, compositingFrameData.mRawData,
763                         compositingFrameData.mRect, prevFrameData.mBlendMethod,
764                         prevFrameData.mBlendRect);
765           }
766         }
767     }
768   } else if (needToBlankComposite) {
769     // If we just created the composite, it could have anything in its
770     // buffers. Clear them
771     ClearFrame(compositingFrameData.mRawData, compositingFrameData.mRect);
772   }
773 
774   // Check if the frame we are composing wants the previous image restored after
775   // it is done. Don't store it (again) if last frame wanted its image restored
776   // too
777   if ((nextFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) &&
778       (prevFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) {
779     // We are storing the whole image.
780     // It would be better if we just stored the area that nextFrame is going to
781     // overwrite.
782     if (!mCompositingPrevFrame) {
783       RefPtr<imgFrame> newFrame = new imgFrame;
784       nsresult rv = newFrame->InitForAnimator(mSize, SurfaceFormat::B8G8R8A8);
785       if (NS_FAILED(rv)) {
786         mCompositingPrevFrame.reset();
787         return false;
788       }
789 
790       mCompositingPrevFrame = newFrame->RawAccessRef();
791     }
792 
793     AnimationData compositingPrevFrameData =
794         mCompositingPrevFrame->GetAnimationData();
795 
796     CopyFrameImage(compositingFrameData.mRawData, compositingFrameData.mRect,
797                    compositingPrevFrameData.mRawData,
798                    compositingPrevFrameData.mRect);
799 
800     mCompositingPrevFrame->Finish();
801   }
802 
803   // blit next frame into it's correct spot
804   DrawFrameTo(nextFrameData.mRawData, nextFrameData.mRect,
805               nextFrameData.mPaletteDataLength, nextFrameData.mHasAlpha,
806               compositingFrameData.mRawData, compositingFrameData.mRect,
807               nextFrameData.mBlendMethod, nextFrameData.mBlendRect);
808 
809   // Tell the image that it is fully 'downloaded'.
810   mCompositingFrame->Finish();
811 
812   mLastCompositedFrameIndex = int32_t(aNextFrameIndex);
813 
814   return true;
815 }
816 
817 //******************************************************************************
818 // Fill aFrame with black. Does also clears the mask.
ClearFrame(uint8_t * aFrameData,const IntRect & aFrameRect)819 void FrameAnimator::ClearFrame(uint8_t* aFrameData, const IntRect& aFrameRect) {
820   if (!aFrameData) {
821     return;
822   }
823 
824   memset(aFrameData, 0, aFrameRect.Width() * aFrameRect.Height() * 4);
825 }
826 
827 //******************************************************************************
ClearFrame(uint8_t * aFrameData,const IntRect & aFrameRect,const IntRect & aRectToClear)828 void FrameAnimator::ClearFrame(uint8_t* aFrameData, const IntRect& aFrameRect,
829                                const IntRect& aRectToClear) {
830   if (!aFrameData || aFrameRect.Width() <= 0 || aFrameRect.Height() <= 0 ||
831       aRectToClear.Width() <= 0 || aRectToClear.Height() <= 0) {
832     return;
833   }
834 
835   IntRect toClear = aFrameRect.Intersect(aRectToClear);
836   if (toClear.IsEmpty()) {
837     return;
838   }
839 
840   uint32_t bytesPerRow = aFrameRect.Width() * 4;
841   for (int row = toClear.Y(); row < toClear.YMost(); ++row) {
842     memset(aFrameData + toClear.X() * 4 + row * bytesPerRow, 0,
843            toClear.Width() * 4);
844   }
845 }
846 
847 //******************************************************************************
848 // Whether we succeed or fail will not cause a crash, and there's not much
849 // we can do about a failure, so there we don't return a nsresult
CopyFrameImage(const uint8_t * aDataSrc,const IntRect & aRectSrc,uint8_t * aDataDest,const IntRect & aRectDest)850 bool FrameAnimator::CopyFrameImage(const uint8_t* aDataSrc,
851                                    const IntRect& aRectSrc, uint8_t* aDataDest,
852                                    const IntRect& aRectDest) {
853   uint32_t dataLengthSrc = aRectSrc.Width() * aRectSrc.Height() * 4;
854   uint32_t dataLengthDest = aRectDest.Width() * aRectDest.Height() * 4;
855 
856   if (!aDataDest || !aDataSrc || dataLengthSrc != dataLengthDest) {
857     return false;
858   }
859 
860   memcpy(aDataDest, aDataSrc, dataLengthDest);
861 
862   return true;
863 }
864 
DrawFrameTo(const uint8_t * aSrcData,const IntRect & aSrcRect,uint32_t aSrcPaletteLength,bool aSrcHasAlpha,uint8_t * aDstPixels,const IntRect & aDstRect,BlendMethod aBlendMethod,const Maybe<IntRect> & aBlendRect)865 nsresult FrameAnimator::DrawFrameTo(const uint8_t* aSrcData,
866                                     const IntRect& aSrcRect,
867                                     uint32_t aSrcPaletteLength,
868                                     bool aSrcHasAlpha, uint8_t* aDstPixels,
869                                     const IntRect& aDstRect,
870                                     BlendMethod aBlendMethod,
871                                     const Maybe<IntRect>& aBlendRect) {
872   NS_ENSURE_ARG_POINTER(aSrcData);
873   NS_ENSURE_ARG_POINTER(aDstPixels);
874 
875   // According to both AGIF and APNG specs, offsets are unsigned
876   if (aSrcRect.X() < 0 || aSrcRect.Y() < 0) {
877     NS_WARNING("FrameAnimator::DrawFrameTo: negative offsets not allowed");
878     return NS_ERROR_FAILURE;
879   }
880 
881   // Outside the destination frame, skip it
882   if ((aSrcRect.X() > aDstRect.Width()) || (aSrcRect.Y() > aDstRect.Height())) {
883     return NS_OK;
884   }
885 
886   if (aSrcPaletteLength) {
887     // Larger than the destination frame, clip it
888     int32_t width = std::min(aSrcRect.Width(), aDstRect.Width() - aSrcRect.X());
889     int32_t height =
890         std::min(aSrcRect.Height(), aDstRect.Height() - aSrcRect.Y());
891 
892     // The clipped image must now fully fit within destination image frame
893     NS_ASSERTION((aSrcRect.X() >= 0) && (aSrcRect.Y() >= 0) &&
894                      (aSrcRect.X() + width <= aDstRect.Width()) &&
895                      (aSrcRect.Y() + height <= aDstRect.Height()),
896                  "FrameAnimator::DrawFrameTo: Invalid aSrcRect");
897 
898     // clipped image size may be smaller than source, but not larger
899     NS_ASSERTION(
900         (width <= aSrcRect.Width()) && (height <= aSrcRect.Height()),
901         "FrameAnimator::DrawFrameTo: source must be smaller than dest");
902 
903     // Get pointers to image data
904     const uint8_t* srcPixels = aSrcData + aSrcPaletteLength;
905     uint32_t* dstPixels = reinterpret_cast<uint32_t*>(aDstPixels);
906     const uint32_t* colormap = reinterpret_cast<const uint32_t*>(aSrcData);
907 
908     // Skip to the right offset
909     dstPixels += aSrcRect.X() + (aSrcRect.Y() * aDstRect.Width());
910     if (!aSrcHasAlpha) {
911       for (int32_t r = height; r > 0; --r) {
912         for (int32_t c = 0; c < width; c++) {
913           dstPixels[c] = colormap[srcPixels[c]];
914         }
915         // Go to the next row in the source resp. destination image
916         srcPixels += aSrcRect.Width();
917         dstPixels += aDstRect.Width();
918       }
919     } else {
920       for (int32_t r = height; r > 0; --r) {
921         for (int32_t c = 0; c < width; c++) {
922           const uint32_t color = colormap[srcPixels[c]];
923           if (color) {
924             dstPixels[c] = color;
925           }
926         }
927         // Go to the next row in the source resp. destination image
928         srcPixels += aSrcRect.Width();
929         dstPixels += aDstRect.Width();
930       }
931     }
932   } else {
933     pixman_image_t* src = pixman_image_create_bits(
934         aSrcHasAlpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8, aSrcRect.Width(),
935         aSrcRect.Height(),
936         reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(aSrcData)),
937         aSrcRect.Width() * 4);
938     if (!src) {
939       return NS_ERROR_OUT_OF_MEMORY;
940     }
941     pixman_image_t* dst = pixman_image_create_bits(
942         PIXMAN_a8r8g8b8, aDstRect.Width(), aDstRect.Height(),
943         reinterpret_cast<uint32_t*>(aDstPixels), aDstRect.Width() * 4);
944     if (!dst) {
945       pixman_image_unref(src);
946       return NS_ERROR_OUT_OF_MEMORY;
947     }
948 
949     // XXX(seth): This is inefficient but we'll remove it quite soon when we
950     // move frame compositing into SurfacePipe. For now we need this because
951     // RemoveFrameRectFilter has transformed PNG frames with frame rects into
952     // imgFrame's with no frame rects, but with a region of 0 alpha where the
953     // frame rect should be. This works really nicely if we're using
954     // BlendMethod::OVER, but BlendMethod::SOURCE will result in that frame rect
955     // area overwriting the previous frame, which makes the animation look
956     // wrong. This quick hack fixes that by first compositing the whle new frame
957     // with BlendMethod::OVER, and then recopying the area that uses
958     // BlendMethod::SOURCE if needed. To make this work, the decoder has to
959     // provide a "blend rect" that tells us where to do this. This is just the
960     // frame rect, but hidden in a way that makes it invisible to most of the
961     // system, so we can keep eliminating dependencies on it.
962     auto op =
963         aBlendMethod == BlendMethod::SOURCE ? PIXMAN_OP_SRC : PIXMAN_OP_OVER;
964 
965     if (aBlendMethod == BlendMethod::OVER || !aBlendRect ||
966         (aBlendMethod == BlendMethod::SOURCE &&
967          aSrcRect.IsEqualEdges(*aBlendRect))) {
968       // We don't need to do anything clever. (Or, in the case where no blend
969       // rect was specified, we can't.)
970       pixman_image_composite32(op, src, nullptr, dst, 0, 0, 0, 0, aSrcRect.X(),
971                                aSrcRect.Y(), aSrcRect.Width(),
972                                aSrcRect.Height());
973     } else {
974       // We need to do the OVER followed by SOURCE trick above.
975       pixman_image_composite32(PIXMAN_OP_OVER, src, nullptr, dst, 0, 0, 0, 0,
976                                aSrcRect.X(), aSrcRect.Y(), aSrcRect.Width(),
977                                aSrcRect.Height());
978       pixman_image_composite32(PIXMAN_OP_SRC, src, nullptr, dst,
979                                aBlendRect->X(), aBlendRect->Y(), 0, 0,
980                                aBlendRect->X(), aBlendRect->Y(),
981                                aBlendRect->Width(), aBlendRect->Height());
982     }
983 
984     pixman_image_unref(src);
985     pixman_image_unref(dst);
986   }
987 
988   return NS_OK;
989 }
990 
991 }  // namespace image
992 }  // namespace mozilla
993