[skottie] Add a custom property manager util class
To facilitate demo code consolidation, introduce a custom property
manager which filters for node names starting with '$' and treats all
properties sharing the same name unitarily.
Update the Colorize GM to use this new helper.
Also revisit the PropertyObserver interface:
* aliases for client-facing value types
* introduce a new (decomposed) TransformPropertyValue, to replace component-wise setters
* consolidate the PropertyHandle interface to only expose get()/set()
Bug: skia:
Change-Id: I9aa9ee80c1fb57bbfbacab0fc3f017da909b24d9
Reviewed-on: https://skia-review.googlesource.com/c/173220
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skottie/gm/SkottieGM.cpp b/modules/skottie/gm/SkottieGM.cpp
index 040b8b3..8febf25 100644
--- a/modules/skottie/gm/SkottieGM.cpp
+++ b/modules/skottie/gm/SkottieGM.cpp
@@ -90,6 +90,8 @@
DEF_GM(return new SkottieWebFontGM;)
+using namespace skottie_utils;
+
class SkottieColorizeGM : public skiagm::GM {
protected:
SkString onShortName() override {
@@ -102,10 +104,12 @@
void onOnceBeforeDraw() override {
if (auto stream = GetResourceAsStream("skottie/skottie_sample_search.json")) {
- fColorizer = sk_make_sp<Colorizer>();
- fAnimation = Animation::Builder()
- .setPropertyObserver(fColorizer)
- .make(stream.get());
+ auto propBuilder = sk_make_sp<CustomPropertyManagerBuilder>();
+ fAnimation = Animation::Builder()
+ .setPropertyObserver(propBuilder)
+ .make(stream.get());
+ fPropManager = propBuilder->build();
+ fColors = fPropManager->getColorProps();
}
}
@@ -139,7 +143,9 @@
if (uni == 'c') {
fColorIndex = (fColorIndex + 1) % SK_ARRAY_COUNT(kColors);
- fColorizer->colorize(kColors[fColorIndex]);
+ for (const auto& prop : fColors) {
+ fPropManager->setColor(prop, kColors[fColorIndex]);
+ }
return true;
}
@@ -147,28 +153,12 @@
}
private:
- class Colorizer final : public PropertyObserver {
- public:
- void onColorProperty(const char node_name[],
- const PropertyObserver::LazyHandle<ColorPropertyHandle>& lh) override {
- fColorHandles.push_back(lh());
- }
-
- void colorize(SkColor c) {
- for (const auto& handle : fColorHandles) {
- handle->setColor(c);
- }
- }
-
- private:
- std::vector<std::unique_ptr<skottie::ColorPropertyHandle>> fColorHandles;
- };
-
static constexpr SkScalar kSize = 800;
- sk_sp<Animation> fAnimation;
- sk_sp<Colorizer> fColorizer;
- size_t fColorIndex = 0;
+ sk_sp<Animation> fAnimation;
+ std::unique_ptr<CustomPropertyManager> fPropManager;
+ std::vector<CustomPropertyManager::PropKey> fColors;
+ size_t fColorIndex = 0;
using INHERITED = skiagm::GM;
};
diff --git a/modules/skottie/include/SkottieProperty.h b/modules/skottie/include/SkottieProperty.h
index 291b086..a738697 100644
--- a/modules/skottie/include/SkottieProperty.h
+++ b/modules/skottie/include/SkottieProperty.h
@@ -25,9 +25,48 @@
namespace skottie {
-class ColorPropertyHandle;
-class OpacityPropertyHandle;
-class TransformPropertyHandle;
+using ColorPropertyValue = SkColor;
+using OpacityPropertyValue = float;
+
+struct TransformPropertyValue {
+ SkPoint fAnchorPoint,
+ fPosition;
+ SkVector fScale;
+ SkScalar fRotation,
+ fSkew,
+ fSkewAxis;
+
+ bool operator==(const TransformPropertyValue& other) const;
+ bool operator!=(const TransformPropertyValue& other) const;
+};
+
+namespace internal { class AnimationBuilder; }
+
+/**
+ * Property handles are adapters between user-facing AE model/values
+ * and the internal scene-graph representation.
+ */
+template <typename ValueT, typename NodeT>
+class SK_API PropertyHandle final {
+public:
+ ~PropertyHandle();
+
+ ValueT get() const;
+ void set(const ValueT&);
+
+private:
+ explicit PropertyHandle(sk_sp<NodeT> node) : fNode(std::move(node)) {}
+
+ friend class skottie::internal::AnimationBuilder;
+
+ const sk_sp<NodeT> fNode;
+};
+
+class TransformAdapter;
+
+using ColorPropertyHandle = PropertyHandle<ColorPropertyValue , sksg::Color >;
+using OpacityPropertyHandle = PropertyHandle<OpacityPropertyValue , sksg::OpacityEffect >;
+using TransformPropertyHandle = PropertyHandle<TransformPropertyValue, TransformAdapter >;
/**
* A PropertyObserver can be used to track and manipulate certain properties of "interesting"
@@ -37,7 +76,7 @@
* various properties of layer and shape nodes. The |node_name| argument corresponds to the
* name ("nm") node property.
*/
-class PropertyObserver : public SkRefCnt {
+class SK_API PropertyObserver : public SkRefCnt {
public:
template <typename T>
using LazyHandle = std::function<std::unique_ptr<T>()>;
@@ -50,72 +89,6 @@
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/src/SkottieProperty.cpp b/modules/skottie/src/SkottieProperty.cpp
index 7ca35b9..0d27017 100644
--- a/modules/skottie/src/SkottieProperty.cpp
+++ b/modules/skottie/src/SkottieProperty.cpp
@@ -13,93 +13,68 @@
namespace skottie {
-ColorPropertyHandle::ColorPropertyHandle(sk_sp<sksg::Color> color)
- : fColor(std::move(color)) {
- SkASSERT(fColor);
+bool TransformPropertyValue::operator==(const TransformPropertyValue& other) const {
+ return this->fAnchorPoint == other.fAnchorPoint
+ && this->fPosition == other.fPosition
+ && this->fScale == other.fScale
+ && this->fSkew == other.fSkew
+ && this->fSkewAxis == other.fSkewAxis;
}
-ColorPropertyHandle::~ColorPropertyHandle() = default;
-
-SkColor ColorPropertyHandle::getColor() const {
- return fColor->getColor();
+bool TransformPropertyValue::operator!=(const TransformPropertyValue& other) const {
+ return !(*this == other);
}
-void ColorPropertyHandle::setColor(SkColor color) {
- fColor->setColor(color);
+template <>
+PropertyHandle<ColorPropertyValue, sksg::Color>::~PropertyHandle() {}
+
+template <>
+ColorPropertyValue PropertyHandle<ColorPropertyValue, sksg::Color>::get() const {
+ return fNode->getColor();
}
-OpacityPropertyHandle::OpacityPropertyHandle(sk_sp<sksg::OpacityEffect> opacity)
- : fOpacity(std::move(opacity)) {
- SkASSERT(fOpacity);
+template <>
+void PropertyHandle<ColorPropertyValue, sksg::Color>::set(const ColorPropertyValue& c) {
+ fNode->setColor(c);
}
-OpacityPropertyHandle::~OpacityPropertyHandle() = default;
+template <>
+PropertyHandle<OpacityPropertyValue, sksg::OpacityEffect>::~PropertyHandle() {}
-float OpacityPropertyHandle::getOpacity() const {
- return fOpacity->getOpacity() * 100;
+template <>
+OpacityPropertyValue PropertyHandle<OpacityPropertyValue, sksg::OpacityEffect>::get() const {
+ return fNode->getOpacity() * 100;
}
-void OpacityPropertyHandle::setOpacity(float opacity) {
- fOpacity->setOpacity(opacity / 100);
+template <>
+void PropertyHandle<OpacityPropertyValue, sksg::OpacityEffect>::set(const OpacityPropertyValue& o) {
+ fNode->setOpacity(o / 100);
}
-TransformPropertyHandle::TransformPropertyHandle(sk_sp<TransformAdapter> transform)
- : fTransform(std::move(transform)) {
- SkASSERT(fTransform);
+template <>
+PropertyHandle<TransformPropertyValue, TransformAdapter>::~PropertyHandle() {}
+
+template <>
+TransformPropertyValue PropertyHandle<TransformPropertyValue, TransformAdapter>::get() const {
+ return {
+ fNode->getAnchorPoint(),
+ fNode->getPosition(),
+ fNode->getScale(),
+ fNode->getRotation(),
+ fNode->getSkew(),
+ fNode->getSkewAxis()
+ };
}
-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();
+template <>
+void PropertyHandle<TransformPropertyValue, TransformAdapter>::set(
+ const TransformPropertyValue& t) {
+ fNode->setAnchorPoint(t.fAnchorPoint);
+ fNode->setPosition(t.fPosition);
+ fNode->setScale(t.fScale);
+ fNode->setRotation(t.fRotation);
+ fNode->setSkew(t.fSkew);
+ fNode->setSkewAxis(t.fSkewAxis);
}
void PropertyObserver::onColorProperty(const char[],
diff --git a/modules/skottie/src/SkottieTest.cpp b/modules/skottie/src/SkottieTest.cpp
index a44bc15..268b5dd 100644
--- a/modules/skottie/src/SkottieTest.cpp
+++ b/modules/skottie/src/SkottieTest.cpp
@@ -83,23 +83,23 @@
};
struct TransformInfo {
- SkString node_name;
- SkMatrix matrix;
+ SkString node_name;
+ skottie::TransformPropertyValue transform;
};
void onColorProperty(const char node_name[],
const PropertyObserver::LazyHandle<ColorPropertyHandle>& lh) override {
- fColors.push_back({SkString(node_name), lh()->getColor()});
+ fColors.push_back({SkString(node_name), lh()->get()});
}
void onOpacityProperty(const char node_name[],
const PropertyObserver::LazyHandle<OpacityPropertyHandle>& lh) override {
- fOpacities.push_back({SkString(node_name), lh()->getOpacity()});
+ fOpacities.push_back({SkString(node_name), lh()->get()});
}
void onTransformProperty(const char node_name[],
const PropertyObserver::LazyHandle<TransformPropertyHandle>& lh) override {
- fTransforms.push_back({SkString(node_name), lh()->getTotalMatrix()});
+ fTransforms.push_back({SkString(node_name), lh()->get()});
}
const std::vector<ColorInfo>& colors() const { return fColors; }
@@ -136,9 +136,23 @@
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[0].transform == skottie::TransformPropertyValue({
+ SkPoint::Make(0, 0),
+ SkPoint::Make(0, 0),
+ SkVector::Make(50, 50),
+ 0,
+ 0,
+ 0
+ }));
REPORTER_ASSERT(reporter, transforms[1].node_name.equals("layer_0"));
- REPORTER_ASSERT(reporter, transforms[1].matrix == SkMatrix::I());
+ REPORTER_ASSERT(reporter, transforms[1].transform == skottie::TransformPropertyValue({
+ SkPoint::Make(0, 0),
+ SkPoint::Make(0, 0),
+ SkVector::Make(100, 100),
+ 0,
+ 0,
+ 0
+ }));
}
DEF_TEST(Skottie_Annotations, reporter) {
diff --git a/modules/skottie/utils/SkottieUtils.cpp b/modules/skottie/utils/SkottieUtils.cpp
index d8f39d3..92efc2f 100644
--- a/modules/skottie/utils/SkottieUtils.cpp
+++ b/modules/skottie/utils/SkottieUtils.cpp
@@ -60,4 +60,123 @@
return MultiFrameImageAsset::Make(this->load(resource_path, resource_name));
}
+CustomPropertyManagerBuilder::CustomPropertyManagerBuilder() = default;
+CustomPropertyManagerBuilder::~CustomPropertyManagerBuilder() = default;
+
+std::unique_ptr<CustomPropertyManager> CustomPropertyManagerBuilder::build() {
+ return std::unique_ptr<CustomPropertyManager>(
+ new CustomPropertyManager(std::move(fColorMap),
+ std::move(fOpacityMap),
+ std::move(fTransformMap)));
+}
+
+void CustomPropertyManagerBuilder::onColorProperty(
+ const char node_name[],
+ const LazyHandle<skottie::ColorPropertyHandle>& c) {
+ const auto key = this->acceptProperty(node_name);
+ if (!key.empty()) {
+ fColorMap[key].push_back(c());
+ }
+}
+
+void CustomPropertyManagerBuilder::onOpacityProperty(
+ const char node_name[],
+ const LazyHandle<skottie::OpacityPropertyHandle>& o) {
+ const auto key = this->acceptProperty(node_name);
+ if (!key.empty()) {
+ fOpacityMap[key].push_back(o());
+ }
+}
+
+void CustomPropertyManagerBuilder::onTransformProperty(
+ const char node_name[],
+ const LazyHandle<skottie::TransformPropertyHandle>& t) {
+ const auto key = this->acceptProperty(node_name);
+ if (!key.empty()) {
+ fTransformMap[key].push_back(t());
+ }
+}
+
+CustomPropertyManager::CustomPropertyManager(PropMap<skottie::ColorPropertyHandle> cmap,
+ PropMap<skottie::OpacityPropertyHandle> omap,
+ PropMap<skottie::TransformPropertyHandle> tmap)
+ : fColorMap(std::move(cmap))
+ , fOpacityMap(std::move(omap))
+ , fTransformMap(std::move(tmap)) {}
+
+CustomPropertyManager::~CustomPropertyManager() = default;
+
+template <typename T>
+std::vector<CustomPropertyManager::PropKey>
+CustomPropertyManager::getProps(const PropMap<T>& container) const {
+ std::vector<PropKey> props;
+
+ for (const auto& prop_list : container) {
+ SkASSERT(!prop_list.second.empty());
+ props.push_back(prop_list.first);
+ }
+
+ return props;
+}
+
+template <typename V, typename T>
+V CustomPropertyManager::get(const PropKey& key, const PropMap<T>& container) const {
+ auto prop_group = container.find(key);
+
+ return prop_group == container.end()
+ ? V()
+ : prop_group->second.front()->get();
+}
+
+template <typename V, typename T>
+bool CustomPropertyManager::set(const PropKey& key, const V& val, const PropMap<T>& container) {
+ auto prop_group = container.find(key);
+
+ if (prop_group == container.end()) {
+ return false;
+ }
+
+ for (auto& handle : prop_group->second) {
+ handle->set(val);
+ }
+
+ return true;
+}
+
+std::vector<CustomPropertyManager::PropKey>
+CustomPropertyManager::getColorProps() const {
+ return this->getProps(fColorMap);
+}
+
+skottie::ColorPropertyValue CustomPropertyManager::getColor(const PropKey& key) const {
+ return this->get<skottie::ColorPropertyValue>(key, fColorMap);
+}
+
+bool CustomPropertyManager::setColor(const PropKey& key, const skottie::ColorPropertyValue& c) {
+ return this->set(key, c, fColorMap);
+}
+
+std::vector<CustomPropertyManager::PropKey>
+CustomPropertyManager::getOpacityProps() const {
+ return this->getProps(fOpacityMap);
+}
+
+skottie::OpacityPropertyValue CustomPropertyManager::getOpacity(const PropKey& key) const {
+ return this->get<skottie::OpacityPropertyValue>(key, fOpacityMap);
+}
+
+bool CustomPropertyManager::setOpacity(const PropKey& key, const skottie::OpacityPropertyValue& o) {
+ return this->set(key, o, fOpacityMap);
+}
+
+std::vector<CustomPropertyManager::PropKey>
+CustomPropertyManager::getTransformProps() const {
+ return this->getProps(fTransformMap);
+}
+
+bool CustomPropertyManager::setTransform(const PropKey& key,
+ const skottie::TransformPropertyValue& t) {
+ return this->set(key, t, fTransformMap);
+}
+
} // namespace skottie_utils
diff --git a/modules/skottie/utils/SkottieUtils.h b/modules/skottie/utils/SkottieUtils.h
index 06f134a..23dec6c 100644
--- a/modules/skottie/utils/SkottieUtils.h
+++ b/modules/skottie/utils/SkottieUtils.h
@@ -8,10 +8,16 @@
#ifndef SkottieUtils_DEFINED
#define SkottieUtils_DEFINED
+#include "SkColor.h"
#include "Skottie.h"
+#include "SkottieProperty.h"
#include "SkString.h"
+#include "SkTHash.h"
#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
class SkAnimCodecPlayer;
class SkData;
@@ -51,6 +57,96 @@
using INHERITED = skottie::ResourceProvider;
};
+/**
+ * CustomPropertyManager implements a property management scheme where color/opacity/transform
+ * attributes are grouped and manipulated by name (one-to-many mapping).
+ *
+ * - setters apply the value to all properties in a named group
+ *
+ * - getters return all the managed property groups, and the first value within each of them
+ * (unchecked assumption: all properties within the same group have the same value)
+ *
+ * Use CustomPropertyManagerBuilder to filter nodes at animation build time, and instantiate a
+ * CustomPropertyManager.
+ */
+class CustomPropertyManager final {
+public:
+ ~CustomPropertyManager();
+
+ using PropKey = std::string;
+
+ std::vector<PropKey> getColorProps() const;
+ skottie::ColorPropertyValue getColor(const PropKey&) const;
+ bool setColor(const PropKey&, const skottie::ColorPropertyValue&);
+
+ std::vector<PropKey> getOpacityProps() const;
+ skottie::OpacityPropertyValue getOpacity(const PropKey&) const;
+ bool setOpacity(const PropKey&, const skottie::OpacityPropertyValue&);
+
+ std::vector<PropKey> getTransformProps() const;
+ skottie::TransformPropertyValue getTransform(const PropKey&) const;
+ bool setTransform(const PropKey&, const skottie::TransformPropertyValue&);
+
+private:
+ friend class CustomPropertyManagerBuilder;
+
+ template <typename T>
+ using PropGroup = std::vector<std::unique_ptr<T>>;
+
+ template <typename T>
+ using PropMap = std::unordered_map<PropKey, PropGroup<T>>;
+
+ template <typename T>
+ std::vector<PropKey> getProps(const PropMap<T>& container) const;
+
+ template <typename V, typename T>
+ V get(const PropKey&, const PropMap<T>& container) const;
+
+ template <typename V, typename T>
+ bool set(const PropKey&, const V&, const PropMap<T>& container);
+
+ CustomPropertyManager(PropMap<skottie::ColorPropertyHandle>,
+ PropMap<skottie::OpacityPropertyHandle>,
+ PropMap<skottie::TransformPropertyHandle>);
+
+ PropMap<skottie::ColorPropertyHandle> fColorMap;
+ PropMap<skottie::OpacityPropertyHandle> fOpacityMap;
+ PropMap<skottie::TransformPropertyHandle> fTransformMap;
+};
+
+/**
+ * A builder for CustomPropertyManager. Only accepts node names starting with '$'.
+ */
+class CustomPropertyManagerBuilder final : public skottie::PropertyObserver {
+public:
+ CustomPropertyManagerBuilder();
+ ~CustomPropertyManagerBuilder() override;
+
+ std::unique_ptr<CustomPropertyManager> build();
+
+ void onColorProperty (const char node_name[],
+ const LazyHandle<skottie::ColorPropertyHandle>&) override;
+ void onOpacityProperty (const char node_name[],
+ const LazyHandle<skottie::OpacityPropertyHandle>&) override;
+ void onTransformProperty(const char node_name[],
+ const LazyHandle<skottie::TransformPropertyHandle>&) override;
+
+private:
+ std::string acceptProperty(const char* name) const {
+ static constexpr char kPrefix = '$';
+
+ return (name[0] == kPrefix && name[1] != '\0')
+ ? std::string(name + 1)
+ : std::string();
+ }
+
+ CustomPropertyManager::PropMap<skottie::ColorPropertyHandle> fColorMap;
+ CustomPropertyManager::PropMap<skottie::OpacityPropertyHandle> fOpacityMap;
+ CustomPropertyManager::PropMap<skottie::TransformPropertyHandle> fTransformMap;
+
+ using INHERITED = skottie::PropertyObserver;
+};
+
} // namespace skottie_utils
#endif // SkottieUtils_DEFINED
diff --git a/resources/skottie/skottie_sample_search.json b/resources/skottie/skottie_sample_search.json
index dddb766..f880f0a 100644
--- a/resources/skottie/skottie_sample_search.json
+++ b/resources/skottie/skottie_sample_search.json
@@ -117,7 +117,7 @@
"lc": 2,
"lj": 2,
"mn": "ADBE Vector Graphic - Stroke",
- "nm": "Stroke 1",
+ "nm": "$Stroke 1",
"o": {
"a": 0,
"ix": 4,
@@ -143,7 +143,7 @@
},
"hd": false,
"mn": "ADBE Vector Graphic - Fill",
- "nm": "Fill 1",
+ "nm": "$Fill 1",
"o": {
"a": 0,
"ix": 5,
@@ -249,7 +249,7 @@
"lc": 1,
"lj": 2,
"mn": "ADBE Vector Graphic - Stroke",
- "nm": "Stroke 1",
+ "nm": "$Stroke 1",
"o": {
"a": 0,
"ix": 4,