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