• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 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/SkColor.h"
10 #include "include/core/SkImage.h"
11 #include "include/core/SkPaint.h"
12 #include "include/core/SkRect.h"
13 #include "include/core/SkSize.h"
14 #include "include/core/SkString.h"
15 #include "include/core/SkSurface.h"
16 #include "include/core/SkTypes.h"
17 #include "modules/skottie/include/Skottie.h"
18 #include "modules/sksg/include/SkSGInvalidationController.h"
19 
20 #include <string>
21 #include <vector>
22 
23 #include <emscripten.h>
24 #include <emscripten/bind.h>
25 
26 #if SK_INCLUDE_MANAGED_SKOTTIE
27 #include "modules/skottie/utils/SkottieUtils.h"
28 #include "modules/skresources/include/SkResources.h"
29 #endif // SK_INCLUDE_MANAGED_SKOTTIE
30 
31 #ifdef SK_GL
32 #include "include/core/SkImageInfo.h"
33 #include "include/gpu/GrBackendSurface.h"
34 #include "include/gpu/GrDirectContext.h"
35 #include "include/gpu/gl/GrGLInterface.h"
36 #include "include/gpu/gl/GrGLTypes.h"
37 
38 #include <GL/gl.h>
39 #include <emscripten/html5.h>
40 #endif
41 
42 using namespace emscripten;
43 
44 // Self-documenting types
45 using JSArray = emscripten::val;
46 using JSObject = emscripten::val;
47 using JSString = emscripten::val;
48 using SkPathOrNull = emscripten::val;
49 using Uint8Array = emscripten::val;
50 using Float32Array = emscripten::val;
51 
52 #if SK_INCLUDE_MANAGED_SKOTTIE
53 namespace {
54 
55 class SkottieAssetProvider : public skottie::ResourceProvider {
56 public:
57     ~SkottieAssetProvider() override = default;
58 
59     // Tried using a map, but that gave strange errors like
60     // https://emscripten.org/docs/porting/guidelines/function_pointer_issues.html
61     // Not entirely sure why, but perhaps the iterator in the map was
62     // confusing enscripten.
63     using AssetVec = std::vector<std::pair<SkString, sk_sp<SkData>>>;
64 
Make(AssetVec assets)65     static sk_sp<SkottieAssetProvider> Make(AssetVec assets) {
66         if (assets.empty()) {
67             return nullptr;
68         }
69 
70         return sk_sp<SkottieAssetProvider>(new SkottieAssetProvider(std::move(assets)));
71     }
72 
loadImageAsset(const char[],const char name[],const char[]) const73     sk_sp<skottie::ImageAsset> loadImageAsset(const char[] /* path */,
74                                               const char name[],
75                                               const char[] /* id */) const override {
76         // For CK/Skottie we ignore paths & IDs, and identify images based solely on name.
77         if (auto data = this->findAsset(name)) {
78             return skresources::MultiFrameImageAsset::Make(std::move(data));
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 
89 private:
SkottieAssetProvider(AssetVec assets)90     explicit SkottieAssetProvider(AssetVec assets) : fAssets(std::move(assets)) {}
91 
findAsset(const char name[]) const92     sk_sp<SkData> findAsset(const char name[]) const {
93         for (const auto& asset : fAssets) {
94             if (asset.first.equals(name)) {
95                 return asset.second;
96             }
97         }
98 
99         SkDebugf("Could not find %s\n", name);
100         return nullptr;
101     }
102 
103     const AssetVec fAssets;
104 };
105 
106 class ManagedAnimation final : public SkRefCnt {
107 public:
Make(const std::string & json,sk_sp<skottie::ResourceProvider> rp)108     static sk_sp<ManagedAnimation> Make(const std::string& json,
109                                         sk_sp<skottie::ResourceProvider> rp) {
110         auto mgr = std::make_unique<skottie_utils::CustomPropertyManager>();
111         auto animation = skottie::Animation::Builder()
112                             .setMarkerObserver(mgr->getMarkerObserver())
113                             .setPropertyObserver(mgr->getPropertyObserver())
114                             .setResourceProvider(rp)
115                             .make(json.c_str(), json.size());
116 
117         return animation
118             ? sk_sp<ManagedAnimation>(new ManagedAnimation(std::move(animation), std::move(mgr)))
119             : nullptr;
120     }
121 
122     ~ManagedAnimation() override = default;
123 
124     // skottie::Animation API
render(SkCanvas * canvas) const125     void render(SkCanvas* canvas) const { fAnimation->render(canvas, nullptr); }
render(SkCanvas * canvas,const SkRect & dst) const126     void render(SkCanvas* canvas, const SkRect& dst) const { fAnimation->render(canvas, &dst); }
127     // Returns a damage rect.
seek(SkScalar t)128     SkRect seek(SkScalar t) {
129         sksg::InvalidationController ic;
130         fAnimation->seek(t, &ic);
131         return ic.bounds();
132     }
133     // Returns a damage rect.
seekFrame(double t)134     SkRect seekFrame(double t) {
135         sksg::InvalidationController ic;
136         fAnimation->seekFrame(t, &ic);
137         return ic.bounds();
138     }
duration() const139     double duration() const { return fAnimation->duration(); }
fps() const140     double fps() const { return fAnimation->fps(); }
size() const141     const SkSize& size() const { return fAnimation->size(); }
version() const142     std::string version() const { return std::string(fAnimation->version().c_str()); }
143 
144     // CustomPropertyManager API
getColorProps() const145     JSArray getColorProps() const {
146         JSArray props = emscripten::val::array();
147 
148         for (const auto& cp : fPropMgr->getColorProps()) {
149             JSObject prop = emscripten::val::object();
150             prop.set("key", cp);
151             prop.set("value", fPropMgr->getColor(cp));
152             props.call<void>("push", prop);
153         }
154 
155         return props;
156     }
157 
getOpacityProps() const158     JSArray getOpacityProps() const {
159         JSArray props = emscripten::val::array();
160 
161         for (const auto& op : fPropMgr->getOpacityProps()) {
162             JSObject prop = emscripten::val::object();
163             prop.set("key", op);
164             prop.set("value", fPropMgr->getOpacity(op));
165             props.call<void>("push", prop);
166         }
167 
168         return props;
169     }
170 
setColor(const std::string & key,SkColor c)171     bool setColor(const std::string& key, SkColor c) {
172         return fPropMgr->setColor(key, c);
173     }
174 
setOpacity(const std::string & key,float o)175     bool setOpacity(const std::string& key, float o) {
176         return fPropMgr->setOpacity(key, o);
177     }
178 
getMarkers() const179     JSArray getMarkers() const {
180         JSArray markers = emscripten::val::array();
181         for (const auto& m : fPropMgr->markers()) {
182             JSObject marker = emscripten::val::object();
183             marker.set("name", m.name);
184             marker.set("t0"  , m.t0);
185             marker.set("t1"  , m.t1);
186             markers.call<void>("push", marker);
187         }
188         return markers;
189     }
190 
191 private:
ManagedAnimation(sk_sp<skottie::Animation> animation,std::unique_ptr<skottie_utils::CustomPropertyManager> propMgr)192     ManagedAnimation(sk_sp<skottie::Animation> animation,
193                      std::unique_ptr<skottie_utils::CustomPropertyManager> propMgr)
194         : fAnimation(std::move(animation))
195         , fPropMgr(std::move(propMgr)) {}
196 
197     sk_sp<skottie::Animation>                             fAnimation;
198     std::unique_ptr<skottie_utils::CustomPropertyManager> fPropMgr;
199 };
200 
201 } // anonymous ns
202 #endif // SK_INCLUDE_MANAGED_SKOTTIE
203 
204 struct SimpleImageInfo {
205     int width;
206     int height;
207     SkColorType colorType;
208     SkAlphaType alphaType;
209     // TODO color spaces?
210 };
211 
toSkImageInfo(const SimpleImageInfo & sii)212 SkImageInfo toSkImageInfo(const SimpleImageInfo& sii) {
213     return SkImageInfo::Make(sii.width, sii.height, sii.colorType, sii.alphaType);
214 }
215 
216 #ifdef SK_GL
MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)217 sk_sp<GrDirectContext> MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
218 {
219     EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
220     if (r < 0) {
221         printf("failed to make webgl context current %d\n", r);
222         return nullptr;
223     }
224     // setup GrDirectContext
225     auto interface = GrGLMakeNativeInterface();
226     return GrDirectContext::MakeGL(interface);
227 }
228 
MakeOnScreenGLSurface(sk_sp<GrDirectContext> grContext,int width,int height)229 sk_sp<SkSurface> MakeOnScreenGLSurface(sk_sp<GrDirectContext> grContext, int width, int height) {
230     glClearColor(0, 0, 0, 0);
231     glClearStencil(0);
232     glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
233 
234 
235     // Wrap the frame buffer object attached to the screen in a Skia render
236     // target so Skia can render to it
237     GrGLint buffer;
238     glGetIntegerv(GL_FRAMEBUFFER_BINDING, &buffer);
239     GrGLFramebufferInfo info;
240     info.fFBOID = (GrGLuint) buffer;
241     SkColorType colorType;
242 
243     GrGLint stencil;
244     glGetIntegerv(GL_STENCIL_BITS, &stencil);
245 
246     info.fFormat = GL_RGBA8;
247     colorType = kRGBA_8888_SkColorType;
248 
249     GrBackendRenderTarget target(width, height, 0, stencil, info);
250 
251     sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(grContext.get(), target,
252                                                                     kBottomLeft_GrSurfaceOrigin,
253                                                                     colorType, nullptr, nullptr));
254     return surface;
255 }
256 
MakeRenderTarget(sk_sp<GrDirectContext> grContext,int width,int height)257 sk_sp<SkSurface> MakeRenderTarget(sk_sp<GrDirectContext> grContext, int width, int height) {
258     SkImageInfo info = SkImageInfo::MakeN32(width, height, SkAlphaType::kPremul_SkAlphaType);
259 
260     sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(grContext.get(),
261                              SkBudgeted::kYes,
262                              info, 0,
263                              kBottomLeft_GrSurfaceOrigin,
264                              nullptr, true));
265     return surface;
266 }
267 
MakeRenderTarget(sk_sp<GrDirectContext> grContext,SimpleImageInfo sii)268 sk_sp<SkSurface> MakeRenderTarget(sk_sp<GrDirectContext> grContext, SimpleImageInfo sii) {
269     sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(grContext.get(),
270                              SkBudgeted::kYes,
271                              toSkImageInfo(sii), 0,
272                              kBottomLeft_GrSurfaceOrigin,
273                              nullptr, true));
274     return surface;
275 }
276 #endif // SK_GL
277 
278 // Some signatures below have uintptr_t instead of a pointer to a primitive
279 // type (e.g. SkScalar). This is necessary because we can't use "bind" (EMSCRIPTEN_BINDINGS)
280 // and pointers to primitive types (Only bound types like SkPoint). We could if we used
281 // cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97)
282 // but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like
283 // SkPath or SkCanvas.
284 //
285 // So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primitive pointers
286 // in our function type signatures. (this gives an error message like "Cannot call foo due to unbound
287 // types Pi, Pf").  But, we can just pretend they are numbers and cast them to be pointers and
288 // the compiler is happy.
EMSCRIPTEN_BINDINGS(Skottie)289 EMSCRIPTEN_BINDINGS(Skottie) {
290 #ifdef SK_GL
291     function("currentContext", &emscripten_webgl_get_current_context);
292     function("setCurrentContext", &emscripten_webgl_make_context_current);
293     function("MakeGrContext", &MakeGrContext);
294     function("MakeOnScreenGLSurface", &MakeOnScreenGLSurface);
295     function("MakeRenderTarget",
296         select_overload<sk_sp<SkSurface>(sk_sp<GrDirectContext>, int, int)>(&MakeRenderTarget));
297     function("MakeRenderTarget",
298         select_overload<sk_sp<SkSurface>(sk_sp<GrDirectContext>, SimpleImageInfo)>(&MakeRenderTarget));
299 
300     constant("gpu", true);
301 
302     class_<GrDirectContext>("GrDirectContext")
303     .smart_ptr<sk_sp<GrDirectContext>>("sk_sp<GrDirectContext>");
304     // .function("getResourceCacheLimitBytes", optional_override([](GrDirectContext& self)->size_t {
305     //     int maxResources = 0;// ignored
306     //     size_t currMax = 0;
307     //     self.getResourceCacheLimits(&maxResources, &currMax);
308     //     return currMax;
309     // }))
310     // .function("getResourceCacheUsageBytes", optional_override([](GrDirectContext& self)->size_t {
311     //     int usedResources = 0;// ignored
312     //     size_t currUsage = 0;
313     //     self.getResourceCacheUsage(&usedResources, &currUsage);
314     //     return currUsage;
315     // }))
316     // .function("releaseResourcesAndAbandonContext", &GrDirectContext::releaseResourcesAndAbandonContext)
317     // .function("setResourceCacheLimitBytes", optional_override([](GrDirectContext& self, size_t maxResourceBytes) {
318     //     int maxResources = 0;
319     //     size_t currMax = 0; // ignored
320     //     self.getResourceCacheLimits(&maxResources, &currMax);
321     //     self.setResourceCacheLimits(maxResources, maxResourceBytes);
322     // }));
323 #endif
324 
325     // function("getDecodeCacheLimitBytes", &SkResourceCache::GetTotalByteLimit);
326     // function("setDecodeCacheLimitBytes", &SkResourceCache::SetTotalByteLimit);
327     // function("getDecodeCacheUsedBytes" , &SkResourceCache::GetTotalBytesUsed);
328 
329     function("_getRasterDirectSurface", optional_override([](const SimpleImageInfo ii,
330                                                              uintptr_t /* uint8_t*  */ pPtr,
331                                                              size_t rowBytes)->sk_sp<SkSurface> {
332         uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
333         SkImageInfo imageInfo = toSkImageInfo(ii);
334         return SkSurface::MakeRasterDirect(imageInfo, pixels, rowBytes, nullptr);
335     }), allow_raw_pointers());
336 
337     class_<SkCanvas>("SkCanvas")
338         .function("_clear", optional_override([](SkCanvas& self, uintptr_t /* float* */ cPtr) {
339             // See comment above for uintptr_t explanation
340             float* fourFloats = reinterpret_cast<float*>(cPtr);
341             SkColor4f color = { fourFloats[0], fourFloats[1], fourFloats[2], fourFloats[3]};
342             // TODO(reed): SkCanvas.clear() should take SkColor4f, then we could just use it here.
343             SkPaint p;
344             p.setBlendMode(SkBlendMode::kSrc);
345             p.setColor4f(color);
346             self.drawPaint(p);
347         }));
348 
349     class_<SkSurface>("SkSurface")
350         .smart_ptr<sk_sp<SkSurface>>("sk_sp<SkSurface>")
351         .function("_flush", select_overload<void()>(&SkSurface::flush))
352         .function("getCanvas", &SkSurface::getCanvas, allow_raw_pointers());
353 
354     class_<skottie::Animation>("Animation")
355         .smart_ptr<sk_sp<skottie::Animation>>("sk_sp<Animation>")
356         .function("version", optional_override([](skottie::Animation& self)->std::string {
357             return std::string(self.version().c_str());
358         }))
359         .function("size"    , &skottie::Animation::size)
360         .function("duration", &skottie::Animation::duration)
361         .function("fps"     , &skottie::Animation::fps)
362         .function("seek", optional_override([](skottie::Animation& self, SkScalar t)->void {
363             self.seek(t);
364         }))
365         .function("seekFrame", optional_override([](skottie::Animation& self, double t)->void {
366             self.seekFrame(t);
367         }))
368         .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas)->void {
369             self.render(canvas, nullptr);
370         }), allow_raw_pointers())
371         .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas,
372                                                  const SkRect r)->void {
373             self.render(canvas, &r);
374         }), allow_raw_pointers());
375 
376     function("MakeAnimation", optional_override([](std::string json)->sk_sp<skottie::Animation> {
377         return skottie::Animation::Make(json.c_str(), json.length());
378     }));
379     constant("skottie", true);
380 
381 #if SK_INCLUDE_MANAGED_SKOTTIE
382     class_<ManagedAnimation>("ManagedAnimation")
383         .smart_ptr<sk_sp<ManagedAnimation>>("sk_sp<ManagedAnimation>")
384         .function("version"   , &ManagedAnimation::version)
385         .function("size"      , &ManagedAnimation::size)
386         .function("duration"  , &ManagedAnimation::duration)
387         .function("fps"       , &ManagedAnimation::fps)
388         .function("seek"      , &ManagedAnimation::seek)
389         .function("seekFrame" , &ManagedAnimation::seekFrame)
390         .function("render"    , select_overload<void(SkCanvas*) const>(&ManagedAnimation::render), allow_raw_pointers())
391         .function("render"    , select_overload<void(SkCanvas*, const SkRect&) const>
392                                     (&ManagedAnimation::render), allow_raw_pointers());
393         // .function("setColor"  , optional_override([](ManagedAnimation& self, const std::string& key, SimpleColor4f c) {
394         //     self.setColor(key, c.toSkColor());
395         // }))
396         // .function("setOpacity", &ManagedAnimation::setOpacity)
397         // .function("getMarkers", &ManagedAnimation::getMarkers)
398         // .function("getColorProps"  , &ManagedAnimation::getColorProps)
399         // .function("getOpacityProps", &ManagedAnimation::getOpacityProps);
400 
401     function("_MakeManagedAnimation", optional_override([](std::string json,
402                                                            size_t assetCount,
403                                                            uintptr_t /* char**     */ nptr,
404                                                            uintptr_t /* uint8_t**  */ dptr,
405                                                            uintptr_t /* size_t*    */ sptr)
406                                                         ->sk_sp<ManagedAnimation> {
407         // See the comment in canvaskit_bindings.cpp about the use of uintptr_t
408         const auto assetNames = reinterpret_cast<char**   >(nptr);
409         const auto assetDatas = reinterpret_cast<uint8_t**>(dptr);
410         const auto assetSizes = reinterpret_cast<size_t*  >(sptr);
411 
412         SkottieAssetProvider::AssetVec assets;
413         assets.reserve(assetCount);
414 
415         for (size_t i = 0; i < assetCount; i++) {
416             auto name  = SkString(assetNames[i]);
417             auto bytes = SkData::MakeFromMalloc(assetDatas[i], assetSizes[i]);
418             assets.push_back(std::make_pair(std::move(name), std::move(bytes)));
419         }
420 
421         return ManagedAnimation::Make(json,
422                  skresources::DataURIResourceProviderProxy::Make(
423                     SkottieAssetProvider::Make(std::move(assets))));
424     }));
425     constant("managed_skottie", true);
426 #endif // SK_INCLUDE_MANAGED_SKOTTIE
427 
428     value_object<SimpleImageInfo>("SkImageInfo")
429         .field("width",     &SimpleImageInfo::width)
430         .field("height",    &SimpleImageInfo::height)
431         .field("colorType", &SimpleImageInfo::colorType)
432         .field("alphaType", &SimpleImageInfo::alphaType);
433 
434     value_object<SkRect>("SkRect")
435         .field("fLeft",   &SkRect::fLeft)
436         .field("fTop",    &SkRect::fTop)
437         .field("fRight",  &SkRect::fRight)
438         .field("fBottom", &SkRect::fBottom);
439 
440     // {"w": Number, "h", Number}
441     value_object<SkSize>("SkSize")
442         .field("w",   &SkSize::fWidth)
443         .field("h",   &SkSize::fHeight);
444 
445     enum_<SkAlphaType>("AlphaType")
446         .value("Opaque",   SkAlphaType::kOpaque_SkAlphaType)
447         .value("Premul",   SkAlphaType::kPremul_SkAlphaType)
448         .value("Unpremul", SkAlphaType::kUnpremul_SkAlphaType);
449 
450     enum_<SkColorType>("ColorType")
451         .value("RGBA_8888", SkColorType::kRGBA_8888_SkColorType);
452 
453 }
454