/* * Copyright 2019 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/SkImage.h" #include "include/core/SkString.h" #include "include/core/SkTypes.h" #include "modules/skottie/include/Skottie.h" #include "modules/sksg/include/SkSGInvalidationController.h" #include #include #include #include #include "modules/canvaskit/WasmCommon.h" #if SK_INCLUDE_MANAGED_SKOTTIE #include "modules/skottie/include/SkottieProperty.h" #include "modules/skottie/utils/SkottieUtils.h" #include "modules/skresources/include/SkResources.h" #endif // SK_INCLUDE_MANAGED_SKOTTIE using namespace emscripten; #if SK_INCLUDE_MANAGED_SKOTTIE namespace { // WebTrack wraps a JS object that has a 'seek' method. // Playback logic is kept there. class WebTrack final : public skresources::ExternalTrackAsset { public: explicit WebTrack(emscripten::val player) : fPlayer(std::move(player)) {} private: void seek(float t) override { fPlayer.call("seek", val(t)); } const emscripten::val fPlayer; }; 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, emscripten::val soundMap) { return sk_sp(new SkottieAssetProvider(std::move(assets), std::move(soundMap))); } 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 loadAudioAsset(const char[] /* path */, const char[] /* name */, const char id[]) override { emscripten::val player = this->findSoundAsset(id); if (player.as()) { return sk_make_sp(std::move(player)); } 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); } sk_sp load(const char[]/*path*/, const char name[]) const override { // Ignore paths. return this->findAsset(name); } private: explicit SkottieAssetProvider(AssetVec assets, emscripten::val soundMap) : fAssets(std::move(assets)) , fSoundMap(std::move(soundMap)) {} 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; } emscripten::val findSoundAsset(const char name[]) const { if (fSoundMap.as() && fSoundMap.hasOwnProperty("getPlayer")) { emscripten::val player = fSoundMap.call("getPlayer", val(name)); if (player.as() && player.hasOwnProperty("seek")) { return player; } } return emscripten::val::null(); } const AssetVec fAssets; const emscripten::val fSoundMap; }; // Wraps a JS object with 'onError' and 'onWarning' methods. class JSLogger final : public skottie::Logger { public: static sk_sp Make(emscripten::val logger) { return logger.as() && logger.hasOwnProperty(kWrnFunc) && logger.hasOwnProperty(kErrFunc) ? sk_sp(new JSLogger(std::move(logger))) : nullptr; } private: explicit JSLogger(emscripten::val logger) : fLogger(std::move(logger)) {} void log(Level lvl, const char msg[], const char* json) override { const auto* func = lvl == Level::kError ? kErrFunc : kWrnFunc; fLogger.call(func, std::string(msg), std::string(json)); } static constexpr char kWrnFunc[] = "onWarning", kErrFunc[] = "onError"; const emscripten::val fLogger; }; class ManagedAnimation final : public SkRefCnt { public: static sk_sp Make(const std::string& json, sk_sp rp, std::string prop_prefix, emscripten::val logger) { auto mgr = std::make_unique( skottie_utils::CustomPropertyManager::Mode::kCollapseProperties, prop_prefix.empty() ? nullptr : prop_prefix.c_str()); static constexpr char kInterceptPrefix[] = "__"; auto pinterceptor = sk_make_sp(rp, kInterceptPrefix); auto animation = skottie::Animation::Builder() .setMarkerObserver(mgr->getMarkerObserver()) .setPropertyObserver(mgr->getPropertyObserver()) .setResourceProvider(std::move(rp)) .setPrecompInterceptor(std::move(pinterceptor)) .setLogger(JSLogger::Make(std::move(logger))) .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 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; } JSArray getTextProps() const { JSArray props = emscripten::val::array(); for (const auto& key : fPropMgr->getTextProps()) { const auto txt = fPropMgr->getText(key); JSObject txt_val = emscripten::val::object(); txt_val.set("text", txt.fText.c_str()); txt_val.set("size", txt.fTextSize); JSObject prop = emscripten::val::object(); prop.set("key", key); prop.set("value", std::move(txt_val)); 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); } bool setText(const std::string& key, std::string text, float size) { // preserve all other text fields auto t = fPropMgr->getText(key); t.fText = SkString(text); t.fTextSize = size; return fPropMgr->setText(key, t); } 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)) {} const sk_sp fAnimation; const std::unique_ptr fPropMgr; }; } // anonymous ns #endif // SK_INCLUDE_MANAGED_SKOTTIE EMSCRIPTEN_BINDINGS(Skottie) { // Animation things (may eventually go in own library) class_("Animation") .smart_ptr>("sk_sp") .function("version", optional_override([](skottie::Animation& self)->std::string { return std::string(self.version().c_str()); })) .function("_size", optional_override([](skottie::Animation& self, WASMPointerF32 oPtr)->void { SkSize* output = reinterpret_cast(oPtr); *output = self.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, WASMPointerF32 fPtr)->void { const SkRect* dst = reinterpret_cast(fPtr); self.render(canvas, dst); }), 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", optional_override([](ManagedAnimation& self, WASMPointerF32 oPtr)->void { SkSize* output = reinterpret_cast(oPtr); *output = self.size(); })) .function("duration" , &ManagedAnimation::duration) .function("fps" , &ManagedAnimation::fps) .function("_render", optional_override([](ManagedAnimation& self, SkCanvas* canvas, WASMPointerF32 fPtr)->void { const SkRect* dst = reinterpret_cast(fPtr); self.render(canvas, dst); }), allow_raw_pointers()) .function("_seek", optional_override([](ManagedAnimation& self, SkScalar t, WASMPointerF32 fPtr) { SkRect* damageRect = reinterpret_cast(fPtr); damageRect[0] = self.seek(t); })) .function("_seekFrame", optional_override([](ManagedAnimation& self, double frame, WASMPointerF32 fPtr) { SkRect* damageRect = reinterpret_cast(fPtr); damageRect[0] = self.seekFrame(frame); })) .function("seekFrame" , &ManagedAnimation::seekFrame) .function("_setColor" , optional_override([](ManagedAnimation& self, const std::string& key, WASMPointerF32 cPtr) { float* fourFloats = reinterpret_cast(cPtr); SkColor4f color = { fourFloats[0], fourFloats[1], fourFloats[2], fourFloats[3] }; return self.setColor(key, color.toSkColor()); })) .function("setOpacity", &ManagedAnimation::setOpacity) .function("getMarkers", &ManagedAnimation::getMarkers) .function("getColorProps" , &ManagedAnimation::getColorProps) .function("getOpacityProps", &ManagedAnimation::getOpacityProps) .function("getTextProps" , &ManagedAnimation::getTextProps) .function("setText" , &ManagedAnimation::setText); function("_MakeManagedAnimation", optional_override([](std::string json, size_t assetCount, WASMPointerU32 nptr, WASMPointerU32 dptr, WASMPointerU32 sptr, std::string prop_prefix, emscripten::val soundMap, emscripten::val logger) ->sk_sp { 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), std::move(soundMap))), prop_prefix, std::move(logger)); })); constant("managed_skottie", true); #endif // SK_INCLUDE_MANAGED_SKOTTIE }