1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "AnimatedImageDrawable.h"
18 #ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread
19 #include "AnimatedImageThread.h"
20 #endif
21
22 #include <gui/TraceUtils.h>
23 #include "pipeline/skia/SkiaUtils.h"
24
25 #include <SkPicture.h>
26 #include <SkRefCnt.h>
27
28 #include <optional>
29
30 namespace android {
31
AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage,size_t bytesUsed,SkEncodedImageFormat format)32 AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed,
33 SkEncodedImageFormat format)
34 : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed), mFormat(format) {
35 mTimeToShowNextSnapshot = ms2ns(currentFrameDuration());
36 setStagingBounds(mSkAnimatedImage->getBounds());
37 }
38
syncProperties()39 void AnimatedImageDrawable::syncProperties() {
40 mProperties = mStagingProperties;
41 }
42
start()43 bool AnimatedImageDrawable::start() {
44 if (mRunning) {
45 return false;
46 }
47
48 mStarting = true;
49
50 mRunning = true;
51 return true;
52 }
53
stop()54 bool AnimatedImageDrawable::stop() {
55 bool wasRunning = mRunning;
56 mRunning = false;
57 return wasRunning;
58 }
59
isRunning()60 bool AnimatedImageDrawable::isRunning() {
61 return mRunning;
62 }
63
nextSnapshotReady() const64 bool AnimatedImageDrawable::nextSnapshotReady() const {
65 return mNextSnapshot.valid() &&
66 mNextSnapshot.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
67 }
68
69 // Only called on the RenderThread while UI thread is locked.
isDirty(nsecs_t * outDelay)70 bool AnimatedImageDrawable::isDirty(nsecs_t* outDelay) {
71 *outDelay = 0;
72 const nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
73 const nsecs_t lastWallTime = mLastWallTime;
74
75 mLastWallTime = currentTime;
76 if (!mRunning) {
77 return false;
78 }
79
80 std::unique_lock lock{mSwapLock};
81 mCurrentTime += currentTime - lastWallTime;
82
83 if (!mNextSnapshot.valid()) {
84 // Need to trigger onDraw in order to start decoding the next frame.
85 *outDelay = mTimeToShowNextSnapshot - mCurrentTime;
86 return true;
87 }
88
89 if (mTimeToShowNextSnapshot > mCurrentTime) {
90 *outDelay = mTimeToShowNextSnapshot - mCurrentTime;
91 } else if (nextSnapshotReady()) {
92 // We have not yet updated mTimeToShowNextSnapshot. Read frame duration
93 // directly from mSkAnimatedImage.
94 lock.unlock();
95 std::unique_lock imageLock{mImageLock};
96 *outDelay = ms2ns(currentFrameDuration());
97 return true;
98 } else {
99 // The next snapshot has not yet been decoded, but we've already passed
100 // time to draw it. There's not a good way to know when decoding will
101 // finish, so request an update immediately.
102 *outDelay = 0;
103 }
104
105 return false;
106 }
107
108 // Only called on the AnimatedImageThread.
decodeNextFrame()109 AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() {
110 Snapshot snap;
111 {
112 std::unique_lock lock{mImageLock};
113 snap.mDurationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame());
114 snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
115 }
116
117 return snap;
118 }
119
120 // Only called on the AnimatedImageThread.
reset()121 AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() {
122 Snapshot snap;
123 {
124 std::unique_lock lock{mImageLock};
125 mSkAnimatedImage->reset();
126 snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
127 snap.mDurationMS = currentFrameDuration();
128 }
129
130 return snap;
131 }
132
133 // Update the matrix to map from the intrinsic bounds of the SkAnimatedImage to
134 // the bounds specified by Drawable#setBounds.
handleBounds(SkMatrix * matrix,const SkRect & intrinsicBounds,const SkRect & bounds)135 static void handleBounds(SkMatrix* matrix, const SkRect& intrinsicBounds, const SkRect& bounds) {
136 matrix->preTranslate(bounds.left(), bounds.top());
137 matrix->preScale(bounds.width() / intrinsicBounds.width(),
138 bounds.height() / intrinsicBounds.height());
139 }
140
141 // Only called on the RenderThread.
onDraw(SkCanvas * canvas)142 void AnimatedImageDrawable::onDraw(SkCanvas* canvas) {
143 // Store the matrix used to handle bounds and mirroring separate from the
144 // canvas. We may need to invert the matrix to determine the proper bounds
145 // to pass to saveLayer, and this matrix (as opposed to, potentially, the
146 // canvas' matrix) only uses scale and translate, so it must be invertible.
147 SkMatrix matrix;
148 SkAutoCanvasRestore acr(canvas, true);
149 handleBounds(&matrix, mSkAnimatedImage->getBounds(), mProperties.mBounds);
150
151 if (mProperties.mMirrored) {
152 matrix.preTranslate(mSkAnimatedImage->getBounds().width(), 0);
153 matrix.preScale(-1, 1);
154 }
155
156 std::optional<SkPaint> lazyPaint;
157 if (mProperties.mAlpha != SK_AlphaOPAQUE || mProperties.mColorFilter.get()) {
158 lazyPaint.emplace();
159 lazyPaint->setAlpha(mProperties.mAlpha);
160 lazyPaint->setColorFilter(mProperties.mColorFilter);
161 }
162
163 canvas->concat(matrix);
164
165 const bool starting = mStarting;
166 mStarting = false;
167
168 const bool drawDirectly = !mSnapshot.mPic;
169 if (drawDirectly) {
170 // The image is not animating, and never was. Draw directly from
171 // mSkAnimatedImage.
172 if (lazyPaint) {
173 SkMatrix inverse;
174 (void) matrix.invert(&inverse);
175 SkRect r = mProperties.mBounds;
176 inverse.mapRect(&r);
177 canvas->saveLayer(r, &*lazyPaint);
178 }
179
180 std::unique_lock lock{mImageLock};
181 mSkAnimatedImage->draw(canvas);
182 if (!mRunning) {
183 return;
184 }
185 } else if (starting) {
186 // The image has animated, and now is being reset. Queue up the first
187 // frame, but keep showing the current frame until the first is ready.
188 #ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread
189 auto& thread = uirenderer::AnimatedImageThread::getInstance();
190 mNextSnapshot = thread.reset(sk_ref_sp(this));
191 #endif
192 }
193
194 bool finalFrame = false;
195 if (mRunning && nextSnapshotReady()) {
196 std::unique_lock lock{mSwapLock};
197 if (mCurrentTime >= mTimeToShowNextSnapshot) {
198 mSnapshot = mNextSnapshot.get();
199 const nsecs_t timeToShowCurrentSnap = mTimeToShowNextSnapshot;
200 if (mSnapshot.mDurationMS == SkAnimatedImage::kFinished) {
201 finalFrame = true;
202 mRunning = false;
203 } else {
204 mTimeToShowNextSnapshot += ms2ns(mSnapshot.mDurationMS);
205 if (mCurrentTime >= mTimeToShowNextSnapshot) {
206 // This would mean showing the current frame very briefly. It's
207 // possible that not being displayed for a time resulted in
208 // mCurrentTime being far ahead. Prevent showing many frames
209 // rapidly by going back to the beginning of this frame time.
210 mCurrentTime = timeToShowCurrentSnap;
211 }
212 }
213 }
214 }
215
216 if (mRunning && !mNextSnapshot.valid()) {
217 #ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread
218 auto& thread = uirenderer::AnimatedImageThread::getInstance();
219 mNextSnapshot = thread.decodeNextFrame(sk_ref_sp(this));
220 #endif
221 }
222
223 if (!drawDirectly) {
224 // No other thread will modify mCurrentSnap so this should be safe to
225 // use without locking.
226 canvas->drawPicture(mSnapshot.mPic, nullptr, lazyPaint ? &*lazyPaint : nullptr);
227 }
228
229 if (finalFrame) {
230 if (mEndListener) {
231 mEndListener->onAnimationEnd();
232 }
233 }
234 }
235
drawStaging(SkCanvas * canvas)236 int AnimatedImageDrawable::drawStaging(SkCanvas* canvas) {
237 // Store the matrix used to handle bounds and mirroring separate from the
238 // canvas. We may need to invert the matrix to determine the proper bounds
239 // to pass to saveLayer, and this matrix (as opposed to, potentially, the
240 // canvas' matrix) only uses scale and translate, so it must be invertible.
241 SkMatrix matrix;
242 SkAutoCanvasRestore acr(canvas, true);
243 handleBounds(&matrix, mSkAnimatedImage->getBounds(), mStagingProperties.mBounds);
244
245 if (mStagingProperties.mMirrored) {
246 matrix.preTranslate(mSkAnimatedImage->getBounds().width(), 0);
247 matrix.preScale(-1, 1);
248 }
249
250 canvas->concat(matrix);
251
252 if (mStagingProperties.mAlpha != SK_AlphaOPAQUE || mStagingProperties.mColorFilter.get()) {
253 SkPaint paint;
254 paint.setAlpha(mStagingProperties.mAlpha);
255 paint.setColorFilter(mStagingProperties.mColorFilter);
256
257 SkMatrix inverse;
258 (void) matrix.invert(&inverse);
259 SkRect r = mStagingProperties.mBounds;
260 inverse.mapRect(&r);
261 canvas->saveLayer(r, &paint);
262 }
263
264 if (!mRunning) {
265 // Continue drawing the current frame, and return 0 to indicate no need
266 // to redraw.
267 std::unique_lock lock{mImageLock};
268 canvas->drawDrawable(mSkAnimatedImage.get());
269 return 0;
270 }
271
272 if (mStarting) {
273 mStarting = false;
274 int durationMS = 0;
275 {
276 std::unique_lock lock{mImageLock};
277 mSkAnimatedImage->reset();
278 durationMS = currentFrameDuration();
279 }
280 {
281 std::unique_lock lock{mSwapLock};
282 mLastWallTime = 0;
283 // The current time will be added later, below.
284 mTimeToShowNextSnapshot = ms2ns(durationMS);
285 }
286 }
287
288 bool update = false;
289 {
290 const nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
291 std::unique_lock lock{mSwapLock};
292 // mLastWallTime starts off at 0. If it is still 0, just update it to
293 // the current time and avoid updating
294 if (mLastWallTime == 0) {
295 mCurrentTime = currentTime;
296 // mTimeToShowNextSnapshot is already set to the duration of the
297 // first frame.
298 mTimeToShowNextSnapshot += currentTime;
299 } else if (mRunning) {
300 mCurrentTime += currentTime - mLastWallTime;
301 update = mCurrentTime >= mTimeToShowNextSnapshot;
302 }
303 mLastWallTime = currentTime;
304 }
305
306 int durationMS = 0;
307 {
308 std::unique_lock lock{mImageLock};
309 if (update) {
310 durationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame());
311 }
312
313 canvas->drawDrawable(mSkAnimatedImage.get());
314 }
315
316 std::unique_lock lock{mSwapLock};
317 if (update) {
318 if (durationMS == SkAnimatedImage::kFinished) {
319 mRunning = false;
320 return SkAnimatedImage::kFinished;
321 }
322
323 const nsecs_t timeToShowCurrentSnapshot = mTimeToShowNextSnapshot;
324 mTimeToShowNextSnapshot += ms2ns(durationMS);
325 if (mCurrentTime >= mTimeToShowNextSnapshot) {
326 // As in onDraw, prevent speedy catch-up behavior.
327 mCurrentTime = timeToShowCurrentSnapshot;
328 }
329 }
330
331 return ns2ms(mTimeToShowNextSnapshot - mCurrentTime);
332 }
333
onGetBounds()334 SkRect AnimatedImageDrawable::onGetBounds() {
335 // This must return a bounds that is valid for all possible states,
336 // including if e.g. the client calls setBounds.
337 return SkRectMakeLargest();
338 }
339
adjustFrameDuration(int durationMs)340 int AnimatedImageDrawable::adjustFrameDuration(int durationMs) {
341 if (durationMs == SkAnimatedImage::kFinished) {
342 return SkAnimatedImage::kFinished;
343 }
344
345 if (mFormat == SkEncodedImageFormat::kGIF) {
346 // Match Chrome & Firefox behavior that gifs with a duration <= 10ms is bumped to 100ms
347 return durationMs <= 10 ? 100 : durationMs;
348 }
349 return durationMs;
350 }
351
currentFrameDuration()352 int AnimatedImageDrawable::currentFrameDuration() {
353 return adjustFrameDuration(mSkAnimatedImage->currentFrameDuration());
354 }
355
356 } // namespace android
357