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#include "AnimatedImageThread.h"
19
20#include "utils/TraceUtils.h"
21
22#include <SkPicture.h>
23#include <SkRefCnt.h>
24#include <SkTLazy.h>
25
26namespace android {
27
28AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed)
29        : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed) {
30    mTimeToShowNextSnapshot = ms2ns(mSkAnimatedImage->currentFrameDuration());
31}
32
33void AnimatedImageDrawable::syncProperties() {
34    mProperties = mStagingProperties;
35}
36
37bool AnimatedImageDrawable::start() {
38    if (mRunning) {
39        return false;
40    }
41
42    mStarting = true;
43
44    mRunning = true;
45    return true;
46}
47
48bool AnimatedImageDrawable::stop() {
49    bool wasRunning = mRunning;
50    mRunning = false;
51    return wasRunning;
52}
53
54bool AnimatedImageDrawable::isRunning() {
55    return mRunning;
56}
57
58bool AnimatedImageDrawable::nextSnapshotReady() const {
59    return mNextSnapshot.valid() &&
60           mNextSnapshot.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
61}
62
63// Only called on the RenderThread while UI thread is locked.
64bool AnimatedImageDrawable::isDirty(nsecs_t* outDelay) {
65    *outDelay = 0;
66    const nsecs_t currentTime = systemTime(CLOCK_MONOTONIC);
67    const nsecs_t lastWallTime = mLastWallTime;
68
69    mLastWallTime = currentTime;
70    if (!mRunning) {
71        return false;
72    }
73
74    std::unique_lock lock{mSwapLock};
75    mCurrentTime += currentTime - lastWallTime;
76
77    if (!mNextSnapshot.valid()) {
78        // Need to trigger onDraw in order to start decoding the next frame.
79        *outDelay = mTimeToShowNextSnapshot - mCurrentTime;
80        return true;
81    }
82
83    if (mTimeToShowNextSnapshot > mCurrentTime) {
84        *outDelay = mTimeToShowNextSnapshot - mCurrentTime;
85    } else if (nextSnapshotReady()) {
86        // We have not yet updated mTimeToShowNextSnapshot. Read frame duration
87        // directly from mSkAnimatedImage.
88        lock.unlock();
89        std::unique_lock imageLock{mImageLock};
90        *outDelay = ms2ns(mSkAnimatedImage->currentFrameDuration());
91        return true;
92    } else {
93        // The next snapshot has not yet been decoded, but we've already passed
94        // time to draw it. There's not a good way to know when decoding will
95        // finish, so request an update immediately.
96        *outDelay = 0;
97    }
98
99    return false;
100}
101
102// Only called on the AnimatedImageThread.
103AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() {
104    Snapshot snap;
105    {
106        std::unique_lock lock{mImageLock};
107        snap.mDurationMS = mSkAnimatedImage->decodeNextFrame();
108        snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
109    }
110
111    return snap;
112}
113
114// Only called on the AnimatedImageThread.
115AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() {
116    Snapshot snap;
117    {
118        std::unique_lock lock{mImageLock};
119        mSkAnimatedImage->reset();
120        snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
121        snap.mDurationMS = mSkAnimatedImage->currentFrameDuration();
122    }
123
124    return snap;
125}
126
127// Only called on the RenderThread.
128void AnimatedImageDrawable::onDraw(SkCanvas* canvas) {
129    SkTLazy<SkPaint> lazyPaint;
130    SkAutoCanvasRestore acr(canvas, false);
131    if (mProperties.mAlpha != SK_AlphaOPAQUE || mProperties.mColorFilter.get()) {
132        lazyPaint.init();
133        lazyPaint.get()->setAlpha(mProperties.mAlpha);
134        lazyPaint.get()->setColorFilter(mProperties.mColorFilter);
135        lazyPaint.get()->setFilterQuality(kLow_SkFilterQuality);
136    }
137    if (mProperties.mMirrored) {
138        canvas->save();
139        canvas->translate(mSkAnimatedImage->getBounds().width(), 0);
140        canvas->scale(-1, 1);
141    }
142
143    const bool starting = mStarting;
144    mStarting = false;
145
146    const bool drawDirectly = !mSnapshot.mPic;
147    if (drawDirectly) {
148        // The image is not animating, and never was. Draw directly from
149        // mSkAnimatedImage.
150        if (lazyPaint.isValid()) {
151            canvas->saveLayer(mSkAnimatedImage->getBounds(), lazyPaint.get());
152        }
153
154        std::unique_lock lock{mImageLock};
155        mSkAnimatedImage->draw(canvas);
156        if (!mRunning) {
157            return;
158        }
159    } else if (starting) {
160        // The image has animated, and now is being reset. Queue up the first
161        // frame, but keep showing the current frame until the first is ready.
162        auto& thread = uirenderer::AnimatedImageThread::getInstance();
163        mNextSnapshot = thread.reset(sk_ref_sp(this));
164    }
165
166    bool finalFrame = false;
167    if (mRunning && nextSnapshotReady()) {
168        std::unique_lock lock{mSwapLock};
169        if (mCurrentTime >= mTimeToShowNextSnapshot) {
170            mSnapshot = mNextSnapshot.get();
171            const nsecs_t timeToShowCurrentSnap = mTimeToShowNextSnapshot;
172            if (mSnapshot.mDurationMS == SkAnimatedImage::kFinished) {
173                finalFrame = true;
174                mRunning = false;
175            } else {
176                mTimeToShowNextSnapshot += ms2ns(mSnapshot.mDurationMS);
177                if (mCurrentTime >= mTimeToShowNextSnapshot) {
178                    // This would mean showing the current frame very briefly. It's
179                    // possible that not being displayed for a time resulted in
180                    // mCurrentTime being far ahead. Prevent showing many frames
181                    // rapidly by going back to the beginning of this frame time.
182                    mCurrentTime = timeToShowCurrentSnap;
183                }
184            }
185        }
186    }
187
188    if (mRunning && !mNextSnapshot.valid()) {
189        auto& thread = uirenderer::AnimatedImageThread::getInstance();
190        mNextSnapshot = thread.decodeNextFrame(sk_ref_sp(this));
191    }
192
193    if (!drawDirectly) {
194        // No other thread will modify mCurrentSnap so this should be safe to
195        // use without locking.
196        canvas->drawPicture(mSnapshot.mPic, nullptr, lazyPaint.getMaybeNull());
197    }
198
199    if (finalFrame) {
200        if (mEndListener) {
201            mEndListener->onAnimationEnd();
202        }
203    }
204}
205
206int AnimatedImageDrawable::drawStaging(SkCanvas* canvas) {
207    SkAutoCanvasRestore acr(canvas, false);
208    if (mStagingProperties.mAlpha != SK_AlphaOPAQUE || mStagingProperties.mColorFilter.get()) {
209        SkPaint paint;
210        paint.setAlpha(mStagingProperties.mAlpha);
211        paint.setColorFilter(mStagingProperties.mColorFilter);
212        canvas->saveLayer(mSkAnimatedImage->getBounds(), &paint);
213    }
214    if (mStagingProperties.mMirrored) {
215        canvas->save();
216        canvas->translate(mSkAnimatedImage->getBounds().width(), 0);
217        canvas->scale(-1, 1);
218    }
219
220    if (!mRunning) {
221        // Continue drawing the current frame, and return 0 to indicate no need
222        // to redraw.
223        std::unique_lock lock{mImageLock};
224        canvas->drawDrawable(mSkAnimatedImage.get());
225        return 0;
226    }
227
228    if (mStarting) {
229        mStarting = false;
230        int durationMS = 0;
231        {
232            std::unique_lock lock{mImageLock};
233            mSkAnimatedImage->reset();
234            durationMS = mSkAnimatedImage->currentFrameDuration();
235        }
236        {
237            std::unique_lock lock{mSwapLock};
238            mLastWallTime = 0;
239            // The current time will be added later, below.
240            mTimeToShowNextSnapshot = ms2ns(durationMS);
241        }
242    }
243
244    bool update = false;
245    {
246        const nsecs_t currentTime = systemTime(CLOCK_MONOTONIC);
247        std::unique_lock lock{mSwapLock};
248        // mLastWallTime starts off at 0. If it is still 0, just update it to
249        // the current time and avoid updating
250        if (mLastWallTime == 0) {
251            mCurrentTime = currentTime;
252            // mTimeToShowNextSnapshot is already set to the duration of the
253            // first frame.
254            mTimeToShowNextSnapshot += currentTime;
255        } else if (mRunning) {
256            mCurrentTime += currentTime - mLastWallTime;
257            update = mCurrentTime >= mTimeToShowNextSnapshot;
258        }
259        mLastWallTime = currentTime;
260    }
261
262    int durationMS = 0;
263    {
264        std::unique_lock lock{mImageLock};
265        if (update) {
266            durationMS = mSkAnimatedImage->decodeNextFrame();
267        }
268
269        canvas->drawDrawable(mSkAnimatedImage.get());
270    }
271
272    std::unique_lock lock{mSwapLock};
273    if (update) {
274        if (durationMS == SkAnimatedImage::kFinished) {
275            mRunning = false;
276            return SkAnimatedImage::kFinished;
277        }
278
279        const nsecs_t timeToShowCurrentSnapshot = mTimeToShowNextSnapshot;
280        mTimeToShowNextSnapshot += ms2ns(durationMS);
281        if (mCurrentTime >= mTimeToShowNextSnapshot) {
282            // As in onDraw, prevent speedy catch-up behavior.
283            mCurrentTime = timeToShowCurrentSnapshot;
284        }
285    }
286
287    return ns2ms(mTimeToShowNextSnapshot - mCurrentTime);
288}
289
290}  // namespace android
291