/* * Copyright 2020 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/core/SkCanvas.h" #include "include/core/SkColor.h" #include "include/core/SkImage.h" #include "include/core/SkPaint.h" #include "include/core/SkRect.h" #include "include/core/SkSize.h" #include "include/core/SkString.h" #include "include/core/SkSurface.h" #include "include/core/SkTypes.h" #include "modules/skottie/include/Skottie.h" #include "modules/sksg/include/SkSGInvalidationController.h" #include #include #include #include #if SK_INCLUDE_MANAGED_SKOTTIE #include "modules/skottie/utils/SkottieUtils.h" #include "modules/skresources/include/SkResources.h" #endif // SK_INCLUDE_MANAGED_SKOTTIE #ifdef SK_GL #include "include/core/SkImageInfo.h" #include "include/gpu/GrBackendSurface.h" #include "include/gpu/GrContext.h" #include "include/gpu/gl/GrGLInterface.h" #include "include/gpu/gl/GrGLTypes.h" #include #include #endif using namespace emscripten; // Self-documenting types using JSArray = emscripten::val; using JSObject = emscripten::val; using JSString = emscripten::val; using SkPathOrNull = emscripten::val; using Uint8Array = emscripten::val; using Float32Array = emscripten::val; #if SK_INCLUDE_MANAGED_SKOTTIE namespace { class SkottieAssetProvider : public skottie::ResourceProvider { public: ~SkottieAssetProvider() override = default; // Tried using a map, but that gave strange errors like // https://emscripten.org/docs/porting/guidelines/function_pointer_issues.html // Not entirely sure why, but perhaps the iterator in the map was // confusing enscripten. using AssetVec = std::vector>>; static sk_sp Make(AssetVec assets) { if (assets.empty()) { return nullptr; } return sk_sp(new SkottieAssetProvider(std::move(assets))); } sk_sp loadImageAsset(const char[] /* path */, const char name[], const char[] /* id */) const override { // For CK/Skottie we ignore paths & IDs, and identify images based solely on name. if (auto data = this->findAsset(name)) { return skresources::MultiFrameImageAsset::Make(std::move(data)); } return nullptr; } sk_sp loadFont(const char name[], const char[] /* url */) const override { // Same as images paths, we ignore font URLs. return this->findAsset(name); } private: explicit SkottieAssetProvider(AssetVec assets) : fAssets(std::move(assets)) {} sk_sp findAsset(const char name[]) const { for (const auto& asset : fAssets) { if (asset.first.equals(name)) { return asset.second; } } SkDebugf("Could not find %s\n", name); return nullptr; } const AssetVec fAssets; }; class ManagedAnimation final : public SkRefCnt { public: static sk_sp Make(const std::string& json, sk_sp rp) { auto mgr = std::make_unique(); auto animation = skottie::Animation::Builder() .setMarkerObserver(mgr->getMarkerObserver()) .setPropertyObserver(mgr->getPropertyObserver()) .setResourceProvider(rp) .make(json.c_str(), json.size()); return animation ? sk_sp(new ManagedAnimation(std::move(animation), std::move(mgr))) : nullptr; } ~ManagedAnimation() override = default; // skottie::Animation API void render(SkCanvas* canvas) const { fAnimation->render(canvas, nullptr); } void render(SkCanvas* canvas, const SkRect& dst) const { fAnimation->render(canvas, &dst); } // Returns a damage rect. SkRect seek(SkScalar t) { sksg::InvalidationController ic; fAnimation->seek(t, &ic); return ic.bounds(); } // Returns a damage rect. SkRect seekFrame(double t) { sksg::InvalidationController ic; fAnimation->seekFrame(t, &ic); return ic.bounds(); } double duration() const { return fAnimation->duration(); } double fps() const { return fAnimation->fps(); } const SkSize& size() const { return fAnimation->size(); } std::string version() const { return std::string(fAnimation->version().c_str()); } // CustomPropertyManager API JSArray getColorProps() const { JSArray props = emscripten::val::array(); for (const auto& cp : fPropMgr->getColorProps()) { JSObject prop = emscripten::val::object(); prop.set("key", cp); prop.set("value", fPropMgr->getColor(cp)); props.call("push", prop); } return props; } JSArray getOpacityProps() const { JSArray props = emscripten::val::array(); for (const auto& op : fPropMgr->getOpacityProps()) { JSObject prop = emscripten::val::object(); prop.set("key", op); prop.set("value", fPropMgr->getOpacity(op)); props.call("push", prop); } return props; } bool setColor(const std::string& key, SkColor c) { return fPropMgr->setColor(key, c); } bool setOpacity(const std::string& key, float o) { return fPropMgr->setOpacity(key, o); } JSArray getMarkers() const { JSArray markers = emscripten::val::array(); for (const auto& m : fPropMgr->markers()) { JSObject marker = emscripten::val::object(); marker.set("name", m.name); marker.set("t0" , m.t0); marker.set("t1" , m.t1); markers.call("push", marker); } return markers; } private: ManagedAnimation(sk_sp animation, std::unique_ptr propMgr) : fAnimation(std::move(animation)) , fPropMgr(std::move(propMgr)) {} sk_sp fAnimation; std::unique_ptr fPropMgr; }; } // anonymous ns #endif // SK_INCLUDE_MANAGED_SKOTTIE struct SimpleImageInfo { int width; int height; SkColorType colorType; SkAlphaType alphaType; // TODO color spaces? }; SkImageInfo toSkImageInfo(const SimpleImageInfo& sii) { return SkImageInfo::Make(sii.width, sii.height, sii.colorType, sii.alphaType); } #ifdef SK_GL sk_sp MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context) { EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context); if (r < 0) { printf("failed to make webgl context current %d\n", r); return nullptr; } // setup GrContext auto interface = GrGLMakeNativeInterface(); // setup contexts sk_sp grContext(GrContext::MakeGL(interface)); return grContext; } sk_sp MakeOnScreenGLSurface(sk_sp grContext, int width, int height) { glClearColor(0, 0, 0, 0); glClearStencil(0); glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Wrap the frame buffer object attached to the screen in a Skia render // target so Skia can render to it GrGLint buffer; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &buffer); GrGLFramebufferInfo info; info.fFBOID = (GrGLuint) buffer; SkColorType colorType; GrGLint stencil; glGetIntegerv(GL_STENCIL_BITS, &stencil); info.fFormat = GL_RGBA8; colorType = kRGBA_8888_SkColorType; GrBackendRenderTarget target(width, height, 0, stencil, info); sk_sp surface(SkSurface::MakeFromBackendRenderTarget(grContext.get(), target, kBottomLeft_GrSurfaceOrigin, colorType, nullptr, nullptr)); return surface; } sk_sp MakeRenderTarget(sk_sp grContext, int width, int height) { SkImageInfo info = SkImageInfo::MakeN32(width, height, SkAlphaType::kPremul_SkAlphaType); sk_sp surface(SkSurface::MakeRenderTarget(grContext.get(), SkBudgeted::kYes, info, 0, kBottomLeft_GrSurfaceOrigin, nullptr, true)); return surface; } sk_sp MakeRenderTarget(sk_sp grContext, SimpleImageInfo sii) { sk_sp surface(SkSurface::MakeRenderTarget(grContext.get(), SkBudgeted::kYes, toSkImageInfo(sii), 0, kBottomLeft_GrSurfaceOrigin, nullptr, true)); return surface; } #endif // SK_GL // Some signatures below have uintptr_t instead of a pointer to a primitive // type (e.g. SkScalar). This is necessary because we can't use "bind" (EMSCRIPTEN_BINDINGS) // and pointers to primitive types (Only bound types like SkPoint). We could if we used // cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97) // but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like // SkPath or SkCanvas. // // So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primitive pointers // in our function type signatures. (this gives an error message like "Cannot call foo due to unbound // types Pi, Pf"). But, we can just pretend they are numbers and cast them to be pointers and // the compiler is happy. EMSCRIPTEN_BINDINGS(Skottie) { #ifdef SK_GL function("currentContext", &emscripten_webgl_get_current_context); function("setCurrentContext", &emscripten_webgl_make_context_current); function("MakeGrContext", &MakeGrContext); function("MakeOnScreenGLSurface", &MakeOnScreenGLSurface); function("MakeRenderTarget", select_overload(sk_sp, int, int)>(&MakeRenderTarget)); function("MakeRenderTarget", select_overload(sk_sp, SimpleImageInfo)>(&MakeRenderTarget)); constant("gpu", true); class_("GrContext") .smart_ptr>("sk_sp"); // .function("getResourceCacheLimitBytes", optional_override([](GrContext& self)->size_t { // int maxResources = 0;// ignored // size_t currMax = 0; // self.getResourceCacheLimits(&maxResources, &currMax); // return currMax; // })) // .function("getResourceCacheUsageBytes", optional_override([](GrContext& self)->size_t { // int usedResources = 0;// ignored // size_t currUsage = 0; // self.getResourceCacheUsage(&usedResources, &currUsage); // return currUsage; // })) // .function("releaseResourcesAndAbandonContext", &GrContext::releaseResourcesAndAbandonContext) // .function("setResourceCacheLimitBytes", optional_override([](GrContext& self, size_t maxResourceBytes)->void { // int maxResources = 0; // size_t currMax = 0; // ignored // self.getResourceCacheLimits(&maxResources, &currMax); // self.setResourceCacheLimits(maxResources, maxResourceBytes); // })); #endif // function("getDecodeCacheLimitBytes", &SkResourceCache::GetTotalByteLimit); // function("setDecodeCacheLimitBytes", &SkResourceCache::SetTotalByteLimit); // function("getDecodeCacheUsedBytes" , &SkResourceCache::GetTotalBytesUsed); function("_getRasterDirectSurface", optional_override([](const SimpleImageInfo ii, uintptr_t /* uint8_t* */ pPtr, size_t rowBytes)->sk_sp { uint8_t* pixels = reinterpret_cast(pPtr); SkImageInfo imageInfo = toSkImageInfo(ii); return SkSurface::MakeRasterDirect(imageInfo, pixels, rowBytes, nullptr); }), allow_raw_pointers()); class_("SkCanvas") .function("_clear", optional_override([](SkCanvas& self, uintptr_t /* float* */ cPtr) { // See comment above for uintptr_t explanation float* fourFloats = reinterpret_cast(cPtr); SkColor4f color = { fourFloats[0], fourFloats[1], fourFloats[2], fourFloats[3]}; // TODO(reed): SkCanvas.clear() should take SkColor4f, then we could just use it here. SkPaint p; p.setBlendMode(SkBlendMode::kSrc); p.setColor4f(color); self.drawPaint(p); })); class_("SkSurface") .smart_ptr>("sk_sp") .function("_flush", select_overload(&SkSurface::flush)) .function("getCanvas", &SkSurface::getCanvas, allow_raw_pointers()); class_("Animation") .smart_ptr>("sk_sp") .function("version", optional_override([](skottie::Animation& self)->std::string { return std::string(self.version().c_str()); })) .function("size" , &skottie::Animation::size) .function("duration", &skottie::Animation::duration) .function("fps" , &skottie::Animation::fps) .function("seek", optional_override([](skottie::Animation& self, SkScalar t)->void { self.seek(t); })) .function("seekFrame", optional_override([](skottie::Animation& self, double t)->void { self.seekFrame(t); })) .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas)->void { self.render(canvas, nullptr); }), allow_raw_pointers()) .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas, const SkRect r)->void { self.render(canvas, &r); }), allow_raw_pointers()); function("MakeAnimation", optional_override([](std::string json)->sk_sp { return skottie::Animation::Make(json.c_str(), json.length()); })); constant("skottie", true); #if SK_INCLUDE_MANAGED_SKOTTIE class_("ManagedAnimation") .smart_ptr>("sk_sp") .function("version" , &ManagedAnimation::version) .function("size" , &ManagedAnimation::size) .function("duration" , &ManagedAnimation::duration) .function("fps" , &ManagedAnimation::fps) .function("seek" , &ManagedAnimation::seek) .function("seekFrame" , &ManagedAnimation::seekFrame) .function("render" , select_overload(&ManagedAnimation::render), allow_raw_pointers()) .function("render" , select_overload (&ManagedAnimation::render), allow_raw_pointers()); // .function("setColor" , optional_override([](ManagedAnimation& self, const std::string& key, SimpleColor4f c) { // self.setColor(key, c.toSkColor()); // })) // .function("setOpacity", &ManagedAnimation::setOpacity) // .function("getMarkers", &ManagedAnimation::getMarkers) // .function("getColorProps" , &ManagedAnimation::getColorProps) // .function("getOpacityProps", &ManagedAnimation::getOpacityProps); function("_MakeManagedAnimation", optional_override([](std::string json, size_t assetCount, uintptr_t /* char** */ nptr, uintptr_t /* uint8_t** */ dptr, uintptr_t /* size_t* */ sptr) ->sk_sp { // See the comment in canvaskit_bindings.cpp about the use of uintptr_t const auto assetNames = reinterpret_cast(nptr); const auto assetDatas = reinterpret_cast(dptr); const auto assetSizes = reinterpret_cast(sptr); SkottieAssetProvider::AssetVec assets; assets.reserve(assetCount); for (size_t i = 0; i < assetCount; i++) { auto name = SkString(assetNames[i]); auto bytes = SkData::MakeFromMalloc(assetDatas[i], assetSizes[i]); assets.push_back(std::make_pair(std::move(name), std::move(bytes))); } return ManagedAnimation::Make(json, skresources::DataURIResourceProviderProxy::Make( SkottieAssetProvider::Make(std::move(assets)))); })); constant("managed_skottie", true); #endif // SK_INCLUDE_MANAGED_SKOTTIE value_object("SkImageInfo") .field("width", &SimpleImageInfo::width) .field("height", &SimpleImageInfo::height) .field("colorType", &SimpleImageInfo::colorType) .field("alphaType", &SimpleImageInfo::alphaType); value_object("SkRect") .field("fLeft", &SkRect::fLeft) .field("fTop", &SkRect::fTop) .field("fRight", &SkRect::fRight) .field("fBottom", &SkRect::fBottom); // {"w": Number, "h", Number} value_object("SkSize") .field("w", &SkSize::fWidth) .field("h", &SkSize::fHeight); enum_("AlphaType") .value("Opaque", SkAlphaType::kOpaque_SkAlphaType) .value("Premul", SkAlphaType::kPremul_SkAlphaType) .value("Unpremul", SkAlphaType::kUnpremul_SkAlphaType); enum_("ColorType") .value("RGBA_8888", SkColorType::kRGBA_8888_SkColorType); }