xref: /aosp_15_r20/external/skia/tools/skottie_ios_app/SkottieViewController.mm (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker// Copyright 2019 Google LLC.
2*c8dee2aaSAndroid Build Coastguard Worker// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
3*c8dee2aaSAndroid Build Coastguard Worker
4*c8dee2aaSAndroid Build Coastguard Worker#include "tools/skottie_ios_app/SkottieViewController.h"
5*c8dee2aaSAndroid Build Coastguard Worker
6*c8dee2aaSAndroid Build Coastguard Worker#include "include/core/SkCanvas.h"
7*c8dee2aaSAndroid Build Coastguard Worker#include "include/core/SkPaint.h"
8*c8dee2aaSAndroid Build Coastguard Worker#include "include/core/SkSurface.h"
9*c8dee2aaSAndroid Build Coastguard Worker#include "modules/skottie/include/Skottie.h"
10*c8dee2aaSAndroid Build Coastguard Worker#include "src/base/SkTime.h"
11*c8dee2aaSAndroid Build Coastguard Worker
12*c8dee2aaSAndroid Build Coastguard Worker#include <cmath>
13*c8dee2aaSAndroid Build Coastguard Worker
14*c8dee2aaSAndroid Build Coastguard Worker////////////////////////////////////////////////////////////////////////////////
15*c8dee2aaSAndroid Build Coastguard Worker
16*c8dee2aaSAndroid Build Coastguard Workerclass SkAnimationDraw {
17*c8dee2aaSAndroid Build Coastguard Workerpublic:
18*c8dee2aaSAndroid Build Coastguard Worker    SkAnimationDraw() = default;
19*c8dee2aaSAndroid Build Coastguard Worker    ~SkAnimationDraw() = default;
20*c8dee2aaSAndroid Build Coastguard Worker
21*c8dee2aaSAndroid Build Coastguard Worker    explicit operator bool() const { return fAnimation != nullptr; }
22*c8dee2aaSAndroid Build Coastguard Worker
23*c8dee2aaSAndroid Build Coastguard Worker    void draw(SkSize size, SkCanvas* canvas) {
24*c8dee2aaSAndroid Build Coastguard Worker        if (size.width() != fSize.width() || size.height() != fSize.height()) {
25*c8dee2aaSAndroid Build Coastguard Worker            // Cache the current matrix; change only if size changes.
26*c8dee2aaSAndroid Build Coastguard Worker            if (fAnimationSize.width() > 0 && fAnimationSize.height() > 0) {
27*c8dee2aaSAndroid Build Coastguard Worker                float scale = std::min(size.width() / fAnimationSize.width(),
28*c8dee2aaSAndroid Build Coastguard Worker                                       size.height() / fAnimationSize.height());
29*c8dee2aaSAndroid Build Coastguard Worker                fMatrix.setScaleTranslate(
30*c8dee2aaSAndroid Build Coastguard Worker                        scale, scale,
31*c8dee2aaSAndroid Build Coastguard Worker                        (size.width()  - fAnimationSize.width()  * scale) * 0.5f,
32*c8dee2aaSAndroid Build Coastguard Worker                        (size.height() - fAnimationSize.height() * scale) * 0.5f);
33*c8dee2aaSAndroid Build Coastguard Worker            } else {
34*c8dee2aaSAndroid Build Coastguard Worker                fMatrix = SkMatrix();
35*c8dee2aaSAndroid Build Coastguard Worker            }
36*c8dee2aaSAndroid Build Coastguard Worker            fSize = size;
37*c8dee2aaSAndroid Build Coastguard Worker        }
38*c8dee2aaSAndroid Build Coastguard Worker        canvas->concat(fMatrix);
39*c8dee2aaSAndroid Build Coastguard Worker        SkRect rect = {0, 0, fAnimationSize.width(), fAnimationSize.height()};
40*c8dee2aaSAndroid Build Coastguard Worker        canvas->drawRect(rect, SkPaint(SkColors::kWhite));
41*c8dee2aaSAndroid Build Coastguard Worker        fAnimation->render(canvas);
42*c8dee2aaSAndroid Build Coastguard Worker    }
43*c8dee2aaSAndroid Build Coastguard Worker
44*c8dee2aaSAndroid Build Coastguard Worker    void load(const void* data, size_t length) {
45*c8dee2aaSAndroid Build Coastguard Worker        skottie::Animation::Builder builder;
46*c8dee2aaSAndroid Build Coastguard Worker        fAnimation = builder.make((const char*)data, (size_t)length);
47*c8dee2aaSAndroid Build Coastguard Worker        fSize = {0, 0};
48*c8dee2aaSAndroid Build Coastguard Worker        fAnimationSize = fAnimation ? fAnimation->size() : SkSize{0, 0};
49*c8dee2aaSAndroid Build Coastguard Worker    }
50*c8dee2aaSAndroid Build Coastguard Worker
51*c8dee2aaSAndroid Build Coastguard Worker    void seek(double time) { if (fAnimation) { fAnimation->seekFrameTime(time, nullptr); } }
52*c8dee2aaSAndroid Build Coastguard Worker
53*c8dee2aaSAndroid Build Coastguard Worker    float duration() { return fAnimation ? fAnimation->duration() : 0; }
54*c8dee2aaSAndroid Build Coastguard Worker
55*c8dee2aaSAndroid Build Coastguard Worker    SkSize size() { return fAnimationSize; }
56*c8dee2aaSAndroid Build Coastguard Worker
57*c8dee2aaSAndroid Build Coastguard Workerprivate:
58*c8dee2aaSAndroid Build Coastguard Worker    sk_sp<skottie::Animation> fAnimation; // owner
59*c8dee2aaSAndroid Build Coastguard Worker    SkSize fSize;
60*c8dee2aaSAndroid Build Coastguard Worker    SkSize fAnimationSize;
61*c8dee2aaSAndroid Build Coastguard Worker    SkMatrix fMatrix;
62*c8dee2aaSAndroid Build Coastguard Worker
63*c8dee2aaSAndroid Build Coastguard Worker    SkAnimationDraw(const SkAnimationDraw&) = delete;
64*c8dee2aaSAndroid Build Coastguard Worker    SkAnimationDraw& operator=(const SkAnimationDraw&) = delete;
65*c8dee2aaSAndroid Build Coastguard Worker};
66*c8dee2aaSAndroid Build Coastguard Worker
67*c8dee2aaSAndroid Build Coastguard Worker////////////////////////////////////////////////////////////////////////////////
68*c8dee2aaSAndroid Build Coastguard Worker
69*c8dee2aaSAndroid Build Coastguard Workerclass SkTimeKeeper {
70*c8dee2aaSAndroid Build Coastguard Workerprivate:
71*c8dee2aaSAndroid Build Coastguard Worker    double fStartTime = 0; // used when running
72*c8dee2aaSAndroid Build Coastguard Worker    float fAnimationMoment = 0; // when paused.
73*c8dee2aaSAndroid Build Coastguard Worker    float fDuration = 0;
74*c8dee2aaSAndroid Build Coastguard Worker    bool fPaused = false;
75*c8dee2aaSAndroid Build Coastguard Worker    bool fStopAtEnd = false;
76*c8dee2aaSAndroid Build Coastguard Worker
77*c8dee2aaSAndroid Build Coastguard Workerpublic:
78*c8dee2aaSAndroid Build Coastguard Worker    void setStopAtEnd(bool s) { fStopAtEnd = s; }
79*c8dee2aaSAndroid Build Coastguard Worker
80*c8dee2aaSAndroid Build Coastguard Worker    float currentTime() {
81*c8dee2aaSAndroid Build Coastguard Worker        if (0 == fDuration) {
82*c8dee2aaSAndroid Build Coastguard Worker            return 0;
83*c8dee2aaSAndroid Build Coastguard Worker        }
84*c8dee2aaSAndroid Build Coastguard Worker        if (fPaused) {
85*c8dee2aaSAndroid Build Coastguard Worker            return fAnimationMoment;
86*c8dee2aaSAndroid Build Coastguard Worker        }
87*c8dee2aaSAndroid Build Coastguard Worker        double time = 1e-9 * (SkTime::GetNSecs() - fStartTime);
88*c8dee2aaSAndroid Build Coastguard Worker        if (fStopAtEnd && time >= fDuration) {
89*c8dee2aaSAndroid Build Coastguard Worker            fPaused = true;
90*c8dee2aaSAndroid Build Coastguard Worker            fAnimationMoment = fDuration;
91*c8dee2aaSAndroid Build Coastguard Worker            return fAnimationMoment;
92*c8dee2aaSAndroid Build Coastguard Worker        }
93*c8dee2aaSAndroid Build Coastguard Worker        return std::fmod(time, fDuration);
94*c8dee2aaSAndroid Build Coastguard Worker    }
95*c8dee2aaSAndroid Build Coastguard Worker
96*c8dee2aaSAndroid Build Coastguard Worker    void setDuration(float d) {
97*c8dee2aaSAndroid Build Coastguard Worker        fDuration = d;
98*c8dee2aaSAndroid Build Coastguard Worker        fStartTime = SkTime::GetNSecs();
99*c8dee2aaSAndroid Build Coastguard Worker        fAnimationMoment = 0;
100*c8dee2aaSAndroid Build Coastguard Worker    }
101*c8dee2aaSAndroid Build Coastguard Worker
102*c8dee2aaSAndroid Build Coastguard Worker    bool paused() const { return fPaused; }
103*c8dee2aaSAndroid Build Coastguard Worker
104*c8dee2aaSAndroid Build Coastguard Worker    float duration() const { return fDuration; }
105*c8dee2aaSAndroid Build Coastguard Worker
106*c8dee2aaSAndroid Build Coastguard Worker    void seek(float seconds) {
107*c8dee2aaSAndroid Build Coastguard Worker        if (fPaused) {
108*c8dee2aaSAndroid Build Coastguard Worker            fAnimationMoment = std::fmod(seconds, fDuration);
109*c8dee2aaSAndroid Build Coastguard Worker        } else {
110*c8dee2aaSAndroid Build Coastguard Worker            fStartTime = SkTime::GetNSecs() - 1e9 * seconds;
111*c8dee2aaSAndroid Build Coastguard Worker        }
112*c8dee2aaSAndroid Build Coastguard Worker    }
113*c8dee2aaSAndroid Build Coastguard Worker
114*c8dee2aaSAndroid Build Coastguard Worker    void togglePaused() {
115*c8dee2aaSAndroid Build Coastguard Worker        if (fPaused) {
116*c8dee2aaSAndroid Build Coastguard Worker            double offset = (fAnimationMoment >= fDuration) ? 0 : -1e9 * fAnimationMoment;
117*c8dee2aaSAndroid Build Coastguard Worker            fStartTime = SkTime::GetNSecs() + offset;
118*c8dee2aaSAndroid Build Coastguard Worker            fPaused = false;
119*c8dee2aaSAndroid Build Coastguard Worker        } else {
120*c8dee2aaSAndroid Build Coastguard Worker            fAnimationMoment = this->currentTime();
121*c8dee2aaSAndroid Build Coastguard Worker            fPaused = true;
122*c8dee2aaSAndroid Build Coastguard Worker        }
123*c8dee2aaSAndroid Build Coastguard Worker    }
124*c8dee2aaSAndroid Build Coastguard Worker};
125*c8dee2aaSAndroid Build Coastguard Worker
126*c8dee2aaSAndroid Build Coastguard Worker////////////////////////////////////////////////////////////////////////////////
127*c8dee2aaSAndroid Build Coastguard Worker
128*c8dee2aaSAndroid Build Coastguard Worker@implementation SkottieViewController {
129*c8dee2aaSAndroid Build Coastguard Worker    SkAnimationDraw fDraw;
130*c8dee2aaSAndroid Build Coastguard Worker    SkTimeKeeper fClock;
131*c8dee2aaSAndroid Build Coastguard Worker}
132*c8dee2aaSAndroid Build Coastguard Worker
133*c8dee2aaSAndroid Build Coastguard Worker- (bool)loadAnimation:(NSData*) data {
134*c8dee2aaSAndroid Build Coastguard Worker    fDraw.load((const void*)[data bytes], (size_t)[data length]);
135*c8dee2aaSAndroid Build Coastguard Worker    fClock.setDuration(fDraw.duration());
136*c8dee2aaSAndroid Build Coastguard Worker    return (bool)fDraw;
137*c8dee2aaSAndroid Build Coastguard Worker}
138*c8dee2aaSAndroid Build Coastguard Worker
139*c8dee2aaSAndroid Build Coastguard Worker- (void)setStopAtEnd:(bool)stop { fClock.setStopAtEnd(stop); }
140*c8dee2aaSAndroid Build Coastguard Worker
141*c8dee2aaSAndroid Build Coastguard Worker- (float)animationDurationSeconds { return fClock.duration(); }
142*c8dee2aaSAndroid Build Coastguard Worker
143*c8dee2aaSAndroid Build Coastguard Worker- (float)currentTime { return fDraw ? fClock.currentTime() : 0; }
144*c8dee2aaSAndroid Build Coastguard Worker
145*c8dee2aaSAndroid Build Coastguard Worker- (void)seek:(float)seconds {
146*c8dee2aaSAndroid Build Coastguard Worker    if (fDraw) {
147*c8dee2aaSAndroid Build Coastguard Worker        fClock.seek(seconds);
148*c8dee2aaSAndroid Build Coastguard Worker    }
149*c8dee2aaSAndroid Build Coastguard Worker}
150*c8dee2aaSAndroid Build Coastguard Worker
151*c8dee2aaSAndroid Build Coastguard Worker- (CGSize)size { return {(CGFloat)fDraw.size().width(), (CGFloat)fDraw.size().height()}; }
152*c8dee2aaSAndroid Build Coastguard Worker
153*c8dee2aaSAndroid Build Coastguard Worker- (bool)togglePaused {
154*c8dee2aaSAndroid Build Coastguard Worker    fClock.togglePaused();
155*c8dee2aaSAndroid Build Coastguard Worker    return fClock.paused();
156*c8dee2aaSAndroid Build Coastguard Worker}
157*c8dee2aaSAndroid Build Coastguard Worker
158*c8dee2aaSAndroid Build Coastguard Worker- (bool)isPaused { return fClock.paused(); }
159*c8dee2aaSAndroid Build Coastguard Worker
160*c8dee2aaSAndroid Build Coastguard Worker- (void)draw:(CGRect)rect toCanvas:(SkCanvas*)canvas atSize:(CGSize)size {
161*c8dee2aaSAndroid Build Coastguard Worker    // TODO(halcanary): Use the rect and the InvalidationController to speed up rendering.
162*c8dee2aaSAndroid Build Coastguard Worker    if (rect.size.width > 0 && rect.size.height > 0 && fDraw && canvas) {
163*c8dee2aaSAndroid Build Coastguard Worker        if (!fClock.paused()) {
164*c8dee2aaSAndroid Build Coastguard Worker            fDraw.seek(fClock.currentTime());
165*c8dee2aaSAndroid Build Coastguard Worker        }
166*c8dee2aaSAndroid Build Coastguard Worker        fDraw.draw(SkSize{(float)size.width, (float)size.height}, canvas);
167*c8dee2aaSAndroid Build Coastguard Worker    }
168*c8dee2aaSAndroid Build Coastguard Worker}
169*c8dee2aaSAndroid Build Coastguard Worker
170*c8dee2aaSAndroid Build Coastguard Worker@end
171