/src/mozilla-central/image/FrameAnimator.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | |
8 | | #include "mozilla/MemoryReporting.h" |
9 | | #include "mozilla/Move.h" |
10 | | #include "mozilla/CheckedInt.h" |
11 | | #include "imgIContainer.h" |
12 | | #include "LookupResult.h" |
13 | | #include "MainThreadUtils.h" |
14 | | #include "RasterImage.h" |
15 | | #include "gfxPrefs.h" |
16 | | |
17 | | #include "pixman.h" |
18 | | #include <algorithm> |
19 | | |
20 | | namespace mozilla { |
21 | | |
22 | | using namespace gfx; |
23 | | |
24 | | namespace image { |
25 | | |
26 | | /////////////////////////////////////////////////////////////////////////////// |
27 | | // AnimationState implementation. |
28 | | /////////////////////////////////////////////////////////////////////////////// |
29 | | |
30 | | const gfx::IntRect |
31 | | AnimationState::UpdateState(bool aAnimationFinished, |
32 | | RasterImage *aImage, |
33 | | const gfx::IntSize& aSize, |
34 | | bool aAllowInvalidation /* = true */) |
35 | 0 | { |
36 | 0 | LookupResult result = |
37 | 0 | SurfaceCache::Lookup(ImageKey(aImage), |
38 | 0 | RasterSurfaceKey(aSize, |
39 | 0 | DefaultSurfaceFlags(), |
40 | 0 | PlaybackType::eAnimated)); |
41 | 0 |
|
42 | 0 | return UpdateStateInternal(result, aAnimationFinished, aSize, aAllowInvalidation); |
43 | 0 | } |
44 | | |
45 | | const gfx::IntRect |
46 | | AnimationState::UpdateStateInternal(LookupResult& aResult, |
47 | | bool aAnimationFinished, |
48 | | const gfx::IntSize& aSize, |
49 | | bool aAllowInvalidation /* = true */) |
50 | 0 | { |
51 | 0 | // Update mDiscarded and mIsCurrentlyDecoded. |
52 | 0 | if (aResult.Type() == MatchType::NOT_FOUND) { |
53 | 0 | // no frames, we've either been discarded, or never been decoded before. |
54 | 0 | mDiscarded = mHasBeenDecoded; |
55 | 0 | mIsCurrentlyDecoded = false; |
56 | 0 | } else if (aResult.Type() == MatchType::PENDING) { |
57 | 0 | // no frames yet, but a decoder is or will be working on it. |
58 | 0 | mDiscarded = false; |
59 | 0 | mIsCurrentlyDecoded = false; |
60 | 0 | mHasRequestedDecode = true; |
61 | 0 | } else { |
62 | 0 | MOZ_ASSERT(aResult.Type() == MatchType::EXACT); |
63 | 0 | mDiscarded = false; |
64 | 0 | mHasRequestedDecode = true; |
65 | 0 |
|
66 | 0 | // If mHasBeenDecoded is true then we know the true total frame count and |
67 | 0 | // we can use it to determine if we have all the frames now so we know if |
68 | 0 | // we are currently fully decoded. |
69 | 0 | // If mHasBeenDecoded is false then we'll get another UpdateState call |
70 | 0 | // when the decode finishes. |
71 | 0 | if (mHasBeenDecoded) { |
72 | 0 | Maybe<uint32_t> frameCount = FrameCount(); |
73 | 0 | MOZ_ASSERT(frameCount.isSome()); |
74 | 0 | mIsCurrentlyDecoded = aResult.Surface().IsFullyDecoded(); |
75 | 0 | } |
76 | 0 | } |
77 | 0 |
|
78 | 0 | gfx::IntRect ret; |
79 | 0 |
|
80 | 0 | if (aAllowInvalidation) { |
81 | 0 | // Update the value of mCompositedFrameInvalid. |
82 | 0 | if (mIsCurrentlyDecoded || aAnimationFinished) { |
83 | 0 | // Animated images that have finished their animation (ie because it is a |
84 | 0 | // finite length animation) don't have RequestRefresh called on them, and so |
85 | 0 | // mCompositedFrameInvalid would never get cleared. We clear it here (and |
86 | 0 | // also in RasterImage::Decode when we create a decoder for an image that |
87 | 0 | // has finished animated so it can display sooner than waiting until the |
88 | 0 | // decode completes). We also do it if we are fully decoded. This is safe |
89 | 0 | // to do for images that aren't finished animating because before we paint |
90 | 0 | // the refresh driver will call into us to advance to the correct frame, |
91 | 0 | // and that will succeed because we have all the frames. |
92 | 0 | if (mCompositedFrameInvalid) { |
93 | 0 | // Invalidate if we are marking the composited frame valid. |
94 | 0 | ret.SizeTo(aSize); |
95 | 0 | } |
96 | 0 | mCompositedFrameInvalid = false; |
97 | 0 | } else if (aResult.Type() == MatchType::NOT_FOUND || |
98 | 0 | aResult.Type() == MatchType::PENDING) { |
99 | 0 | if (mHasRequestedDecode) { |
100 | 0 | MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable()); |
101 | 0 | mCompositedFrameInvalid = true; |
102 | 0 | } |
103 | 0 | } |
104 | 0 | // Otherwise don't change the value of mCompositedFrameInvalid, it will be |
105 | 0 | // updated by RequestRefresh. |
106 | 0 | } |
107 | 0 |
|
108 | 0 | return ret; |
109 | 0 | } |
110 | | |
111 | | void |
112 | | AnimationState::NotifyDecodeComplete() |
113 | 0 | { |
114 | 0 | mHasBeenDecoded = true; |
115 | 0 | } |
116 | | |
117 | | void |
118 | | AnimationState::ResetAnimation() |
119 | 0 | { |
120 | 0 | mCurrentAnimationFrameIndex = 0; |
121 | 0 | } |
122 | | |
123 | | void |
124 | | AnimationState::SetAnimationMode(uint16_t aAnimationMode) |
125 | 0 | { |
126 | 0 | mAnimationMode = aAnimationMode; |
127 | 0 | } |
128 | | |
129 | | void |
130 | | AnimationState::UpdateKnownFrameCount(uint32_t aFrameCount) |
131 | 0 | { |
132 | 0 | if (aFrameCount <= mFrameCount) { |
133 | 0 | // Nothing to do. Since we can redecode animated images, we may see the same |
134 | 0 | // sequence of updates replayed again, so seeing a smaller frame count than |
135 | 0 | // what we already know about doesn't indicate an error. |
136 | 0 | return; |
137 | 0 | } |
138 | 0 | |
139 | 0 | MOZ_ASSERT(!mHasBeenDecoded, "Adding new frames after decoding is finished?"); |
140 | 0 | MOZ_ASSERT(aFrameCount <= mFrameCount + 1, "Skipped a frame?"); |
141 | 0 |
|
142 | 0 | mFrameCount = aFrameCount; |
143 | 0 | } |
144 | | |
145 | | Maybe<uint32_t> |
146 | | AnimationState::FrameCount() const |
147 | 0 | { |
148 | 0 | return mHasBeenDecoded ? Some(mFrameCount) : Nothing(); |
149 | 0 | } |
150 | | |
151 | | void |
152 | | AnimationState::SetFirstFrameRefreshArea(const IntRect& aRefreshArea) |
153 | 0 | { |
154 | 0 | mFirstFrameRefreshArea = aRefreshArea; |
155 | 0 | } |
156 | | |
157 | | void |
158 | | AnimationState::InitAnimationFrameTimeIfNecessary() |
159 | 0 | { |
160 | 0 | if (mCurrentAnimationFrameTime.IsNull()) { |
161 | 0 | mCurrentAnimationFrameTime = TimeStamp::Now(); |
162 | 0 | } |
163 | 0 | } |
164 | | |
165 | | void |
166 | | AnimationState::SetAnimationFrameTime(const TimeStamp& aTime) |
167 | 0 | { |
168 | 0 | mCurrentAnimationFrameTime = aTime; |
169 | 0 | } |
170 | | |
171 | | bool |
172 | | AnimationState::MaybeAdvanceAnimationFrameTime(const TimeStamp& aTime) |
173 | 0 | { |
174 | 0 | if (!gfxPrefs::ImageAnimatedResumeFromLastDisplayed() || |
175 | 0 | mCurrentAnimationFrameTime >= aTime) { |
176 | 0 | return false; |
177 | 0 | } |
178 | 0 | |
179 | 0 | // We are configured to stop an animation when it is out of view, and restart |
180 | 0 | // it from the same point when it comes back into view. The same applies if it |
181 | 0 | // was discarded while out of view. |
182 | 0 | mCurrentAnimationFrameTime = aTime; |
183 | 0 | return true; |
184 | 0 | } |
185 | | |
186 | | uint32_t |
187 | | AnimationState::GetCurrentAnimationFrameIndex() const |
188 | 0 | { |
189 | 0 | return mCurrentAnimationFrameIndex; |
190 | 0 | } |
191 | | |
192 | | FrameTimeout |
193 | | AnimationState::LoopLength() const |
194 | 0 | { |
195 | 0 | // If we don't know the loop length yet, we have to treat it as infinite. |
196 | 0 | if (!mLoopLength) { |
197 | 0 | return FrameTimeout::Forever(); |
198 | 0 | } |
199 | 0 | |
200 | 0 | MOZ_ASSERT(mHasBeenDecoded, "We know the loop length but decoding isn't done?"); |
201 | 0 |
|
202 | 0 | // If we're not looping, a single loop time has no meaning. |
203 | 0 | if (mAnimationMode != imgIContainer::kNormalAnimMode) { |
204 | 0 | return FrameTimeout::Forever(); |
205 | 0 | } |
206 | 0 | |
207 | 0 | return *mLoopLength; |
208 | 0 | } |
209 | | |
210 | | |
211 | | /////////////////////////////////////////////////////////////////////////////// |
212 | | // FrameAnimator implementation. |
213 | | /////////////////////////////////////////////////////////////////////////////// |
214 | | |
215 | | TimeStamp |
216 | | FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState, |
217 | | FrameTimeout aCurrentTimeout) const |
218 | 0 | { |
219 | 0 | if (aCurrentTimeout == FrameTimeout::Forever()) { |
220 | 0 | // We need to return a sentinel value in this case, because our logic |
221 | 0 | // doesn't work correctly if we have an infinitely long timeout. We use one |
222 | 0 | // year in the future as the sentinel because it works with the loop in |
223 | 0 | // RequestRefresh() below. |
224 | 0 | // XXX(seth): It'd be preferable to make our logic work correctly with |
225 | 0 | // infinitely long timeouts. |
226 | 0 | return TimeStamp::NowLoRes() + |
227 | 0 | TimeDuration::FromMilliseconds(31536000.0); |
228 | 0 | } |
229 | 0 | |
230 | 0 | TimeDuration durationOfTimeout = |
231 | 0 | TimeDuration::FromMilliseconds(double(aCurrentTimeout.AsMilliseconds())); |
232 | 0 | return aState.mCurrentAnimationFrameTime + durationOfTimeout; |
233 | 0 | } |
234 | | |
235 | | RefreshResult |
236 | | FrameAnimator::AdvanceFrame(AnimationState& aState, |
237 | | DrawableSurface& aFrames, |
238 | | RawAccessFrameRef& aCurrentFrame, |
239 | | TimeStamp aTime) |
240 | 0 | { |
241 | 0 | AUTO_PROFILER_LABEL("FrameAnimator::AdvanceFrame", GRAPHICS); |
242 | 0 |
|
243 | 0 | RefreshResult ret; |
244 | 0 |
|
245 | 0 | // Determine what the next frame is, taking into account looping. |
246 | 0 | uint32_t currentFrameIndex = aState.mCurrentAnimationFrameIndex; |
247 | 0 | uint32_t nextFrameIndex = currentFrameIndex + 1; |
248 | 0 |
|
249 | 0 | // Check if we're at the end of the loop. (FrameCount() returns Nothing() if |
250 | 0 | // we don't know the total count yet.) |
251 | 0 | if (aState.FrameCount() == Some(nextFrameIndex)) { |
252 | 0 | // If we are not looping forever, initialize the loop counter |
253 | 0 | if (aState.mLoopRemainingCount < 0 && aState.LoopCount() >= 0) { |
254 | 0 | aState.mLoopRemainingCount = aState.LoopCount(); |
255 | 0 | } |
256 | 0 |
|
257 | 0 | // If animation mode is "loop once", or we're at end of loop counter, |
258 | 0 | // it's time to stop animating. |
259 | 0 | if (aState.mAnimationMode == imgIContainer::kLoopOnceAnimMode || |
260 | 0 | aState.mLoopRemainingCount == 0) { |
261 | 0 | ret.mAnimationFinished = true; |
262 | 0 | } |
263 | 0 |
|
264 | 0 | nextFrameIndex = 0; |
265 | 0 |
|
266 | 0 | if (aState.mLoopRemainingCount > 0) { |
267 | 0 | aState.mLoopRemainingCount--; |
268 | 0 | } |
269 | 0 |
|
270 | 0 | // If we're done, exit early. |
271 | 0 | if (ret.mAnimationFinished) { |
272 | 0 | return ret; |
273 | 0 | } |
274 | 0 | } |
275 | 0 | |
276 | 0 | if (nextFrameIndex >= aState.KnownFrameCount()) { |
277 | 0 | // We've already advanced to the last decoded frame, nothing more we can do. |
278 | 0 | // We're blocked by network/decoding from displaying the animation at the |
279 | 0 | // rate specified, so that means the frame we are displaying (the latest |
280 | 0 | // available) is the frame we want to be displaying at this time. So we |
281 | 0 | // update the current animation time. If we didn't update the current |
282 | 0 | // animation time then it could lag behind, which would indicate that we are |
283 | 0 | // behind in the animation and should try to catch up. When we are done |
284 | 0 | // decoding (and thus can loop around back to the start of the animation) we |
285 | 0 | // would then jump to a random point in the animation to try to catch up. |
286 | 0 | // But we were never behind in the animation. |
287 | 0 | aState.mCurrentAnimationFrameTime = aTime; |
288 | 0 | return ret; |
289 | 0 | } |
290 | 0 | |
291 | 0 | // There can be frames in the surface cache with index >= KnownFrameCount() |
292 | 0 | // which GetRawFrame() can access because an async decoder has decoded them, |
293 | 0 | // but which AnimationState doesn't know about yet because we haven't received |
294 | 0 | // the appropriate notification on the main thread. Make sure we stay in sync |
295 | 0 | // with AnimationState. |
296 | 0 | MOZ_ASSERT(nextFrameIndex < aState.KnownFrameCount()); |
297 | 0 | RawAccessFrameRef nextFrame = aFrames.RawAccessRef(nextFrameIndex); |
298 | 0 |
|
299 | 0 | // We should always check to see if we have the next frame even if we have |
300 | 0 | // previously finished decoding. If we needed to redecode (e.g. due to a draw |
301 | 0 | // failure) we would have discarded all the old frames and may not yet have |
302 | 0 | // the new ones. DrawableSurface::RawAccessRef promises to only return |
303 | 0 | // finished frames. |
304 | 0 | if (!nextFrame) { |
305 | 0 | // Uh oh, the frame we want to show is currently being decoded (partial). |
306 | 0 | // Similar to the above case, we could be blocked by network or decoding, |
307 | 0 | // and so we should advance our current time rather than risk jumping |
308 | 0 | // through the animation. We will wait until the next refresh driver tick |
309 | 0 | // and try again. |
310 | 0 | aState.mCurrentAnimationFrameTime = aTime; |
311 | 0 | return ret; |
312 | 0 | } |
313 | 0 | |
314 | 0 | if (nextFrame->GetTimeout() == FrameTimeout::Forever()) { |
315 | 0 | ret.mAnimationFinished = true; |
316 | 0 | } |
317 | 0 |
|
318 | 0 | if (nextFrameIndex == 0) { |
319 | 0 | MOZ_ASSERT(nextFrame->IsFullFrame()); |
320 | 0 | ret.mDirtyRect = aState.FirstFrameRefreshArea(); |
321 | 0 | } else if (!nextFrame->IsFullFrame()) { |
322 | 0 | MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1); |
323 | 0 | // Change frame |
324 | 0 | if (!DoBlend(aCurrentFrame, nextFrame, nextFrameIndex, &ret.mDirtyRect)) { |
325 | 0 | // something went wrong, move on to next |
326 | 0 | NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed"); |
327 | 0 | nextFrame->SetCompositingFailed(true); |
328 | 0 | aState.mCurrentAnimationFrameTime = |
329 | 0 | GetCurrentImgFrameEndTime(aState, aCurrentFrame->GetTimeout()); |
330 | 0 | aState.mCurrentAnimationFrameIndex = nextFrameIndex; |
331 | 0 | aState.mCompositedFrameRequested = false; |
332 | 0 | aCurrentFrame = std::move(nextFrame); |
333 | 0 | aFrames.Advance(nextFrameIndex); |
334 | 0 |
|
335 | 0 | return ret; |
336 | 0 | } |
337 | 0 |
|
338 | 0 | nextFrame->SetCompositingFailed(false); |
339 | 0 | } else { |
340 | 0 | ret.mDirtyRect = nextFrame->GetDirtyRect(); |
341 | 0 | } |
342 | 0 |
|
343 | 0 | aState.mCurrentAnimationFrameTime = |
344 | 0 | GetCurrentImgFrameEndTime(aState, aCurrentFrame->GetTimeout()); |
345 | 0 |
|
346 | 0 | // If we can get closer to the current time by a multiple of the image's loop |
347 | 0 | // time, we should. We can only do this if we're done decoding; otherwise, we |
348 | 0 | // don't know the full loop length, and LoopLength() will have to return |
349 | 0 | // FrameTimeout::Forever(). We also skip this for images with a finite loop |
350 | 0 | // count if we have initialized mLoopRemainingCount (it only gets initialized |
351 | 0 | // after one full loop). |
352 | 0 | FrameTimeout loopTime = aState.LoopLength(); |
353 | 0 | if (loopTime != FrameTimeout::Forever() && |
354 | 0 | (aState.LoopCount() < 0 || aState.mLoopRemainingCount >= 0)) { |
355 | 0 | TimeDuration delay = aTime - aState.mCurrentAnimationFrameTime; |
356 | 0 | if (delay.ToMilliseconds() > loopTime.AsMilliseconds()) { |
357 | 0 | // Explicitly use integer division to get the floor of the number of |
358 | 0 | // loops. |
359 | 0 | uint64_t loops = static_cast<uint64_t>(delay.ToMilliseconds()) |
360 | 0 | / loopTime.AsMilliseconds(); |
361 | 0 |
|
362 | 0 | // If we have a finite loop count limit the number of loops we advance. |
363 | 0 | if (aState.mLoopRemainingCount >= 0) { |
364 | 0 | MOZ_ASSERT(aState.LoopCount() >= 0); |
365 | 0 | loops = std::min(loops, CheckedUint64(aState.mLoopRemainingCount).value()); |
366 | 0 | } |
367 | 0 |
|
368 | 0 | aState.mCurrentAnimationFrameTime += |
369 | 0 | TimeDuration::FromMilliseconds(loops * loopTime.AsMilliseconds()); |
370 | 0 |
|
371 | 0 | if (aState.mLoopRemainingCount >= 0) { |
372 | 0 | MOZ_ASSERT(loops <= CheckedUint64(aState.mLoopRemainingCount).value()); |
373 | 0 | aState.mLoopRemainingCount -= CheckedInt32(loops).value(); |
374 | 0 | } |
375 | 0 | } |
376 | 0 | } |
377 | 0 |
|
378 | 0 | // Set currentAnimationFrameIndex at the last possible moment |
379 | 0 | aState.mCurrentAnimationFrameIndex = nextFrameIndex; |
380 | 0 | aState.mCompositedFrameRequested = false; |
381 | 0 | aCurrentFrame = std::move(nextFrame); |
382 | 0 | aFrames.Advance(nextFrameIndex); |
383 | 0 |
|
384 | 0 | // If we're here, we successfully advanced the frame. |
385 | 0 | ret.mFrameAdvanced = true; |
386 | 0 |
|
387 | 0 | return ret; |
388 | 0 | } |
389 | | |
390 | | void |
391 | | FrameAnimator::ResetAnimation(AnimationState& aState) |
392 | 0 | { |
393 | 0 | aState.ResetAnimation(); |
394 | 0 |
|
395 | 0 | // Our surface provider is synchronized to our state, so we need to reset its |
396 | 0 | // state as well, if we still have one. |
397 | 0 | LookupResult result = |
398 | 0 | SurfaceCache::Lookup(ImageKey(mImage), |
399 | 0 | RasterSurfaceKey(mSize, |
400 | 0 | DefaultSurfaceFlags(), |
401 | 0 | PlaybackType::eAnimated)); |
402 | 0 | if (!result) { |
403 | 0 | return; |
404 | 0 | } |
405 | 0 | |
406 | 0 | result.Surface().Reset(); |
407 | 0 | } |
408 | | |
409 | | RefreshResult |
410 | | FrameAnimator::RequestRefresh(AnimationState& aState, |
411 | | const TimeStamp& aTime, |
412 | | bool aAnimationFinished) |
413 | 0 | { |
414 | 0 | // By default, an empty RefreshResult. |
415 | 0 | RefreshResult ret; |
416 | 0 |
|
417 | 0 | if (aState.IsDiscarded()) { |
418 | 0 | aState.MaybeAdvanceAnimationFrameTime(aTime); |
419 | 0 | return ret; |
420 | 0 | } |
421 | 0 | |
422 | 0 | // Get the animation frames once now, and pass them down to callees because |
423 | 0 | // the surface could be discarded at anytime on a different thread. This is |
424 | 0 | // must easier to reason about then trying to write code that is safe to |
425 | 0 | // having the surface disappear at anytime. |
426 | 0 | LookupResult result = |
427 | 0 | SurfaceCache::Lookup(ImageKey(mImage), |
428 | 0 | RasterSurfaceKey(mSize, |
429 | 0 | DefaultSurfaceFlags(), |
430 | 0 | PlaybackType::eAnimated)); |
431 | 0 |
|
432 | 0 | ret.mDirtyRect = aState.UpdateStateInternal(result, aAnimationFinished, mSize); |
433 | 0 | if (aState.IsDiscarded() || !result) { |
434 | 0 | aState.MaybeAdvanceAnimationFrameTime(aTime); |
435 | 0 | if (!ret.mDirtyRect.IsEmpty()) { |
436 | 0 | ret.mFrameAdvanced = true; |
437 | 0 | } |
438 | 0 | return ret; |
439 | 0 | } |
440 | 0 |
|
441 | 0 | RawAccessFrameRef currentFrame = |
442 | 0 | result.Surface().RawAccessRef(aState.mCurrentAnimationFrameIndex); |
443 | 0 |
|
444 | 0 | // only advance the frame if the current time is greater than or |
445 | 0 | // equal to the current frame's end time. |
446 | 0 | if (!currentFrame) { |
447 | 0 | MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable()); |
448 | 0 | MOZ_ASSERT(aState.GetHasRequestedDecode() && !aState.GetIsCurrentlyDecoded()); |
449 | 0 | MOZ_ASSERT(aState.mCompositedFrameInvalid); |
450 | 0 | // Nothing we can do but wait for our previous current frame to be decoded |
451 | 0 | // again so we can determine what to do next. |
452 | 0 | aState.MaybeAdvanceAnimationFrameTime(aTime); |
453 | 0 | return ret; |
454 | 0 | } |
455 | 0 |
|
456 | 0 | TimeStamp currentFrameEndTime = |
457 | 0 | GetCurrentImgFrameEndTime(aState, currentFrame->GetTimeout()); |
458 | 0 |
|
459 | 0 | // If nothing has accessed the composited frame since the last time we |
460 | 0 | // advanced, then there is no point in continuing to advance the animation. |
461 | 0 | // This has the effect of freezing the animation while not in view. |
462 | 0 | if (!aState.mCompositedFrameRequested && |
463 | 0 | aState.MaybeAdvanceAnimationFrameTime(aTime)) { |
464 | 0 | return ret; |
465 | 0 | } |
466 | 0 | |
467 | 0 | while (currentFrameEndTime <= aTime) { |
468 | 0 | TimeStamp oldFrameEndTime = currentFrameEndTime; |
469 | 0 |
|
470 | 0 | RefreshResult frameRes = AdvanceFrame(aState, result.Surface(), |
471 | 0 | currentFrame, aTime); |
472 | 0 |
|
473 | 0 | // Accumulate our result for returning to callers. |
474 | 0 | ret.Accumulate(frameRes); |
475 | 0 |
|
476 | 0 | // currentFrame was updated by AdvanceFrame so it is still current. |
477 | 0 | currentFrameEndTime = |
478 | 0 | GetCurrentImgFrameEndTime(aState, currentFrame->GetTimeout()); |
479 | 0 |
|
480 | 0 | // If we didn't advance a frame, and our frame end time didn't change, |
481 | 0 | // then we need to break out of this loop & wait for the frame(s) |
482 | 0 | // to finish downloading. |
483 | 0 | if (!frameRes.mFrameAdvanced && currentFrameEndTime == oldFrameEndTime) { |
484 | 0 | break; |
485 | 0 | } |
486 | 0 | } |
487 | 0 |
|
488 | 0 | // We should only mark the composited frame as valid and reset the dirty rect |
489 | 0 | // if we advanced (meaning the next frame was actually produced somehow), the |
490 | 0 | // composited frame was previously invalid (so we may need to repaint |
491 | 0 | // everything) and the frame index is valid (to know we were doing blending |
492 | 0 | // on the main thread, instead of on the decoder threads in advance). |
493 | 0 | if (currentFrameEndTime > aTime && aState.mCompositedFrameInvalid && |
494 | 0 | mLastCompositedFrameIndex >= 0) { |
495 | 0 | aState.mCompositedFrameInvalid = false; |
496 | 0 | ret.mDirtyRect = IntRect(IntPoint(0,0), mSize); |
497 | 0 | } |
498 | 0 |
|
499 | 0 | MOZ_ASSERT(!aState.mIsCurrentlyDecoded || !aState.mCompositedFrameInvalid); |
500 | 0 |
|
501 | 0 | return ret; |
502 | 0 | } |
503 | | |
504 | | LookupResult |
505 | | FrameAnimator::GetCompositedFrame(AnimationState& aState) |
506 | 0 | { |
507 | 0 | aState.mCompositedFrameRequested = true; |
508 | 0 |
|
509 | 0 | // If we have a composited version of this frame, return that. |
510 | 0 | if (!aState.mCompositedFrameInvalid && mLastCompositedFrameIndex >= 0 && |
511 | 0 | (uint32_t(mLastCompositedFrameIndex) == aState.mCurrentAnimationFrameIndex)) { |
512 | 0 | return LookupResult(DrawableSurface(mCompositingFrame->DrawableRef()), |
513 | 0 | MatchType::EXACT); |
514 | 0 | } |
515 | 0 | |
516 | 0 | LookupResult result = |
517 | 0 | SurfaceCache::Lookup(ImageKey(mImage), |
518 | 0 | RasterSurfaceKey(mSize, |
519 | 0 | DefaultSurfaceFlags(), |
520 | 0 | PlaybackType::eAnimated)); |
521 | 0 |
|
522 | 0 | if (aState.mCompositedFrameInvalid) { |
523 | 0 | MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable()); |
524 | 0 | MOZ_ASSERT(aState.GetHasRequestedDecode()); |
525 | 0 | MOZ_ASSERT(!aState.GetIsCurrentlyDecoded()); |
526 | 0 | if (result.Type() == MatchType::NOT_FOUND) { |
527 | 0 | return result; |
528 | 0 | } |
529 | 0 | return LookupResult(MatchType::PENDING); |
530 | 0 | } |
531 | 0 | |
532 | 0 | // Otherwise return the raw frame. DoBlend is required to ensure that we only |
533 | 0 | // hit this case if the frame is not paletted and doesn't require compositing. |
534 | 0 | if (!result) { |
535 | 0 | return result; |
536 | 0 | } |
537 | 0 | |
538 | 0 | // Seek to the appropriate frame. If seeking fails, it means that we couldn't |
539 | 0 | // get the frame we're looking for; treat this as if the lookup failed. |
540 | 0 | if (NS_FAILED(result.Surface().Seek(aState.mCurrentAnimationFrameIndex))) { |
541 | 0 | if (result.Type() == MatchType::NOT_FOUND) { |
542 | 0 | return result; |
543 | 0 | } |
544 | 0 | return LookupResult(MatchType::PENDING); |
545 | 0 | } |
546 | 0 | |
547 | 0 | MOZ_ASSERT(!result.Surface()->GetIsPaletted(), |
548 | 0 | "About to return a paletted frame"); |
549 | 0 |
|
550 | 0 | return result; |
551 | 0 | } |
552 | | |
553 | | static void |
554 | | DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef& aSurface, |
555 | | SurfaceMemoryCounterType aType, |
556 | | nsTArray<SurfaceMemoryCounter>& aCounters, |
557 | | MallocSizeOf aMallocSizeOf) |
558 | 0 | { |
559 | 0 | // Concoct a SurfaceKey for this surface. |
560 | 0 | SurfaceKey key = RasterSurfaceKey(aSurface->GetImageSize(), |
561 | 0 | DefaultSurfaceFlags(), |
562 | 0 | PlaybackType::eStatic); |
563 | 0 |
|
564 | 0 | // Create a counter for this surface. |
565 | 0 | SurfaceMemoryCounter counter(key, /* aIsLocked = */ true, |
566 | 0 | /* aCannotSubstitute */ false, |
567 | 0 | /* aIsFactor2 */ false, aType); |
568 | 0 |
|
569 | 0 | // Extract the surface's memory usage information. |
570 | 0 | size_t heap = 0, nonHeap = 0, handles = 0; |
571 | 0 | aSurface->AddSizeOfExcludingThis(aMallocSizeOf, heap, nonHeap, handles); |
572 | 0 | counter.Values().SetDecodedHeap(heap); |
573 | 0 | counter.Values().SetDecodedNonHeap(nonHeap); |
574 | 0 | counter.Values().SetExternalHandles(handles); |
575 | 0 |
|
576 | 0 | // Record it. |
577 | 0 | aCounters.AppendElement(counter); |
578 | 0 | } |
579 | | |
580 | | void |
581 | | FrameAnimator::CollectSizeOfCompositingSurfaces( |
582 | | nsTArray<SurfaceMemoryCounter>& aCounters, |
583 | | MallocSizeOf aMallocSizeOf) const |
584 | 0 | { |
585 | 0 | if (mCompositingFrame) { |
586 | 0 | DoCollectSizeOfCompositingSurfaces(mCompositingFrame, |
587 | 0 | SurfaceMemoryCounterType::COMPOSITING, |
588 | 0 | aCounters, |
589 | 0 | aMallocSizeOf); |
590 | 0 | } |
591 | 0 |
|
592 | 0 | if (mCompositingPrevFrame) { |
593 | 0 | DoCollectSizeOfCompositingSurfaces(mCompositingPrevFrame, |
594 | 0 | SurfaceMemoryCounterType::COMPOSITING_PREV, |
595 | 0 | aCounters, |
596 | 0 | aMallocSizeOf); |
597 | 0 | } |
598 | 0 | } |
599 | | |
600 | | //****************************************************************************** |
601 | | // DoBlend gets called when the timer for animation get fired and we have to |
602 | | // update the composited frame of the animation. |
603 | | bool |
604 | | FrameAnimator::DoBlend(const RawAccessFrameRef& aPrevFrame, |
605 | | const RawAccessFrameRef& aNextFrame, |
606 | | uint32_t aNextFrameIndex, |
607 | | IntRect* aDirtyRect) |
608 | 0 | { |
609 | 0 | MOZ_ASSERT(aPrevFrame && aNextFrame, "Should have frames here"); |
610 | 0 |
|
611 | 0 | DisposalMethod prevDisposalMethod = aPrevFrame->GetDisposalMethod(); |
612 | 0 | bool prevHasAlpha = aPrevFrame->FormatHasAlpha(); |
613 | 0 | if (prevDisposalMethod == DisposalMethod::RESTORE_PREVIOUS && |
614 | 0 | !mCompositingPrevFrame) { |
615 | 0 | prevDisposalMethod = DisposalMethod::CLEAR; |
616 | 0 | } |
617 | 0 |
|
618 | 0 | IntRect prevRect = aPrevFrame->GetBoundedBlendRect(); |
619 | 0 | bool isFullPrevFrame = prevRect.IsEqualRect(0, 0, mSize.width, mSize.height); |
620 | 0 |
|
621 | 0 | // Optimization: DisposeClearAll if the previous frame is the same size as |
622 | 0 | // container and it's clearing itself |
623 | 0 | if (isFullPrevFrame && |
624 | 0 | (prevDisposalMethod == DisposalMethod::CLEAR)) { |
625 | 0 | prevDisposalMethod = DisposalMethod::CLEAR_ALL; |
626 | 0 | } |
627 | 0 |
|
628 | 0 | DisposalMethod nextDisposalMethod = aNextFrame->GetDisposalMethod(); |
629 | 0 | bool nextHasAlpha = aNextFrame->FormatHasAlpha(); |
630 | 0 |
|
631 | 0 | IntRect nextRect = aNextFrame->GetBoundedBlendRect(); |
632 | 0 | bool isFullNextFrame = nextRect.IsEqualRect(0, 0, mSize.width, mSize.height); |
633 | 0 |
|
634 | 0 | if (!aNextFrame->GetIsPaletted()) { |
635 | 0 | // Optimization: Skip compositing if the previous frame wants to clear the |
636 | 0 | // whole image |
637 | 0 | if (prevDisposalMethod == DisposalMethod::CLEAR_ALL) { |
638 | 0 | aDirtyRect->SetRect(0, 0, mSize.width, mSize.height); |
639 | 0 | return true; |
640 | 0 | } |
641 | 0 | |
642 | 0 | // Optimization: Skip compositing if this frame is the same size as the |
643 | 0 | // container and it's fully drawing over prev frame (no alpha) |
644 | 0 | if (isFullNextFrame && |
645 | 0 | (nextDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) && |
646 | 0 | !nextHasAlpha) { |
647 | 0 | aDirtyRect->SetRect(0, 0, mSize.width, mSize.height); |
648 | 0 | return true; |
649 | 0 | } |
650 | 0 | } |
651 | 0 | |
652 | 0 | // Calculate area that needs updating |
653 | 0 | switch (prevDisposalMethod) { |
654 | 0 | default: |
655 | 0 | MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod"); |
656 | 0 | case DisposalMethod::NOT_SPECIFIED: |
657 | 0 | case DisposalMethod::KEEP: |
658 | 0 | *aDirtyRect = nextRect; |
659 | 0 | break; |
660 | 0 |
|
661 | 0 | case DisposalMethod::CLEAR_ALL: |
662 | 0 | // Whole image container is cleared |
663 | 0 | aDirtyRect->SetRect(0, 0, mSize.width, mSize.height); |
664 | 0 | break; |
665 | 0 |
|
666 | 0 | case DisposalMethod::CLEAR: |
667 | 0 | // Calc area that needs to be redrawn (the combination of previous and |
668 | 0 | // this frame) |
669 | 0 | // XXX - This could be done with multiple framechanged calls |
670 | 0 | // Having aPrevFrame way at the top of the image, and aNextFrame |
671 | 0 | // way at the bottom, and both frames being small, we'd be |
672 | 0 | // telling framechanged to refresh the whole image when only two |
673 | 0 | // small areas are needed. |
674 | 0 | aDirtyRect->UnionRect(nextRect, prevRect); |
675 | 0 | break; |
676 | 0 |
|
677 | 0 | case DisposalMethod::RESTORE_PREVIOUS: |
678 | 0 | aDirtyRect->SetRect(0, 0, mSize.width, mSize.height); |
679 | 0 | break; |
680 | 0 | } |
681 | 0 | |
682 | 0 | // Optimization: |
683 | 0 | // Skip compositing if the last composited frame is this frame |
684 | 0 | // (Only one composited frame was made for this animation. Example: |
685 | 0 | // Only Frame 3 of a 10 frame image required us to build a composite frame |
686 | 0 | // On the second loop, we do not need to rebuild the frame |
687 | 0 | // since it's still sitting in compositingFrame) |
688 | 0 | if (mLastCompositedFrameIndex == int32_t(aNextFrameIndex)) { |
689 | 0 | return true; |
690 | 0 | } |
691 | 0 | |
692 | 0 | bool needToBlankComposite = false; |
693 | 0 |
|
694 | 0 | // Create the Compositing Frame |
695 | 0 | if (!mCompositingFrame) { |
696 | 0 | RefPtr<imgFrame> newFrame = new imgFrame; |
697 | 0 | nsresult rv = newFrame->InitForAnimator(mSize, |
698 | 0 | SurfaceFormat::B8G8R8A8); |
699 | 0 | if (NS_FAILED(rv)) { |
700 | 0 | mCompositingFrame.reset(); |
701 | 0 | return false; |
702 | 0 | } |
703 | 0 | mCompositingFrame = newFrame->RawAccessRef(); |
704 | 0 | needToBlankComposite = true; |
705 | 0 | } else if (int32_t(aNextFrameIndex) != mLastCompositedFrameIndex+1) { |
706 | 0 |
|
707 | 0 | // If we are not drawing on top of last composited frame, |
708 | 0 | // then we are building a new composite frame, so let's clear it first. |
709 | 0 | needToBlankComposite = true; |
710 | 0 | } |
711 | 0 |
|
712 | 0 | // More optimizations possible when next frame is not transparent |
713 | 0 | // But if the next frame has DisposalMethod::RESTORE_PREVIOUS, |
714 | 0 | // this "no disposal" optimization is not possible, |
715 | 0 | // because the frame in "after disposal operation" state |
716 | 0 | // needs to be stored in compositingFrame, so it can be |
717 | 0 | // copied into compositingPrevFrame later. |
718 | 0 | bool doDisposal = true; |
719 | 0 | if (!nextHasAlpha && nextDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) { |
720 | 0 | if (isFullNextFrame) { |
721 | 0 | // Optimization: No need to dispose prev.frame when |
722 | 0 | // next frame is full frame and not transparent. |
723 | 0 | doDisposal = false; |
724 | 0 | // No need to blank the composite frame |
725 | 0 | needToBlankComposite = false; |
726 | 0 | } else { |
727 | 0 | if ((prevRect.X() >= nextRect.X()) && (prevRect.Y() >= nextRect.Y()) && |
728 | 0 | (prevRect.XMost() <= nextRect.XMost()) && |
729 | 0 | (prevRect.YMost() <= nextRect.YMost())) { |
730 | 0 | // Optimization: No need to dispose prev.frame when |
731 | 0 | // next frame fully overlaps previous frame. |
732 | 0 | doDisposal = false; |
733 | 0 | } |
734 | 0 | } |
735 | 0 | } |
736 | 0 |
|
737 | 0 | if (doDisposal) { |
738 | 0 | // Dispose of previous: clear, restore, or keep (copy) |
739 | 0 | switch (prevDisposalMethod) { |
740 | 0 | case DisposalMethod::CLEAR: |
741 | 0 | if (needToBlankComposite) { |
742 | 0 | // If we just created the composite, it could have anything in its |
743 | 0 | // buffer. Clear whole frame |
744 | 0 | ClearFrame(mCompositingFrame.Data(), |
745 | 0 | mCompositingFrame->GetRect()); |
746 | 0 | } else { |
747 | 0 | // Only blank out previous frame area (both color & Mask/Alpha) |
748 | 0 | ClearFrame(mCompositingFrame.Data(), |
749 | 0 | mCompositingFrame->GetRect(), |
750 | 0 | prevRect); |
751 | 0 | } |
752 | 0 | break; |
753 | 0 |
|
754 | 0 | case DisposalMethod::CLEAR_ALL: |
755 | 0 | ClearFrame(mCompositingFrame.Data(), |
756 | 0 | mCompositingFrame->GetRect()); |
757 | 0 | break; |
758 | 0 |
|
759 | 0 | case DisposalMethod::RESTORE_PREVIOUS: |
760 | 0 | // It would be better to copy only the area changed back to |
761 | 0 | // compositingFrame. |
762 | 0 | if (mCompositingPrevFrame) { |
763 | 0 | CopyFrameImage(mCompositingPrevFrame.Data(), |
764 | 0 | mCompositingPrevFrame->GetRect(), |
765 | 0 | mCompositingFrame.Data(), |
766 | 0 | mCompositingFrame->GetRect()); |
767 | 0 |
|
768 | 0 | // destroy only if we don't need it for this frame's disposal |
769 | 0 | if (nextDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) { |
770 | 0 | mCompositingPrevFrame.reset(); |
771 | 0 | } |
772 | 0 | } else { |
773 | 0 | ClearFrame(mCompositingFrame.Data(), |
774 | 0 | mCompositingFrame->GetRect()); |
775 | 0 | } |
776 | 0 | break; |
777 | 0 |
|
778 | 0 | default: |
779 | 0 | MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod"); |
780 | 0 | case DisposalMethod::NOT_SPECIFIED: |
781 | 0 | case DisposalMethod::KEEP: |
782 | 0 | // Copy previous frame into compositingFrame before we put the new |
783 | 0 | // frame on top |
784 | 0 | // Assumes that the previous frame represents a full frame (it could be |
785 | 0 | // smaller in size than the container, as long as the frame before it |
786 | 0 | // erased itself) |
787 | 0 | // Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1) |
788 | 0 | // will always be a valid frame number. |
789 | 0 | if (mLastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) { |
790 | 0 | if (isFullPrevFrame && !aPrevFrame->GetIsPaletted()) { |
791 | 0 | // Just copy the bits |
792 | 0 | CopyFrameImage(aPrevFrame.Data(), |
793 | 0 | prevRect, |
794 | 0 | mCompositingFrame.Data(), |
795 | 0 | mCompositingFrame->GetRect()); |
796 | 0 | } else { |
797 | 0 | if (needToBlankComposite) { |
798 | 0 | // Only blank composite when prev is transparent or not full. |
799 | 0 | if (prevHasAlpha || !isFullPrevFrame) { |
800 | 0 | ClearFrame(mCompositingFrame.Data(), |
801 | 0 | mCompositingFrame->GetRect()); |
802 | 0 | } |
803 | 0 | } |
804 | 0 | DrawFrameTo(aPrevFrame.Data(), aPrevFrame->GetRect(), |
805 | 0 | aPrevFrame.PaletteDataLength(), |
806 | 0 | prevHasAlpha, |
807 | 0 | mCompositingFrame.Data(), |
808 | 0 | mCompositingFrame->GetRect(), |
809 | 0 | aPrevFrame->GetBlendMethod(), |
810 | 0 | aPrevFrame->GetBlendRect()); |
811 | 0 | } |
812 | 0 | } |
813 | 0 | } |
814 | 0 | } else if (needToBlankComposite) { |
815 | 0 | // If we just created the composite, it could have anything in its |
816 | 0 | // buffers. Clear them |
817 | 0 | ClearFrame(mCompositingFrame.Data(), |
818 | 0 | mCompositingFrame->GetRect()); |
819 | 0 | } |
820 | 0 |
|
821 | 0 | // Check if the frame we are composing wants the previous image restored after |
822 | 0 | // it is done. Don't store it (again) if last frame wanted its image restored |
823 | 0 | // too |
824 | 0 | if ((nextDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) && |
825 | 0 | (prevDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) { |
826 | 0 | // We are storing the whole image. |
827 | 0 | // It would be better if we just stored the area that aNextFrame is going to |
828 | 0 | // overwrite. |
829 | 0 | if (!mCompositingPrevFrame) { |
830 | 0 | RefPtr<imgFrame> newFrame = new imgFrame; |
831 | 0 | nsresult rv = newFrame->InitForAnimator(mSize, |
832 | 0 | SurfaceFormat::B8G8R8A8); |
833 | 0 | if (NS_FAILED(rv)) { |
834 | 0 | mCompositingPrevFrame.reset(); |
835 | 0 | return false; |
836 | 0 | } |
837 | 0 | |
838 | 0 | mCompositingPrevFrame = newFrame->RawAccessRef(); |
839 | 0 | } |
840 | 0 |
|
841 | 0 | CopyFrameImage(mCompositingFrame.Data(), |
842 | 0 | mCompositingFrame->GetRect(), |
843 | 0 | mCompositingPrevFrame.Data(), |
844 | 0 | mCompositingPrevFrame->GetRect()); |
845 | 0 |
|
846 | 0 | mCompositingPrevFrame->Finish(); |
847 | 0 | } |
848 | 0 |
|
849 | 0 | // blit next frame into it's correct spot |
850 | 0 | DrawFrameTo(aNextFrame.Data(), aNextFrame->GetRect(), |
851 | 0 | aNextFrame.PaletteDataLength(), |
852 | 0 | nextHasAlpha, |
853 | 0 | mCompositingFrame.Data(), |
854 | 0 | mCompositingFrame->GetRect(), |
855 | 0 | aNextFrame->GetBlendMethod(), |
856 | 0 | aNextFrame->GetBlendRect()); |
857 | 0 |
|
858 | 0 | // Tell the image that it is fully 'downloaded'. |
859 | 0 | mCompositingFrame->Finish(); |
860 | 0 |
|
861 | 0 | mLastCompositedFrameIndex = int32_t(aNextFrameIndex); |
862 | 0 |
|
863 | 0 | return true; |
864 | 0 | } |
865 | | |
866 | | //****************************************************************************** |
867 | | // Fill aFrame with black. Does also clears the mask. |
868 | | void |
869 | | FrameAnimator::ClearFrame(uint8_t* aFrameData, const IntRect& aFrameRect) |
870 | 0 | { |
871 | 0 | if (!aFrameData) { |
872 | 0 | return; |
873 | 0 | } |
874 | 0 | |
875 | 0 | memset(aFrameData, 0, aFrameRect.Width() * aFrameRect.Height() * 4); |
876 | 0 | } |
877 | | |
878 | | //****************************************************************************** |
879 | | void |
880 | | FrameAnimator::ClearFrame(uint8_t* aFrameData, const IntRect& aFrameRect, |
881 | | const IntRect& aRectToClear) |
882 | 0 | { |
883 | 0 | if (!aFrameData || aFrameRect.Width() <= 0 || aFrameRect.Height() <= 0 || |
884 | 0 | aRectToClear.Width() <= 0 || aRectToClear.Height() <= 0) { |
885 | 0 | return; |
886 | 0 | } |
887 | 0 | |
888 | 0 | IntRect toClear = aFrameRect.Intersect(aRectToClear); |
889 | 0 | if (toClear.IsEmpty()) { |
890 | 0 | return; |
891 | 0 | } |
892 | 0 | |
893 | 0 | uint32_t bytesPerRow = aFrameRect.Width() * 4; |
894 | 0 | for (int row = toClear.Y(); row < toClear.YMost(); ++row) { |
895 | 0 | memset(aFrameData + toClear.X() * 4 + row * bytesPerRow, 0, |
896 | 0 | toClear.Width() * 4); |
897 | 0 | } |
898 | 0 | } |
899 | | |
900 | | //****************************************************************************** |
901 | | // Whether we succeed or fail will not cause a crash, and there's not much |
902 | | // we can do about a failure, so there we don't return a nsresult |
903 | | bool |
904 | | FrameAnimator::CopyFrameImage(const uint8_t* aDataSrc, |
905 | | const IntRect& aRectSrc, |
906 | | uint8_t* aDataDest, |
907 | | const IntRect& aRectDest) |
908 | 0 | { |
909 | 0 | uint32_t dataLengthSrc = aRectSrc.Width() * aRectSrc.Height() * 4; |
910 | 0 | uint32_t dataLengthDest = aRectDest.Width() * aRectDest.Height() * 4; |
911 | 0 |
|
912 | 0 | if (!aDataDest || !aDataSrc || dataLengthSrc != dataLengthDest) { |
913 | 0 | return false; |
914 | 0 | } |
915 | 0 | |
916 | 0 | memcpy(aDataDest, aDataSrc, dataLengthDest); |
917 | 0 |
|
918 | 0 | return true; |
919 | 0 | } |
920 | | |
921 | | nsresult |
922 | | FrameAnimator::DrawFrameTo(const uint8_t* aSrcData, const IntRect& aSrcRect, |
923 | | uint32_t aSrcPaletteLength, bool aSrcHasAlpha, |
924 | | uint8_t* aDstPixels, const IntRect& aDstRect, |
925 | | BlendMethod aBlendMethod, const IntRect& aBlendRect) |
926 | 0 | { |
927 | 0 | NS_ENSURE_ARG_POINTER(aSrcData); |
928 | 0 | NS_ENSURE_ARG_POINTER(aDstPixels); |
929 | 0 |
|
930 | 0 | // According to both AGIF and APNG specs, offsets are unsigned |
931 | 0 | if (aSrcRect.X() < 0 || aSrcRect.Y() < 0) { |
932 | 0 | NS_WARNING("FrameAnimator::DrawFrameTo: negative offsets not allowed"); |
933 | 0 | return NS_ERROR_FAILURE; |
934 | 0 | } |
935 | 0 |
|
936 | 0 | // Outside the destination frame, skip it |
937 | 0 | if ((aSrcRect.X() > aDstRect.Width()) || (aSrcRect.Y() > aDstRect.Height())) { |
938 | 0 | return NS_OK; |
939 | 0 | } |
940 | 0 | |
941 | 0 | if (aSrcPaletteLength) { |
942 | 0 | // Larger than the destination frame, clip it |
943 | 0 | int32_t width = std::min(aSrcRect.Width(), aDstRect.Width() - aSrcRect.X()); |
944 | 0 | int32_t height = std::min(aSrcRect.Height(), aDstRect.Height() - aSrcRect.Y()); |
945 | 0 |
|
946 | 0 | // The clipped image must now fully fit within destination image frame |
947 | 0 | NS_ASSERTION((aSrcRect.X() >= 0) && (aSrcRect.Y() >= 0) && |
948 | 0 | (aSrcRect.X() + width <= aDstRect.Width()) && |
949 | 0 | (aSrcRect.Y() + height <= aDstRect.Height()), |
950 | 0 | "FrameAnimator::DrawFrameTo: Invalid aSrcRect"); |
951 | 0 |
|
952 | 0 | // clipped image size may be smaller than source, but not larger |
953 | 0 | NS_ASSERTION((width <= aSrcRect.Width()) && (height <= aSrcRect.Height()), |
954 | 0 | "FrameAnimator::DrawFrameTo: source must be smaller than dest"); |
955 | 0 |
|
956 | 0 | // Get pointers to image data |
957 | 0 | const uint8_t* srcPixels = aSrcData + aSrcPaletteLength; |
958 | 0 | uint32_t* dstPixels = reinterpret_cast<uint32_t*>(aDstPixels); |
959 | 0 | const uint32_t* colormap = reinterpret_cast<const uint32_t*>(aSrcData); |
960 | 0 |
|
961 | 0 | // Skip to the right offset |
962 | 0 | dstPixels += aSrcRect.X() + (aSrcRect.Y() * aDstRect.Width()); |
963 | 0 | if (!aSrcHasAlpha) { |
964 | 0 | for (int32_t r = height; r > 0; --r) { |
965 | 0 | for (int32_t c = 0; c < width; c++) { |
966 | 0 | dstPixels[c] = colormap[srcPixels[c]]; |
967 | 0 | } |
968 | 0 | // Go to the next row in the source resp. destination image |
969 | 0 | srcPixels += aSrcRect.Width(); |
970 | 0 | dstPixels += aDstRect.Width(); |
971 | 0 | } |
972 | 0 | } else { |
973 | 0 | for (int32_t r = height; r > 0; --r) { |
974 | 0 | for (int32_t c = 0; c < width; c++) { |
975 | 0 | const uint32_t color = colormap[srcPixels[c]]; |
976 | 0 | if (color) { |
977 | 0 | dstPixels[c] = color; |
978 | 0 | } |
979 | 0 | } |
980 | 0 | // Go to the next row in the source resp. destination image |
981 | 0 | srcPixels += aSrcRect.Width(); |
982 | 0 | dstPixels += aDstRect.Width(); |
983 | 0 | } |
984 | 0 | } |
985 | 0 | } else { |
986 | 0 | pixman_image_t* src = |
987 | 0 | pixman_image_create_bits( |
988 | 0 | aSrcHasAlpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8, |
989 | 0 | aSrcRect.Width(), aSrcRect.Height(), |
990 | 0 | reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(aSrcData)), |
991 | 0 | aSrcRect.Width() * 4); |
992 | 0 | if (!src) { |
993 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
994 | 0 | } |
995 | 0 | pixman_image_t* dst = |
996 | 0 | pixman_image_create_bits(PIXMAN_a8r8g8b8, |
997 | 0 | aDstRect.Width(), |
998 | 0 | aDstRect.Height(), |
999 | 0 | reinterpret_cast<uint32_t*>(aDstPixels), |
1000 | 0 | aDstRect.Width() * 4); |
1001 | 0 | if (!dst) { |
1002 | 0 | pixman_image_unref(src); |
1003 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
1004 | 0 | } |
1005 | 0 |
|
1006 | 0 | // XXX(seth): This is inefficient but we'll remove it quite soon when we |
1007 | 0 | // move frame compositing into SurfacePipe. For now we need this because |
1008 | 0 | // RemoveFrameRectFilter has transformed PNG frames with frame rects into |
1009 | 0 | // imgFrame's with no frame rects, but with a region of 0 alpha where the |
1010 | 0 | // frame rect should be. This works really nicely if we're using |
1011 | 0 | // BlendMethod::OVER, but BlendMethod::SOURCE will result in that frame rect |
1012 | 0 | // area overwriting the previous frame, which makes the animation look |
1013 | 0 | // wrong. This quick hack fixes that by first compositing the whle new frame |
1014 | 0 | // with BlendMethod::OVER, and then recopying the area that uses |
1015 | 0 | // BlendMethod::SOURCE if needed. To make this work, the decoder has to |
1016 | 0 | // provide a "blend rect" that tells us where to do this. This is just the |
1017 | 0 | // frame rect, but hidden in a way that makes it invisible to most of the |
1018 | 0 | // system, so we can keep eliminating dependencies on it. |
1019 | 0 | auto op = aBlendMethod == BlendMethod::SOURCE ? PIXMAN_OP_SRC |
1020 | 0 | : PIXMAN_OP_OVER; |
1021 | 0 |
|
1022 | 0 | if (aBlendMethod == BlendMethod::OVER || |
1023 | 0 | (aBlendMethod == BlendMethod::SOURCE && aSrcRect.IsEqualEdges(aBlendRect))) { |
1024 | 0 | // We don't need to do anything clever. (Or, in the case where no blend |
1025 | 0 | // rect was specified, we can't.) |
1026 | 0 | pixman_image_composite32(op, |
1027 | 0 | src, |
1028 | 0 | nullptr, |
1029 | 0 | dst, |
1030 | 0 | 0, 0, |
1031 | 0 | 0, 0, |
1032 | 0 | aSrcRect.X(), aSrcRect.Y(), |
1033 | 0 | aSrcRect.Width(), aSrcRect.Height()); |
1034 | 0 | } else { |
1035 | 0 | // We need to do the OVER followed by SOURCE trick above. |
1036 | 0 | pixman_image_composite32(PIXMAN_OP_OVER, |
1037 | 0 | src, |
1038 | 0 | nullptr, |
1039 | 0 | dst, |
1040 | 0 | 0, 0, |
1041 | 0 | 0, 0, |
1042 | 0 | aSrcRect.X(), aSrcRect.Y(), |
1043 | 0 | aSrcRect.Width(), aSrcRect.Height()); |
1044 | 0 | pixman_image_composite32(PIXMAN_OP_SRC, |
1045 | 0 | src, |
1046 | 0 | nullptr, |
1047 | 0 | dst, |
1048 | 0 | aBlendRect.X(), aBlendRect.Y(), |
1049 | 0 | 0, 0, |
1050 | 0 | aBlendRect.X(), aBlendRect.Y(), |
1051 | 0 | aBlendRect.Width(), aBlendRect.Height()); |
1052 | 0 | } |
1053 | 0 |
|
1054 | 0 | pixman_image_unref(src); |
1055 | 0 | pixman_image_unref(dst); |
1056 | 0 | } |
1057 | 0 |
|
1058 | 0 | return NS_OK; |
1059 | 0 | } |
1060 | | |
1061 | | } // namespace image |
1062 | | } // namespace mozilla |