Bug 1288040 (Part 9) - Determine the loop length of animated images while decoding them. r=edwin
authorSeth Fowler <mark.seth.fowler@gmail.com>
Tue, 19 Jul 2016 17:14:30 -0700
changeset 305963 ec558fefe42f16f4f8e1c0345762d9f59facd49b
parent 305962 b6b8a4bc9168e7f0422c6bf612776a875ea32dc6
child 305964 7a652ffa8bfb8fc9a9d71f78573f7a639a129b00
push id30474
push usercbook@mozilla.com
push dateThu, 21 Jul 2016 14:25:10 +0000
treeherdermozilla-central@6b180266ac16 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersedwin
bugs1288040
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1288040 (Part 9) - Determine the loop length of animated images while decoding them. r=edwin
image/Decoder.cpp
image/Decoder.h
image/FrameAnimator.cpp
image/FrameAnimator.h
image/ImageMetadata.h
image/RasterImage.cpp
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -47,16 +47,17 @@ private:
 Decoder::Decoder(RasterImage* aImage)
   : mImageData(nullptr)
   , mImageDataLength(0)
   , mColormap(nullptr)
   , mColormapSize(0)
   , mImage(aImage)
   , mProgress(NoProgress)
   , mFrameCount(0)
+  , mLoopLength(FrameTimeout::Zero())
   , mDecoderFlags(DefaultDecoderFlags())
   , mSurfaceFlags(DefaultSurfaceFlags())
   , mInitialized(false)
   , mMetadataDecode(false)
   , mInFrame(false)
   , mReachedTerminalState(false)
   , mDecodeDone(false)
   , mError(false)
@@ -418,34 +419,35 @@ Decoder::PostIsAnimated(FrameTimeout aFi
   mImageMetadata.SetFirstFrameTimeout(aFirstFrameTimeout);
 }
 
 void
 Decoder::PostFrameStop(Opacity aFrameOpacity
                          /* = Opacity::SOME_TRANSPARENCY */,
                        DisposalMethod aDisposalMethod
                          /* = DisposalMethod::KEEP */,
-                       FrameTimeout aTimeout
-                         /* = FrameTimeout::FromRawMilliseconds(0) */,
+                       FrameTimeout aTimeout /* = FrameTimeout::Forever() */,
                        BlendMethod aBlendMethod /* = BlendMethod::OVER */,
                        const Maybe<nsIntRect>& aBlendRect /* = Nothing() */)
 {
   // We should be mid-frame
   MOZ_ASSERT(!IsMetadataDecode(), "Stopping frame during metadata decode");
   MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one");
   MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");
 
   // Update our state
   mInFrame = false;
 
   mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout,
                         aBlendMethod, aBlendRect);
 
   mProgress |= FLAG_FRAME_COMPLETE;
 
+  mLoopLength += aTimeout;
+
   // If we're not sending partial invalidations, then we send an invalidation
   // here when the first frame is complete.
   if (!ShouldSendPartialInvalidations() && mFrameCount == 1) {
     mInvalidRect.UnionRect(mInvalidRect,
                            gfx::IntRect(gfx::IntPoint(0, 0), GetSize()));
   }
 }
 
