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