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