image/src/FrameAnimator.cpp

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:dab7c7ef9ce5
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 #include "FrameBlender.h"
8
9 #include "imgIContainer.h"
10
11 using namespace mozilla::image;
12 using namespace mozilla;
13
14 FrameAnimator::FrameAnimator(FrameBlender& aFrameBlender,
15 uint16_t aAnimationMode)
16 : mCurrentAnimationFrameIndex(0)
17 , mLoopCounter(-1)
18 , mFrameBlender(aFrameBlender)
19 , mAnimationMode(aAnimationMode)
20 , mDoneDecoding(false)
21 {
22 }
23
24 int32_t
25 FrameAnimator::GetSingleLoopTime() const
26 {
27 // If we aren't done decoding, we don't know the image's full play time.
28 if (!mDoneDecoding) {
29 return -1;
30 }
31
32 // If we're not looping, a single loop time has no meaning
33 if (mAnimationMode != imgIContainer::kNormalAnimMode) {
34 return -1;
35 }
36
37 uint32_t looptime = 0;
38 for (uint32_t i = 0; i < mFrameBlender.GetNumFrames(); ++i) {
39 int32_t timeout = mFrameBlender.GetTimeoutForFrame(i);
40 if (timeout >= 0) {
41 looptime += static_cast<uint32_t>(timeout);
42 } else {
43 // If we have a frame that never times out, we're probably in an error
44 // case, but let's handle it more gracefully.
45 NS_WARNING("Negative frame timeout - how did this happen?");
46 return -1;
47 }
48 }
49
50 return looptime;
51 }
52
53 TimeStamp
54 FrameAnimator::GetCurrentImgFrameEndTime() const
55 {
56 TimeStamp currentFrameTime = mCurrentAnimationFrameTime;
57 int32_t timeout = mFrameBlender.GetTimeoutForFrame(mCurrentAnimationFrameIndex);
58
59 if (timeout < 0) {
60 // We need to return a sentinel value in this case, because our logic
61 // doesn't work correctly if we have a negative timeout value. We use
62 // one year in the future as the sentinel because it works with the loop
63 // in RequestRefresh() below.
64 // XXX(seth): It'd be preferable to make our logic work correctly with
65 // negative timeouts.
66 return TimeStamp::NowLoRes() +
67 TimeDuration::FromMilliseconds(31536000.0);
68 }
69
70 TimeDuration durationOfTimeout =
71 TimeDuration::FromMilliseconds(static_cast<double>(timeout));
72 TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
73
74 return currentFrameEndTime;
75 }
76
77 FrameAnimator::RefreshResult
78 FrameAnimator::AdvanceFrame(TimeStamp aTime)
79 {
80 NS_ASSERTION(aTime <= TimeStamp::Now(),
81 "Given time appears to be in the future");
82
83 uint32_t currentFrameIndex = mCurrentAnimationFrameIndex;
84 uint32_t nextFrameIndex = currentFrameIndex + 1;
85 int32_t timeout = 0;
86
87 RefreshResult ret;
88
89 // If we're done decoding, we know we've got everything we're going to get.
90 // If we aren't, we only display fully-downloaded frames; everything else
91 // gets delayed.
92 bool canDisplay = mDoneDecoding ||
93 (mFrameBlender.RawGetFrame(nextFrameIndex) &&
94 mFrameBlender.RawGetFrame(nextFrameIndex)->ImageComplete());
95
96 if (!canDisplay) {
97 // Uh oh, the frame we want to show is currently being decoded (partial)
98 // Wait until the next refresh driver tick and try again
99 return ret;
100 }
101
102 // If we're done decoding the next frame, go ahead and display it now and
103 // reinit with the next frame's delay time.
104 if (mFrameBlender.GetNumFrames() == nextFrameIndex) {
105 // End of an animation loop...
106
107 // If we are not looping forever, initialize the loop counter
108 if (mLoopCounter < 0 && mFrameBlender.GetLoopCount() >= 0) {
109 mLoopCounter = mFrameBlender.GetLoopCount();
110 }
111
112 // If animation mode is "loop once", or we're at end of loop counter, it's time to stop animating
113 if (mAnimationMode == imgIContainer::kLoopOnceAnimMode || mLoopCounter == 0) {
114 ret.animationFinished = true;
115 }
116
117 nextFrameIndex = 0;
118
119 if (mLoopCounter > 0) {
120 mLoopCounter--;
121 }
122
123 // If we're done, exit early.
124 if (ret.animationFinished) {
125 return ret;
126 }
127 }
128
129 timeout = mFrameBlender.GetTimeoutForFrame(nextFrameIndex);
130
131 // Bad data
132 if (timeout < 0) {
133 ret.animationFinished = true;
134 ret.error = true;
135 }
136
137 if (nextFrameIndex == 0) {
138 ret.dirtyRect = mFirstFrameRefreshArea;
139 } else {
140 // Change frame
141 if (!mFrameBlender.DoBlend(&ret.dirtyRect, currentFrameIndex, nextFrameIndex)) {
142 // something went wrong, move on to next
143 NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
144 mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(true);
145 mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime();
146 mCurrentAnimationFrameIndex = nextFrameIndex;
147
148 ret.error = true;
149 return ret;
150 }
151
152 mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(false);
153 }
154
155 mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime();
156
157 // If we can get closer to the current time by a multiple of the image's loop
158 // time, we should.
159 uint32_t loopTime = GetSingleLoopTime();
160 if (loopTime > 0) {
161 TimeDuration delay = aTime - mCurrentAnimationFrameTime;
162 if (delay.ToMilliseconds() > loopTime) {
163 // Explicitly use integer division to get the floor of the number of
164 // loops.
165 uint32_t loops = static_cast<uint32_t>(delay.ToMilliseconds()) / loopTime;
166 mCurrentAnimationFrameTime += TimeDuration::FromMilliseconds(loops * loopTime);
167 }
168 }
169
170 // Set currentAnimationFrameIndex at the last possible moment
171 mCurrentAnimationFrameIndex = nextFrameIndex;
172
173 // If we're here, we successfully advanced the frame.
174 ret.frameAdvanced = true;
175
176 return ret;
177 }
178
179 FrameAnimator::RefreshResult
180 FrameAnimator::RequestRefresh(const mozilla::TimeStamp& aTime)
181 {
182 // only advance the frame if the current time is greater than or
183 // equal to the current frame's end time.
184 TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime();
185
186 // By default, an empty RefreshResult.
187 RefreshResult ret;
188
189 while (currentFrameEndTime <= aTime) {
190 TimeStamp oldFrameEndTime = currentFrameEndTime;
191
192 RefreshResult frameRes = AdvanceFrame(aTime);
193
194 // Accumulate our result for returning to callers.
195 ret.Accumulate(frameRes);
196
197 currentFrameEndTime = GetCurrentImgFrameEndTime();
198
199 // if we didn't advance a frame, and our frame end time didn't change,
200 // then we need to break out of this loop & wait for the frame(s)
201 // to finish downloading
202 if (!frameRes.frameAdvanced && (currentFrameEndTime == oldFrameEndTime)) {
203 break;
204 }
205 }
206
207 return ret;
208 }
209
210 void
211 FrameAnimator::ResetAnimation()
212 {
213 mCurrentAnimationFrameIndex = 0;
214 }
215
216 void
217 FrameAnimator::SetDoneDecoding(bool aDone)
218 {
219 mDoneDecoding = aDone;
220 }
221
222 void
223 FrameAnimator::SetAnimationMode(uint16_t aAnimationMode)
224 {
225 mAnimationMode = aAnimationMode;
226 }
227
228 void
229 FrameAnimator::InitAnimationFrameTimeIfNecessary()
230 {
231 if (mCurrentAnimationFrameTime.IsNull()) {
232 mCurrentAnimationFrameTime = TimeStamp::Now();
233 }
234 }
235
236 void
237 FrameAnimator::SetAnimationFrameTime(const TimeStamp& aTime)
238 {
239 mCurrentAnimationFrameTime = aTime;
240 }
241
242 void
243 FrameAnimator::SetFirstFrameRefreshArea(const nsIntRect& aRect)
244 {
245 mFirstFrameRefreshArea = aRect;
246 }
247
248 void
249 FrameAnimator::UnionFirstFrameRefreshArea(const nsIntRect& aRect)
250 {
251 mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, aRect);
252 }
253
254 uint32_t
255 FrameAnimator::GetCurrentAnimationFrameIndex() const
256 {
257 return mCurrentAnimationFrameIndex;
258 }
259
260 nsIntRect
261 FrameAnimator::GetFirstFrameRefreshArea() const
262 {
263 return mFirstFrameRefreshArea;
264 }
265
266

mercurial