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/WasmCommon.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 // WebTrack wraps a JS object that has a 'seek' method.
34 // Playback logic is kept there.
35 class WebTrack final : public skresources::ExternalTrackAsset {
36 public:
WebTrack(emscripten::val player)37 explicit WebTrack(emscripten::val player) : fPlayer(std::move(player)) {}
38
39 private:
seek(float t)40 void seek(float t) override {
41 fPlayer.call<void>("seek", val(t));
42 }
43
44 const emscripten::val fPlayer;
45 };
46
47 class SkottieAssetProvider : public skottie::ResourceProvider {
48 public:
49 ~SkottieAssetProvider() override = default;
50
51 // Tried using a map, but that gave strange errors like
52 // https://emscripten.org/docs/porting/guidelines/function_pointer_issues.html
53 // Not entirely sure why, but perhaps the iterator in the map was
54 // confusing enscripten.
55 using AssetVec = std::vector<std::pair<SkString, sk_sp<SkData>>>;
56
Make(AssetVec assets,emscripten::val soundMap)57 static sk_sp<SkottieAssetProvider> Make(AssetVec assets, emscripten::val soundMap) {
58 return sk_sp<SkottieAssetProvider>(new SkottieAssetProvider(std::move(assets),
59 std::move(soundMap)));
60 }
61
loadImageAsset(const char[],const char name[],const char[]) const62 sk_sp<skottie::ImageAsset> loadImageAsset(const char[] /* path */,
63 const char name[],
64 const char[] /* id */) const override {
65 // For CK/Skottie we ignore paths & IDs, and identify images based solely on name.
66 if (auto data = this->findAsset(name)) {
67 return skresources::MultiFrameImageAsset::Make(std::move(data));
68 }
69
70 return nullptr;
71 }
72
loadAudioAsset(const char[],const char[],const char id[])73 sk_sp<skresources::ExternalTrackAsset> loadAudioAsset(const char[] /* path */,
74 const char[] /* name */,
75 const char id[]) override {
76 emscripten::val player = this->findSoundAsset(id);
77 if (player.as<bool>()) {
78 return sk_make_sp<WebTrack>(std::move(player));
79 }
80
81 return nullptr;
82 }
83
loadFont(const char name[],const char[]) const84 sk_sp<SkData> loadFont(const char name[], const char[] /* url */) const override {
85 // Same as images paths, we ignore font URLs.
86 return this->findAsset(name);
87 }
88
load(const char[],const char name[]) const89 sk_sp<SkData> load(const char[]/*path*/, const char name[]) const override {
90 // Ignore paths.
91 return this->findAsset(name);
92 }
93
94 private:
SkottieAssetProvider(AssetVec assets,emscripten::val soundMap)95 explicit SkottieAssetProvider(AssetVec assets, emscripten::val soundMap)
96 : fAssets(std::move(assets))
97 , fSoundMap(std::move(soundMap)) {}
98
findAsset(const char name[]) const99 sk_sp<SkData> findAsset(const char name[]) const {
100 for (const auto& asset : fAssets) {
101 if (asset.first.equals(name)) {
102 return asset.second;
103 }
104 }
105
106 SkDebugf("Could not find %s\n", name);
107 return nullptr;
108 }
109
findSoundAsset(const char name[]) const110 emscripten::val findSoundAsset(const char name[]) const {
111 if (fSoundMap.as<bool>() && fSoundMap.hasOwnProperty("getPlayer")) {
112 emscripten::val player = fSoundMap.call<emscripten::val>("getPlayer", val(name));
113 if (player.as<bool>() && player.hasOwnProperty("seek")) {
114 return player;
115 }
116 }
117 return emscripten::val::null();
118 }
119
120 const AssetVec fAssets;
121 const emscripten::val fSoundMap;
122 };
123
124 // Wraps a JS object with 'onError' and 'onWarning' methods.
125 class JSLogger final : public skottie::Logger {
126 public:
Make(emscripten::val logger)127 static sk_sp<JSLogger> Make(emscripten::val logger) {
128 return logger.as<bool>()
129 && logger.hasOwnProperty(kWrnFunc)
130 && logger.hasOwnProperty(kErrFunc)
131 ? sk_sp<JSLogger>(new JSLogger(std::move(logger)))
132 : nullptr;
133 }
134
135 private:
JSLogger(emscripten::val logger)136 explicit JSLogger(emscripten::val logger) : fLogger(std::move(logger)) {}
137
log(Level lvl,const char msg[],const char * json)138 void log(Level lvl, const char msg[], const char* json) override {
139 const auto* func = lvl == Level::kError ? kErrFunc : kWrnFunc;
140 fLogger.call<void>(func, std::string(msg), std::string(json));
141 }
142
143 inline static constexpr char kWrnFunc[] = "onWarning",
144 kErrFunc[] = "onError";
145
146 const emscripten::val fLogger;
147 };
148
149 class ManagedAnimation final : public SkRefCnt {
150 public:
Make(const std::string & json,sk_sp<skottie::ResourceProvider> rp,std::string prop_prefix,emscripten::val logger)151 static sk_sp<ManagedAnimation> Make(const std::string& json,
152 sk_sp<skottie::ResourceProvider> rp,
153 std::string prop_prefix,
154 emscripten::val logger) {
155 auto mgr = std::make_unique<skottie_utils::CustomPropertyManager>(
156 skottie_utils::CustomPropertyManager::Mode::kCollapseProperties,
157 prop_prefix.empty() ? nullptr : prop_prefix.c_str());
158 static constexpr char kInterceptPrefix[] = "__";
159 auto pinterceptor =
160 sk_make_sp<skottie_utils::ExternalAnimationPrecompInterceptor>(rp, kInterceptPrefix);
161 auto animation = skottie::Animation::Builder()
162 .setMarkerObserver(mgr->getMarkerObserver())
163 .setPropertyObserver(mgr->getPropertyObserver())
164 .setResourceProvider(std::move(rp))
165 .setPrecompInterceptor(std::move(pinterceptor))
166 .setLogger(JSLogger::Make(std::move(logger)))
167 .make(json.c_str(), json.size());
168
169 return animation
170 ? sk_sp<ManagedAnimation>(new ManagedAnimation(std::move(animation), std::move(mgr)))
171 : nullptr;
172 }
173
174 ~ManagedAnimation() override = default;
175
176 // skottie::Animation API
render(SkCanvas * canvas,const SkRect * dst) const177 void render(SkCanvas* canvas, const SkRect* dst) const { fAnimation->render(canvas, dst); }
178 // Returns a damage rect.
seek(SkScalar t)179 SkRect seek(SkScalar t) {
180 sksg::InvalidationController ic;
181 fAnimation->seek(t, &ic);
182 return ic.bounds();
183 }
184 // Returns a damage rect.
seekFrame(double t)185 SkRect seekFrame(double t) {
186 sksg::InvalidationController ic;
187 fAnimation->seekFrame(t, &ic);
188 return ic.bounds();
189 }
duration() const190 double duration() const { return fAnimation->duration(); }
fps() const191 double fps() const { return fAnimation->fps(); }
size() const192 const SkSize& size() const { return fAnimation->size(); }
version() const193 std::string version() const { return std::string(fAnimation->version().c_str()); }
194
195 // CustomPropertyManager API
getColorProps() const196 JSArray getColorProps() const {
197 JSArray props = emscripten::val::array();
198
199 for (const auto& cp : fPropMgr->getColorProps()) {
200 JSObject prop = emscripten::val::object();
201 prop.set("key", cp);
202 prop.set("value", fPropMgr->getColor(cp));
203 props.call<void>("push", prop);
204 }
205
206 return props;
207 }
208
getOpacityProps() const209 JSArray getOpacityProps() const {
210 JSArray props = emscripten::val::array();
211
212 for (const auto& op : fPropMgr->getOpacityProps()) {
213 JSObject prop = emscripten::val::object();
214 prop.set("key", op);
215 prop.set("value", fPropMgr->getOpacity(op));
216 props.call<void>("push", prop);
217 }
218
219 return props;
220 }
221
getTextProps() const222 JSArray getTextProps() const {
223 JSArray props = emscripten::val::array();
224
225 for (const auto& key : fPropMgr->getTextProps()) {
226 const auto txt = fPropMgr->getText(key);
227 JSObject txt_val = emscripten::val::object();
228 txt_val.set("text", txt.fText.c_str());
229 txt_val.set("size", txt.fTextSize);
230
231 JSObject prop = emscripten::val::object();
232 prop.set("key", key);
233 prop.set("value", std::move(txt_val));
234
235 props.call<void>("push", prop);
236 }
237
238 return props;
239 }
240
setColor(const std::string & key,SkColor c)241 bool setColor(const std::string& key, SkColor c) {
242 return fPropMgr->setColor(key, c);
243 }
244
setOpacity(const std::string & key,float o)245 bool setOpacity(const std::string& key, float o) {
246 return fPropMgr->setOpacity(key, o);
247 }
248
setText(const std::string & key,std::string text,float size)249 bool setText(const std::string& key, std::string text, float size) {
250 // preserve all other text fields
251 auto t = fPropMgr->getText(key);
252
253 t.fText = SkString(text);
254 t.fTextSize = size;
255
256 return fPropMgr->setText(key, t);
257 }
258
getMarkers() const259 JSArray getMarkers() const {
260 JSArray markers = emscripten::val::array();
261 for (const auto& m : fPropMgr->markers()) {
262 JSObject marker = emscripten::val::object();
263 marker.set("name", m.name);
264 marker.set("t0" , m.t0);
265 marker.set("t1" , m.t1);
266 markers.call<void>("push", marker);
267 }
268 return markers;
269 }
270
271 private:
ManagedAnimation(sk_sp<skottie::Animation> animation,std::unique_ptr<skottie_utils::CustomPropertyManager> propMgr)272 ManagedAnimation(sk_sp<skottie::Animation> animation,
273 std::unique_ptr<skottie_utils::CustomPropertyManager> propMgr)
274 : fAnimation(std::move(animation))
275 , fPropMgr(std::move(propMgr))
276 {}
277
278 const sk_sp<skottie::Animation> fAnimation;
279 const std::unique_ptr<skottie_utils::CustomPropertyManager> fPropMgr;
280 };
281
282 } // anonymous ns
283 #endif // SK_INCLUDE_MANAGED_SKOTTIE
284
EMSCRIPTEN_BINDINGS(Skottie)285 EMSCRIPTEN_BINDINGS(Skottie) {
286 // Animation things (may eventually go in own library)
287 class_<skottie::Animation>("Animation")
288 .smart_ptr<sk_sp<skottie::Animation>>("sk_sp<Animation>")
289 .function("version", optional_override([](skottie::Animation& self)->std::string {
290 return std::string(self.version().c_str());
291 }))
292 .function("_size", optional_override([](skottie::Animation& self,
293 WASMPointerF32 oPtr)->void {
294 SkSize* output = reinterpret_cast<SkSize*>(oPtr);
295 *output = self.size();
296 }))
297 .function("duration", &skottie::Animation::duration)
298 .function("fps" , &skottie::Animation::fps)
299 .function("seek", optional_override([](skottie::Animation& self, SkScalar t)->void {
300 self.seek(t);
301 }))
302 .function("seekFrame", optional_override([](skottie::Animation& self, double t)->void {
303 self.seekFrame(t);
304 }))
305 .function("_render", optional_override([](skottie::Animation& self, SkCanvas* canvas,
306 WASMPointerF32 fPtr)->void {
307 const SkRect* dst = reinterpret_cast<const SkRect*>(fPtr);
308 self.render(canvas, dst);
309 }), allow_raw_pointers());
310
311 function("MakeAnimation", optional_override([](std::string json)->sk_sp<skottie::Animation> {
312 return skottie::Animation::Make(json.c_str(), json.length());
313 }));
314 constant("skottie", true);
315
316 #if SK_INCLUDE_MANAGED_SKOTTIE
317 class_<ManagedAnimation>("ManagedAnimation")
318 .smart_ptr<sk_sp<ManagedAnimation>>("sk_sp<ManagedAnimation>")
319 .function("version" , &ManagedAnimation::version)
320 .function("_size", optional_override([](ManagedAnimation& self,
321 WASMPointerF32 oPtr)->void {
322 SkSize* output = reinterpret_cast<SkSize*>(oPtr);
323 *output = self.size();
324 }))
325 .function("duration" , &ManagedAnimation::duration)
326 .function("fps" , &ManagedAnimation::fps)
327 .function("_render", optional_override([](ManagedAnimation& self, SkCanvas* canvas,
328 WASMPointerF32 fPtr)->void {
329 const SkRect* dst = reinterpret_cast<const SkRect*>(fPtr);
330 self.render(canvas, dst);
331 }), allow_raw_pointers())
332 .function("_seek", optional_override([](ManagedAnimation& self, SkScalar t,
333 WASMPointerF32 fPtr) {
334 SkRect* damageRect = reinterpret_cast<SkRect*>(fPtr);
335 damageRect[0] = self.seek(t);
336 }))
337 .function("_seekFrame", optional_override([](ManagedAnimation& self, double frame,
338 WASMPointerF32 fPtr) {
339 SkRect* damageRect = reinterpret_cast<SkRect*>(fPtr);
340 damageRect[0] = self.seekFrame(frame);
341 }))
342 .function("seekFrame" , &ManagedAnimation::seekFrame)
343 .function("_setColor" , optional_override([](ManagedAnimation& self, const std::string& key, WASMPointerF32 cPtr) {
344 float* fourFloats = reinterpret_cast<float*>(cPtr);
345 SkColor4f color = { fourFloats[0], fourFloats[1], fourFloats[2], fourFloats[3] };
346 return self.setColor(key, color.toSkColor());
347 }))
348 .function("setOpacity", &ManagedAnimation::setOpacity)
349 .function("getMarkers", &ManagedAnimation::getMarkers)
350 .function("getColorProps" , &ManagedAnimation::getColorProps)
351 .function("getOpacityProps", &ManagedAnimation::getOpacityProps)
352 .function("getTextProps" , &ManagedAnimation::getTextProps)
353 .function("setText" , &ManagedAnimation::setText);
354
355 function("_MakeManagedAnimation", optional_override([](std::string json,
356 size_t assetCount,
357 WASMPointerU32 nptr,
358 WASMPointerU32 dptr,
359 WASMPointerU32 sptr,
360 std::string prop_prefix,
361 emscripten::val soundMap,
362 emscripten::val logger)
363 ->sk_sp<ManagedAnimation> {
364 const auto assetNames = reinterpret_cast<char** >(nptr);
365 const auto assetDatas = reinterpret_cast<uint8_t**>(dptr);
366 const auto assetSizes = reinterpret_cast<size_t* >(sptr);
367
368 SkottieAssetProvider::AssetVec assets;
369 assets.reserve(assetCount);
370
371 for (size_t i = 0; i < assetCount; i++) {
372 auto name = SkString(assetNames[i]);
373 auto bytes = SkData::MakeFromMalloc(assetDatas[i], assetSizes[i]);
374 assets.push_back(std::make_pair(std::move(name), std::move(bytes)));
375 }
376
377 return ManagedAnimation::Make(json,
378 skresources::DataURIResourceProviderProxy::Make(
379 SkottieAssetProvider::Make(std::move(assets),
380 std::move(soundMap))),
381 prop_prefix, std::move(logger));
382 }));
383 constant("managed_skottie", true);
384 #endif // SK_INCLUDE_MANAGED_SKOTTIE
385 }
386