|
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 |