Hal Canary | 118df7c | 2019-12-18 16:26:19 -0500 | [diff] [blame] | 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 | |
| 16 | class SkAnimationDraw { |
| 17 | public: |
| 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 | |
| 57 | private: |
| 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 | |
| 69 | class SkTimeKeeper { |
| 70 | private: |
| 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 | |
| 77 | public: |
| 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 |