michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "FrameAnimator.h" michael@0: #include "FrameBlender.h" michael@0: michael@0: #include "imgIContainer.h" michael@0: michael@0: using namespace mozilla::image; michael@0: using namespace mozilla; michael@0: michael@0: FrameAnimator::FrameAnimator(FrameBlender& aFrameBlender, michael@0: uint16_t aAnimationMode) michael@0: : mCurrentAnimationFrameIndex(0) michael@0: , mLoopCounter(-1) michael@0: , mFrameBlender(aFrameBlender) michael@0: , mAnimationMode(aAnimationMode) michael@0: , mDoneDecoding(false) michael@0: { michael@0: } michael@0: michael@0: int32_t michael@0: FrameAnimator::GetSingleLoopTime() const michael@0: { michael@0: // If we aren't done decoding, we don't know the image's full play time. michael@0: if (!mDoneDecoding) { michael@0: return -1; michael@0: } michael@0: michael@0: // If we're not looping, a single loop time has no meaning michael@0: if (mAnimationMode != imgIContainer::kNormalAnimMode) { michael@0: return -1; michael@0: } michael@0: michael@0: uint32_t looptime = 0; michael@0: for (uint32_t i = 0; i < mFrameBlender.GetNumFrames(); ++i) { michael@0: int32_t timeout = mFrameBlender.GetTimeoutForFrame(i); michael@0: if (timeout >= 0) { michael@0: looptime += static_cast(timeout); michael@0: } else { michael@0: // If we have a frame that never times out, we're probably in an error michael@0: // case, but let's handle it more gracefully. michael@0: NS_WARNING("Negative frame timeout - how did this happen?"); michael@0: return -1; michael@0: } michael@0: } michael@0: michael@0: return looptime; michael@0: } michael@0: michael@0: TimeStamp michael@0: FrameAnimator::GetCurrentImgFrameEndTime() const michael@0: { michael@0: TimeStamp currentFrameTime = mCurrentAnimationFrameTime; michael@0: int32_t timeout = mFrameBlender.GetTimeoutForFrame(mCurrentAnimationFrameIndex); michael@0: michael@0: if (timeout < 0) { michael@0: // We need to return a sentinel value in this case, because our logic michael@0: // doesn't work correctly if we have a negative timeout value. We use michael@0: // one year in the future as the sentinel because it works with the loop michael@0: // in RequestRefresh() below. michael@0: // XXX(seth): It'd be preferable to make our logic work correctly with michael@0: // negative timeouts. michael@0: return TimeStamp::NowLoRes() + michael@0: TimeDuration::FromMilliseconds(31536000.0); michael@0: } michael@0: michael@0: TimeDuration durationOfTimeout = michael@0: TimeDuration::FromMilliseconds(static_cast(timeout)); michael@0: TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout; michael@0: michael@0: return currentFrameEndTime; michael@0: } michael@0: michael@0: FrameAnimator::RefreshResult michael@0: FrameAnimator::AdvanceFrame(TimeStamp aTime) michael@0: { michael@0: NS_ASSERTION(aTime <= TimeStamp::Now(), michael@0: "Given time appears to be in the future"); michael@0: michael@0: uint32_t currentFrameIndex = mCurrentAnimationFrameIndex; michael@0: uint32_t nextFrameIndex = currentFrameIndex + 1; michael@0: int32_t timeout = 0; michael@0: michael@0: RefreshResult ret; michael@0: michael@0: // If we're done decoding, we know we've got everything we're going to get. michael@0: // If we aren't, we only display fully-downloaded frames; everything else michael@0: // gets delayed. michael@0: bool canDisplay = mDoneDecoding || michael@0: (mFrameBlender.RawGetFrame(nextFrameIndex) && michael@0: mFrameBlender.RawGetFrame(nextFrameIndex)->ImageComplete()); michael@0: michael@0: if (!canDisplay) { michael@0: // Uh oh, the frame we want to show is currently being decoded (partial) michael@0: // Wait until the next refresh driver tick and try again michael@0: return ret; michael@0: } michael@0: michael@0: // If we're done decoding the next frame, go ahead and display it now and michael@0: // reinit with the next frame's delay time. michael@0: if (mFrameBlender.GetNumFrames() == nextFrameIndex) { michael@0: // End of an animation loop... michael@0: michael@0: // If we are not looping forever, initialize the loop counter michael@0: if (mLoopCounter < 0 && mFrameBlender.GetLoopCount() >= 0) { michael@0: mLoopCounter = mFrameBlender.GetLoopCount(); michael@0: } michael@0: michael@0: // If animation mode is "loop once", or we're at end of loop counter, it's time to stop animating michael@0: if (mAnimationMode == imgIContainer::kLoopOnceAnimMode || mLoopCounter == 0) { michael@0: ret.animationFinished = true; michael@0: } michael@0: michael@0: nextFrameIndex = 0; michael@0: michael@0: if (mLoopCounter > 0) { michael@0: mLoopCounter--; michael@0: } michael@0: michael@0: // If we're done, exit early. michael@0: if (ret.animationFinished) { michael@0: return ret; michael@0: } michael@0: } michael@0: michael@0: timeout = mFrameBlender.GetTimeoutForFrame(nextFrameIndex); michael@0: michael@0: // Bad data michael@0: if (timeout < 0) { michael@0: ret.animationFinished = true; michael@0: ret.error = true; michael@0: } michael@0: michael@0: if (nextFrameIndex == 0) { michael@0: ret.dirtyRect = mFirstFrameRefreshArea; michael@0: } else { michael@0: // Change frame michael@0: if (!mFrameBlender.DoBlend(&ret.dirtyRect, currentFrameIndex, nextFrameIndex)) { michael@0: // something went wrong, move on to next michael@0: NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed"); michael@0: mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(true); michael@0: mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(); michael@0: mCurrentAnimationFrameIndex = nextFrameIndex; michael@0: michael@0: ret.error = true; michael@0: return ret; michael@0: } michael@0: michael@0: mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(false); michael@0: } michael@0: michael@0: mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(); michael@0: michael@0: // If we can get closer to the current time by a multiple of the image's loop michael@0: // time, we should. michael@0: uint32_t loopTime = GetSingleLoopTime(); michael@0: if (loopTime > 0) { michael@0: TimeDuration delay = aTime - mCurrentAnimationFrameTime; michael@0: if (delay.ToMilliseconds() > loopTime) { michael@0: // Explicitly use integer division to get the floor of the number of michael@0: // loops. michael@0: uint32_t loops = static_cast(delay.ToMilliseconds()) / loopTime; michael@0: mCurrentAnimationFrameTime += TimeDuration::FromMilliseconds(loops * loopTime); michael@0: } michael@0: } michael@0: michael@0: // Set currentAnimationFrameIndex at the last possible moment michael@0: mCurrentAnimationFrameIndex = nextFrameIndex; michael@0: michael@0: // If we're here, we successfully advanced the frame. michael@0: ret.frameAdvanced = true; michael@0: michael@0: return ret; michael@0: } michael@0: michael@0: FrameAnimator::RefreshResult michael@0: FrameAnimator::RequestRefresh(const mozilla::TimeStamp& aTime) michael@0: { michael@0: // only advance the frame if the current time is greater than or michael@0: // equal to the current frame's end time. michael@0: TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime(); michael@0: michael@0: // By default, an empty RefreshResult. michael@0: RefreshResult ret; michael@0: michael@0: while (currentFrameEndTime <= aTime) { michael@0: TimeStamp oldFrameEndTime = currentFrameEndTime; michael@0: michael@0: RefreshResult frameRes = AdvanceFrame(aTime); michael@0: michael@0: // Accumulate our result for returning to callers. michael@0: ret.Accumulate(frameRes); michael@0: michael@0: currentFrameEndTime = GetCurrentImgFrameEndTime(); michael@0: michael@0: // if we didn't advance a frame, and our frame end time didn't change, michael@0: // then we need to break out of this loop & wait for the frame(s) michael@0: // to finish downloading michael@0: if (!frameRes.frameAdvanced && (currentFrameEndTime == oldFrameEndTime)) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return ret; michael@0: } michael@0: michael@0: void michael@0: FrameAnimator::ResetAnimation() michael@0: { michael@0: mCurrentAnimationFrameIndex = 0; michael@0: } michael@0: michael@0: void michael@0: FrameAnimator::SetDoneDecoding(bool aDone) michael@0: { michael@0: mDoneDecoding = aDone; michael@0: } michael@0: michael@0: void michael@0: FrameAnimator::SetAnimationMode(uint16_t aAnimationMode) michael@0: { michael@0: mAnimationMode = aAnimationMode; michael@0: } michael@0: michael@0: void michael@0: FrameAnimator::InitAnimationFrameTimeIfNecessary() michael@0: { michael@0: if (mCurrentAnimationFrameTime.IsNull()) { michael@0: mCurrentAnimationFrameTime = TimeStamp::Now(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: FrameAnimator::SetAnimationFrameTime(const TimeStamp& aTime) michael@0: { michael@0: mCurrentAnimationFrameTime = aTime; michael@0: } michael@0: michael@0: void michael@0: FrameAnimator::SetFirstFrameRefreshArea(const nsIntRect& aRect) michael@0: { michael@0: mFirstFrameRefreshArea = aRect; michael@0: } michael@0: michael@0: void michael@0: FrameAnimator::UnionFirstFrameRefreshArea(const nsIntRect& aRect) michael@0: { michael@0: mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, aRect); michael@0: } michael@0: michael@0: uint32_t michael@0: FrameAnimator::GetCurrentAnimationFrameIndex() const michael@0: { michael@0: return mCurrentAnimationFrameIndex; michael@0: } michael@0: michael@0: nsIntRect michael@0: FrameAnimator::GetFirstFrameRefreshArea() const michael@0: { michael@0: return mFirstFrameRefreshArea; michael@0: } michael@0: michael@0: