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