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