skia2/modules/canvaskit/skottie_bindings.cpp
Florin Malita 3f45e4b322 [skottie] Custom prop manager updates
Currently we use a hard-coded name prefix ('$') and the properties are
"namespaced" (the tree hierarchy is part of the key name).

This doesn't work well for most clients looking to test property
manipulation, as they would rather handle similarly named props in
bulk.

E.g. instead of

  precomp1.layer1.Group1.COLOR_01
  precomp1.layer1.Group1.COLOR_02
  precomp1.layer2.Group1.COLOR_01
  precomp1.layer2.Group2.COLOR_02

the UI should simply present

  COLOR_01
  COLOR_02

To support this, introduce a new operation mode for
CustomPropertyManager (kCollapseProperties), and keep the old behavior
around as kNamespacedProperties.

Also drop filtering for markers as anyone interested in markers would
want to see all of them.

Plumb these options all the way into CK (to be added to the player
later).

Change-Id: I57ec78c669f3870939d48fbfc492b97f63ea600d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/312301
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Commit-Queue: Florin Malita <fmalita@google.com>
2020-08-20 19:59:22 +00:00

274 lines
11 KiB
C++

/*
* 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 <string>
#include <vector>
#include <emscripten.h>
#include <emscripten/bind.h>
#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 {
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<std::pair<SkString, sk_sp<SkData>>>;
static sk_sp<SkottieAssetProvider> Make(AssetVec assets) {
if (assets.empty()) {
return nullptr;
}
return sk_sp<SkottieAssetProvider>(new SkottieAssetProvider(std::move(assets)));
}
sk_sp<skottie::ImageAsset> 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<SkData> loadFont(const char name[], const char[] /* url */) const override {
// Same as images paths, we ignore font URLs.
return this->findAsset(name);
}
sk_sp<SkData> load(const char[]/*path*/, const char name[]) const override {
// Ignore paths.
return this->findAsset(name);
}
private:
explicit SkottieAssetProvider(AssetVec assets) : fAssets(std::move(assets)) {}
sk_sp<SkData> 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<ManagedAnimation> Make(const std::string& json,
sk_sp<skottie::ResourceProvider> rp,
std::string prop_prefix) {
auto mgr = std::make_unique<skottie_utils::CustomPropertyManager>(
skottie_utils::CustomPropertyManager::Mode::kCollapseProperties,
prop_prefix.empty() ? nullptr : prop_prefix.c_str());
static constexpr char kInterceptPrefix[] = "__";
auto pinterceptor =
sk_make_sp<skottie_utils::ExternalAnimationPrecompInterceptor>(rp, kInterceptPrefix);
auto animation = skottie::Animation::Builder()
.setMarkerObserver(mgr->getMarkerObserver())
.setPropertyObserver(mgr->getPropertyObserver())
.setResourceProvider(std::move(rp))
.setPrecompInterceptor(std::move(pinterceptor))
.make(json.c_str(), json.size());
return animation
? sk_sp<ManagedAnimation>(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<void>("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<void>("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<void>("push", marker);
}
return markers;
}
private:
ManagedAnimation(sk_sp<skottie::Animation> animation,
std::unique_ptr<skottie_utils::CustomPropertyManager> propMgr)
: fAnimation(std::move(animation))
, fPropMgr(std::move(propMgr)) {}
sk_sp<skottie::Animation> fAnimation;
std::unique_ptr<skottie_utils::CustomPropertyManager> fPropMgr;
};
} // anonymous ns
#endif // SK_INCLUDE_MANAGED_SKOTTIE
EMSCRIPTEN_BINDINGS(Skottie) {
// Animation things (may eventually go in own library)
class_<skottie::Animation>("Animation")
.smart_ptr<sk_sp<skottie::Animation>>("sk_sp<Animation>")
.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<skottie::Animation> {
return skottie::Animation::Make(json.c_str(), json.length());
}));
constant("skottie", true);
#if SK_INCLUDE_MANAGED_SKOTTIE
class_<ManagedAnimation>("ManagedAnimation")
.smart_ptr<sk_sp<ManagedAnimation>>("sk_sp<ManagedAnimation>")
.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<void(SkCanvas*) const>(&ManagedAnimation::render), allow_raw_pointers())
.function("render" , select_overload<void(SkCanvas*, const SkRect&) const>
(&ManagedAnimation::render), allow_raw_pointers())
.function("_setColor" , optional_override([](ManagedAnimation& self, const std::string& key, uintptr_t /* float* */ cPtr) {
float* fourFloats = reinterpret_cast<float*>(cPtr);
SkColor4f color = { fourFloats[0], fourFloats[1], fourFloats[2], fourFloats[3] };
self.setColor(key, color.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,
std::string prop_prefix)
->sk_sp<ManagedAnimation> {
// See the comment in canvaskit_bindings.cpp about the use of uintptr_t
const auto assetNames = reinterpret_cast<char** >(nptr);
const auto assetDatas = reinterpret_cast<uint8_t**>(dptr);
const auto assetSizes = reinterpret_cast<size_t* >(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))),
prop_prefix);
}));
constant("managed_skottie", true);
#endif // SK_INCLUDE_MANAGED_SKOTTIE
}