Reland: [skottie] Initial property setters
Introduce a PropertyObserver to receive property notifications for layer
and shape nodes.
Properties are communicated using strongly-typed "handles", which act
as impedance adapters between the AE/BM model and the internal Skottie
model.
Reviewed-by: Mike Reed <reed@google.com>
Change-Id: Id155076faa8595f6b4d81672559f01c2e0c7455a
TBR=
Reviewed-on: https://skia-review.googlesource.com/156626
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skottie/include/Skottie.h b/modules/skottie/include/Skottie.h
index ebe7408..e6e5960 100644
--- a/modules/skottie/include/Skottie.h
+++ b/modules/skottie/include/Skottie.h
@@ -26,6 +26,8 @@
namespace skottie {
+class PropertyObserver;
+
/**
* ResourceProvider allows Skottie embedders to control loading of external
* Skottie resources -- e.g. images, fonts, nested animations.
@@ -62,6 +64,9 @@
class Builder final {
public:
+ Builder();
+ ~Builder();
+
struct Stats {
float fTotalLoadTimeMS = 0, // Total animation instantiation time.
fJsonParseTimeMS = 0, // Time spent building a JSON DOM.
@@ -88,6 +93,14 @@
Builder& setFontManager(sk_sp<SkFontMgr>);
/**
+ * Specify a PropertyObserver to receive callbacks during parsing.
+ *
+ * See SkottieProperty.h for more details.
+ *
+ */
+ Builder& setPropertyObserver(sk_sp<PropertyObserver>);
+
+ /**
* Animation factories.
*/
sk_sp<Animation> make(SkStream*);
@@ -97,6 +110,7 @@
private:
sk_sp<ResourceProvider> fResourceProvider;
sk_sp<SkFontMgr> fFontMgr;
+ sk_sp<PropertyObserver> fPropertyObserver;
Stats fStats;
};
diff --git a/modules/skottie/include/SkottieProperty.h b/modules/skottie/include/SkottieProperty.h
new file mode 100644
index 0000000..291b086
--- /dev/null
+++ b/modules/skottie/include/SkottieProperty.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkottieProperty_DEFINED
+#define SkottieProperty_DEFINED
+
+#include "SkColor.h"
+#include "SkPoint.h"
+#include "SkRefCnt.h"
+
+#include <functional>
+
+class SkMatrix;
+
+namespace sksg {
+
+class Color;
+class OpacityEffect;
+
+} // namespace sksg
+
+namespace skottie {
+
+class ColorPropertyHandle;
+class OpacityPropertyHandle;
+class TransformPropertyHandle;
+
+/**
+ * A PropertyObserver can be used to track and manipulate certain properties of "interesting"
+ * Lottie nodes.
+ *
+ * When registered with an animation builder, PropertyObserver receives notifications for
+ * various properties of layer and shape nodes. The |node_name| argument corresponds to the
+ * name ("nm") node property.
+ */
+class PropertyObserver : public SkRefCnt {
+public:
+ template <typename T>
+ using LazyHandle = std::function<std::unique_ptr<T>()>;
+
+ virtual void onColorProperty (const char node_name[],
+ const LazyHandle<ColorPropertyHandle>&);
+ virtual void onOpacityProperty (const char node_name[],
+ const LazyHandle<OpacityPropertyHandle>&);
+ virtual void onTransformProperty(const char node_name[],
+ const LazyHandle<TransformPropertyHandle>&);
+};
+
+namespace internal { class AnimationBuilder; }
+
+class ColorPropertyHandle final {
+public:
+ ~ColorPropertyHandle();
+
+ SkColor getColor() const;
+ void setColor(SkColor);
+
+private:
+ explicit ColorPropertyHandle(sk_sp<sksg::Color>);
+
+ friend class skottie::internal::AnimationBuilder;
+
+ const sk_sp<sksg::Color> fColor;
+};
+
+class OpacityPropertyHandle final {
+public:
+ ~OpacityPropertyHandle();
+
+ float getOpacity() const;
+ void setOpacity(float);
+
+private:
+ explicit OpacityPropertyHandle(sk_sp<sksg::OpacityEffect>);
+
+ friend class skottie::internal::AnimationBuilder;
+
+ const sk_sp<sksg::OpacityEffect> fOpacity;
+};
+
+class TransformAdapter;
+
+class TransformPropertyHandle final {
+public:
+ ~TransformPropertyHandle();
+
+ SkPoint getAnchorPoint() const;
+ void setAnchorPoint(const SkPoint&);
+
+ SkPoint getPosition() const;
+ void setPosition(const SkPoint&);
+
+ SkVector getScale() const;
+ void setScale(const SkVector&);
+
+ SkScalar getRotation() const;
+ void setRotation(SkScalar);
+
+ SkScalar getSkew() const;
+ void setSkew(SkScalar);
+
+ SkScalar getSkewAxis() const;
+ void setSkewAxis(SkScalar);
+
+ SkMatrix getTotalMatrix() const;
+
+private:
+ explicit TransformPropertyHandle(sk_sp<TransformAdapter>);
+
+ friend class skottie::internal::AnimationBuilder;
+
+ const sk_sp<TransformAdapter> fTransform;
+};
+
+} // namespace skottie
+
+#endif // SkottieProperty_DEFINED
diff --git a/modules/skottie/skottie.gni b/modules/skottie/skottie.gni
index 6d285ea..6cb5a38 100644
--- a/modules/skottie/skottie.gni
+++ b/modules/skottie/skottie.gni
@@ -7,7 +7,10 @@
_src = get_path_info("src", "abspath")
_include = get_path_info("include", "abspath")
-skia_skottie_public = [ "$_include/Skottie.h" ]
+skia_skottie_public = [
+ "$_include/Skottie.h",
+ "$_include/SkottieProperty.h",
+]
skia_skottie_sources = [
"$_src/Skottie.cpp",
@@ -20,6 +23,7 @@
"$_src/SkottieLayerEffect.cpp",
"$_src/SkottiePriv.h",
"$_src/SkottiePrecompLayer.cpp",
+ "$_src/SkottieProperty.cpp",
"$_src/SkottieShapeLayer.cpp",
"$_src/SkottieTextLayer.cpp",
"$_src/SkottieValue.cpp",
diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp
index 5f5516c..9588e48 100644
--- a/modules/skottie/src/Skottie.cpp
+++ b/modules/skottie/src/Skottie.cpp
@@ -27,6 +27,7 @@
#include "SkottieAdapter.h"
#include "SkottieJson.h"
#include "SkottiePriv.h"
+#include "SkottieProperty.h"
#include "SkottieValue.h"
#include <cmath>
@@ -83,7 +84,9 @@
adapter->setSkewAxis(sa);
}, 0.0f);
- return bound ? matrix : parentMatrix;
+ const auto dispatched = this->dispatchTransformProperty(adapter);
+
+ return (bound || dispatched) ? matrix : parentMatrix;
}
sk_sp<sksg::RenderNode> AnimationBuilder::attachOpacity(const skjson::ObjectValue& jtransform,
@@ -94,16 +97,15 @@
auto opacityNode = sksg::OpacityEffect::Make(childNode);
- if (!this->bindProperty<ScalarValue>(jtransform["o"], ascope,
+ const auto bound = this->bindProperty<ScalarValue>(jtransform["o"], ascope,
[opacityNode](const ScalarValue& o) {
// BM opacity is [0..100]
opacityNode->setOpacity(o * 0.01f);
- }, 100.0f)) {
- // We can ignore static full opacity.
- return childNode;
- }
+ }, 100.0f);
+ const auto dispatched = this->dispatchOpacityProperty(opacityNode);
- return std::move(opacityNode);
+ // We can ignore constant full opacity.
+ return (bound || dispatched) ? std::move(opacityNode) : childNode;
}
sk_sp<sksg::Path> AnimationBuilder::attachPath(const skjson::Value& jpath,
@@ -124,19 +126,23 @@
AnimatorScope* ascope,
const char prop_name[]) const {
auto color_node = sksg::Color::Make(SK_ColorBLACK);
+
this->bindProperty<VectorValue>(jcolor[prop_name], ascope,
[color_node](const VectorValue& c) {
color_node->setColor(ValueTraits<VectorValue>::As<SkColor>(c));
});
+ this->dispatchColorProperty(color_node);
return color_node;
}
AnimationBuilder::AnimationBuilder(sk_sp<ResourceProvider> rp, sk_sp<SkFontMgr> fontmgr,
+ sk_sp<PropertyObserver> pobserver,
Animation::Builder::Stats* stats,
float duration, float framerate)
: fResourceProvider(std::move(rp))
, fLazyFontMgr(std::move(fontmgr))
+ , fPropertyObserver(std::move(pobserver))
, fStats(stats)
, fDuration(duration)
, fFrameRate(framerate) {}
@@ -165,6 +171,56 @@
}
}
+bool AnimationBuilder::dispatchColorProperty(const sk_sp<sksg::Color>& c) const {
+ bool dispatched = false;
+
+ if (fPropertyObserver) {
+ fPropertyObserver->onColorProperty(fPropertyObserverContext,
+ [&]() {
+ dispatched = true;
+ return std::unique_ptr<ColorPropertyHandle>(new ColorPropertyHandle(c));
+ });
+ }
+
+ return dispatched;
+}
+
+bool AnimationBuilder::dispatchOpacityProperty(const sk_sp<sksg::OpacityEffect>& o) const {
+ bool dispatched = false;
+
+ if (fPropertyObserver) {
+ fPropertyObserver->onOpacityProperty(fPropertyObserverContext,
+ [&]() {
+ dispatched = true;
+ return std::unique_ptr<OpacityPropertyHandle>(new OpacityPropertyHandle(o));
+ });
+ }
+
+ return dispatched;
+}
+
+bool AnimationBuilder::dispatchTransformProperty(const sk_sp<TransformAdapter>& t) const {
+ bool dispatched = false;
+
+ if (fPropertyObserver) {
+ fPropertyObserver->onTransformProperty(fPropertyObserverContext,
+ [&]() {
+ dispatched = true;
+ return std::unique_ptr<TransformPropertyHandle>(new TransformPropertyHandle(t));
+ });
+ }
+
+ return dispatched;
+}
+
+void AnimationBuilder::AutoPropertyTracker::updateContext(PropertyObserver* observer,
+ const skjson::ObjectValue& obj) {
+
+ const skjson::StringValue* name = obj["nm"];
+
+ fBuilder->fPropertyObserverContext = name ? name->begin() : nullptr;
+}
+
} // namespace internal
sk_sp<SkData> ResourceProvider::load(const char[], const char[]) const {
@@ -175,6 +231,9 @@
return nullptr;
}
+Animation::Builder::Builder() = default;
+Animation::Builder::~Builder() = default;
+
Animation::Builder& Animation::Builder::setResourceProvider(sk_sp<ResourceProvider> rp) {
fResourceProvider = std::move(rp);
return *this;
@@ -185,6 +244,11 @@
return *this;
}
+Animation::Builder& Animation::Builder::setPropertyObserver(sk_sp<PropertyObserver> pobserver) {
+ fPropertyObserver = std::move(pobserver);
+ return *this;
+}
+
sk_sp<Animation> Animation::Builder::make(SkStream* stream) {
if (!stream->hasLength()) {
// TODO: handle explicit buffering?
@@ -243,7 +307,7 @@
SkASSERT(resolvedProvider);
internal::AnimationBuilder builder(std::move(resolvedProvider), fFontMgr,
- &fStats, duration, fps);
+ std::move(fPropertyObserver), &fStats, duration, fps);
auto scene = builder.parse(json);
const auto t2 = SkTime::GetMSecs();
diff --git a/modules/skottie/src/SkottieAdapter.cpp b/modules/skottie/src/SkottieAdapter.cpp
index 3c0fb54..b317c75 100644
--- a/modules/skottie/src/SkottieAdapter.cpp
+++ b/modules/skottie/src/SkottieAdapter.cpp
@@ -43,7 +43,7 @@
TransformAdapter::TransformAdapter(sk_sp<sksg::Matrix> matrix)
: fMatrixNode(std::move(matrix)) {}
-void TransformAdapter::apply() {
+SkMatrix TransformAdapter::totalMatrix() const {
SkMatrix t = SkMatrix::MakeTrans(-fAnchorPoint.x(), -fAnchorPoint.y());
t.postScale(fScale.x() / 100, fScale.y() / 100); // 100% based
@@ -51,7 +51,11 @@
t.postTranslate(fPosition.x(), fPosition.y());
// TODO: skew
- fMatrixNode->setMatrix(t);
+ return t;
+}
+
+void TransformAdapter::apply() {
+ fMatrixNode->setMatrix(this->totalMatrix());
}
PolyStarAdapter::PolyStarAdapter(sk_sp<sksg::Path> wrapped_node, Type t)
diff --git a/modules/skottie/src/SkottieAdapter.h b/modules/skottie/src/SkottieAdapter.h
index 37dd077..f9a40fd 100644
--- a/modules/skottie/src/SkottieAdapter.h
+++ b/modules/skottie/src/SkottieAdapter.h
@@ -32,6 +32,9 @@
namespace skottie {
#define ADAPTER_PROPERTY(p_name, p_type, p_default) \
+ const p_type& get##p_name() const { \
+ return f##p_name; \
+ } \
void set##p_name(const p_type& p) { \
if (p == f##p_name) return; \
f##p_name = p; \
@@ -93,6 +96,8 @@
ADAPTER_PROPERTY(Skew , SkScalar, 0)
ADAPTER_PROPERTY(SkewAxis , SkScalar, 0)
+ SkMatrix totalMatrix() const;
+
private:
void apply();
diff --git a/modules/skottie/src/SkottieLayer.cpp b/modules/skottie/src/SkottieLayer.cpp
index b01acba..5924356 100644
--- a/modules/skottie/src/SkottieLayer.cpp
+++ b/modules/skottie/src/SkottieLayer.cpp
@@ -408,6 +408,8 @@
AttachLayerContext* layerCtx) const {
if (!jlayer) return nullptr;
+ const AutoPropertyTracker apt(this, *jlayer);
+
using LayerAttacher = sk_sp<sksg::RenderNode> (AnimationBuilder::*)(const skjson::ObjectValue&,
AnimatorScope*) const;
static constexpr LayerAttacher gLayerAttachers[] = {
diff --git a/modules/skottie/src/SkottiePriv.h b/modules/skottie/src/SkottiePriv.h
index 02ccbd9..b8c69b4 100644
--- a/modules/skottie/src/SkottiePriv.h
+++ b/modules/skottie/src/SkottiePriv.h
@@ -11,6 +11,7 @@
#include "Skottie.h"
#include "SkFontStyle.h"
+#include "SkottieProperty.h"
#include "SkSGScene.h"
#include "SkString.h"
#include "SkTHash.h"
@@ -44,8 +45,8 @@
class AnimationBuilder final : public SkNoncopyable {
public:
- AnimationBuilder(sk_sp<ResourceProvider>, sk_sp<SkFontMgr>, Animation::Builder::Stats*,
- float duration, float framerate);
+ AnimationBuilder(sk_sp<ResourceProvider>, sk_sp<SkFontMgr>, sk_sp<PropertyObserver>,
+ Animation::Builder::Stats*, float duration, float framerate);
std::unique_ptr<sksg::Scene> parse(const skjson::ObjectValue&);
@@ -77,6 +78,7 @@
private:
struct AttachLayerContext;
+ struct AttachShapeContext;
void parseAssets(const skjson::ArrayValue*);
void parseFonts (const skjson::ObjectValue* jfonts,
@@ -87,6 +89,7 @@
sk_sp<sksg::RenderNode> attachLayerEffects(const skjson::ArrayValue& jeffects, AnimatorScope*,
sk_sp<sksg::RenderNode>) const;
+ sk_sp<sksg::RenderNode> attachShape(const skjson::ArrayValue*, AttachShapeContext*) const;
sk_sp<sksg::RenderNode> attachAssetRef(const skjson::ObjectValue&, AnimatorScope*,
sk_sp<sksg::RenderNode>(AnimationBuilder::*)(const skjson::ObjectValue&,
AnimatorScope* ctx) const) const;
@@ -101,6 +104,10 @@
sk_sp<sksg::RenderNode> attachSolidLayer (const skjson::ObjectValue&, AnimatorScope*) const;
sk_sp<sksg::RenderNode> attachTextLayer (const skjson::ObjectValue&, AnimatorScope*) const;
+ bool dispatchColorProperty(const sk_sp<sksg::Color>&) const;
+ bool dispatchOpacityProperty(const sk_sp<sksg::OpacityEffect>&) const;
+ bool dispatchTransformProperty(const sk_sp<TransformAdapter>&) const;
+
// Delay resolving the fontmgr until it is actually needed.
struct LazyResolveFontMgr {
LazyResolveFontMgr(sk_sp<SkFontMgr> fontMgr) : fFontMgr(std::move(fontMgr)) {}
@@ -119,12 +126,37 @@
sk_sp<SkFontMgr> fFontMgr;
};
+ class AutoPropertyTracker {
+ public:
+ AutoPropertyTracker(const AnimationBuilder* builder, const skjson::ObjectValue& obj)
+ : fBuilder(builder)
+ , fPrevContext(builder->fPropertyObserverContext) {
+ if (fBuilder->fPropertyObserver) {
+ this->updateContext(builder->fPropertyObserver.get(), obj);
+ }
+ }
+
+ ~AutoPropertyTracker() {
+ if (fBuilder->fPropertyObserver) {
+ fBuilder->fPropertyObserverContext = fPrevContext;
+ }
+ }
+ private:
+ void updateContext(PropertyObserver*, const skjson::ObjectValue&);
+
+ const AnimationBuilder* fBuilder;
+ const char* fPrevContext;
+ };
+
sk_sp<ResourceProvider> fResourceProvider;
LazyResolveFontMgr fLazyFontMgr;
+ sk_sp<PropertyObserver> fPropertyObserver;
Animation::Builder::Stats* fStats;
const float fDuration,
fFrameRate;
+ mutable const char* fPropertyObserverContext;
+
struct AssetInfo {
const skjson::ObjectValue* fAsset;
mutable bool fIsAttaching; // Used for cycle detection
diff --git a/modules/skottie/src/SkottieProperty.cpp b/modules/skottie/src/SkottieProperty.cpp
new file mode 100644
index 0000000..7ca35b9
--- /dev/null
+++ b/modules/skottie/src/SkottieProperty.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkottieProperty.h"
+
+#include "SkottieAdapter.h"
+#include "SkSGColor.h"
+#include "SkSGOpacityEffect.h"
+
+namespace skottie {
+
+ColorPropertyHandle::ColorPropertyHandle(sk_sp<sksg::Color> color)
+ : fColor(std::move(color)) {
+ SkASSERT(fColor);
+}
+
+ColorPropertyHandle::~ColorPropertyHandle() = default;
+
+SkColor ColorPropertyHandle::getColor() const {
+ return fColor->getColor();
+}
+
+void ColorPropertyHandle::setColor(SkColor color) {
+ fColor->setColor(color);
+}
+
+OpacityPropertyHandle::OpacityPropertyHandle(sk_sp<sksg::OpacityEffect> opacity)
+ : fOpacity(std::move(opacity)) {
+ SkASSERT(fOpacity);
+}
+
+OpacityPropertyHandle::~OpacityPropertyHandle() = default;
+
+float OpacityPropertyHandle::getOpacity() const {
+ return fOpacity->getOpacity() * 100;
+}
+
+void OpacityPropertyHandle::setOpacity(float opacity) {
+ fOpacity->setOpacity(opacity / 100);
+}
+
+TransformPropertyHandle::TransformPropertyHandle(sk_sp<TransformAdapter> transform)
+ : fTransform(std::move(transform)) {
+ SkASSERT(fTransform);
+}
+
+TransformPropertyHandle::~TransformPropertyHandle() = default;
+
+SkPoint TransformPropertyHandle::getAnchorPoint() const {
+ return fTransform->getAnchorPoint();
+}
+
+void TransformPropertyHandle::setAnchorPoint(const SkPoint& ap) {
+ fTransform->setAnchorPoint(ap);
+}
+
+SkPoint TransformPropertyHandle::getPosition() const {
+ return fTransform->getPosition();
+}
+
+void TransformPropertyHandle::setPosition(const SkPoint& position) {
+ fTransform->setPosition(position);
+}
+
+SkVector TransformPropertyHandle::getScale() const {
+ return fTransform->getScale();
+}
+
+void TransformPropertyHandle::setScale(const SkVector& scale) {
+ fTransform->setScale(scale);
+}
+
+SkScalar TransformPropertyHandle::getRotation() const {
+ return fTransform->getRotation();
+}
+
+void TransformPropertyHandle::setRotation(SkScalar rotation) {
+ fTransform->setRotation(rotation);
+}
+
+SkScalar TransformPropertyHandle::getSkew() const {
+ return fTransform->getSkew();
+}
+
+void TransformPropertyHandle::setSkew(SkScalar skew) {
+ fTransform->setSkew(skew);
+}
+
+SkScalar TransformPropertyHandle::getSkewAxis() const {
+ return fTransform->getSkewAxis();
+}
+
+void TransformPropertyHandle::setSkewAxis(SkScalar sa) {
+ fTransform->setSkewAxis(sa);
+}
+
+SkMatrix TransformPropertyHandle::getTotalMatrix() const {
+ return fTransform->totalMatrix();
+}
+
+void PropertyObserver::onColorProperty(const char[],
+ const LazyHandle<ColorPropertyHandle>&) {}
+
+void PropertyObserver::onOpacityProperty(const char[],
+ const LazyHandle<OpacityPropertyHandle>&) {}
+
+void PropertyObserver::onTransformProperty(const char[],
+ const LazyHandle<TransformPropertyHandle>&) {}
+
+} // namespace skottie
diff --git a/modules/skottie/src/SkottieShapeLayer.cpp b/modules/skottie/src/SkottieShapeLayer.cpp
index b6e7a27..12f2695 100644
--- a/modules/skottie/src/SkottieShapeLayer.cpp
+++ b/modules/skottie/src/SkottieShapeLayer.cpp
@@ -430,26 +430,26 @@
GeometryEffectAttacherT fAttach;
};
-struct AttachShapeContext {
- AttachShapeContext(const AnimationBuilder* abuilder,
- AnimatorScope* ascope,
+} // namespace
+
+struct AnimationBuilder::AttachShapeContext {
+ AttachShapeContext(AnimatorScope* ascope,
std::vector<sk_sp<sksg::GeometryNode>>* geos,
std::vector<GeometryEffectRec>* effects,
size_t committedAnimators)
- : fBuilder(abuilder)
- , fScope(ascope)
+ : fScope(ascope)
, fGeometryStack(geos)
, fGeometryEffectStack(effects)
, fCommittedAnimators(committedAnimators) {}
- const AnimationBuilder* fBuilder;
AnimatorScope* fScope;
std::vector<sk_sp<sksg::GeometryNode>>* fGeometryStack;
std::vector<GeometryEffectRec>* fGeometryEffectStack;
size_t fCommittedAnimators;
};
-sk_sp<sksg::RenderNode> AttachShape(const skjson::ArrayValue* jshape, AttachShapeContext* ctx) {
+sk_sp<sksg::RenderNode> AnimationBuilder::attachShape(const skjson::ArrayValue* jshape,
+ AttachShapeContext* ctx) const {
if (!jshape)
return nullptr;
@@ -504,11 +504,13 @@
std::vector<sk_sp<sksg::GeometryNode>> geos;
std::vector<sk_sp<sksg::RenderNode >> draws;
for (auto rec = recs.rbegin(); rec != recs.rend(); ++rec) {
+ const AutoPropertyTracker apt(this, rec->fJson);
+
switch (rec->fInfo.fShapeType) {
case ShapeType::kGeometry: {
SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gGeometryAttachers));
if (auto geo = gGeometryAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
- ctx->fBuilder,
+ this,
ctx->fScope)) {
geos.push_back(std::move(geo));
}
@@ -518,7 +520,7 @@
SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
if (!geos.empty()) {
geos = gGeometryEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
- ctx->fBuilder,
+ this,
ctx->fScope,
std::move(geos));
}
@@ -529,12 +531,11 @@
ctx->fGeometryEffectStack->pop_back();
} break;
case ShapeType::kGroup: {
- AttachShapeContext groupShapeCtx(ctx->fBuilder,
- ctx->fScope,
+ AttachShapeContext groupShapeCtx(ctx->fScope,
&geos,
ctx->fGeometryEffectStack,
ctx->fCommittedAnimators);
- if (auto subgroup = AttachShape(rec->fJson["it"], &groupShapeCtx)) {
+ if (auto subgroup = this->attachShape(rec->fJson["it"], &groupShapeCtx)) {
draws.push_back(std::move(subgroup));
SkASSERT(groupShapeCtx.fCommittedAnimators >= ctx->fCommittedAnimators);
ctx->fCommittedAnimators = groupShapeCtx.fCommittedAnimators;
@@ -543,7 +544,7 @@
case ShapeType::kPaint: {
SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gPaintAttachers));
auto paint = gPaintAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
- ctx->fBuilder,
+ this,
ctx->fScope);
if (!paint || geos.empty())
break;
@@ -553,7 +554,7 @@
// Apply all pending effects from the stack.
for (auto it = ctx->fGeometryEffectStack->rbegin();
it != ctx->fGeometryEffectStack->rend(); ++it) {
- drawGeos = it->fAttach(it->fJson, ctx->fBuilder, ctx->fScope, std::move(drawGeos));
+ drawGeos = it->fAttach(it->fJson, this, ctx->fScope, std::move(drawGeos));
}
// If we still have multiple geos, reduce using 'merge'.
@@ -588,16 +589,17 @@
sk_sp<sksg::Matrix> shape_matrix;
if (jtransform) {
+ const AutoPropertyTracker apt(this, *jtransform);
+
// This is tricky due to the interaction with ctx->fCommittedAnimators: we want any
// animators related to tranform/opacity to be committed => they must be inserted in front
// of the dangling/uncommitted ones.
AnimatorScope local_scope;
- if ((shape_matrix = ctx->fBuilder->attachMatrix(*jtransform, &local_scope, nullptr))) {
+ if ((shape_matrix = this->attachMatrix(*jtransform, &local_scope, nullptr))) {
shape_wrapper = sksg::Transform::Make(std::move(shape_wrapper), shape_matrix);
}
- shape_wrapper = ctx->fBuilder->attachOpacity(*jtransform, &local_scope,
- std::move(shape_wrapper));
+ shape_wrapper = this->attachOpacity(*jtransform, &local_scope, std::move(shape_wrapper));
ctx->fScope->insert(ctx->fScope->begin() + ctx->fCommittedAnimators,
std::make_move_iterator(local_scope.begin()),
@@ -615,14 +617,12 @@
return shape_wrapper;
}
-} // namespace
-
sk_sp<sksg::RenderNode> AnimationBuilder::attachShapeLayer(const skjson::ObjectValue& layer,
AnimatorScope* ascope) const {
std::vector<sk_sp<sksg::GeometryNode>> geometryStack;
std::vector<GeometryEffectRec> geometryEffectStack;
- AttachShapeContext shapeCtx(this, ascope, &geometryStack, &geometryEffectStack, ascope->size());
- auto shapeNode = AttachShape(layer["shapes"], &shapeCtx);
+ AttachShapeContext shapeCtx(ascope, &geometryStack, &geometryEffectStack, ascope->size());
+ auto shapeNode = this->attachShape(layer["shapes"], &shapeCtx);
// Trim uncommitted animators: AttachShape consumes effects on the fly, and greedily attaches
// geometries => at the end, we can end up with unused geometries, which are nevertheless alive
diff --git a/modules/skottie/src/SkottieTest.cpp b/modules/skottie/src/SkottieTest.cpp
index 1c4cc07..e3bd68b 100644
--- a/modules/skottie/src/SkottieTest.cpp
+++ b/modules/skottie/src/SkottieTest.cpp
@@ -5,13 +5,19 @@
* found in the LICENSE file.
*/
+#include "SkMatrix.h"
#include "Skottie.h"
+#include "SkottieProperty.h"
#include "SkStream.h"
#include "Test.h"
+#include <vector>
+
+using namespace skottie;
+
DEF_TEST(Skottie_OssFuzz8956, reporter) {
- static constexpr const char json[] =
+ static constexpr char json[] =
"{\"v\":\" \",\"fr\":3,\"w\":4,\"h\":3,\"layers\":[{\"ty\": 1, \"sw\": 10, \"sh\": 10,"
" \"sc\":\"#ffffff\", \"ks\":{\"o\":{\"a\": true, \"k\":"
" [{\"t\": 0, \"s\": 0, \"e\": 1, \"i\": {\"x\":[]}}]}}}]}";
@@ -19,5 +25,117 @@
SkMemoryStream stream(json, strlen(json));
// Passes if parsing doesn't crash.
- auto animation = skottie::Animation::Make(&stream);
+ auto animation = Animation::Make(&stream);
+}
+
+DEF_TEST(Skottie_Properties, reporter) {
+ static constexpr char json[] = R"({
+ "v": "5.2.1",
+ "w": 100,
+ "h": 100,
+ "fr": 1,
+ "ip": 0,
+ "op": 1,
+ "layers": [
+ {
+ "ty": 4,
+ "nm": "layer_0",
+ "ind": 0,
+ "ip": 0,
+ "op": 1,
+ "ks": {
+ "o": { "a": 0, "k": 50 }
+ },
+ "shapes": [
+ {
+ "ty": "el",
+ "nm": "geometry_0",
+ "p": { "a": 0, "k": [ 50, 50 ] },
+ "s": { "a": 0, "k": [ 50, 50 ] }
+ },
+ {
+ "ty": "fl",
+ "nm": "fill_0",
+ "c": { "a": 0, "k": [ 1, 0, 0] }
+ },
+ {
+ "ty": "tr",
+ "nm": "shape_transform_0",
+ "o": { "a": 0, "k": 100 },
+ "s": { "a": 0, "k": [ 50, 50 ] }
+ }
+ ]
+ }
+ ]
+ })";
+
+ class TestPropertyObserver final : public PropertyObserver {
+ public:
+ struct ColorInfo {
+ SkString node_name;
+ SkColor color;
+ };
+
+ struct OpacityInfo {
+ SkString node_name;
+ float opacity;
+ };
+
+ struct TransformInfo {
+ SkString node_name;
+ SkMatrix matrix;
+ };
+
+ void onColorProperty(const char node_name[],
+ const PropertyObserver::LazyHandle<ColorPropertyHandle>& lh) override {
+ fColors.push_back({SkString(node_name), lh()->getColor()});
+ }
+
+ void onOpacityProperty(const char node_name[],
+ const PropertyObserver::LazyHandle<OpacityPropertyHandle>& lh) override {
+ fOpacities.push_back({SkString(node_name), lh()->getOpacity()});
+ }
+
+ void onTransformProperty(const char node_name[],
+ const PropertyObserver::LazyHandle<TransformPropertyHandle>& lh) override {
+ fTransforms.push_back({SkString(node_name), lh()->getTotalMatrix()});
+ }
+
+ const std::vector<ColorInfo>& colors() const { return fColors; }
+ const std::vector<OpacityInfo>& opacities() const { return fOpacities; }
+ const std::vector<TransformInfo>& transforms() const { return fTransforms; }
+
+ private:
+ std::vector<ColorInfo> fColors;
+ std::vector<OpacityInfo> fOpacities;
+ std::vector<TransformInfo> fTransforms;
+ };
+
+ SkMemoryStream stream(json, strlen(json));
+ auto observer = sk_make_sp<TestPropertyObserver>();
+
+ auto animation = skottie::Animation::Builder()
+ .setPropertyObserver(observer)
+ .make(&stream);
+
+ REPORTER_ASSERT(reporter, animation);
+
+ const auto& colors = observer->colors();
+ REPORTER_ASSERT(reporter, colors.size() == 1);
+ REPORTER_ASSERT(reporter, colors[0].node_name.equals("fill_0"));
+ REPORTER_ASSERT(reporter, colors[0].color == 0xffff0000);
+
+ const auto& opacities = observer->opacities();
+ REPORTER_ASSERT(reporter, opacities.size() == 2);
+ REPORTER_ASSERT(reporter, opacities[0].node_name.equals("shape_transform_0"));
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(opacities[0].opacity, 100));
+ REPORTER_ASSERT(reporter, opacities[1].node_name.equals("layer_0"));
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(opacities[1].opacity, 50));
+
+ const auto& transforms = observer->transforms();
+ REPORTER_ASSERT(reporter, transforms.size() == 2);
+ REPORTER_ASSERT(reporter, transforms[0].node_name.equals("shape_transform_0"));
+ REPORTER_ASSERT(reporter, transforms[0].matrix == SkMatrix::MakeScale(0.5, 0.5));
+ REPORTER_ASSERT(reporter, transforms[1].node_name.equals("layer_0"));
+ REPORTER_ASSERT(reporter, transforms[1].matrix == SkMatrix::I());
}