1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/image/src/FrameAnimator.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,266 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "FrameAnimator.h" 1.10 +#include "FrameBlender.h" 1.11 + 1.12 +#include "imgIContainer.h" 1.13 + 1.14 +using namespace mozilla::image; 1.15 +using namespace mozilla; 1.16 + 1.17 +FrameAnimator::FrameAnimator(FrameBlender& aFrameBlender, 1.18 + uint16_t aAnimationMode) 1.19 + : mCurrentAnimationFrameIndex(0) 1.20 + , mLoopCounter(-1) 1.21 + , mFrameBlender(aFrameBlender) 1.22 + , mAnimationMode(aAnimationMode) 1.23 + , mDoneDecoding(false) 1.24 +{ 1.25 +} 1.26 + 1.27 +int32_t 1.28 +FrameAnimator::GetSingleLoopTime() const 1.29 +{ 1.30 + // If we aren't done decoding, we don't know the image's full play time. 1.31 + if (!mDoneDecoding) { 1.32 + return -1; 1.33 + } 1.34 + 1.35 + // If we're not looping, a single loop time has no meaning 1.36 + if (mAnimationMode != imgIContainer::kNormalAnimMode) { 1.37 + return -1; 1.38 + } 1.39 + 1.40 + uint32_t looptime = 0; 1.41 + for (uint32_t i = 0; i < mFrameBlender.GetNumFrames(); ++i) { 1.42 + int32_t timeout = mFrameBlender.GetTimeoutForFrame(i); 1.43 + if (timeout >= 0) { 1.44 + looptime += static_cast<uint32_t>(timeout); 1.45 + } else { 1.46 + // If we have a frame that never times out, we're probably in an error 1.47 + // case, but let's handle it more gracefully. 1.48 + NS_WARNING("Negative frame timeout - how did this happen?"); 1.49 + return -1; 1.50 + } 1.51 + } 1.52 + 1.53 + return looptime; 1.54 +} 1.55 + 1.56 +TimeStamp 1.57 +FrameAnimator::GetCurrentImgFrameEndTime() const 1.58 +{ 1.59 + TimeStamp currentFrameTime = mCurrentAnimationFrameTime; 1.60 + int32_t timeout = mFrameBlender.GetTimeoutForFrame(mCurrentAnimationFrameIndex); 1.61 + 1.62 + if (timeout < 0) { 1.63 + // We need to return a sentinel value in this case, because our logic 1.64 + // doesn't work correctly if we have a negative timeout value. We use 1.65 + // one year in the future as the sentinel because it works with the loop 1.66 + // in RequestRefresh() below. 1.67 + // XXX(seth): It'd be preferable to make our logic work correctly with 1.68 + // negative timeouts. 1.69 + return TimeStamp::NowLoRes() + 1.70 + TimeDuration::FromMilliseconds(31536000.0); 1.71 + } 1.72 + 1.73 + TimeDuration durationOfTimeout = 1.74 + TimeDuration::FromMilliseconds(static_cast<double>(timeout)); 1.75 + TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout; 1.76 + 1.77 + return currentFrameEndTime; 1.78 +} 1.79 + 1.80 +FrameAnimator::RefreshResult 1.81 +FrameAnimator::AdvanceFrame(TimeStamp aTime) 1.82 +{ 1.83 + NS_ASSERTION(aTime <= TimeStamp::Now(), 1.84 + "Given time appears to be in the future"); 1.85 + 1.86 + uint32_t currentFrameIndex = mCurrentAnimationFrameIndex; 1.87 + uint32_t nextFrameIndex = currentFrameIndex + 1; 1.88 + int32_t timeout = 0; 1.89 + 1.90 + RefreshResult ret; 1.91 + 1.92 + // If we're done decoding, we know we've got everything we're going to get. 1.93 + // If we aren't, we only display fully-downloaded frames; everything else 1.94 + // gets delayed. 1.95 + bool canDisplay = mDoneDecoding || 1.96 + (mFrameBlender.RawGetFrame(nextFrameIndex) && 1.97 + mFrameBlender.RawGetFrame(nextFrameIndex)->ImageComplete()); 1.98 + 1.99 + if (!canDisplay) { 1.100 + // Uh oh, the frame we want to show is currently being decoded (partial) 1.101 + // Wait until the next refresh driver tick and try again 1.102 + return ret; 1.103 + } 1.104 + 1.105 + // If we're done decoding the next frame, go ahead and display it now and 1.106 + // reinit with the next frame's delay time. 1.107 + if (mFrameBlender.GetNumFrames() == nextFrameIndex) { 1.108 + // End of an animation loop... 1.109 + 1.110 + // If we are not looping forever, initialize the loop counter 1.111 + if (mLoopCounter < 0 && mFrameBlender.GetLoopCount() >= 0) { 1.112 + mLoopCounter = mFrameBlender.GetLoopCount(); 1.113 + } 1.114 + 1.115 + // If animation mode is "loop once", or we're at end of loop counter, it's time to stop animating 1.116 + if (mAnimationMode == imgIContainer::kLoopOnceAnimMode || mLoopCounter == 0) { 1.117 + ret.animationFinished = true; 1.118 + } 1.119 + 1.120 + nextFrameIndex = 0; 1.121 + 1.122 + if (mLoopCounter > 0) { 1.123 + mLoopCounter--; 1.124 + } 1.125 + 1.126 + // If we're done, exit early. 1.127 + if (ret.animationFinished) { 1.128 + return ret; 1.129 + } 1.130 + } 1.131 + 1.132 + timeout = mFrameBlender.GetTimeoutForFrame(nextFrameIndex); 1.133 + 1.134 + // Bad data 1.135 + if (timeout < 0) { 1.136 + ret.animationFinished = true; 1.137 + ret.error = true; 1.138 + } 1.139 + 1.140 + if (nextFrameIndex == 0) { 1.141 + ret.dirtyRect = mFirstFrameRefreshArea; 1.142 + } else { 1.143 + // Change frame 1.144 + if (!mFrameBlender.DoBlend(&ret.dirtyRect, currentFrameIndex, nextFrameIndex)) { 1.145 + // something went wrong, move on to next 1.146 + NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed"); 1.147 + mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(true); 1.148 + mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(); 1.149 + mCurrentAnimationFrameIndex = nextFrameIndex; 1.150 + 1.151 + ret.error = true; 1.152 + return ret; 1.153 + } 1.154 + 1.155 + mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(false); 1.156 + } 1.157 + 1.158 + mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(); 1.159 + 1.160 + // If we can get closer to the current time by a multiple of the image's loop 1.161 + // time, we should. 1.162 + uint32_t loopTime = GetSingleLoopTime(); 1.163 + if (loopTime > 0) { 1.164 + TimeDuration delay = aTime - mCurrentAnimationFrameTime; 1.165 + if (delay.ToMilliseconds() > loopTime) { 1.166 + // Explicitly use integer division to get the floor of the number of 1.167 + // loops. 1.168 + uint32_t loops = static_cast<uint32_t>(delay.ToMilliseconds()) / loopTime; 1.169 + mCurrentAnimationFrameTime += TimeDuration::FromMilliseconds(loops * loopTime); 1.170 + } 1.171 + } 1.172 + 1.173 + // Set currentAnimationFrameIndex at the last possible moment 1.174 + mCurrentAnimationFrameIndex = nextFrameIndex; 1.175 + 1.176 + // If we're here, we successfully advanced the frame. 1.177 + ret.frameAdvanced = true; 1.178 + 1.179 + return ret; 1.180 +} 1.181 + 1.182 +FrameAnimator::RefreshResult 1.183 +FrameAnimator::RequestRefresh(const mozilla::TimeStamp& aTime) 1.184 +{ 1.185 + // only advance the frame if the current time is greater than or 1.186 + // equal to the current frame's end time. 1.187 + TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime(); 1.188 + 1.189 + // By default, an empty RefreshResult. 1.190 + RefreshResult ret; 1.191 + 1.192 + while (currentFrameEndTime <= aTime) { 1.193 + TimeStamp oldFrameEndTime = currentFrameEndTime; 1.194 + 1.195 + RefreshResult frameRes = AdvanceFrame(aTime); 1.196 + 1.197 + // Accumulate our result for returning to callers. 1.198 + ret.Accumulate(frameRes); 1.199 + 1.200 + currentFrameEndTime = GetCurrentImgFrameEndTime(); 1.201 + 1.202 + // if we didn't advance a frame, and our frame end time didn't change, 1.203 + // then we need to break out of this loop & wait for the frame(s) 1.204 + // to finish downloading 1.205 + if (!frameRes.frameAdvanced && (currentFrameEndTime == oldFrameEndTime)) { 1.206 + break; 1.207 + } 1.208 + } 1.209 + 1.210 + return ret; 1.211 +} 1.212 + 1.213 +void 1.214 +FrameAnimator::ResetAnimation() 1.215 +{ 1.216 + mCurrentAnimationFrameIndex = 0; 1.217 +} 1.218 + 1.219 +void 1.220 +FrameAnimator::SetDoneDecoding(bool aDone) 1.221 +{ 1.222 + mDoneDecoding = aDone; 1.223 +} 1.224 + 1.225 +void 1.226 +FrameAnimator::SetAnimationMode(uint16_t aAnimationMode) 1.227 +{ 1.228 + mAnimationMode = aAnimationMode; 1.229 +} 1.230 + 1.231 +void 1.232 +FrameAnimator::InitAnimationFrameTimeIfNecessary() 1.233 +{ 1.234 + if (mCurrentAnimationFrameTime.IsNull()) { 1.235 + mCurrentAnimationFrameTime = TimeStamp::Now(); 1.236 + } 1.237 +} 1.238 + 1.239 +void 1.240 +FrameAnimator::SetAnimationFrameTime(const TimeStamp& aTime) 1.241 +{ 1.242 + mCurrentAnimationFrameTime = aTime; 1.243 +} 1.244 + 1.245 +void 1.246 +FrameAnimator::SetFirstFrameRefreshArea(const nsIntRect& aRect) 1.247 +{ 1.248 + mFirstFrameRefreshArea = aRect; 1.249 +} 1.250 + 1.251 +void 1.252 +FrameAnimator::UnionFirstFrameRefreshArea(const nsIntRect& aRect) 1.253 +{ 1.254 + mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, aRect); 1.255 +} 1.256 + 1.257 +uint32_t 1.258 +FrameAnimator::GetCurrentAnimationFrameIndex() const 1.259 +{ 1.260 + return mCurrentAnimationFrameIndex; 1.261 +} 1.262 + 1.263 +nsIntRect 1.264 +FrameAnimator::GetFirstFrameRefreshArea() const 1.265 +{ 1.266 + return mFirstFrameRefreshArea; 1.267 +} 1.268 + 1.269 +