• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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