1 /*
2 * Copyright 2019 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/core/SkCanvas.h"
9 #include "include/core/SkImage.h"
10 #include "include/core/SkString.h"
11 #include "include/core/SkTypes.h"
12 #include "modules/skottie/include/Skottie.h"
13 #include "modules/sksg/include/SkSGInvalidationController.h"
14
15 #include <string>
16 #include <vector>
17
18 #include <emscripten.h>
19 #include <emscripten/bind.h>
20 #include "modules/canvaskit/WasmAliases.h"
21
22 #if SK_INCLUDE_MANAGED_SKOTTIE
23 #include "modules/skottie/include/SkottieProperty.h"
24 #include "modules/skottie/utils/SkottieUtils.h"
25 #include "modules/skresources/include/SkResources.h"
26 #endif // SK_INCLUDE_MANAGED_SKOTTIE
27
28 using namespace emscripten;
29
30 #if SK_INCLUDE_MANAGED_SKOTTIE
31 namespace {
32
33 class SkottieAssetProvider : public skottie::ResourceProvider {
34 public:
35 ~SkottieAssetProvider() override = default;
36
37 // Tried using a map, but that gave strange errors like
38 // https://emscripten.org/docs/porting/guidelines/function_pointer_issues.html
39 // Not entirely sure why, but perhaps the iterator in the map was
40 // confusing enscripten.
41 using AssetVec = std::vector<std::pair<SkString, sk_sp<SkData>>>;
42
Make(AssetVec assets)43 static sk_sp<SkottieAssetProvider> Make(AssetVec assets) {
44 if (assets.empty()) {
45 return nullptr;
46 }
47
48 return sk_sp<SkottieAssetProvider>(new SkottieAssetProvider(std::move(assets)));
49 }
50
loadImageAsset(const char[],const char name[],const char[]) const51 sk_sp<skottie::ImageAsset> loadImageAsset(const char[] /* path */,
52 const char name[],
53 const char[] /* id */) const override {
54 // For CK/Skottie we ignore paths & IDs, and identify images based solely on name.
55 if (auto data = this->findAsset(name)) {
56 return skresources::MultiFrameImageAsset::Make(std::move(data));
57 }
58
59 return nullptr;
60 }
61
loadFont(const char name[],const char[]) const62 sk_sp<SkData> loadFont(const char name[], const char[] /* url */) const override {
63 // Same as images paths, we ignore font URLs.
64 return this->findAsset(name);
65 }
66
67 private:
SkottieAssetProvider(AssetVec assets)68 explicit SkottieAssetProvider(AssetVec assets) : fAssets(std::move(assets)) {}
69
findAsset(const char name[]) const70 sk_sp<SkData> findAsset(const char name[]) const {
71 for (const auto& asset : fAssets) {
72 if (asset.first.equals(name)) {
73 return asset.second;
74 }
75 }
76
77 SkDebugf("Could not find %s\n", name);
78 return nullptr;
79 }
80
81 const AssetVec fAssets;
82 };
83
84 class ManagedAnimation final : public SkRefCnt {
85 public:
Make(const std::string & json,sk_sp<skottie::ResourceProvider> rp)86 static sk_sp<ManagedAnimation> Make(const std::string& json,
87 sk_sp<skottie::ResourceProvider> rp) {
88 auto mgr = std::make_unique<skottie_utils::CustomPropertyManager>();
89 auto animation = skottie::Animation::Builder()
90 .setMarkerObserver(mgr->getMarkerObserver())
91 .setPropertyObserver(mgr->getPropertyObserver())
92 .setResourceProvider(rp)
93 .make(json.c_str(), json.size());
94
95 return animation
96 ? sk_sp<ManagedAnimation>(new ManagedAnimation(std::move(animation), std::move(mgr)))
97 : nullptr;
98 }
99
100 ~ManagedAnimation() override = default;
101
102 // skottie::Animation API
render(SkCanvas * canvas) const103 void render(SkCanvas* canvas) const { fAnimation->render(canvas, nullptr); }
render(SkCanvas * canvas,const SkRect & dst) const104 void render(SkCanvas* canvas, const SkRect& dst) const { fAnimation->render(canvas, &dst); }
105 // Returns a damage rect.
seek(SkScalar t)106 SkRect seek(SkScalar t) {
107 sksg::InvalidationController ic;
108 fAnimation->seek(t, &ic);
109 return ic.bounds();
110 }
111 // Returns a damage rect.
seekFrame(double t)112 SkRect seekFrame(double t) {
113 sksg::InvalidationController ic;
114 fAnimation->seekFrame(t, &ic);
115 return ic.bounds();
116 }
duration() const117 double duration() const { return fAnimation->duration(); }
fps() const118 double fps() const { return fAnimation->fps(); }
size() const119 const SkSize& size() const { return fAnimation->size(); }
version() const120 std::string version() const { return std::string(fAnimation->version().c_str()); }
121
122 // CustomPropertyManager API
getColorProps() const123 JSArray getColorProps() const {
124 JSArray props = emscripten::val::array();
125
126 for (const auto& cp : fPropMgr->getColorProps()) {
127 JSObject prop = emscripten::val::object();
128 prop.set("key", cp);
129 prop.set("value", fPropMgr->getColor(cp));
130 props.call<void>("push", prop);
131 }
132
133 return props;
134 }
135
getOpacityProps() const136 JSArray getOpacityProps() const {
137 JSArray props = emscripten::val::array();
138
139 for (const auto& op : fPropMgr->getOpacityProps()) {
140 JSObject prop = emscripten::val::object();
141 prop.set("key", op);
142 prop.set("value", fPropMgr->getOpacity(op));
143 props.call<void>("push", prop);
144 }
145
146 return props;
147 }
148
setColor(const std::string & key,SkColor c)149 bool setColor(const std::string& key, SkColor c) {
150 return fPropMgr->setColor(key, c);
151 }
152
setOpacity(const std::string & key,float o)153 bool setOpacity(const std::string& key, float o) {
154 return fPropMgr->setOpacity(key, o);
155 }
156
getMarkers() const157 JSArray getMarkers() const {
158 JSArray markers = emscripten::val::array();
159 for (const auto& m : fPropMgr->markers()) {
160 JSObject marker = emscripten::val::object();
161 marker.set("name", m.name);
162 marker.set("t0" , m.t0);
163 marker.set("t1" , m.t1);
164 markers.call<void>("push", marker);
165 }
166 return markers;
167 }
168
169 private:
ManagedAnimation(sk_sp<skottie::Animation> animation,std::unique_ptr<skottie_utils::CustomPropertyManager> propMgr)170 ManagedAnimation(sk_sp<skottie::Animation> animation,
171 std::unique_ptr<skottie_utils::CustomPropertyManager> propMgr)
172 : fAnimation(std::move(animation))
173 , fPropMgr(std::move(propMgr)) {}
174
175 sk_sp<skottie::Animation> fAnimation;
176 std::unique_ptr<skottie_utils::CustomPropertyManager> fPropMgr;
177 };
178
179 } // anonymous ns
180 #endif // SK_INCLUDE_MANAGED_SKOTTIE
181
EMSCRIPTEN_BINDINGS(Skottie)182 EMSCRIPTEN_BINDINGS(Skottie) {
183 // Animation things (may eventually go in own library)
184 class_<skottie::Animation>("Animation")
185 .smart_ptr<sk_sp<skottie::Animation>>("sk_sp<Animation>")
186 .function("version", optional_override([](skottie::Animation& self)->std::string {
187 return std::string(self.version().c_str());
188 }))
189 .function("size" , &skottie::Animation::size)
190 .function("duration", &skottie::Animation::duration)
191 .function("fps" , &skottie::Animation::fps)
192 .function("seek", optional_override([](skottie::Animation& self, SkScalar t)->void {
193 self.seek(t);
194 }))
195 .function("seekFrame", optional_override([](skottie::Animation& self, double t)->void {
196 self.seekFrame(t);
197 }))
198 .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas)->void {
199 self.render(canvas, nullptr);
200 }), allow_raw_pointers())
201 .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas,
202 const SkRect r)->void {
203 self.render(canvas, &r);
204 }), allow_raw_pointers());
205
206 function("MakeAnimation", optional_override([](std::string json)->sk_sp<skottie::Animation> {
207 return skottie::Animation::Make(json.c_str(), json.length());
208 }));
209 constant("skottie", true);
210
211 #if SK_INCLUDE_MANAGED_SKOTTIE
212 class_<ManagedAnimation>("ManagedAnimation")
213 .smart_ptr<sk_sp<ManagedAnimation>>("sk_sp<ManagedAnimation>")
214 .function("version" , &ManagedAnimation::version)
215 .function("size" , &ManagedAnimation::size)
216 .function("duration" , &ManagedAnimation::duration)
217 .function("fps" , &ManagedAnimation::fps)
218 .function("seek" , &ManagedAnimation::seek)
219 .function("seekFrame" , &ManagedAnimation::seekFrame)
220 .function("render" , select_overload<void(SkCanvas*) const>(&ManagedAnimation::render), allow_raw_pointers())
221 .function("render" , select_overload<void(SkCanvas*, const SkRect&) const>
222 (&ManagedAnimation::render), allow_raw_pointers())
223 .function("setColor" , &ManagedAnimation::setColor)
224 .function("setOpacity", &ManagedAnimation::setOpacity)
225 .function("getMarkers", &ManagedAnimation::getMarkers)
226 .function("getColorProps" , &ManagedAnimation::getColorProps)
227 .function("getOpacityProps", &ManagedAnimation::getOpacityProps);
228
229 function("_MakeManagedAnimation", optional_override([](std::string json,
230 size_t assetCount,
231 uintptr_t /* char** */ nptr,
232 uintptr_t /* uint8_t** */ dptr,
233 uintptr_t /* size_t* */ sptr)
234 ->sk_sp<ManagedAnimation> {
235 // See the comment in canvaskit_bindings.cpp about the use of uintptr_t
236 const auto assetNames = reinterpret_cast<char** >(nptr);
237 const auto assetDatas = reinterpret_cast<uint8_t**>(dptr);
238 const auto assetSizes = reinterpret_cast<size_t* >(sptr);
239
240 SkottieAssetProvider::AssetVec assets;
241 assets.reserve(assetCount);
242
243 for (size_t i = 0; i < assetCount; i++) {
244 auto name = SkString(assetNames[i]);
245 auto bytes = SkData::MakeFromMalloc(assetDatas[i], assetSizes[i]);
246 assets.push_back(std::make_pair(std::move(name), std::move(bytes)));
247 }
248
249 return ManagedAnimation::Make(json,
250 skresources::DataURIResourceProviderProxy::Make(
251 SkottieAssetProvider::Make(std::move(assets))));
252 }));
253 constant("managed_skottie", true);
254 #endif // SK_INCLUDE_MANAGED_SKOTTIE
255 }
256