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