/src/mozilla-central/image/FrameAnimator.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
2 | | * |
3 | | * This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #ifndef mozilla_image_FrameAnimator_h |
8 | | #define mozilla_image_FrameAnimator_h |
9 | | |
10 | | #include "mozilla/Maybe.h" |
11 | | #include "mozilla/MemoryReporting.h" |
12 | | #include "mozilla/TimeStamp.h" |
13 | | #include "gfxTypes.h" |
14 | | #include "imgFrame.h" |
15 | | #include "nsCOMPtr.h" |
16 | | #include "nsRect.h" |
17 | | #include "SurfaceCache.h" |
18 | | #include "gfxPrefs.h" |
19 | | |
20 | | namespace mozilla { |
21 | | namespace image { |
22 | | |
23 | | class RasterImage; |
24 | | class DrawableSurface; |
25 | | |
26 | | class AnimationState |
27 | | { |
28 | | public: |
29 | | explicit AnimationState(uint16_t aAnimationMode) |
30 | | : mFrameCount(0) |
31 | | , mCurrentAnimationFrameIndex(0) |
32 | | , mLoopRemainingCount(-1) |
33 | | , mLoopCount(-1) |
34 | | , mFirstFrameTimeout(FrameTimeout::FromRawMilliseconds(0)) |
35 | | , mAnimationMode(aAnimationMode) |
36 | | , mHasBeenDecoded(false) |
37 | | , mHasRequestedDecode(false) |
38 | | , mIsCurrentlyDecoded(false) |
39 | | , mCompositedFrameInvalid(false) |
40 | | , mCompositedFrameRequested(false) |
41 | | , mDiscarded(false) |
42 | 0 | { } |
43 | | |
44 | | /** |
45 | | * Call this whenever a decode completes, a decode starts, or the image is |
46 | | * discarded. It will update the internal state. Specifically mDiscarded, |
47 | | * mCompositedFrameInvalid, and mIsCurrentlyDecoded. If aAllowInvalidation |
48 | | * is true then returns a rect to invalidate. |
49 | | */ |
50 | | const gfx::IntRect UpdateState(bool aAnimationFinished, |
51 | | RasterImage *aImage, |
52 | | const gfx::IntSize& aSize, |
53 | | bool aAllowInvalidation = true); |
54 | | private: |
55 | | const gfx::IntRect UpdateStateInternal(LookupResult& aResult, |
56 | | bool aAnimationFinished, |
57 | | const gfx::IntSize& aSize, |
58 | | bool aAllowInvalidation = true); |
59 | | |
60 | | public: |
61 | | /** |
62 | | * Call when a decode of this image has been completed. |
63 | | */ |
64 | | void NotifyDecodeComplete(); |
65 | | |
66 | | /** |
67 | | * Returns true if this image has been fully decoded before. |
68 | | */ |
69 | 0 | bool GetHasBeenDecoded() { return mHasBeenDecoded; } |
70 | | |
71 | | /** |
72 | | * Returns true if this image has ever requested a decode before. |
73 | | */ |
74 | 0 | bool GetHasRequestedDecode() { return mHasRequestedDecode; } |
75 | | |
76 | | /** |
77 | | * Returns true if this image has been discarded and a decoded has not yet |
78 | | * been created to redecode it. |
79 | | */ |
80 | 0 | bool IsDiscarded() { return mDiscarded; } |
81 | | |
82 | | /** |
83 | | * Sets the composited frame as valid or invalid. |
84 | | */ |
85 | 0 | void SetCompositedFrameInvalid(bool aInvalid) { |
86 | 0 | MOZ_ASSERT(!aInvalid || gfxPrefs::ImageMemAnimatedDiscardable()); |
87 | 0 | mCompositedFrameInvalid = aInvalid; |
88 | 0 | } |
89 | | |
90 | | /** |
91 | | * Returns whether the composited frame is valid to draw to the screen. |
92 | | */ |
93 | 0 | bool GetCompositedFrameInvalid() { |
94 | 0 | return mCompositedFrameInvalid; |
95 | 0 | } |
96 | | |
97 | | /** |
98 | | * Returns whether the image is currently full decoded.. |
99 | | */ |
100 | 0 | bool GetIsCurrentlyDecoded() { |
101 | 0 | return mIsCurrentlyDecoded; |
102 | 0 | } |
103 | | |
104 | | /** |
105 | | * Call when you need to re-start animating. Ensures we start from the first |
106 | | * frame. |
107 | | */ |
108 | | void ResetAnimation(); |
109 | | |
110 | | /** |
111 | | * The animation mode of the image. |
112 | | * |
113 | | * Constants defined in imgIContainer.idl. |
114 | | */ |
115 | | void SetAnimationMode(uint16_t aAnimationMode); |
116 | | |
117 | | /// Update the number of frames of animation this image is known to have. |
118 | | void UpdateKnownFrameCount(uint32_t aFrameCount); |
119 | | |
120 | | /// @return the number of frames of animation we know about so far. |
121 | 0 | uint32_t KnownFrameCount() const { return mFrameCount; } |
122 | | |
123 | | /// @return the number of frames this animation has, if we know for sure. |
124 | | /// (In other words, if decoding is finished.) Otherwise, returns Nothing(). |
125 | | Maybe<uint32_t> FrameCount() const; |
126 | | |
127 | | /** |
128 | | * Get or set the area of the image to invalidate when we loop around to the |
129 | | * first frame. |
130 | | */ |
131 | | void SetFirstFrameRefreshArea(const gfx::IntRect& aRefreshArea); |
132 | 0 | gfx::IntRect FirstFrameRefreshArea() const { return mFirstFrameRefreshArea; } |
133 | | |
134 | | /** |
135 | | * If the animation frame time has not yet been set, set it to |
136 | | * TimeStamp::Now(). |
137 | | */ |
138 | | void InitAnimationFrameTimeIfNecessary(); |
139 | | |
140 | | /** |
141 | | * Set the animation frame time to @aTime. |
142 | | */ |
143 | | void SetAnimationFrameTime(const TimeStamp& aTime); |
144 | | |
145 | | /** |
146 | | * Set the animation frame time to @aTime if we are configured to stop the |
147 | | * animation when not visible and aTime is later than the current time. |
148 | | * Returns true if the time was updated, else false. |
149 | | */ |
150 | | bool MaybeAdvanceAnimationFrameTime(const TimeStamp& aTime); |
151 | | |
152 | | /** |
153 | | * The current frame we're on, from 0 to (numFrames - 1). |
154 | | */ |
155 | | uint32_t GetCurrentAnimationFrameIndex() const; |
156 | | |
157 | | /* |
158 | | * Set number of times to loop the image. |
159 | | * @note -1 means loop forever. |
160 | | */ |
161 | 0 | void SetLoopCount(int32_t aLoopCount) { mLoopCount = aLoopCount; } |
162 | 0 | int32_t LoopCount() const { return mLoopCount; } |
163 | | |
164 | | /// Set the @aLength of a single loop through this image. |
165 | 0 | void SetLoopLength(FrameTimeout aLength) { mLoopLength = Some(aLength); } |
166 | | |
167 | | /** |
168 | | * @return the length of a single loop of this image. If this image is not |
169 | | * finished decoding, is not animated, or it is animated but does not loop, |
170 | | * returns FrameTimeout::Forever(). |
171 | | */ |
172 | | FrameTimeout LoopLength() const; |
173 | | |
174 | | /* |
175 | | * Get or set the timeout for the first frame. This is used to allow animation |
176 | | * scheduling even before a full decode runs for this image. |
177 | | */ |
178 | 0 | void SetFirstFrameTimeout(FrameTimeout aTimeout) { mFirstFrameTimeout = aTimeout; } |
179 | 0 | FrameTimeout FirstFrameTimeout() const { return mFirstFrameTimeout; } |
180 | | |
181 | | private: |
182 | | friend class FrameAnimator; |
183 | | |
184 | | //! Area of the first frame that needs to be redrawn on subsequent loops. |
185 | | gfx::IntRect mFirstFrameRefreshArea; |
186 | | |
187 | | //! the time that the animation advanced to the current frame |
188 | | TimeStamp mCurrentAnimationFrameTime; |
189 | | |
190 | | //! The number of frames of animation this image has. |
191 | | uint32_t mFrameCount; |
192 | | |
193 | | //! The current frame index we're on, in the range [0, mFrameCount). |
194 | | uint32_t mCurrentAnimationFrameIndex; |
195 | | |
196 | | //! number of loops remaining before animation stops (-1 no stop) |
197 | | int32_t mLoopRemainingCount; |
198 | | |
199 | | //! The total number of loops for the image. |
200 | | int32_t mLoopCount; |
201 | | |
202 | | //! The length of a single loop through this image. |
203 | | Maybe<FrameTimeout> mLoopLength; |
204 | | |
205 | | //! The timeout for the first frame of this image. |
206 | | FrameTimeout mFirstFrameTimeout; |
207 | | |
208 | | //! The animation mode of this image. Constants defined in imgIContainer. |
209 | | uint16_t mAnimationMode; |
210 | | |
211 | | /** |
212 | | * The following four bools (mHasBeenDecoded, mIsCurrentlyDecoded, |
213 | | * mCompositedFrameInvalid, mDiscarded) track the state of the image with |
214 | | * regards to decoding. They all start out false, including mDiscarded, |
215 | | * because we want to treat being discarded differently from "not yet decoded |
216 | | * for the first time". |
217 | | * |
218 | | * (When we are decoding the image for the first time we want to show the |
219 | | * image at the speed of data coming in from the network or the speed |
220 | | * specified in the image file, whichever is slower. But when redecoding we |
221 | | * want to show nothing until the frame for the current time has been |
222 | | * decoded. The prevents the user from seeing the image "fast forward" |
223 | | * to the expected spot.) |
224 | | * |
225 | | * When the image is decoded for the first time mHasBeenDecoded and |
226 | | * mIsCurrentlyDecoded get set to true. When the image is discarded |
227 | | * mIsCurrentlyDecoded gets set to false, and mCompositedFrameInvalid |
228 | | * & mDiscarded get set to true. When we create a decoder to redecode the |
229 | | * image mDiscarded gets set to false. mCompositedFrameInvalid gets set to |
230 | | * false when we are able to advance to the frame that should be showing |
231 | | * for the current time. mIsCurrentlyDecoded gets set to true when the |
232 | | * redecode finishes. |
233 | | */ |
234 | | |
235 | | //! Whether this image has been decoded at least once. |
236 | | bool mHasBeenDecoded; |
237 | | |
238 | | //! Whether this image has ever requested a decode. |
239 | | bool mHasRequestedDecode; |
240 | | |
241 | | //! Whether this image is currently fully decoded. |
242 | | bool mIsCurrentlyDecoded; |
243 | | |
244 | | //! Whether the composited frame is valid to draw to the screen, note that |
245 | | //! the composited frame can exist and be filled with image data but not |
246 | | //! valid to draw to the screen. |
247 | | bool mCompositedFrameInvalid; |
248 | | |
249 | | //! Whether the composited frame was requested from the animator since the |
250 | | //! last time we advanced the animation. |
251 | | bool mCompositedFrameRequested; |
252 | | |
253 | | //! Whether this image is currently discarded. Only set to true after the |
254 | | //! image has been decoded at least once. |
255 | | bool mDiscarded; |
256 | | }; |
257 | | |
258 | | /** |
259 | | * RefreshResult is used to let callers know how the state of the animation |
260 | | * changed during a call to FrameAnimator::RequestRefresh(). |
261 | | */ |
262 | | struct RefreshResult |
263 | | { |
264 | | RefreshResult() |
265 | | : mFrameAdvanced(false) |
266 | | , mAnimationFinished(false) |
267 | 0 | { } |
268 | | |
269 | | /// Merges another RefreshResult's changes into this RefreshResult. |
270 | | void Accumulate(const RefreshResult& aOther) |
271 | 0 | { |
272 | 0 | mFrameAdvanced = mFrameAdvanced || aOther.mFrameAdvanced; |
273 | 0 | mAnimationFinished = mAnimationFinished || aOther.mAnimationFinished; |
274 | 0 | mDirtyRect = mDirtyRect.Union(aOther.mDirtyRect); |
275 | 0 | } |
276 | | |
277 | | // The region of the image that has changed. |
278 | | gfx::IntRect mDirtyRect; |
279 | | |
280 | | // If true, we changed frames at least once. Note that, due to looping, we |
281 | | // could still have ended up on the same frame! |
282 | | bool mFrameAdvanced : 1; |
283 | | |
284 | | // Whether the animation has finished playing. |
285 | | bool mAnimationFinished : 1; |
286 | | }; |
287 | | |
288 | | class FrameAnimator |
289 | | { |
290 | | public: |
291 | | FrameAnimator(RasterImage* aImage, const gfx::IntSize& aSize) |
292 | | : mImage(aImage) |
293 | | , mSize(aSize) |
294 | | , mLastCompositedFrameIndex(-1) |
295 | 0 | { |
296 | 0 | MOZ_COUNT_CTOR(FrameAnimator); |
297 | 0 | } |
298 | | |
299 | | ~FrameAnimator() |
300 | 0 | { |
301 | 0 | MOZ_COUNT_DTOR(FrameAnimator); |
302 | 0 | } |
303 | | |
304 | | /** |
305 | | * Call when you need to re-start animating. Ensures we start from the first |
306 | | * frame. |
307 | | */ |
308 | | void ResetAnimation(AnimationState& aState); |
309 | | |
310 | | /** |
311 | | * Re-evaluate what frame we're supposed to be on, and do whatever blending |
312 | | * is necessary to get us to that frame. |
313 | | * |
314 | | * Returns the result of that blending, including whether the current frame |
315 | | * changed and what the resulting dirty rectangle is. |
316 | | */ |
317 | | RefreshResult RequestRefresh(AnimationState& aState, |
318 | | const TimeStamp& aTime, |
319 | | bool aAnimationFinished); |
320 | | |
321 | | /** |
322 | | * Get the full frame for the current frame of the animation (it may or may |
323 | | * not have required compositing). It may not be available because it hasn't |
324 | | * been decoded yet, in which case we return an empty LookupResult. |
325 | | */ |
326 | | LookupResult GetCompositedFrame(AnimationState& aState); |
327 | | |
328 | | /** |
329 | | * Collect an accounting of the memory occupied by the compositing surfaces we |
330 | | * use during animation playback. All of the actual animation frames are |
331 | | * stored in the SurfaceCache, so we don't need to report them here. |
332 | | */ |
333 | | void CollectSizeOfCompositingSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters, |
334 | | MallocSizeOf aMallocSizeOf) const; |
335 | | |
336 | | private: // methods |
337 | | /** |
338 | | * Advances the animation. Typically, this will advance a single frame, but it |
339 | | * may advance multiple frames. This may happen if we have infrequently |
340 | | * "ticking" refresh drivers (e.g. in background tabs), or extremely short- |
341 | | * lived animation frames. |
342 | | * |
343 | | * @param aTime the time that the animation should advance to. This will |
344 | | * typically be <= TimeStamp::Now(). |
345 | | * |
346 | | * @param aCurrentFrame the currently displayed frame of the animation. If |
347 | | * we advance, it will replace aCurrentFrame with the |
348 | | * new current frame we advanced to. |
349 | | * |
350 | | * @returns a RefreshResult that shows whether the frame was successfully |
351 | | * advanced, and its resulting dirty rect. |
352 | | */ |
353 | | RefreshResult AdvanceFrame(AnimationState& aState, |
354 | | DrawableSurface& aFrames, |
355 | | RawAccessFrameRef& aCurrentFrame, |
356 | | TimeStamp aTime); |
357 | | |
358 | | /** |
359 | | * Get the time the frame we're currently displaying is supposed to end. |
360 | | * |
361 | | * In the error case (like if the requested frame is not currently |
362 | | * decoded), returns None(). |
363 | | */ |
364 | | TimeStamp GetCurrentImgFrameEndTime(AnimationState& aState, |
365 | | FrameTimeout aCurrentTimeout) const; |
366 | | |
367 | | bool DoBlend(const RawAccessFrameRef& aPrevFrame, |
368 | | const RawAccessFrameRef& aNextFrame, |
369 | | uint32_t aNextFrameIndex, |
370 | | gfx::IntRect* aDirtyRect); |
371 | | |
372 | | /** Clears an area of <aFrame> with transparent black. |
373 | | * |
374 | | * @param aFrameData Target Frame data |
375 | | * @param aFrameRect The rectangle of the data pointed ot by aFrameData |
376 | | * |
377 | | * @note Does also clears the transparency mask |
378 | | */ |
379 | | static void ClearFrame(uint8_t* aFrameData, const gfx::IntRect& aFrameRect); |
380 | | |
381 | | //! @overload |
382 | | static void ClearFrame(uint8_t* aFrameData, const gfx::IntRect& aFrameRect, |
383 | | const gfx::IntRect& aRectToClear); |
384 | | |
385 | | //! Copy one frame's image and mask into another |
386 | | static bool CopyFrameImage(const uint8_t* aDataSrc, const gfx::IntRect& aRectSrc, |
387 | | uint8_t* aDataDest, const gfx::IntRect& aRectDest); |
388 | | |
389 | | /** |
390 | | * Draws one frame's image to into another, at the position specified by |
391 | | * aSrcRect. |
392 | | * |
393 | | * @aSrcData the raw data of the current frame being drawn |
394 | | * @aSrcRect the size of the source frame, and the position of that frame in |
395 | | * the composition frame |
396 | | * @aSrcPaletteLength the length (in bytes) of the palette at the beginning |
397 | | * of the source data (0 if image is not paletted) |
398 | | * @aSrcHasAlpha whether the source data represents an image with alpha |
399 | | * @aDstPixels the raw data of the composition frame where the current frame |
400 | | * is drawn into (32-bit ARGB) |
401 | | * @aDstRect the size of the composition frame |
402 | | * @aBlendMethod the blend method for how to blend src on the composition |
403 | | * frame. |
404 | | */ |
405 | | static nsresult DrawFrameTo(const uint8_t* aSrcData, |
406 | | const gfx::IntRect& aSrcRect, |
407 | | uint32_t aSrcPaletteLength, bool aSrcHasAlpha, |
408 | | uint8_t* aDstPixels, const gfx::IntRect& aDstRect, |
409 | | BlendMethod aBlendMethod, |
410 | | const gfx::IntRect& aBlendRect); |
411 | | |
412 | | private: // data |
413 | | //! A weak pointer to our owning image. |
414 | | RasterImage* mImage; |
415 | | |
416 | | //! The intrinsic size of the image. |
417 | | gfx::IntSize mSize; |
418 | | |
419 | | /** For managing blending of frames |
420 | | * |
421 | | * Some animations will use the compositingFrame to composite images |
422 | | * and just hand this back to the caller when it is time to draw the frame. |
423 | | * NOTE: When clearing compositingFrame, remember to set |
424 | | * lastCompositedFrameIndex to -1. Code assume that if |
425 | | * lastCompositedFrameIndex >= 0 then compositingFrame exists. |
426 | | */ |
427 | | RawAccessFrameRef mCompositingFrame; |
428 | | |
429 | | /** the previous composited frame, for DISPOSE_RESTORE_PREVIOUS |
430 | | * |
431 | | * The Previous Frame (all frames composited up to the current) needs to be |
432 | | * stored in cases where the image specifies it wants the last frame back |
433 | | * when it's done with the current frame. |
434 | | */ |
435 | | RawAccessFrameRef mCompositingPrevFrame; |
436 | | |
437 | | //! Track the last composited frame for Optimizations (See DoComposite code) |
438 | | int32_t mLastCompositedFrameIndex; |
439 | | }; |
440 | | |
441 | | } // namespace image |
442 | | } // namespace mozilla |
443 | | |
444 | | #endif // mozilla_image_FrameAnimator_h |