@@ -471,16 +473,23 @@ Decoder::PostDecodeDone(int32_t aLoopCou
 {
   MOZ_ASSERT(!IsMetadataDecode(), "Done with decoding in metadata decode");
   MOZ_ASSERT(!mInFrame, "Can't be done decoding if we're mid-frame!");
   MOZ_ASSERT(!mDecodeDone, "Decode already done!");
   mDecodeDone = true;
 
   mImageMetadata.SetLoopCount(aLoopCount);
 
+  // Let the image know how long a single loop through this image is. If this is
+  // a first-frame-only decode, our accumulated loop length only includes the
+  // first frame, so it's not correct and we don't record it.
+  if (!IsFirstFrameDecode()) {
+    mImageMetadata.SetLoopLength(mLoopLength);
+  }
+
   mProgress |= FLAG_DECODE_COMPLETE;
 }
 
 void
 Decoder::PostError()
 {
   mError = true;
 
--- a/image/Decoder.h
+++ b/image/Decoder.h
@@ -338,17 +338,17 @@ protected:
 
   // Called by decoders when they end a frame. Informs the image, sends
   // notifications, and does internal book-keeping.
   // Specify whether this frame is opaque as an optimization.
   // For animated images, specify the disposal, blend method and timeout for
   // this frame.
   void PostFrameStop(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY,
                      DisposalMethod aDisposalMethod = DisposalMethod::KEEP,
-                     FrameTimeout aTimeout = FrameTimeout::FromRawMilliseconds(0),
+                     FrameTimeout aTimeout = FrameTimeout::Forever(),
                      BlendMethod aBlendMethod = BlendMethod::OVER,
                      const Maybe<nsIntRect>& aBlendRect = Nothing());
 
   /**
    * Called by the decoders when they have a region to invalidate. We may not
    * actually pass these invalidations on right away.
    *
    * @param aRect The invalidation rect in the coordinate system of the unscaled
@@ -422,16 +422,17 @@ private:
   RefPtr<RasterImage> mImage;
   Maybe<SourceBufferIterator> mIterator;
   RawAccessFrameRef mCurrentFrame;
   ImageMetadata mImageMetadata;
   nsIntRect mInvalidRect; // Tracks an invalidation region in the current frame.
   Progress mProgress;
 
   uint32_t mFrameCount; // Number of frames, including anything in-progress
+  FrameTimeout mLoopLength;  // Length of a single loop of this image.
 
   // Telemetry data for this decoder.
   TimeDuration mDecodeTime;
 
   DecoderFlags mDecoderFlags;
   SurfaceFlags mSurfaceFlags;
 
   bool mInitialized : 1;
--- a/image/FrameAnimator.cpp
+++ b/image/FrameAnimator.cpp
@@ -69,50 +69,39 @@ AnimationState::GetCurrentAnimationFrame
 }
 
 nsIntRect
 AnimationState::GetFirstFrameRefreshArea() const
 {
   return mFirstFrameRefreshArea;
 }
 
+FrameTimeout
+AnimationState::LoopLength() const
+{
+  // If we don't know the loop length yet, we have to treat it as infinite.
+  if (!mLoopLength) {
+    return FrameTimeout::Forever();
+  }
+
+  MOZ_ASSERT(mDoneDecoding, "We know the loop length but decoding isn't done?");
+
+  // If we're not looping, a single loop time has no meaning.
+  if (mAnimationMode != imgIContainer::kNormalAnimMode) {
+    return FrameTimeout::Forever();
+  }
+
+  return *mLoopLength;
+}
+
 
 ///////////////////////////////////////////////////////////////////////////////
 // FrameAnimator implementation.
 ///////////////////////////////////////////////////////////////////////////////
 
-FrameTimeout
-FrameAnimator::GetSingleLoopTime(AnimationState& aState) const
-{
-  // If we aren't done decoding, we don't know the image's full play time.
-  if (!aState.mDoneDecoding) {
-    return FrameTimeout::Forever();
-  }
-
-  // If we're not looping, a single loop time has no meaning.
-  if (aState.mAnimationMode != imgIContainer::kNormalAnimMode) {
-    return FrameTimeout::Forever();
-  }
-
-  // Add up the timeouts of all the frames.
-  FrameTimeout loopTime = FrameTimeout::Zero();
-  for (uint32_t i = 0; i < mImage->GetNumFrames(); ++i) {
-    loopTime += GetTimeoutForFrame(i);
-  }
-
-  if (loopTime == FrameTimeout::Forever()) {
-    // We have at least one frame that never times out. This may be an error,
-    // but we'll try to handle it gracefully.
-    // XXX(seth): I don't think this is actually ever an error.
-    NS_WARNING("Infinite frame timeout - how did this happen?");
-  }
-
-  return loopTime;
-}
-
 TimeStamp
 FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState) const
 {
   TimeStamp currentFrameTime = aState.mCurrentAnimationFrameTime;
   FrameTimeout timeout = GetTimeoutForFrame(aState.mCurrentAnimationFrameIndex);
 
   if (timeout == FrameTimeout::Forever()) {
     // We need to return a sentinel value in this case, because our logic
@@ -230,19 +219,20 @@ FrameAnimator::AdvanceFrame(AnimationSta
     }
 
     nextFrame->SetCompositingFailed(false);
   }
 
   aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState);
 
   // If we can get closer to the current time by a multiple of the image's loop
-  // time, we should. We need to be done decoding in order to know the full loop
-  // time though!
-  FrameTimeout loopTime = GetSingleLoopTime(aState);
+  // time, we should. We can only do this if we're done decoding; otherwise, we
+  // don't know the full loop length, and LoopLength() will have to return
+  // FrameTimeout::Forever().
+  FrameTimeout loopTime = aState.LoopLength();
   if (loopTime != FrameTimeout::Forever()) {
     TimeDuration delay = aTime - aState.mCurrentAnimationFrameTime;
     if (delay.ToMilliseconds() > loopTime.AsMilliseconds()) {
       // Explicitly use integer division to get the floor of the number of
       // loops.
       uint64_t loops = static_cast<uint64_t>(delay.ToMilliseconds())
                      / loopTime.AsMilliseconds();
       aState.mCurrentAnimationFrameTime +=
--- a/image/FrameAnimator.h
+++ b/image/FrameAnimator.h
@@ -82,16 +82,26 @@ public:
 
   /*
    * Set number of times to loop the image.
    * @note -1 means loop forever.
    */
   void SetLoopCount(int32_t aLoopCount) { mLoopCount = aLoopCount; }
   int32_t LoopCount() const { return mLoopCount; }
 
+  /// Set the @aLength of a single loop through this image.
+  void SetLoopLength(FrameTimeout aLength) { mLoopLength = Some(aLength); }
+
+  /**
+   * @return the length of a single loop of this image. If this image is not
+   * finished decoding, is not animated, or it is animated but does not loop,
+   * returns FrameTimeout::Forever().
+   */
+  FrameTimeout LoopLength() const;
+
   /*
    * Get or set the timeout for the first frame. This is used to allow animation
    * scheduling even before a full decode runs for this image.
    */
   void SetFirstFrameTimeout(FrameTimeout aTimeout) { mFirstFrameTimeout = aTimeout; }
   FrameTimeout FirstFrameTimeout() const { return mFirstFrameTimeout; }
 
 private:
@@ -107,16 +117,19 @@ private:
   uint32_t mCurrentAnimationFrameIndex;
 
   //! number of loops remaining before animation stops (-1 no stop)
   int32_t mLoopRemainingCount;
 
   //! The total number of loops for the image.
   int32_t mLoopCount;
 
+  //! The length of a single loop through this image.
+  Maybe<FrameTimeout> mLoopLength;
+
   //! The timeout for the first frame of this image.
   FrameTimeout mFirstFrameTimeout;
 
   //! The animation mode of this image. Constants defined in imgIContainer.
   uint16_t mAnimationMode;
 
   //! Whether this image is done being decoded.
   bool mDoneDecoding;
@@ -205,23 +218,16 @@ private: // methods
    */
   RefreshResult AdvanceFrame(AnimationState& aState, TimeStamp aTime);
 
   /**
    * Get the @aIndex-th frame in the frame index, ignoring results of blending.
    */
   RawAccessFrameRef GetRawFrame(uint32_t aFrameNum) const;
 
-  /**
-   * @return the length of a single loop of this image, as a FrameTimeout value
-   * in milliseconds. If this image is not finished decoding, is not animated,
-   * or it is animated but does not loop, returns FrameTimeout::Forever().
-   */
-  FrameTimeout GetSingleLoopTime(AnimationState& aState) const;
-
   /// @return the given frame's timeout.
   FrameTimeout GetTimeoutForFrame(uint32_t aFrameNum) const;
 
   /**
    * Get the time the frame we're currently displaying is supposed to end.
    *
    * In the error case, returns an "infinity" timestamp.
    */
--- a/image/ImageMetadata.h
+++ b/image/ImageMetadata.h
@@ -35,16 +35,20 @@ public:
   bool HasHotspot() const { return mHotspot.isSome(); }
 
   void SetLoopCount(int32_t loopcount)
   {
     mLoopCount = loopcount;
   }
   int32_t GetLoopCount() const { return mLoopCount; }
 
+  void SetLoopLength(FrameTimeout aLength) { mLoopLength = Some(aLength); }
+  FrameTimeout GetLoopLength() const { return *mLoopLength; }
+  bool HasLoopLength() const { return mLoopLength.isSome(); }
+
   void SetFirstFrameTimeout(FrameTimeout aTimeout) { mFirstFrameTimeout = aTimeout; }
   FrameTimeout GetFirstFrameTimeout() const { return mFirstFrameTimeout; }
 
   void SetSize(int32_t width, int32_t height, Orientation orientation)
   {
     if (!HasSize()) {
       mSize.emplace(nsIntSize(width, height));
       mOrientation.emplace(orientation);
@@ -60,16 +64,19 @@ public:
 
 private:
   /// The hotspot found on cursors, if present.
   Maybe<gfx::IntPoint> mHotspot;
 
   /// The loop count for animated images, or -1 for infinite loop.
   int32_t mLoopCount;
 
+  // The total length of a single loop through an animated image.
+  Maybe<FrameTimeout> mLoopLength;
+
   /// The timeout of an animated image's first frame.
   FrameTimeout mFirstFrameTimeout;
 
   Maybe<nsIntSize> mSize;
   Maybe<Orientation> mOrientation;
 
   bool mHasAnimation : 1;
 };
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -869,16 +869,20 @@ RasterImage::SetMetadata(const ImageMeta
       // to discard all existing surfaces and redecode.
       return false;
     }
   }
 
   if (mAnimationState) {
     mAnimationState->SetLoopCount(aMetadata.GetLoopCount());
     mAnimationState->SetFirstFrameTimeout(aMetadata.GetFirstFrameTimeout());
+
+    if (aMetadata.HasLoopLength()) {
+      mAnimationState->SetLoopLength(aMetadata.GetLoopLength());
+    }
   }
 
   if (aMetadata.HasHotspot()) {
     IntPoint hotspot = aMetadata.GetHotspot();
 
     nsCOMPtr<nsISupportsPRUint32> intwrapx =
       do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID);
     nsCOMPtr<nsISupportsPRUint32> intwrapy =