LCOV - code coverage report
Current view: top level - image - FrameAnimator.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 218 423 51.5 %
Date: 2017-07-14 16:53:18 Functions: 20 25 80.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13