[skottie] Cleanup: convert shape layer adapters to new pattern
Split off most shape layer components into own CUs (naming convention
following AE), and convert to new DiscardableAdapter pattern.
TBR=
Change-Id: Iba7800cff1998d3d7cf81dfd89b4193d02b59559
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/265147
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skottie/skottie.gni b/modules/skottie/skottie.gni
index 8d65f41..b9dade6 100644
--- a/modules/skottie/skottie.gni
+++ b/modules/skottie/skottie.gni
@@ -13,6 +13,7 @@
]
skia_skottie_sources = [
+ "$_src/Adapter.h",
"$_src/Animator.cpp",
"$_src/Animator.h",
"$_src/Camera.cpp",
@@ -22,7 +23,6 @@
"$_src/Layer.cpp",
"$_src/Layer.h",
"$_src/Skottie.cpp",
- "$_src/SkottieAdapter.cpp",
"$_src/SkottieAdapter.h",
"$_src/SkottieAnimator.cpp",
"$_src/SkottieJson.cpp",
@@ -55,9 +55,18 @@
"$_src/layers/ImageLayer.cpp",
"$_src/layers/NullLayer.cpp",
"$_src/layers/PrecompLayer.cpp",
- "$_src/layers/ShapeLayer.cpp",
"$_src/layers/SolidLayer.cpp",
"$_src/layers/TextLayer.cpp",
+ "$_src/layers/shapelayer/Ellipse.cpp",
+ "$_src/layers/shapelayer/Gradient.cpp",
+ "$_src/layers/shapelayer/MergePaths.cpp",
+ "$_src/layers/shapelayer/Polystar.cpp",
+ "$_src/layers/shapelayer/Rectangle.cpp",
+ "$_src/layers/shapelayer/Repeater.cpp",
+ "$_src/layers/shapelayer/RoundCorners.cpp",
+ "$_src/layers/shapelayer/ShapeLayer.cpp",
+ "$_src/layers/shapelayer/ShapeLayer.h",
+ "$_src/layers/shapelayer/TrimPaths.cpp",
"$_src/text/RangeSelector.cpp",
"$_src/text/RangeSelector.h",
"$_src/text/SkottieShaper.cpp",
diff --git a/modules/skottie/src/Adapter.h b/modules/skottie/src/Adapter.h
new file mode 100644
index 0000000..dd75129
--- /dev/null
+++ b/modules/skottie/src/Adapter.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkottieAdapter_DEFINED
+#define SkottieAdapter_DEFINED
+
+#include "modules/skottie/src/Animator.h"
+
+namespace skottie {
+namespace internal {
+
+template <typename AdapterT, typename T>
+class DiscardableAdapterBase : public AnimatablePropertyContainer {
+public:
+ template <typename... Args>
+ static sk_sp<AdapterT> Make(Args&&... args) {
+ return sk_sp<AdapterT>(new AdapterT(std::forward<Args>(args)...));
+ }
+
+ const sk_sp<T>& node() const { return fNode; }
+
+protected:
+ DiscardableAdapterBase()
+ : fNode(T::Make()) {}
+
+ explicit DiscardableAdapterBase(sk_sp<T> node)
+ : fNode(std::move(node)) {}
+
+private:
+ const sk_sp<T> fNode;
+};
+
+} // namespace internal
+} // namespace skottie
+
+#endif // SkottieAdapter_DEFINED
diff --git a/modules/skottie/src/Animator.h b/modules/skottie/src/Animator.h
index 9061113..9470591 100644
--- a/modules/skottie/src/Animator.h
+++ b/modules/skottie/src/Animator.h
@@ -29,6 +29,11 @@
template <typename T>
bool bind(const AnimationBuilder&, const skjson::ObjectValue*, T*);
+ template <typename T>
+ bool bind(const AnimationBuilder& abuilder, const skjson::ObjectValue* jobject, T& v) {
+ return this->bind<T>(abuilder, jobject, &v);
+ }
+
bool isStatic() const { return fAnimators.empty(); }
protected:
diff --git a/modules/skottie/src/SkottieAdapter.cpp b/modules/skottie/src/SkottieAdapter.cpp
deleted file mode 100644
index fae0432..0000000
--- a/modules/skottie/src/SkottieAdapter.cpp
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * 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 "modules/skottie/src/SkottieAdapter.h"
-
-#include "include/core/SkFont.h"
-#include "include/core/SkMatrix.h"
-#include "include/core/SkMatrix44.h"
-#include "include/core/SkPath.h"
-#include "include/core/SkRRect.h"
-#include "include/private/SkTo.h"
-#include "include/utils/Sk3D.h"
-#include "modules/skottie/src/SkottieValue.h"
-#include "modules/skottie/src/Transform.h"
-#include "modules/sksg/include/SkSGDraw.h"
-#include "modules/sksg/include/SkSGGradient.h"
-#include "modules/sksg/include/SkSGGroup.h"
-#include "modules/sksg/include/SkSGPaint.h"
-#include "modules/sksg/include/SkSGPath.h"
-#include "modules/sksg/include/SkSGRect.h"
-#include "modules/sksg/include/SkSGTransform.h"
-#include "modules/sksg/include/SkSGTrimEffect.h"
-
-#include <cmath>
-#include <utility>
-
-namespace skottie {
-
-RRectAdapter::RRectAdapter(sk_sp<sksg::RRect> wrapped_node)
- : fRRectNode(std::move(wrapped_node)) {}
-
-RRectAdapter::~RRectAdapter() = default;
-
-void RRectAdapter::apply() {
- // BM "position" == "center position"
- auto rr = SkRRect::MakeRectXY(SkRect::MakeXYWH(fPosition.x() - fSize.width() / 2,
- fPosition.y() - fSize.height() / 2,
- fSize.width(), fSize.height()),
- fRadius.width(),
- fRadius.height());
- fRRectNode->setRRect(rr);
-}
-
-RepeaterAdapter::RepeaterAdapter(sk_sp<sksg::RenderNode> repeater_node, Composite composite)
- : fRepeaterNode(repeater_node)
- , fComposite(composite)
- , fRoot(sksg::Group::Make()) {}
-
-RepeaterAdapter::~RepeaterAdapter() = default;
-
-void RepeaterAdapter::apply() {
- static constexpr SkScalar kMaxCount = 512;
- const auto count = static_cast<size_t>(SkTPin(fCount, 0.0f, kMaxCount) + 0.5f);
-
- const auto& compute_transform = [this] (size_t index) {
- const auto t = fOffset + index;
-
- // Position, scale & rotation are "scaled" by index/offset.
- SkMatrix m = SkMatrix::MakeTrans(-fAnchorPoint.x(),
- -fAnchorPoint.y());
- m.postScale(std::pow(fScale.x() * .01f, fOffset),
- std::pow(fScale.y() * .01f, fOffset));
- m.postRotate(t * fRotation);
- m.postTranslate(t * fPosition.x() + fAnchorPoint.x(),
- t * fPosition.y() + fAnchorPoint.y());
-
- return m;
- };
-
- // TODO: start/end opacity support.
-
- // TODO: we can avoid rebuilding all the fragments in most cases.
- fRoot->clear();
- for (size_t i = 0; i < count; ++i) {
- const auto insert_index = (fComposite == Composite::kAbove) ? i : count - i - 1;
- fRoot->addChild(sksg::TransformEffect::Make(fRepeaterNode,
- compute_transform(insert_index)));
- }
-}
-
-PolyStarAdapter::PolyStarAdapter(sk_sp<sksg::Path> wrapped_node, Type t)
- : fPathNode(std::move(wrapped_node))
- , fType(t) {}
-
-PolyStarAdapter::~PolyStarAdapter() = default;
-
-void PolyStarAdapter::apply() {
- static constexpr int kMaxPointCount = 100000;
- const auto count = SkToUInt(SkTPin(SkScalarRoundToInt(fPointCount), 0, kMaxPointCount));
- const auto arc = sk_ieee_float_divide(SK_ScalarPI * 2, count);
-
- const auto pt_on_circle = [](const SkPoint& c, SkScalar r, SkScalar a) {
- return SkPoint::Make(c.x() + r * std::cos(a),
- c.y() + r * std::sin(a));
- };
-
- // TODO: inner/outer "roundness"?
-
- SkPath poly;
-
- auto angle = SkDegreesToRadians(fRotation - 90);
- poly.moveTo(pt_on_circle(fPosition, fOuterRadius, angle));
- poly.incReserve(fType == Type::kStar ? count * 2 : count);
-
- for (unsigned i = 0; i < count; ++i) {
- if (fType == Type::kStar) {
- poly.lineTo(pt_on_circle(fPosition, fInnerRadius, angle + arc * 0.5f));
- }
- angle += arc;
- poly.lineTo(pt_on_circle(fPosition, fOuterRadius, angle));
- }
-
- poly.close();
- fPathNode->setPath(poly);
-}
-
-GradientAdapter::GradientAdapter(sk_sp<sksg::Gradient> grad, size_t colorStopCount)
- : fGradient(std::move(grad))
- , fColorStopCount(colorStopCount) {}
-
-template <typename T>
-static inline const T* next_rec(const T* rec, const T* end_rec) {
- if (!rec) return nullptr;
-
- SkASSERT(rec < end_rec);
- rec++;
-
- return rec < end_rec ? rec : nullptr;
-};
-
-void GradientAdapter::apply() {
- this->onApply();
-
- // Gradient color stops are specified as a consolidated float vector holding:
- //
- // a) an (optional) array of color/RGB stop records (t, r, g, b)
- //
- // followed by
- //
- // b) an (optional) array of opacity/alpha stop records (t, a)
- //
- struct ColorRec { float t, r, g, b; };
- struct OpacityRec { float t, a; };
-
- // The number of color records is explicit (fColorStopCount),
- // while the number of opacity stops is implicit (based on the size of fStops).
- //
- // |fStops| holds ColorRec x |fColorStopCount| + OpacityRec x N
- const auto c_count = fColorStopCount,
- c_size = c_count * 4,
- o_count = (fStops.size() - c_size) / 2;
- if (fStops.size() < c_size || fStops.size() != (c_count * 4 + o_count * 2)) {
- // apply() may get called before the stops are set, so only log when we have some stops.
- if (!fStops.empty()) {
- SkDebugf("!! Invalid gradient stop array size: %zu\n", fStops.size());
- }
- return;
- }
-
- const auto* c_rec = c_count > 0 ? reinterpret_cast<const ColorRec*>(fStops.data())
- : nullptr;
- const auto* o_rec = o_count > 0 ? reinterpret_cast<const OpacityRec*>(fStops.data() + c_size)
- : nullptr;
- const auto* c_end = c_rec + c_count;
- const auto* o_end = o_rec + o_count;
-
- sksg::Gradient::ColorStop current_stop = {
- 0.0f, {
- c_rec ? c_rec->r : 0,
- c_rec ? c_rec->g : 0,
- c_rec ? c_rec->b : 0,
- o_rec ? o_rec->a : 1,
- }};
-
- std::vector<sksg::Gradient::ColorStop> stops;
- stops.reserve(c_count);
-
- // Merge-sort the color and opacity stops, LERP-ing intermediate channel values as needed.
- while (c_rec || o_rec) {
- // After exhausting one of color recs / opacity recs, continue propagating the last
- // computed values (as if they were specified at the current position).
- const auto& cs = c_rec
- ? *c_rec
- : ColorRec{ o_rec->t,
- current_stop.fColor.fR,
- current_stop.fColor.fG,
- current_stop.fColor.fB };
- const auto& os = o_rec
- ? *o_rec
- : OpacityRec{ c_rec->t, current_stop.fColor.fA };
-
- // Compute component lerp coefficients based on the relative position of the stops
- // being considered. The idea is to select the smaller-pos stop, use its own properties
- // as specified (lerp with t == 1), and lerp (with t < 1) the properties from the
- // larger-pos stop against the previously computed gradient stop values.
- const auto c_pos = std::max(cs.t, current_stop.fPosition),
- o_pos = std::max(os.t, current_stop.fPosition),
- c_pos_rel = c_pos - current_stop.fPosition,
- o_pos_rel = o_pos - current_stop.fPosition,
- t_c = SkTPin(sk_ieee_float_divide(o_pos_rel, c_pos_rel), 0.0f, 1.0f),
- t_o = SkTPin(sk_ieee_float_divide(c_pos_rel, o_pos_rel), 0.0f, 1.0f);
-
- auto lerp = [](float a, float b, float t) { return a + t * (b - a); };
-
- current_stop = {
- std::min(c_pos, o_pos),
- {
- lerp(current_stop.fColor.fR, cs.r, t_c ),
- lerp(current_stop.fColor.fG, cs.g, t_c ),
- lerp(current_stop.fColor.fB, cs.b, t_c ),
- lerp(current_stop.fColor.fA, os.a, t_o)
- }
- };
- stops.push_back(current_stop);
-
- // Consume one of, or both (for coincident positions) color/opacity stops.
- if (c_pos <= o_pos) {
- c_rec = next_rec<ColorRec>(c_rec, c_end);
- }
- if (o_pos <= c_pos) {
- o_rec = next_rec<OpacityRec>(o_rec, o_end);
- }
- }
-
- stops.shrink_to_fit();
- fGradient->setColorStops(std::move(stops));
-}
-
-LinearGradientAdapter::LinearGradientAdapter(sk_sp<sksg::LinearGradient> grad, size_t stopCount)
- : INHERITED(std::move(grad), stopCount) {}
-
-void LinearGradientAdapter::onApply() {
- auto* grad = static_cast<sksg::LinearGradient*>(fGradient.get());
- grad->setStartPoint(this->startPoint());
- grad->setEndPoint(this->endPoint());
-}
-
-RadialGradientAdapter::RadialGradientAdapter(sk_sp<sksg::RadialGradient> grad, size_t stopCount)
- : INHERITED(std::move(grad), stopCount) {}
-
-void RadialGradientAdapter::onApply() {
- auto* grad = static_cast<sksg::RadialGradient*>(fGradient.get());
- grad->setStartCenter(this->startPoint());
- grad->setEndCenter(this->startPoint());
- grad->setStartRadius(0);
- grad->setEndRadius(SkPoint::Distance(this->startPoint(), this->endPoint()));
-}
-
-TrimEffectAdapter::TrimEffectAdapter(sk_sp<sksg::TrimEffect> trimEffect)
- : fTrimEffect(std::move(trimEffect)) {
- SkASSERT(fTrimEffect);
-}
-
-TrimEffectAdapter::~TrimEffectAdapter() = default;
-
-void TrimEffectAdapter::apply() {
- // BM semantics: start/end are percentages, offset is "degrees" (?!).
- const auto start = fStart / 100,
- end = fEnd / 100,
- offset = fOffset / 360;
-
- auto startT = SkTMin(start, end) + offset,
- stopT = SkTMax(start, end) + offset;
- auto mode = SkTrimPathEffect::Mode::kNormal;
-
- if (stopT - startT < 1) {
- startT -= SkScalarFloorToScalar(startT);
- stopT -= SkScalarFloorToScalar(stopT);
-
- if (startT > stopT) {
- using std::swap;
- swap(startT, stopT);
- mode = SkTrimPathEffect::Mode::kInverted;
- }
- } else {
- startT = 0;
- stopT = 1;
- }
-
- fTrimEffect->setStart(startT);
- fTrimEffect->setStop(stopT);
- fTrimEffect->setMode(mode);
-}
-
-} // namespace skottie
diff --git a/modules/skottie/src/SkottieAdapter.h b/modules/skottie/src/SkottieAdapter.h
index 85e2647..7d1b549 100644
--- a/modules/skottie/src/SkottieAdapter.h
+++ b/modules/skottie/src/SkottieAdapter.h
@@ -5,8 +5,8 @@
* found in the LICENSE file.
*/
-#ifndef SkottieAdapter_DEFINED
-#define SkottieAdapter_DEFINED
+#ifndef SkottieAdapter__DEFINED
+#define SkottieAdapter__DEFINED
#include "include/core/SkPoint.h"
#include "include/core/SkRefCnt.h"
@@ -56,131 +56,6 @@
p_type f##p_name = p_default; \
public:
-class RRectAdapter final : public SkNVRefCnt<RRectAdapter> {
-public:
- explicit RRectAdapter(sk_sp<sksg::RRect>);
- ~RRectAdapter();
-
- ADAPTER_PROPERTY(Position, SkPoint , SkPoint::Make(0, 0))
- ADAPTER_PROPERTY(Size , SkSize , SkSize::Make(0, 0))
- ADAPTER_PROPERTY(Radius , SkSize , SkSize::Make(0, 0))
-
-private:
- void apply();
-
- sk_sp<sksg::RRect> fRRectNode;
-};
-
-class PolyStarAdapter final : public SkNVRefCnt<PolyStarAdapter> {
-public:
- enum class Type {
- kStar, kPoly,
- };
-
- PolyStarAdapter(sk_sp<sksg::Path>, Type);
- ~PolyStarAdapter();
-
- ADAPTER_PROPERTY(Position , SkPoint , SkPoint::Make(0, 0))
- ADAPTER_PROPERTY(PointCount , SkScalar, 0)
- ADAPTER_PROPERTY(InnerRadius , SkScalar, 0)
- ADAPTER_PROPERTY(OuterRadius , SkScalar, 0)
- ADAPTER_PROPERTY(InnerRoundness, SkScalar, 0)
- ADAPTER_PROPERTY(OuterRoundness, SkScalar, 0)
- ADAPTER_PROPERTY(Rotation , SkScalar, 0)
-
-private:
- void apply();
-
- sk_sp<sksg::Path> fPathNode;
- Type fType;
-};
-
-class RepeaterAdapter final : public SkNVRefCnt<RepeaterAdapter> {
-public:
- enum class Composite { kAbove, kBelow };
-
- RepeaterAdapter(sk_sp<sksg::RenderNode>, Composite);
- ~RepeaterAdapter();
-
- // Repeater props
- ADAPTER_PROPERTY(Count , SkScalar, 0)
- ADAPTER_PROPERTY(Offset , SkScalar, 0)
-
- // Transform props
- ADAPTER_PROPERTY(AnchorPoint , SkPoint , SkPoint::Make(0, 0))
- ADAPTER_PROPERTY(Position , SkPoint , SkPoint::Make(0, 0))
- ADAPTER_PROPERTY(Scale , SkVector, SkPoint::Make(100, 100))
- ADAPTER_PROPERTY(Rotation , SkScalar, 0)
- ADAPTER_PROPERTY(StartOpacity, SkScalar, 100)
- ADAPTER_PROPERTY(EndOpacity , SkScalar, 100)
-
- const sk_sp<sksg::Group>& root() const { return fRoot; }
-
-private:
- void apply();
-
- const sk_sp<sksg::RenderNode> fRepeaterNode;
- const Composite fComposite;
-
- sk_sp<sksg::Group> fRoot;
-};
-
-class GradientAdapter : public SkRefCnt {
-public:
- ADAPTER_PROPERTY(StartPoint, SkPoint , SkPoint::Make(0, 0) )
- ADAPTER_PROPERTY(EndPoint , SkPoint , SkPoint::Make(0, 0) )
- ADAPTER_PROPERTY(Stops , VectorValue , VectorValue() )
-
-protected:
- GradientAdapter(sk_sp<sksg::Gradient>, size_t colorStopCount);
-
- const SkPoint& startPoint() const { return fStartPoint; }
- const SkPoint& endPoint() const { return fEndPoint; }
-
- sk_sp<sksg::Gradient> fGradient;
- size_t fColorStopCount;
-
- virtual void onApply() = 0;
-
-private:
- void apply();
-};
-
-class LinearGradientAdapter final : public GradientAdapter {
-public:
- LinearGradientAdapter(sk_sp<sksg::LinearGradient>, size_t stopCount);
-
-private:
- void onApply() override;
-
- using INHERITED = GradientAdapter;
-};
-
-class RadialGradientAdapter final : public GradientAdapter {
-public:
- RadialGradientAdapter(sk_sp<sksg::RadialGradient>, size_t stopCount);
-
-private:
- void onApply() override;
-
- using INHERITED = GradientAdapter;
-};
-
-class TrimEffectAdapter final : public SkNVRefCnt<TrimEffectAdapter> {
-public:
- explicit TrimEffectAdapter(sk_sp<sksg::TrimEffect>);
- ~TrimEffectAdapter();
-
- ADAPTER_PROPERTY(Start , SkScalar, 0)
- ADAPTER_PROPERTY(End , SkScalar, 100)
- ADAPTER_PROPERTY(Offset, SkScalar, 0)
-
-private:
- void apply();
-
- sk_sp<sksg::TrimEffect> fTrimEffect;
-};
-
} // namespace skottie
-#endif // SkottieAdapter_DEFINED
+#endif // SkottieAdapter__DEFINED
diff --git a/modules/skottie/src/SkottiePriv.h b/modules/skottie/src/SkottiePriv.h
index 50b5908..dbe127a 100644
--- a/modules/skottie/src/SkottiePriv.h
+++ b/modules/skottie/src/SkottiePriv.h
@@ -115,10 +115,10 @@
AnimatorScope* fPrevScope;
};
- template <typename T, typename... Args>
- sk_sp<sksg::RenderNode> attachDiscardableAdapter(Args&&... args) const {
+ template <typename T, typename NodeType = sk_sp<sksg::RenderNode>, typename... Args>
+ NodeType attachDiscardableAdapter(Args&&... args) const {
if (auto adapter = T::Make(std::forward<Args>(args)...)) {
- sk_sp<sksg::RenderNode> node = adapter->renderNode();
+ auto node = adapter->node();
if (adapter->isStatic()) {
// Fire off a synthetic tick to force a single SG sync before discarding.
adapter->tick(0);
diff --git a/modules/skottie/src/effects/DropShadowEffect.cpp b/modules/skottie/src/effects/DropShadowEffect.cpp
index d376699..565a0ea 100644
--- a/modules/skottie/src/effects/DropShadowEffect.cpp
+++ b/modules/skottie/src/effects/DropShadowEffect.cpp
@@ -25,7 +25,7 @@
return sk_sp<DropShadowAdapter>(new DropShadowAdapter(jprops, std::move(layer), abuilder));
}
- const sk_sp<sksg::RenderNode>& renderNode() const { return fImageFilterEffect; }
+ const sk_sp<sksg::RenderNode>& node() const { return fImageFilterEffect; }
private:
DropShadowAdapter(const skjson::ArrayValue& jprops,
diff --git a/modules/skottie/src/effects/Effects.h b/modules/skottie/src/effects/Effects.h
index 6470a84..b6c0b15 100644
--- a/modules/skottie/src/effects/Effects.h
+++ b/modules/skottie/src/effects/Effects.h
@@ -77,7 +77,7 @@
*/
class MaskFilterEffectBase : public AnimatablePropertyContainer {
public:
- const sk_sp<sksg::MaskFilterEffect>& renderNode() const { return fMaskEffectNode; }
+ const sk_sp<sksg::MaskFilterEffect>& node() const { return fMaskEffectNode; }
protected:
MaskFilterEffectBase(sk_sp<sksg::RenderNode>, const SkSize&);
diff --git a/modules/skottie/src/effects/GaussianBlurEffect.cpp b/modules/skottie/src/effects/GaussianBlurEffect.cpp
index ffee42b..cfa0854 100644
--- a/modules/skottie/src/effects/GaussianBlurEffect.cpp
+++ b/modules/skottie/src/effects/GaussianBlurEffect.cpp
@@ -27,7 +27,7 @@
abuilder));
}
- const sk_sp<sksg::RenderNode>& renderNode() const { return fImageFilterEffect; }
+ const sk_sp<sksg::RenderNode>& node() const { return fImageFilterEffect; }
private:
GaussianBlurEffectAdapter(const skjson::ArrayValue& jprops,
diff --git a/modules/skottie/src/effects/GradientEffect.cpp b/modules/skottie/src/effects/GradientEffect.cpp
index 7812041..7fbc9a7 100644
--- a/modules/skottie/src/effects/GradientEffect.cpp
+++ b/modules/skottie/src/effects/GradientEffect.cpp
@@ -28,7 +28,7 @@
abuilder));
}
- sk_sp<sksg::RenderNode> renderNode() const { return fShaderEffect; }
+ sk_sp<sksg::RenderNode> node() const { return fShaderEffect; }
private:
GradientRampEffectAdapter(const skjson::ArrayValue& jprops,
diff --git a/modules/skottie/src/effects/HueSaturationEffect.cpp b/modules/skottie/src/effects/HueSaturationEffect.cpp
index acf540f..f80954b 100644
--- a/modules/skottie/src/effects/HueSaturationEffect.cpp
+++ b/modules/skottie/src/effects/HueSaturationEffect.cpp
@@ -28,7 +28,7 @@
new HueSaturationEffectAdapter(jprops, std::move(layer), abuilder));
}
- const sk_sp<sksg::ExternalColorFilter>& renderNode() const { return fColorFilter; }
+ const sk_sp<sksg::ExternalColorFilter>& node() const { return fColorFilter; }
private:
HueSaturationEffectAdapter(const skjson::ArrayValue& jprops,
diff --git a/modules/skottie/src/effects/InvertEffect.cpp b/modules/skottie/src/effects/InvertEffect.cpp
index 41a64a8..3851aba 100644
--- a/modules/skottie/src/effects/InvertEffect.cpp
+++ b/modules/skottie/src/effects/InvertEffect.cpp
@@ -26,7 +26,7 @@
new InvertEffectAdapter(jprops, std::move(layer), abuilder));
}
- const sk_sp<sksg::ExternalColorFilter>& renderNode() const { return fColorFilter; }
+ const sk_sp<sksg::ExternalColorFilter>& node() const { return fColorFilter; }
private:
InvertEffectAdapter(const skjson::ArrayValue& jprops,
diff --git a/modules/skottie/src/effects/LevelsEffect.cpp b/modules/skottie/src/effects/LevelsEffect.cpp
index 367a157..c44e1a4 100644
--- a/modules/skottie/src/effects/LevelsEffect.cpp
+++ b/modules/skottie/src/effects/LevelsEffect.cpp
@@ -43,7 +43,7 @@
abuilder));
}
- sk_sp<sksg::RenderNode> renderNode() const { return fEffect; }
+ sk_sp<sksg::RenderNode> node() const { return fEffect; }
private:
LevelsEffectAdapter(const skjson::ArrayValue& jprops,
diff --git a/modules/skottie/src/effects/ShiftChannelsEffect.cpp b/modules/skottie/src/effects/ShiftChannelsEffect.cpp
index 668d356..4711193 100644
--- a/modules/skottie/src/effects/ShiftChannelsEffect.cpp
+++ b/modules/skottie/src/effects/ShiftChannelsEffect.cpp
@@ -35,7 +35,7 @@
new ShiftChannelsEffectAdapter(jprops, std::move(layer), abuilder));
}
- const sk_sp<sksg::ExternalColorFilter>& renderNode() const { return fColorFilter; }
+ const sk_sp<sksg::ExternalColorFilter>& node() const { return fColorFilter; }
private:
ShiftChannelsEffectAdapter(const skjson::ArrayValue& jprops,
diff --git a/modules/skottie/src/layers/ShapeLayer.cpp b/modules/skottie/src/layers/ShapeLayer.cpp
deleted file mode 100644
index 97a96da..0000000
--- a/modules/skottie/src/layers/ShapeLayer.cpp
+++ /dev/null
@@ -1,727 +0,0 @@
-/*
- * 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 "modules/skottie/src/SkottiePriv.h"
-
-#include "include/core/SkPath.h"
-#include "modules/skottie/src/SkottieAdapter.h"
-#include "modules/skottie/src/SkottieJson.h"
-#include "modules/skottie/src/SkottieValue.h"
-#include "modules/sksg/include/SkSGDraw.h"
-#include "modules/sksg/include/SkSGGeometryTransform.h"
-#include "modules/sksg/include/SkSGGradient.h"
-#include "modules/sksg/include/SkSGGroup.h"
-#include "modules/sksg/include/SkSGMerge.h"
-#include "modules/sksg/include/SkSGPaint.h"
-#include "modules/sksg/include/SkSGPath.h"
-#include "modules/sksg/include/SkSGRect.h"
-#include "modules/sksg/include/SkSGRenderEffect.h"
-#include "modules/sksg/include/SkSGRoundEffect.h"
-#include "modules/sksg/include/SkSGTransform.h"
-#include "modules/sksg/include/SkSGTrimEffect.h"
-#include "src/utils/SkJSON.h"
-
-#include <algorithm>
-#include <iterator>
-
-namespace skottie {
-namespace internal {
-
-namespace {
-
-sk_sp<sksg::GeometryNode> AttachPathGeometry(const skjson::ObjectValue& jpath,
- const AnimationBuilder* abuilder) {
- return abuilder->attachPath(jpath["ks"]);
-}
-
-sk_sp<sksg::GeometryNode> AttachRRectGeometry(const skjson::ObjectValue& jrect,
- const AnimationBuilder* abuilder) {
- auto rect_node = sksg::RRect::Make();
- rect_node->setDirection(ParseDefault(jrect["d"], -1) == 3 ? SkPathDirection::kCCW
- : SkPathDirection::kCW);
- rect_node->setInitialPointIndex(2); // starting point: (Right, Top - radius.y)
-
- auto adapter = sk_make_sp<RRectAdapter>(rect_node);
-
- auto p_attached = abuilder->bindProperty<VectorValue>(jrect["p"],
- [adapter](const VectorValue& p) {
- adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
- });
- auto s_attached = abuilder->bindProperty<VectorValue>(jrect["s"],
- [adapter](const VectorValue& s) {
- adapter->setSize(ValueTraits<VectorValue>::As<SkSize>(s));
- });
- auto r_attached = abuilder->bindProperty<ScalarValue>(jrect["r"],
- [adapter](const ScalarValue& r) {
- adapter->setRadius(SkSize::Make(r, r));
- });
-
- if (!p_attached && !s_attached && !r_attached) {
- return nullptr;
- }
-
- return rect_node;
-}
-
-sk_sp<sksg::GeometryNode> AttachEllipseGeometry(const skjson::ObjectValue& jellipse,
- const AnimationBuilder* abuilder) {
- auto rect_node = sksg::RRect::Make();
- rect_node->setDirection(ParseDefault(jellipse["d"], -1) == 3 ? SkPathDirection::kCCW
- : SkPathDirection::kCW);
- rect_node->setInitialPointIndex(1); // starting point: (Center, Top)
-
- auto adapter = sk_make_sp<RRectAdapter>(rect_node);
-
- auto p_attached = abuilder->bindProperty<VectorValue>(jellipse["p"],
- [adapter](const VectorValue& p) {
- adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
- });
- auto s_attached = abuilder->bindProperty<VectorValue>(jellipse["s"],
- [adapter](const VectorValue& s) {
- const auto sz = ValueTraits<VectorValue>::As<SkSize>(s);
- adapter->setSize(sz);
- adapter->setRadius(SkSize::Make(sz.width() / 2, sz.height() / 2));
- });
-
- if (!p_attached && !s_attached) {
- return nullptr;
- }
-
- return rect_node;
-}
-
-sk_sp<sksg::GeometryNode> AttachPolystarGeometry(const skjson::ObjectValue& jstar,
- const AnimationBuilder* abuilder) {
- static constexpr PolyStarAdapter::Type gTypes[] = {
- PolyStarAdapter::Type::kStar, // "sy": 1
- PolyStarAdapter::Type::kPoly, // "sy": 2
- };
-
- const auto type = ParseDefault<size_t>(jstar["sy"], 0) - 1;
- if (type >= SK_ARRAY_COUNT(gTypes)) {
- abuilder->log(Logger::Level::kError, &jstar, "Unknown polystar type.");
- return nullptr;
- }
-
- auto path_node = sksg::Path::Make();
- auto adapter = sk_make_sp<PolyStarAdapter>(path_node, gTypes[type]);
-
- abuilder->bindProperty<VectorValue>(jstar["p"],
- [adapter](const VectorValue& p) {
- adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
- });
- abuilder->bindProperty<ScalarValue>(jstar["pt"],
- [adapter](const ScalarValue& pt) {
- adapter->setPointCount(pt);
- });
- abuilder->bindProperty<ScalarValue>(jstar["ir"],
- [adapter](const ScalarValue& ir) {
- adapter->setInnerRadius(ir);
- });
- abuilder->bindProperty<ScalarValue>(jstar["or"],
- [adapter](const ScalarValue& otr) {
- adapter->setOuterRadius(otr);
- });
- abuilder->bindProperty<ScalarValue>(jstar["is"],
- [adapter](const ScalarValue& is) {
- adapter->setInnerRoundness(is);
- });
- abuilder->bindProperty<ScalarValue>(jstar["os"],
- [adapter](const ScalarValue& os) {
- adapter->setOuterRoundness(os);
- });
- abuilder->bindProperty<ScalarValue>(jstar["r"],
- [adapter](const ScalarValue& r) {
- adapter->setRotation(r);
- });
-
- return path_node;
-}
-
-sk_sp<sksg::ShaderPaint> AttachGradient(const skjson::ObjectValue& jgrad,
- const AnimationBuilder* abuilder) {
- const skjson::ObjectValue* stops = jgrad["g"];
- if (!stops)
- return nullptr;
-
- const auto stopCount = ParseDefault<int>((*stops)["p"], -1);
- if (stopCount < 0)
- return nullptr;
-
- sk_sp<sksg::Gradient> gradient_node;
- sk_sp<GradientAdapter> adapter;
-
- if (ParseDefault<int>(jgrad["t"], 1) == 1) {
- auto linear_node = sksg::LinearGradient::Make();
- adapter = sk_make_sp<LinearGradientAdapter>(linear_node, stopCount);
- gradient_node = std::move(linear_node);
- } else {
- auto radial_node = sksg::RadialGradient::Make();
- adapter = sk_make_sp<RadialGradientAdapter>(radial_node, stopCount);
-
- // TODO: highlight, angle
- gradient_node = std::move(radial_node);
- }
-
- abuilder->bindProperty<VectorValue>((*stops)["k"],
- [adapter](const VectorValue& stops) {
- adapter->setStops(stops);
- });
- abuilder->bindProperty<VectorValue>(jgrad["s"],
- [adapter](const VectorValue& s) {
- adapter->setStartPoint(ValueTraits<VectorValue>::As<SkPoint>(s));
- });
- abuilder->bindProperty<VectorValue>(jgrad["e"],
- [adapter](const VectorValue& e) {
- adapter->setEndPoint(ValueTraits<VectorValue>::As<SkPoint>(e));
- });
-
- return sksg::ShaderPaint::Make(std::move(gradient_node));
-}
-
-sk_sp<sksg::PaintNode> AttachPaint(const skjson::ObjectValue& jpaint,
- const AnimationBuilder* abuilder,
- sk_sp<sksg::PaintNode> paint_node) {
- if (paint_node) {
- paint_node->setAntiAlias(true);
-
- abuilder->bindProperty<ScalarValue>(jpaint["o"],
- [paint_node](const ScalarValue& o) {
- // BM opacity is [0..100]
- paint_node->setOpacity(o * 0.01f);
- });
- }
-
- return paint_node;
-}
-
-sk_sp<sksg::PaintNode> AttachStroke(const skjson::ObjectValue& jstroke,
- const AnimationBuilder* abuilder,
- sk_sp<sksg::PaintNode> stroke_node) {
- if (!stroke_node)
- return nullptr;
-
- stroke_node->setStyle(SkPaint::kStroke_Style);
-
- abuilder->bindProperty<ScalarValue>(jstroke["w"],
- [stroke_node](const ScalarValue& w) {
- stroke_node->setStrokeWidth(w);
- });
-
- stroke_node->setStrokeMiter(ParseDefault<SkScalar>(jstroke["ml"], 4.0f));
-
- static constexpr SkPaint::Join gJoins[] = {
- SkPaint::kMiter_Join,
- SkPaint::kRound_Join,
- SkPaint::kBevel_Join,
- };
- stroke_node->setStrokeJoin(gJoins[SkTMin<size_t>(ParseDefault<size_t>(jstroke["lj"], 1) - 1,
- SK_ARRAY_COUNT(gJoins) - 1)]);
-
- static constexpr SkPaint::Cap gCaps[] = {
- SkPaint::kButt_Cap,
- SkPaint::kRound_Cap,
- SkPaint::kSquare_Cap,
- };
- stroke_node->setStrokeCap(gCaps[SkTMin<size_t>(ParseDefault<size_t>(jstroke["lc"], 1) - 1,
- SK_ARRAY_COUNT(gCaps) - 1)]);
-
- return stroke_node;
-}
-
-sk_sp<sksg::PaintNode> AttachColorFill(const skjson::ObjectValue& jfill,
- const AnimationBuilder* abuilder) {
- return AttachPaint(jfill, abuilder, abuilder->attachColor(jfill, "c"));
-}
-
-sk_sp<sksg::PaintNode> AttachGradientFill(const skjson::ObjectValue& jfill,
- const AnimationBuilder* abuilder) {
- return AttachPaint(jfill, abuilder, AttachGradient(jfill, abuilder));
-}
-
-sk_sp<sksg::PaintNode> AttachColorStroke(const skjson::ObjectValue& jstroke,
- const AnimationBuilder* abuilder) {
- return AttachStroke(jstroke, abuilder, AttachPaint(jstroke, abuilder,
- abuilder->attachColor(jstroke, "c")));
-}
-
-sk_sp<sksg::PaintNode> AttachGradientStroke(const skjson::ObjectValue& jstroke,
- const AnimationBuilder* abuilder) {
- return AttachStroke(jstroke, abuilder, AttachPaint(jstroke, abuilder,
- AttachGradient(jstroke, abuilder)));
-}
-
-sk_sp<sksg::Merge> Merge(std::vector<sk_sp<sksg::GeometryNode>>&& geos, sksg::Merge::Mode mode) {
- std::vector<sksg::Merge::Rec> merge_recs;
- merge_recs.reserve(geos.size());
-
- for (auto& geo : geos) {
- merge_recs.push_back(
- {std::move(geo), merge_recs.empty() ? sksg::Merge::Mode::kMerge : mode});
- }
-
- return sksg::Merge::Make(std::move(merge_recs));
-}
-
-std::vector<sk_sp<sksg::GeometryNode>> AttachMergeGeometryEffect(
- const skjson::ObjectValue& jmerge, const AnimationBuilder*,
- std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
- static constexpr sksg::Merge::Mode gModes[] = {
- sksg::Merge::Mode::kMerge, // "mm": 1
- sksg::Merge::Mode::kUnion, // "mm": 2
- sksg::Merge::Mode::kDifference, // "mm": 3
- sksg::Merge::Mode::kIntersect, // "mm": 4
- sksg::Merge::Mode::kXOR , // "mm": 5
- };
-
- const auto mode = gModes[SkTMin<size_t>(ParseDefault<size_t>(jmerge["mm"], 1) - 1,
- SK_ARRAY_COUNT(gModes) - 1)];
-
- std::vector<sk_sp<sksg::GeometryNode>> merged;
- merged.push_back(Merge(std::move(geos), mode));
-
- return merged;
-}
-
-std::vector<sk_sp<sksg::GeometryNode>> AttachTrimGeometryEffect(
- const skjson::ObjectValue& jtrim, const AnimationBuilder* abuilder,
- std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
-
- enum class Mode {
- kParallel, // "m": 1 (Trim Multiple Shapes: Simultaneously)
- kSerial, // "m": 2 (Trim Multiple Shapes: Individually)
- } gModes[] = { Mode::kParallel, Mode::kSerial};
-
- const auto mode = gModes[SkTMin<size_t>(ParseDefault<size_t>(jtrim["m"], 1) - 1,
- SK_ARRAY_COUNT(gModes) - 1)];
-
- std::vector<sk_sp<sksg::GeometryNode>> inputs;
- if (mode == Mode::kSerial) {
- inputs.push_back(Merge(std::move(geos), sksg::Merge::Mode::kMerge));
- } else {
- inputs = std::move(geos);
- }
-
- std::vector<sk_sp<sksg::GeometryNode>> trimmed;
- trimmed.reserve(inputs.size());
- for (const auto& i : inputs) {
- auto trimEffect = sksg::TrimEffect::Make(i);
- trimmed.push_back(trimEffect);
-
- const auto adapter = sk_make_sp<TrimEffectAdapter>(std::move(trimEffect));
- abuilder->bindProperty<ScalarValue>(jtrim["s"],
- [adapter](const ScalarValue& s) {
- adapter->setStart(s);
- });
- abuilder->bindProperty<ScalarValue>(jtrim["e"],
- [adapter](const ScalarValue& e) {
- adapter->setEnd(e);
- });
- abuilder->bindProperty<ScalarValue>(jtrim["o"],
- [adapter](const ScalarValue& o) {
- adapter->setOffset(o);
- });
- }
-
- return trimmed;
-}
-
-std::vector<sk_sp<sksg::GeometryNode>> AttachRoundGeometryEffect(
- const skjson::ObjectValue& jtrim, const AnimationBuilder* abuilder,
- std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
-
- std::vector<sk_sp<sksg::GeometryNode>> rounded;
- rounded.reserve(geos.size());
-
- for (auto& g : geos) {
- const auto roundEffect = sksg::RoundEffect::Make(std::move(g));
- rounded.push_back(roundEffect);
-
- abuilder->bindProperty<ScalarValue>(jtrim["r"],
- [roundEffect](const ScalarValue& r) {
- roundEffect->setRadius(r);
- });
- }
-
- return rounded;
-}
-
-std::vector<sk_sp<sksg::RenderNode>> AttachRepeaterDrawEffect(
- const skjson::ObjectValue& jrepeater,
- const AnimationBuilder* abuilder,
- std::vector<sk_sp<sksg::RenderNode>>&& draws) {
-
- std::vector<sk_sp<sksg::RenderNode>> repeater_draws;
-
- if (const skjson::ObjectValue* jtransform = jrepeater["tr"]) {
- sk_sp<sksg::RenderNode> repeater_node;
- if (draws.size() > 1) {
- repeater_node = sksg::Group::Make(std::move(draws));
- } else {
- repeater_node = std::move(draws[0]);
- }
-
- const auto repeater_composite = (ParseDefault(jrepeater["m"], 1) == 1)
- ? RepeaterAdapter::Composite::kAbove
- : RepeaterAdapter::Composite::kBelow;
-
- auto adapter = sk_make_sp<RepeaterAdapter>(std::move(repeater_node),
- repeater_composite);
-
- abuilder->bindProperty<ScalarValue>(jrepeater["c"],
- [adapter](const ScalarValue& c) {
- adapter->setCount(c);
- });
- abuilder->bindProperty<ScalarValue>(jrepeater["o"],
- [adapter](const ScalarValue& o) {
- adapter->setOffset(o);
- });
- abuilder->bindProperty<VectorValue>((*jtransform)["a"],
- [adapter](const VectorValue& a) {
- adapter->setAnchorPoint(ValueTraits<VectorValue>::As<SkPoint>(a));
- });
- abuilder->bindProperty<VectorValue>((*jtransform)["p"],
- [adapter](const VectorValue& p) {
- adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
- });
- abuilder->bindProperty<VectorValue>((*jtransform)["s"],
- [adapter](const VectorValue& s) {
- adapter->setScale(ValueTraits<VectorValue>::As<SkVector>(s));
- });
- abuilder->bindProperty<ScalarValue>((*jtransform)["r"],
- [adapter](const ScalarValue& r) {
- adapter->setRotation(r);
- });
- abuilder->bindProperty<ScalarValue>((*jtransform)["so"],
- [adapter](const ScalarValue& so) {
- adapter->setStartOpacity(so);
- });
- abuilder->bindProperty<ScalarValue>((*jtransform)["eo"],
- [adapter](const ScalarValue& eo) {
- adapter->setEndOpacity(eo);
- });
-
- repeater_draws.reserve(1);
- repeater_draws.push_back(adapter->root());
- } else {
- repeater_draws = std::move(draws);
- }
-
- return repeater_draws;
-}
-
-using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const skjson::ObjectValue&,
- const AnimationBuilder*);
-static constexpr GeometryAttacherT gGeometryAttachers[] = {
- AttachPathGeometry,
- AttachRRectGeometry,
- AttachEllipseGeometry,
- AttachPolystarGeometry,
-};
-
-using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const skjson::ObjectValue&,
- const AnimationBuilder*);
-static constexpr PaintAttacherT gPaintAttachers[] = {
- AttachColorFill,
- AttachColorStroke,
- AttachGradientFill,
- AttachGradientStroke,
-};
-
-using GeometryEffectAttacherT =
- std::vector<sk_sp<sksg::GeometryNode>> (*)(const skjson::ObjectValue&,
- const AnimationBuilder*,
- std::vector<sk_sp<sksg::GeometryNode>>&&);
-static constexpr GeometryEffectAttacherT gGeometryEffectAttachers[] = {
- AttachMergeGeometryEffect,
- AttachTrimGeometryEffect,
- AttachRoundGeometryEffect,
-};
-
-using DrawEffectAttacherT =
- std::vector<sk_sp<sksg::RenderNode>> (*)(const skjson::ObjectValue&,
- const AnimationBuilder*,
- std::vector<sk_sp<sksg::RenderNode>>&&);
-
-static constexpr DrawEffectAttacherT gDrawEffectAttachers[] = {
- AttachRepeaterDrawEffect,
-};
-
-enum class ShapeType {
- kGeometry,
- kGeometryEffect,
- kPaint,
- kGroup,
- kTransform,
- kDrawEffect,
-};
-
-struct ShapeInfo {
- const char* fTypeString;
- ShapeType fShapeType;
- uint32_t fAttacherIndex; // index into respective attacher tables
-};
-
-const ShapeInfo* FindShapeInfo(const skjson::ObjectValue& jshape) {
- static constexpr ShapeInfo gShapeInfo[] = {
- { "el", ShapeType::kGeometry , 2 }, // ellipse -> AttachEllipseGeometry
- { "fl", ShapeType::kPaint , 0 }, // fill -> AttachColorFill
- { "gf", ShapeType::kPaint , 2 }, // gfill -> AttachGradientFill
- { "gr", ShapeType::kGroup , 0 }, // group -> Inline handler
- { "gs", ShapeType::kPaint , 3 }, // gstroke -> AttachGradientStroke
- { "mm", ShapeType::kGeometryEffect, 0 }, // merge -> AttachMergeGeometryEffect
- { "rc", ShapeType::kGeometry , 1 }, // rrect -> AttachRRectGeometry
- { "rd", ShapeType::kGeometryEffect, 2 }, // round -> AttachRoundGeometryEffect
- { "rp", ShapeType::kDrawEffect , 0 }, // repeater -> AttachRepeaterDrawEffect
- { "sh", ShapeType::kGeometry , 0 }, // shape -> AttachPathGeometry
- { "sr", ShapeType::kGeometry , 3 }, // polystar -> AttachPolyStarGeometry
- { "st", ShapeType::kPaint , 1 }, // stroke -> AttachColorStroke
- { "tm", ShapeType::kGeometryEffect, 1 }, // trim -> AttachTrimGeometryEffect
- { "tr", ShapeType::kTransform , 0 }, // transform -> Inline handler
- };
-
- const skjson::StringValue* type = jshape["ty"];
- if (!type) {
- return nullptr;
- }
-
- const auto* info = bsearch(type->begin(),
- gShapeInfo,
- SK_ARRAY_COUNT(gShapeInfo),
- sizeof(ShapeInfo),
- [](const void* key, const void* info) {
- return strcmp(static_cast<const char*>(key),
- static_cast<const ShapeInfo*>(info)->fTypeString);
- });
-
- return static_cast<const ShapeInfo*>(info);
-}
-
-struct GeometryEffectRec {
- const skjson::ObjectValue& fJson;
- GeometryEffectAttacherT fAttach;
-};
-
-} // namespace
-
-struct AnimationBuilder::AttachShapeContext {
- AttachShapeContext(std::vector<sk_sp<sksg::GeometryNode>>* geos,
- std::vector<GeometryEffectRec>* effects,
- size_t committedAnimators)
- : fGeometryStack(geos)
- , fGeometryEffectStack(effects)
- , fCommittedAnimators(committedAnimators) {}
-
- std::vector<sk_sp<sksg::GeometryNode>>* fGeometryStack;
- std::vector<GeometryEffectRec>* fGeometryEffectStack;
- size_t fCommittedAnimators;
-};
-
-sk_sp<sksg::RenderNode> AnimationBuilder::attachShape(const skjson::ArrayValue* jshape,
- AttachShapeContext* ctx) const {
- if (!jshape)
- return nullptr;
-
- SkDEBUGCODE(const auto initialGeometryEffects = ctx->fGeometryEffectStack->size();)
-
- const skjson::ObjectValue* jtransform = nullptr;
-
- struct ShapeRec {
- const skjson::ObjectValue& fJson;
- const ShapeInfo& fInfo;
- };
-
- // First pass (bottom->top):
- //
- // * pick up the group transform and opacity
- // * push local geometry effects onto the stack
- // * store recs for next pass
- //
- std::vector<ShapeRec> recs;
- for (size_t i = 0; i < jshape->size(); ++i) {
- const skjson::ObjectValue* shape = (*jshape)[jshape->size() - 1 - i];
- if (!shape) continue;
-
- const auto* info = FindShapeInfo(*shape);
- if (!info) {
- this->log(Logger::Level::kError, &(*shape)["ty"], "Unknown shape.");
- continue;
- }
-
- if (ParseDefault<bool>((*shape)["hd"], false)) {
- // Ignore hidden shapes.
- continue;
- }
-
- recs.push_back({ *shape, *info });
-
- switch (info->fShapeType) {
- case ShapeType::kTransform:
- // Just track the transform property for now -- we'll deal with it later.
- jtransform = shape;
- break;
- case ShapeType::kGeometryEffect:
- SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
- ctx->fGeometryEffectStack->push_back(
- { *shape, gGeometryEffectAttachers[info->fAttacherIndex] });
- break;
- default:
- break;
- }
- }
-
- // Second pass (top -> bottom, after 2x reverse):
- //
- // * track local geometry
- // * emit local paints
- //
- std::vector<sk_sp<sksg::GeometryNode>> geos;
- std::vector<sk_sp<sksg::RenderNode >> draws;
-
- const auto add_draw = [this, &draws](sk_sp<sksg::RenderNode> draw, const ShapeRec& rec) {
- // All draws can have an optional blend mode.
- draws.push_back(this->attachBlendMode(rec.fJson, std::move(draw)));
- };
-
- 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, this)) {
- geos.push_back(std::move(geo));
- }
- } break;
- case ShapeType::kGeometryEffect: {
- // Apply the current effect and pop from the stack.
- SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
- if (!geos.empty()) {
- geos = gGeometryEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
- this,
- std::move(geos));
- }
-
- SkASSERT(&ctx->fGeometryEffectStack->back().fJson == &rec->fJson);
- SkASSERT(ctx->fGeometryEffectStack->back().fAttach ==
- gGeometryEffectAttachers[rec->fInfo.fAttacherIndex]);
- ctx->fGeometryEffectStack->pop_back();
- } break;
- case ShapeType::kGroup: {
- AttachShapeContext groupShapeCtx(&geos,
- ctx->fGeometryEffectStack,
- ctx->fCommittedAnimators);
- if (auto subgroup = this->attachShape(rec->fJson["it"], &groupShapeCtx)) {
- add_draw(std::move(subgroup), *rec);
- SkASSERT(groupShapeCtx.fCommittedAnimators >= ctx->fCommittedAnimators);
- ctx->fCommittedAnimators = groupShapeCtx.fCommittedAnimators;
- }
- } break;
- case ShapeType::kPaint: {
- SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gPaintAttachers));
- auto paint = gPaintAttachers[rec->fInfo.fAttacherIndex](rec->fJson, this);
- if (!paint || geos.empty())
- break;
-
- auto drawGeos = geos;
-
- // Apply all pending effects from the stack.
- for (auto it = ctx->fGeometryEffectStack->rbegin();
- it != ctx->fGeometryEffectStack->rend(); ++it) {
- drawGeos = it->fAttach(it->fJson, this, std::move(drawGeos));
- }
-
- // If we still have multiple geos, reduce using 'merge'.
- auto geo = drawGeos.size() > 1
- ? Merge(std::move(drawGeos), sksg::Merge::Mode::kMerge)
- : drawGeos[0];
-
- SkASSERT(geo);
- add_draw(sksg::Draw::Make(std::move(geo), std::move(paint)), *rec);
- ctx->fCommittedAnimators = fCurrentAnimatorScope->size();
- } break;
- case ShapeType::kDrawEffect: {
- SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gDrawEffectAttachers));
- if (!draws.empty()) {
- draws = gDrawEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
- this,
- std::move(draws));
- ctx->fCommittedAnimators = fCurrentAnimatorScope->size();
- }
- } break;
- default:
- break;
- }
- }
-
- // By now we should have popped all local geometry effects.
- SkASSERT(ctx->fGeometryEffectStack->size() == initialGeometryEffects);
-
- sk_sp<sksg::RenderNode> shape_wrapper;
- if (draws.size() == 1) {
- // For a single draw, we don't need a group.
- shape_wrapper = std::move(draws.front());
- } else if (!draws.empty()) {
- // Emit local draws reversed (bottom->top, per spec).
- std::reverse(draws.begin(), draws.end());
- draws.shrink_to_fit();
-
- // We need a group to dispatch multiple draws.
- shape_wrapper = sksg::Group::Make(std::move(draws));
- }
-
- sk_sp<sksg::Transform> shape_transform;
- 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.
- AutoScope ascope(this);
-
- if ((shape_transform = this->attachMatrix2D(*jtransform, nullptr))) {
- shape_wrapper = sksg::TransformEffect::Make(std::move(shape_wrapper), shape_transform);
- }
- shape_wrapper = this->attachOpacity(*jtransform, std::move(shape_wrapper));
-
- auto local_scope = ascope.release();
- fCurrentAnimatorScope->insert(fCurrentAnimatorScope->begin() + ctx->fCommittedAnimators,
- std::make_move_iterator(local_scope.begin()),
- std::make_move_iterator(local_scope.end()));
- ctx->fCommittedAnimators += local_scope.size();
- }
-
- // Push transformed local geometries to parent list, for subsequent paints.
- for (auto& geo : geos) {
- ctx->fGeometryStack->push_back(shape_transform
- ? sksg::GeometryTransform::Make(std::move(geo), shape_transform)
- : std::move(geo));
- }
-
- return shape_wrapper;
-}
-
-sk_sp<sksg::RenderNode> AnimationBuilder::attachShapeLayer(const skjson::ObjectValue& layer,
- LayerInfo*) const {
- std::vector<sk_sp<sksg::GeometryNode>> geometryStack;
- std::vector<GeometryEffectRec> geometryEffectStack;
- AttachShapeContext shapeCtx(&geometryStack, &geometryEffectStack,
- fCurrentAnimatorScope->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
- // due to attached animators. To avoid this, we track committed animators and discard the
- // orphans here.
- SkASSERT(shapeCtx.fCommittedAnimators <= fCurrentAnimatorScope->size());
- fCurrentAnimatorScope->resize(shapeCtx.fCommittedAnimators);
-
- return shapeNode;
-}
-
-} // namespace internal
-} // namespace skottie
diff --git a/modules/skottie/src/layers/shapelayer/Ellipse.cpp b/modules/skottie/src/layers/shapelayer/Ellipse.cpp
new file mode 100644
index 0000000..469978e
--- /dev/null
+++ b/modules/skottie/src/layers/shapelayer/Ellipse.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/core/SkRRect.h"
+#include "modules/skottie/src/Adapter.h"
+#include "modules/skottie/src/SkottieJson.h"
+#include "modules/skottie/src/SkottiePriv.h"
+#include "modules/skottie/src/SkottieValue.h"
+#include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
+#include "modules/sksg/include/SkSGRect.h"
+
+namespace skottie {
+namespace internal {
+
+namespace {
+
+class EllipseGeometryAdapter final :
+ public DiscardableAdapterBase<EllipseGeometryAdapter, sksg::RRect> {
+public:
+ EllipseGeometryAdapter(const skjson::ObjectValue& jellipse,
+ const AnimationBuilder* abuilder) {
+ this->node()->setDirection(ParseDefault(jellipse["d"], -1) == 3 ? SkPathDirection::kCCW
+ : SkPathDirection::kCW);
+ this->node()->setInitialPointIndex(1); // starting point: (Center, Top)
+
+ this->bind(*abuilder, jellipse["s"], &fSize);
+ this->bind(*abuilder, jellipse["p"], &fPosition);
+ }
+
+private:
+ void onSync() override {
+ const auto size = ValueTraits<VectorValue>::As<SkSize >(fSize);
+ const auto center = ValueTraits<VectorValue>::As<SkPoint>(fPosition);
+
+ const auto bounds = SkRect::MakeXYWH(center.x() - size.width() / 2,
+ center.y() - size.height() / 2,
+ size.width(), size.height());
+
+ this->node()->setRRect(SkRRect::MakeOval(bounds));
+ }
+
+ VectorValue fSize,
+ fPosition;
+};
+
+} // namespace
+
+sk_sp<sksg::GeometryNode> ShapeBuilder::AttachEllipseGeometry(const skjson::ObjectValue& jellipse,
+ const AnimationBuilder* abuilder) {
+ return abuilder->attachDiscardableAdapter<EllipseGeometryAdapter, sk_sp<sksg::GeometryNode>>
+ (jellipse, abuilder);
+}
+
+} // namespace internal
+} // namespace skottie
diff --git a/modules/skottie/src/layers/shapelayer/Gradient.cpp b/modules/skottie/src/layers/shapelayer/Gradient.cpp
new file mode 100644
index 0000000..336b634
--- /dev/null
+++ b/modules/skottie/src/layers/shapelayer/Gradient.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "modules/skottie/src/Adapter.h"
+#include "modules/skottie/src/SkottieJson.h"
+#include "modules/skottie/src/SkottiePriv.h"
+#include "modules/skottie/src/SkottieValue.h"
+#include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
+#include "modules/sksg/include/SkSGGradient.h"
+#include "modules/sksg/include/SkSGPaint.h"
+
+namespace skottie {
+namespace internal {
+
+namespace {
+
+class GradientAdapter final : public AnimatablePropertyContainer {
+public:
+ static sk_sp<GradientAdapter> Make(const skjson::ObjectValue& jgrad,
+ const AnimationBuilder& abuilder) {
+ const skjson::ObjectValue* jstops = jgrad["g"];
+ if (!jstops)
+ return nullptr;
+
+ const auto stopCount = ParseDefault<int>((*jstops)["p"], -1);
+ if (stopCount < 0)
+ return nullptr;
+
+ const auto type = (ParseDefault<int>(jgrad["t"], 1) == 1) ? Type::kLinear
+ : Type::kRadial;
+ auto gradient_node = (type == Type::kLinear)
+ ? sk_sp<sksg::Gradient>(sksg::LinearGradient::Make())
+ : sk_sp<sksg::Gradient>(sksg::RadialGradient::Make());
+
+ return sk_sp<GradientAdapter>(new GradientAdapter(std::move(gradient_node),
+ type,
+ SkToSizeT(stopCount),
+ jgrad, *jstops, abuilder));
+ }
+
+ const sk_sp<sksg::Gradient>& node() const { return fGradient; }
+
+private:
+ enum class Type { kLinear, kRadial };
+
+ GradientAdapter(sk_sp<sksg::Gradient> gradient,
+ Type type,
+ size_t stop_count,
+ const skjson::ObjectValue& jgrad,
+ const skjson::ObjectValue& jstops,
+ const AnimationBuilder& abuilder)
+ : fGradient(std::move(gradient))
+ , fType(type)
+ , fStopCount(stop_count) {
+ this->bind(abuilder, jgrad["s"], &fStartPoint);
+ this->bind(abuilder, jgrad["e"], &fEndPoint);
+ this->bind(abuilder, jstops["k"], &fStops);
+ }
+
+ void onSync() override {
+ const auto s_point = ValueTraits<VectorValue>::As<SkPoint>(this->fStartPoint),
+ e_point = ValueTraits<VectorValue>::As<SkPoint>(this->fEndPoint);
+
+ switch (fType) {
+ case Type::kLinear: {
+ auto* grad = static_cast<sksg::LinearGradient*>(fGradient.get());
+ grad->setStartPoint(s_point);
+ grad->setEndPoint(e_point);
+
+ break;
+ }
+ case Type::kRadial: {
+ auto* grad = static_cast<sksg::RadialGradient*>(fGradient.get());
+ grad->setStartCenter(s_point);
+ grad->setEndCenter(s_point);
+ grad->setStartRadius(0);
+ grad->setEndRadius(SkPoint::Distance(s_point, e_point));
+
+ break;
+ }
+ }
+
+ // Gradient color stops are specified as a consolidated float vector holding:
+ //
+ // a) an (optional) array of color/RGB stop records (t, r, g, b)
+ //
+ // followed by
+ //
+ // b) an (optional) array of opacity/alpha stop records (t, a)
+ //
+ struct ColorRec { float t, r, g, b; };
+ struct OpacityRec { float t, a; };
+
+ // The number of color records is explicit (fColorStopCount),
+ // while the number of opacity stops is implicit (based on the size of fStops).
+ //
+ // |fStops| holds ColorRec x |fColorStopCount| + OpacityRec x N
+ const auto c_count = fStopCount,
+ c_size = c_count * 4,
+ o_count = (fStops.size() - c_size) / 2;
+ if (fStops.size() < c_size || fStops.size() != (c_count * 4 + o_count * 2)) {
+ // apply() may get called before the stops are set, so only log when we have some stops.
+ if (!fStops.empty()) {
+ SkDebugf("!! Invalid gradient stop array size: %zu\n", fStops.size());
+ }
+ return;
+ }
+
+ const auto* c_rec = c_count > 0
+ ? reinterpret_cast<const ColorRec*>(fStops.data())
+ : nullptr;
+ const auto* o_rec = o_count > 0
+ ? reinterpret_cast<const OpacityRec*>(fStops.data() + c_size)
+ : nullptr;
+ const auto* c_end = c_rec + c_count;
+ const auto* o_end = o_rec + o_count;
+
+ sksg::Gradient::ColorStop current_stop = {
+ 0.0f, {
+ c_rec ? c_rec->r : 0,
+ c_rec ? c_rec->g : 0,
+ c_rec ? c_rec->b : 0,
+ o_rec ? o_rec->a : 1,
+ }};
+
+ std::vector<sksg::Gradient::ColorStop> stops;
+ stops.reserve(c_count);
+
+ // Merge-sort the color and opacity stops, LERP-ing intermediate channel values as needed.
+ while (c_rec || o_rec) {
+ // After exhausting one of color recs / opacity recs, continue propagating the last
+ // computed values (as if they were specified at the current position).
+ const auto& cs = c_rec
+ ? *c_rec
+ : ColorRec{ o_rec->t,
+ current_stop.fColor.fR,
+ current_stop.fColor.fG,
+ current_stop.fColor.fB };
+ const auto& os = o_rec
+ ? *o_rec
+ : OpacityRec{ c_rec->t, current_stop.fColor.fA };
+
+ // Compute component lerp coefficients based on the relative position of the stops
+ // being considered. The idea is to select the smaller-pos stop, use its own properties
+ // as specified (lerp with t == 1), and lerp (with t < 1) the properties from the
+ // larger-pos stop against the previously computed gradient stop values.
+ const auto c_pos = std::max(cs.t, current_stop.fPosition),
+ o_pos = std::max(os.t, current_stop.fPosition),
+ c_pos_rel = c_pos - current_stop.fPosition,
+ o_pos_rel = o_pos - current_stop.fPosition,
+ t_c = SkTPin(sk_ieee_float_divide(o_pos_rel, c_pos_rel), 0.0f, 1.0f),
+ t_o = SkTPin(sk_ieee_float_divide(c_pos_rel, o_pos_rel), 0.0f, 1.0f);
+
+ auto lerp = [](float a, float b, float t) { return a + t * (b - a); };
+
+ current_stop = {
+ std::min(c_pos, o_pos),
+ {
+ lerp(current_stop.fColor.fR, cs.r, t_c ),
+ lerp(current_stop.fColor.fG, cs.g, t_c ),
+ lerp(current_stop.fColor.fB, cs.b, t_c ),
+ lerp(current_stop.fColor.fA, os.a, t_o)
+ }
+ };
+ stops.push_back(current_stop);
+
+ // Consume one of, or both (for coincident positions) color/opacity stops.
+ if (c_pos <= o_pos) {
+ c_rec = next_rec<ColorRec>(c_rec, c_end);
+ }
+ if (o_pos <= c_pos) {
+ o_rec = next_rec<OpacityRec>(o_rec, o_end);
+ }
+ }
+
+ stops.shrink_to_fit();
+ fGradient->setColorStops(std::move(stops));
+ }
+
+private:
+ template <typename T>
+ const T* next_rec(const T* rec, const T* end_rec) const {
+ if (!rec) return nullptr;
+
+ SkASSERT(rec < end_rec);
+ rec++;
+
+ return rec < end_rec ? rec : nullptr;
+ }
+
+ const sk_sp<sksg::Gradient> fGradient;
+ const Type fType;
+ const size_t fStopCount;
+
+ VectorValue fStartPoint,
+ fEndPoint,
+ fStops;
+};
+
+} // namespace
+
+sk_sp<sksg::PaintNode> ShapeBuilder::AttachGradient(const skjson::ObjectValue& jgrad,
+ const AnimationBuilder* abuilder) {
+ auto gradient = abuilder->attachDiscardableAdapter<GradientAdapter, sk_sp<sksg::Gradient>>
+ (jgrad, *abuilder);
+
+ return sksg::ShaderPaint::Make(std::move(gradient));
+}
+
+} // namespace internal
+} // namespace skottie
diff --git a/modules/skottie/src/layers/shapelayer/MergePaths.cpp b/modules/skottie/src/layers/shapelayer/MergePaths.cpp
new file mode 100644
index 0000000..5ced51a
--- /dev/null
+++ b/modules/skottie/src/layers/shapelayer/MergePaths.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "modules/skottie/src/Adapter.h"
+#include "modules/skottie/src/SkottieJson.h"
+#include "modules/skottie/src/SkottiePriv.h"
+#include "modules/skottie/src/SkottieValue.h"
+#include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
+
+namespace skottie {
+namespace internal {
+
+sk_sp<sksg::Merge> ShapeBuilder::MergeGeometry(std::vector<sk_sp<sksg::GeometryNode>>&& geos,
+ sksg::Merge::Mode mode) {
+ std::vector<sksg::Merge::Rec> merge_recs;
+ merge_recs.reserve(geos.size());
+
+ for (auto& geo : geos) {
+ merge_recs.push_back(
+ {std::move(geo), merge_recs.empty() ? sksg::Merge::Mode::kMerge : mode});
+ }
+
+ return sksg::Merge::Make(std::move(merge_recs));
+}
+
+std::vector<sk_sp<sksg::GeometryNode>> ShapeBuilder::AttachMergeGeometryEffect(
+ const skjson::ObjectValue& jmerge, const AnimationBuilder*,
+ std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
+ static constexpr sksg::Merge::Mode gModes[] = {
+ sksg::Merge::Mode::kMerge, // "mm": 1
+ sksg::Merge::Mode::kUnion, // "mm": 2
+ sksg::Merge::Mode::kDifference, // "mm": 3
+ sksg::Merge::Mode::kIntersect, // "mm": 4
+ sksg::Merge::Mode::kXOR , // "mm": 5
+ };
+
+ const auto mode = gModes[SkTMin<size_t>(ParseDefault<size_t>(jmerge["mm"], 1) - 1,
+ SK_ARRAY_COUNT(gModes) - 1)];
+
+ std::vector<sk_sp<sksg::GeometryNode>> merged;
+ merged.push_back(ShapeBuilder::MergeGeometry(std::move(geos), mode));
+
+ return merged;
+}
+
+} // namespace internal
+} // namespace skottie
diff --git a/modules/skottie/src/layers/shapelayer/Polystar.cpp b/modules/skottie/src/layers/shapelayer/Polystar.cpp
new file mode 100644
index 0000000..4b1078b
--- /dev/null
+++ b/modules/skottie/src/layers/shapelayer/Polystar.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "modules/skottie/src/Adapter.h"
+#include "modules/skottie/src/SkottieJson.h"
+#include "modules/skottie/src/SkottiePriv.h"
+#include "modules/skottie/src/SkottieValue.h"
+#include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
+#include "modules/sksg/include/SkSGPath.h"
+
+namespace skottie {
+namespace internal {
+
+namespace {
+
+class PolystarGeometryAdapter final :
+ public DiscardableAdapterBase<PolystarGeometryAdapter, sksg::Path> {
+public:
+ enum class Type {
+ kStar, kPoly,
+ };
+
+ PolystarGeometryAdapter(const skjson::ObjectValue& jstar,
+ const AnimationBuilder* abuilder, Type t)
+ : fType(t) {
+ this->bind(*abuilder, jstar["pt"], &fPointCount);
+ this->bind(*abuilder, jstar["p" ], &fPosition);
+ this->bind(*abuilder, jstar["r" ], &fRotation);
+ this->bind(*abuilder, jstar["ir"], &fInnerRadius);
+ this->bind(*abuilder, jstar["or"], &fOuterRadius);
+ this->bind(*abuilder, jstar["is"], &fInnerRoundness);
+ this->bind(*abuilder, jstar["os"], &fOuterRoundness);
+ }
+
+private:
+ void onSync() override {
+ const auto pos = ValueTraits<VectorValue>::As<SkPoint>(fPosition);
+ static constexpr int kMaxPointCount = 100000;
+ const auto count = SkToUInt(SkTPin(SkScalarRoundToInt(fPointCount), 0, kMaxPointCount));
+ const auto arc = sk_ieee_float_divide(SK_ScalarPI * 2, count);
+
+ const auto pt_on_circle = [](const SkPoint& c, SkScalar r, SkScalar a) {
+ return SkPoint::Make(c.x() + r * std::cos(a),
+ c.y() + r * std::sin(a));
+ };
+
+ // TODO: inner/outer "roundness"?
+
+ SkPath poly;
+
+ auto angle = SkDegreesToRadians(fRotation - 90);
+ poly.moveTo(pt_on_circle(pos, fOuterRadius, angle));
+ poly.incReserve(fType == Type::kStar ? count * 2 : count);
+
+ for (unsigned i = 0; i < count; ++i) {
+ if (fType == Type::kStar) {
+ poly.lineTo(pt_on_circle(pos, fInnerRadius, angle + arc * 0.5f));
+ }
+ angle += arc;
+ poly.lineTo(pt_on_circle(pos, fOuterRadius, angle));
+ }
+
+ poly.close();
+ this->node()->setPath(poly);
+ }
+
+ const Type fType;
+
+ VectorValue fPosition;
+ ScalarValue fPointCount = 0,
+ fRotation = 0,
+ fInnerRadius = 0,
+ fOuterRadius = 0,
+ fInnerRoundness = 0,
+ fOuterRoundness = 0;
+};
+
+} // namespace
+
+sk_sp<sksg::GeometryNode> ShapeBuilder::AttachPolystarGeometry(const skjson::ObjectValue& jstar,
+ const AnimationBuilder* abuilder) {
+ static constexpr PolystarGeometryAdapter::Type gTypes[] = {
+ PolystarGeometryAdapter::Type::kStar, // "sy": 1
+ PolystarGeometryAdapter::Type::kPoly, // "sy": 2
+ };
+
+ const auto type = ParseDefault<size_t>(jstar["sy"], 0) - 1;
+ if (type >= SK_ARRAY_COUNT(gTypes)) {
+ abuilder->log(Logger::Level::kError, &jstar, "Unknown polystar type.");
+ return nullptr;
+ }
+
+ return abuilder->attachDiscardableAdapter<PolystarGeometryAdapter, sk_sp<sksg::GeometryNode>>
+ (jstar, abuilder, gTypes[type]);
+}
+
+} // namespace internal
+} // namespace skottie
diff --git a/modules/skottie/src/layers/shapelayer/Rectangle.cpp b/modules/skottie/src/layers/shapelayer/Rectangle.cpp
new file mode 100644
index 0000000..804274d
--- /dev/null
+++ b/modules/skottie/src/layers/shapelayer/Rectangle.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/core/SkRRect.h"
+#include "modules/skottie/src/Adapter.h"
+#include "modules/skottie/src/SkottieJson.h"
+#include "modules/skottie/src/SkottiePriv.h"
+#include "modules/skottie/src/SkottieValue.h"
+#include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
+#include "modules/sksg/include/SkSGRect.h"
+
+namespace skottie {
+namespace internal {
+
+namespace {
+
+class RectangleGeometryAdapter final :
+ public DiscardableAdapterBase<RectangleGeometryAdapter, sksg::RRect> {
+public:
+ RectangleGeometryAdapter(const skjson::ObjectValue& jrect,
+ const AnimationBuilder* abuilder) {
+ this->node()->setDirection(ParseDefault(jrect["d"], -1) == 3 ? SkPathDirection::kCCW
+ : SkPathDirection::kCW);
+ this->node()->setInitialPointIndex(2); // starting point: (Right, Top - radius.y)
+
+ this->bind(*abuilder, jrect["s"], &fSize);
+ this->bind(*abuilder, jrect["p"], &fPosition);
+ this->bind(*abuilder, jrect["r"], &fRoundness);
+ }
+
+private:
+ void onSync() override {
+ const auto size = ValueTraits<VectorValue>::As<SkSize >(fSize);
+ const auto center = ValueTraits<VectorValue>::As<SkPoint>(fPosition);
+
+ const auto bounds = SkRect::MakeXYWH(center.x() - size.width() / 2,
+ center.y() - size.height() / 2,
+ size.width(), size.height());
+
+ this->node()->setRRect(SkRRect::MakeRectXY(bounds, fRoundness, fRoundness));
+ }
+
+ VectorValue fSize,
+ fPosition;
+ ScalarValue fRoundness = 0;
+};
+
+} // namespace
+
+sk_sp<sksg::GeometryNode> ShapeBuilder::AttachRRectGeometry(const skjson::ObjectValue& jrect,
+ const AnimationBuilder* abuilder) {
+ return abuilder->attachDiscardableAdapter<RectangleGeometryAdapter, sk_sp<sksg::GeometryNode>>
+ (jrect, abuilder);
+}
+
+} // namespace internal
+} // namespace skottie
diff --git a/modules/skottie/src/layers/shapelayer/Repeater.cpp b/modules/skottie/src/layers/shapelayer/Repeater.cpp
new file mode 100644
index 0000000..c47c18c
--- /dev/null
+++ b/modules/skottie/src/layers/shapelayer/Repeater.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "modules/skottie/src/Adapter.h"
+#include "modules/skottie/src/SkottieJson.h"
+#include "modules/skottie/src/SkottiePriv.h"
+#include "modules/skottie/src/SkottieValue.h"
+#include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
+#include "modules/sksg/include/SkSGGroup.h"
+#include "modules/sksg/include/SkSGTransform.h"
+
+#include <vector>
+
+namespace skottie {
+namespace internal {
+
+namespace {
+
+class RepeaterAdapter final : public DiscardableAdapterBase<RepeaterAdapter, sksg::Group> {
+public:
+ RepeaterAdapter(const skjson::ObjectValue& jrepeater,
+ const skjson::ObjectValue& jtransform,
+ const AnimationBuilder& abuilder,
+ sk_sp<sksg::RenderNode> repeater_node)
+ : fRepeaterNode(std::move(repeater_node))
+ , fComposite((ParseDefault(jrepeater["m"], 1) == 1) ? Composite::kAbove
+ : Composite::kBelow) {
+ this->bind(abuilder, jrepeater["c"], &fCount);
+ this->bind(abuilder, jrepeater["o"], &fOffset);
+
+ this->bind(abuilder, jtransform["a" ], &fAnchorPoint);
+ this->bind(abuilder, jtransform["p" ], &fPosition);
+ this->bind(abuilder, jtransform["s" ], &fScale);
+ this->bind(abuilder, jtransform["r" ], &fRotation);
+ this->bind(abuilder, jtransform["so"], &fStartOpacity);
+ this->bind(abuilder, jtransform["eo"], &fEndOpacity);
+ }
+
+private:
+ void onSync() override {
+ static constexpr SkScalar kMaxCount = 512;
+ const auto count = static_cast<size_t>(SkTPin(fCount, 0.0f, kMaxCount) + 0.5f);
+
+ const auto anchor_point = ValueTraits<VectorValue>::As<SkPoint>(fAnchorPoint),
+ position = ValueTraits<VectorValue>::As<SkPoint>(fPosition),
+ scale = ValueTraits<VectorValue>::As<SkVector>(fScale);
+
+ const auto& compute_transform = [&] (size_t index) {
+ const auto t = fOffset + index;
+
+ // Position, scale & rotation are "scaled" by index/offset.
+ SkMatrix m = SkMatrix::MakeTrans(-anchor_point.x(),
+ -anchor_point.y());
+ m.postScale(std::pow(scale.x() * .01f, fOffset),
+ std::pow(scale.y() * .01f, fOffset));
+ m.postRotate(t * fRotation);
+ m.postTranslate(t * position.x() + anchor_point.x(),
+ t * position.y() + anchor_point.y());
+
+ return m;
+ };
+
+ // TODO: start/end opacity support.
+
+ // TODO: we can avoid rebuilding all the fragments in most cases.
+ this->node()->clear();
+ for (size_t i = 0; i < count; ++i) {
+ const auto insert_index = (fComposite == Composite::kAbove) ? i : count - i - 1;
+ this->node()->addChild(sksg::TransformEffect::Make(fRepeaterNode,
+ compute_transform(insert_index)));
+ }
+ }
+
+ enum class Composite { kAbove, kBelow };
+
+ const sk_sp<sksg::RenderNode> fRepeaterNode;
+ const Composite fComposite;
+
+ // Repeater props
+ ScalarValue fCount = 0,
+ fOffset = 0;
+
+ // Transform props
+ VectorValue fAnchorPoint,
+ fPosition,
+ fScale = { 100, 100 };
+ ScalarValue fRotation = 0,
+ fStartOpacity = 100,
+ fEndOpacity = 100;
+};
+
+} // namespace
+
+std::vector<sk_sp<sksg::RenderNode>> ShapeBuilder::AttachRepeaterDrawEffect(
+ const skjson::ObjectValue& jrepeater,
+ const AnimationBuilder* abuilder,
+ std::vector<sk_sp<sksg::RenderNode>>&& draws) {
+ std::vector<sk_sp<sksg::RenderNode>> repeater_draws;
+
+ if (const skjson::ObjectValue* jtransform = jrepeater["tr"]) {
+ // We can skip the group if only one draw.
+ auto repeater_node = (draws.size() > 1) ? sksg::Group::Make(std::move(draws))
+ : std::move(draws[0]);
+
+ auto repeater_root =
+ abuilder->attachDiscardableAdapter<RepeaterAdapter>(jrepeater,
+ *jtransform,
+ *abuilder,
+ std::move(repeater_node));
+ repeater_draws.reserve(1);
+ repeater_draws.push_back(std::move(repeater_root));
+ } else {
+ repeater_draws = std::move(draws);
+ }
+
+ return repeater_draws;
+}
+
+} // namespace internal
+} // namespace skottie
diff --git a/modules/skottie/src/layers/shapelayer/RoundCorners.cpp b/modules/skottie/src/layers/shapelayer/RoundCorners.cpp
new file mode 100644
index 0000000..9896584
--- /dev/null
+++ b/modules/skottie/src/layers/shapelayer/RoundCorners.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "modules/skottie/src/Adapter.h"
+#include "modules/skottie/src/SkottieJson.h"
+#include "modules/skottie/src/SkottiePriv.h"
+#include "modules/skottie/src/SkottieValue.h"
+#include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
+#include "modules/sksg/include/SkSGRoundEffect.h"
+
+namespace skottie {
+namespace internal {
+
+namespace {
+
+class RoundCornersAdapter final : public DiscardableAdapterBase<RoundCornersAdapter,
+ sksg::RoundEffect> {
+public:
+ RoundCornersAdapter(const skjson::ObjectValue& jround,
+ const AnimationBuilder& abuilder,
+ sk_sp<sksg::GeometryNode> child)
+ : INHERITED(sksg::RoundEffect::Make(std::move(child))) {
+ this->bind(abuilder, jround["r"], fRadius);
+ }
+
+private:
+ void onSync() override {
+ this->node()->setRadius(fRadius);
+ }
+
+ ScalarValue fRadius = 0;
+
+ using INHERITED = DiscardableAdapterBase<RoundCornersAdapter, sksg::RoundEffect>;
+};
+
+} // namespace
+
+std::vector<sk_sp<sksg::GeometryNode>> ShapeBuilder::AttachRoundGeometryEffect(
+ const skjson::ObjectValue& jround, const AnimationBuilder* abuilder,
+ std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
+ std::vector<sk_sp<sksg::GeometryNode>> rounded;
+ rounded.reserve(geos.size());
+
+ for (auto& g : geos) {
+ rounded.push_back(
+ abuilder->attachDiscardableAdapter<RoundCornersAdapter, sk_sp<sksg::RoundEffect>>
+ (jround, *abuilder, std::move(g)));
+ }
+
+ return rounded;
+}
+
+} // namespace internal
+} // namespace skottie
diff --git a/modules/skottie/src/layers/shapelayer/ShapeLayer.cpp b/modules/skottie/src/layers/shapelayer/ShapeLayer.cpp
new file mode 100644
index 0000000..1b04189
--- /dev/null
+++ b/modules/skottie/src/layers/shapelayer/ShapeLayer.cpp
@@ -0,0 +1,425 @@
+/*
+ * 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 "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
+
+#include "include/core/SkPath.h"
+#include "modules/skottie/src/SkottieAdapter.h"
+#include "modules/skottie/src/SkottieJson.h"
+#include "modules/skottie/src/SkottiePriv.h"
+#include "modules/skottie/src/SkottieValue.h"
+#include "modules/sksg/include/SkSGDraw.h"
+#include "modules/sksg/include/SkSGGeometryTransform.h"
+#include "modules/sksg/include/SkSGGradient.h"
+#include "modules/sksg/include/SkSGGroup.h"
+#include "modules/sksg/include/SkSGMerge.h"
+#include "modules/sksg/include/SkSGPaint.h"
+#include "modules/sksg/include/SkSGPath.h"
+#include "modules/sksg/include/SkSGRect.h"
+#include "modules/sksg/include/SkSGRenderEffect.h"
+#include "modules/sksg/include/SkSGRoundEffect.h"
+#include "modules/sksg/include/SkSGTransform.h"
+#include "modules/sksg/include/SkSGTrimEffect.h"
+#include "src/utils/SkJSON.h"
+
+#include <algorithm>
+#include <iterator>
+
+namespace skottie {
+namespace internal {
+
+namespace {
+
+sk_sp<sksg::PaintNode> AttachPaint(const skjson::ObjectValue& jpaint,
+ const AnimationBuilder* abuilder,
+ sk_sp<sksg::PaintNode> paint_node) {
+ if (paint_node) {
+ paint_node->setAntiAlias(true);
+
+ abuilder->bindProperty<ScalarValue>(jpaint["o"],
+ [paint_node](const ScalarValue& o) {
+ // BM opacity is [0..100]
+ paint_node->setOpacity(o * 0.01f);
+ });
+ }
+
+ return paint_node;
+}
+
+sk_sp<sksg::PaintNode> AttachStroke(const skjson::ObjectValue& jstroke,
+ const AnimationBuilder* abuilder,
+ sk_sp<sksg::PaintNode> stroke_node) {
+ if (!stroke_node)
+ return nullptr;
+
+ stroke_node->setStyle(SkPaint::kStroke_Style);
+
+ abuilder->bindProperty<ScalarValue>(jstroke["w"],
+ [stroke_node](const ScalarValue& w) {
+ stroke_node->setStrokeWidth(w);
+ });
+
+ stroke_node->setStrokeMiter(ParseDefault<SkScalar>(jstroke["ml"], 4.0f));
+
+ static constexpr SkPaint::Join gJoins[] = {
+ SkPaint::kMiter_Join,
+ SkPaint::kRound_Join,
+ SkPaint::kBevel_Join,
+ };
+ stroke_node->setStrokeJoin(gJoins[SkTMin<size_t>(ParseDefault<size_t>(jstroke["lj"], 1) - 1,
+ SK_ARRAY_COUNT(gJoins) - 1)]);
+
+ static constexpr SkPaint::Cap gCaps[] = {
+ SkPaint::kButt_Cap,
+ SkPaint::kRound_Cap,
+ SkPaint::kSquare_Cap,
+ };
+ stroke_node->setStrokeCap(gCaps[SkTMin<size_t>(ParseDefault<size_t>(jstroke["lc"], 1) - 1,
+ SK_ARRAY_COUNT(gCaps) - 1)]);
+
+ return stroke_node;
+}
+
+using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const skjson::ObjectValue&,
+ const AnimationBuilder*);
+static constexpr GeometryAttacherT gGeometryAttachers[] = {
+ ShapeBuilder::AttachPathGeometry,
+ ShapeBuilder::AttachRRectGeometry,
+ ShapeBuilder::AttachEllipseGeometry,
+ ShapeBuilder::AttachPolystarGeometry,
+};
+
+using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const skjson::ObjectValue&,
+ const AnimationBuilder*);
+static constexpr PaintAttacherT gPaintAttachers[] = {
+ ShapeBuilder::AttachColorFill,
+ ShapeBuilder::AttachColorStroke,
+ ShapeBuilder::AttachGradientFill,
+ ShapeBuilder::AttachGradientStroke,
+};
+
+using GeometryEffectAttacherT =
+ std::vector<sk_sp<sksg::GeometryNode>> (*)(const skjson::ObjectValue&,
+ const AnimationBuilder*,
+ std::vector<sk_sp<sksg::GeometryNode>>&&);
+static constexpr GeometryEffectAttacherT gGeometryEffectAttachers[] = {
+ ShapeBuilder::AttachMergeGeometryEffect,
+ ShapeBuilder::AttachTrimGeometryEffect,
+ ShapeBuilder::AttachRoundGeometryEffect,
+};
+
+using DrawEffectAttacherT =
+ std::vector<sk_sp<sksg::RenderNode>> (*)(const skjson::ObjectValue&,
+ const AnimationBuilder*,
+ std::vector<sk_sp<sksg::RenderNode>>&&);
+
+static constexpr DrawEffectAttacherT gDrawEffectAttachers[] = {
+ ShapeBuilder::AttachRepeaterDrawEffect,
+};
+
+enum class ShapeType {
+ kGeometry,
+ kGeometryEffect,
+ kPaint,
+ kGroup,
+ kTransform,
+ kDrawEffect,
+};
+
+struct ShapeInfo {
+ const char* fTypeString;
+ ShapeType fShapeType;
+ uint32_t fAttacherIndex; // index into respective attacher tables
+};
+
+const ShapeInfo* FindShapeInfo(const skjson::ObjectValue& jshape) {
+ static constexpr ShapeInfo gShapeInfo[] = {
+ { "el", ShapeType::kGeometry , 2 }, // ellipse -> AttachEllipseGeometry
+ { "fl", ShapeType::kPaint , 0 }, // fill -> AttachColorFill
+ { "gf", ShapeType::kPaint , 2 }, // gfill -> AttachGradientFill
+ { "gr", ShapeType::kGroup , 0 }, // group -> Inline handler
+ { "gs", ShapeType::kPaint , 3 }, // gstroke -> AttachGradientStroke
+ { "mm", ShapeType::kGeometryEffect, 0 }, // merge -> AttachMergeGeometryEffect
+ { "rc", ShapeType::kGeometry , 1 }, // rrect -> AttachRRectGeometry
+ { "rd", ShapeType::kGeometryEffect, 2 }, // round -> AttachRoundGeometryEffect
+ { "rp", ShapeType::kDrawEffect , 0 }, // repeater -> AttachRepeaterDrawEffect
+ { "sh", ShapeType::kGeometry , 0 }, // shape -> AttachPathGeometry
+ { "sr", ShapeType::kGeometry , 3 }, // polystar -> AttachPolyStarGeometry
+ { "st", ShapeType::kPaint , 1 }, // stroke -> AttachColorStroke
+ { "tm", ShapeType::kGeometryEffect, 1 }, // trim -> AttachTrimGeometryEffect
+ { "tr", ShapeType::kTransform , 0 }, // transform -> Inline handler
+ };
+
+ const skjson::StringValue* type = jshape["ty"];
+ if (!type) {
+ return nullptr;
+ }
+
+ const auto* info = bsearch(type->begin(),
+ gShapeInfo,
+ SK_ARRAY_COUNT(gShapeInfo),
+ sizeof(ShapeInfo),
+ [](const void* key, const void* info) {
+ return strcmp(static_cast<const char*>(key),
+ static_cast<const ShapeInfo*>(info)->fTypeString);
+ });
+
+ return static_cast<const ShapeInfo*>(info);
+}
+
+struct GeometryEffectRec {
+ const skjson::ObjectValue& fJson;
+ GeometryEffectAttacherT fAttach;
+};
+
+} // namespace
+
+sk_sp<sksg::GeometryNode> ShapeBuilder::AttachPathGeometry(const skjson::ObjectValue& jpath,
+ const AnimationBuilder* abuilder) {
+ return abuilder->attachPath(jpath["ks"]);
+}
+
+sk_sp<sksg::PaintNode> ShapeBuilder::AttachColorFill(const skjson::ObjectValue& jfill,
+ const AnimationBuilder* abuilder) {
+ return AttachPaint(jfill, abuilder, abuilder->attachColor(jfill, "c"));
+}
+
+sk_sp<sksg::PaintNode> ShapeBuilder::AttachGradientFill(const skjson::ObjectValue& jfill,
+ const AnimationBuilder* abuilder) {
+ return AttachPaint(jfill, abuilder, ShapeBuilder::AttachGradient(jfill, abuilder));
+}
+
+sk_sp<sksg::PaintNode> ShapeBuilder::AttachColorStroke(const skjson::ObjectValue& jstroke,
+ const AnimationBuilder* abuilder) {
+ return AttachStroke(jstroke, abuilder, AttachPaint(jstroke, abuilder,
+ abuilder->attachColor(jstroke, "c")));
+}
+
+sk_sp<sksg::PaintNode> ShapeBuilder::AttachGradientStroke(const skjson::ObjectValue& jstroke,
+ const AnimationBuilder* abuilder) {
+ return AttachStroke(jstroke, abuilder, AttachPaint(jstroke, abuilder,
+ ShapeBuilder::AttachGradient(jstroke,
+ abuilder)));
+}
+
+struct AnimationBuilder::AttachShapeContext {
+ AttachShapeContext(std::vector<sk_sp<sksg::GeometryNode>>* geos,
+ std::vector<GeometryEffectRec>* effects,
+ size_t committedAnimators)
+ : fGeometryStack(geos)
+ , fGeometryEffectStack(effects)
+ , fCommittedAnimators(committedAnimators) {}
+
+ std::vector<sk_sp<sksg::GeometryNode>>* fGeometryStack;
+ std::vector<GeometryEffectRec>* fGeometryEffectStack;
+ size_t fCommittedAnimators;
+};
+
+sk_sp<sksg::RenderNode> AnimationBuilder::attachShape(const skjson::ArrayValue* jshape,
+ AttachShapeContext* ctx) const {
+ if (!jshape)
+ return nullptr;
+
+ SkDEBUGCODE(const auto initialGeometryEffects = ctx->fGeometryEffectStack->size();)
+
+ const skjson::ObjectValue* jtransform = nullptr;
+
+ struct ShapeRec {
+ const skjson::ObjectValue& fJson;
+ const ShapeInfo& fInfo;
+ };
+
+ // First pass (bottom->top):
+ //
+ // * pick up the group transform and opacity
+ // * push local geometry effects onto the stack
+ // * store recs for next pass
+ //
+ std::vector<ShapeRec> recs;
+ for (size_t i = 0; i < jshape->size(); ++i) {
+ const skjson::ObjectValue* shape = (*jshape)[jshape->size() - 1 - i];
+ if (!shape) continue;
+
+ const auto* info = FindShapeInfo(*shape);
+ if (!info) {
+ this->log(Logger::Level::kError, &(*shape)["ty"], "Unknown shape.");
+ continue;
+ }
+
+ if (ParseDefault<bool>((*shape)["hd"], false)) {
+ // Ignore hidden shapes.
+ continue;
+ }
+
+ recs.push_back({ *shape, *info });
+
+ switch (info->fShapeType) {
+ case ShapeType::kTransform:
+ // Just track the transform property for now -- we'll deal with it later.
+ jtransform = shape;
+ break;
+ case ShapeType::kGeometryEffect:
+ SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
+ ctx->fGeometryEffectStack->push_back(
+ { *shape, gGeometryEffectAttachers[info->fAttacherIndex] });
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Second pass (top -> bottom, after 2x reverse):
+ //
+ // * track local geometry
+ // * emit local paints
+ //
+ std::vector<sk_sp<sksg::GeometryNode>> geos;
+ std::vector<sk_sp<sksg::RenderNode >> draws;
+
+ const auto add_draw = [this, &draws](sk_sp<sksg::RenderNode> draw, const ShapeRec& rec) {
+ // All draws can have an optional blend mode.
+ draws.push_back(this->attachBlendMode(rec.fJson, std::move(draw)));
+ };
+
+ 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, this)) {
+ geos.push_back(std::move(geo));
+ }
+ } break;
+ case ShapeType::kGeometryEffect: {
+ // Apply the current effect and pop from the stack.
+ SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
+ if (!geos.empty()) {
+ geos = gGeometryEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
+ this,
+ std::move(geos));
+ }
+
+ SkASSERT(&ctx->fGeometryEffectStack->back().fJson == &rec->fJson);
+ SkASSERT(ctx->fGeometryEffectStack->back().fAttach ==
+ gGeometryEffectAttachers[rec->fInfo.fAttacherIndex]);
+ ctx->fGeometryEffectStack->pop_back();
+ } break;
+ case ShapeType::kGroup: {
+ AttachShapeContext groupShapeCtx(&geos,
+ ctx->fGeometryEffectStack,
+ ctx->fCommittedAnimators);
+ if (auto subgroup = this->attachShape(rec->fJson["it"], &groupShapeCtx)) {
+ add_draw(std::move(subgroup), *rec);
+ SkASSERT(groupShapeCtx.fCommittedAnimators >= ctx->fCommittedAnimators);
+ ctx->fCommittedAnimators = groupShapeCtx.fCommittedAnimators;
+ }
+ } break;
+ case ShapeType::kPaint: {
+ SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gPaintAttachers));
+ auto paint = gPaintAttachers[rec->fInfo.fAttacherIndex](rec->fJson, this);
+ if (!paint || geos.empty())
+ break;
+
+ auto drawGeos = geos;
+
+ // Apply all pending effects from the stack.
+ for (auto it = ctx->fGeometryEffectStack->rbegin();
+ it != ctx->fGeometryEffectStack->rend(); ++it) {
+ drawGeos = it->fAttach(it->fJson, this, std::move(drawGeos));
+ }
+
+ // If we still have multiple geos, reduce using 'merge'.
+ auto geo = drawGeos.size() > 1
+ ? ShapeBuilder::MergeGeometry(std::move(drawGeos), sksg::Merge::Mode::kMerge)
+ : drawGeos[0];
+
+ SkASSERT(geo);
+ add_draw(sksg::Draw::Make(std::move(geo), std::move(paint)), *rec);
+ ctx->fCommittedAnimators = fCurrentAnimatorScope->size();
+ } break;
+ case ShapeType::kDrawEffect: {
+ SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gDrawEffectAttachers));
+ if (!draws.empty()) {
+ draws = gDrawEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
+ this,
+ std::move(draws));
+ ctx->fCommittedAnimators = fCurrentAnimatorScope->size();
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+
+ // By now we should have popped all local geometry effects.
+ SkASSERT(ctx->fGeometryEffectStack->size() == initialGeometryEffects);
+
+ sk_sp<sksg::RenderNode> shape_wrapper;
+ if (draws.size() == 1) {
+ // For a single draw, we don't need a group.
+ shape_wrapper = std::move(draws.front());
+ } else if (!draws.empty()) {
+ // Emit local draws reversed (bottom->top, per spec).
+ std::reverse(draws.begin(), draws.end());
+ draws.shrink_to_fit();
+
+ // We need a group to dispatch multiple draws.
+ shape_wrapper = sksg::Group::Make(std::move(draws));
+ }
+
+ sk_sp<sksg::Transform> shape_transform;
+ 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.
+ AutoScope ascope(this);
+
+ if ((shape_transform = this->attachMatrix2D(*jtransform, nullptr))) {
+ shape_wrapper = sksg::TransformEffect::Make(std::move(shape_wrapper), shape_transform);
+ }
+ shape_wrapper = this->attachOpacity(*jtransform, std::move(shape_wrapper));
+
+ auto local_scope = ascope.release();
+ fCurrentAnimatorScope->insert(fCurrentAnimatorScope->begin() + ctx->fCommittedAnimators,
+ std::make_move_iterator(local_scope.begin()),
+ std::make_move_iterator(local_scope.end()));
+ ctx->fCommittedAnimators += local_scope.size();
+ }
+
+ // Push transformed local geometries to parent list, for subsequent paints.
+ for (auto& geo : geos) {
+ ctx->fGeometryStack->push_back(shape_transform
+ ? sksg::GeometryTransform::Make(std::move(geo), shape_transform)
+ : std::move(geo));
+ }
+
+ return shape_wrapper;
+}
+
+sk_sp<sksg::RenderNode> AnimationBuilder::attachShapeLayer(const skjson::ObjectValue& layer,
+ LayerInfo*) const {
+ std::vector<sk_sp<sksg::GeometryNode>> geometryStack;
+ std::vector<GeometryEffectRec> geometryEffectStack;
+ AttachShapeContext shapeCtx(&geometryStack, &geometryEffectStack,
+ fCurrentAnimatorScope->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
+ // due to attached animators. To avoid this, we track committed animators and discard the
+ // orphans here.
+ SkASSERT(shapeCtx.fCommittedAnimators <= fCurrentAnimatorScope->size());
+ fCurrentAnimatorScope->resize(shapeCtx.fCommittedAnimators);
+
+ return shapeNode;
+}
+
+} // namespace internal
+} // namespace skottie
diff --git a/modules/skottie/src/layers/shapelayer/ShapeLayer.h b/modules/skottie/src/layers/shapelayer/ShapeLayer.h
new file mode 100644
index 0000000..3e7e572
--- /dev/null
+++ b/modules/skottie/src/layers/shapelayer/ShapeLayer.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkottieShapeLayer_DEFINED
+#define SkottieShapeLayer_DEFINED
+
+#include "include/private/SkNoncopyable.h"
+#include "modules/sksg/include/SkSGMerge.h"
+
+#include <vector>
+
+namespace skjson {
+
+class ObjectValue;
+
+} // namespace skjson
+
+namespace sksg {
+
+class GeometryNode;
+class PaintNode;
+class RenderNode;
+
+} // namespace sksg
+
+namespace skottie {
+namespace internal {
+
+class AnimationBuilder;
+
+// TODO/TRANSITIONAL: not much state here yet, but will eventually hold ShapeLayer-related stuff.
+class ShapeBuilder final : SkNoncopyable {
+public:
+ static sk_sp<sksg::Merge> MergeGeometry(std::vector<sk_sp<sksg::GeometryNode>>&&,
+ sksg::Merge::Mode);
+
+ static sk_sp<sksg::GeometryNode> AttachPathGeometry(const skjson::ObjectValue&,
+ const AnimationBuilder*);
+ static sk_sp<sksg::GeometryNode> AttachRRectGeometry(const skjson::ObjectValue&,
+ const AnimationBuilder*);
+ static sk_sp<sksg::GeometryNode> AttachEllipseGeometry(const skjson::ObjectValue&,
+ const AnimationBuilder*);
+ static sk_sp<sksg::GeometryNode> AttachPolystarGeometry(const skjson::ObjectValue&,
+ const AnimationBuilder*);
+
+ static sk_sp<sksg::PaintNode> AttachGradient(const skjson::ObjectValue&,
+ const AnimationBuilder*);
+ static sk_sp<sksg::PaintNode> AttachColorFill(const skjson::ObjectValue&,
+ const AnimationBuilder*);
+ static sk_sp<sksg::PaintNode> AttachColorStroke(const skjson::ObjectValue&,
+ const AnimationBuilder*);
+ static sk_sp<sksg::PaintNode> AttachGradientFill(const skjson::ObjectValue&,
+ const AnimationBuilder*);
+ static sk_sp<sksg::PaintNode> AttachGradientStroke(const skjson::ObjectValue&,
+ const AnimationBuilder*);
+
+ static std::vector<sk_sp<sksg::GeometryNode>> AttachMergeGeometryEffect(
+ const skjson::ObjectValue&, const AnimationBuilder*,
+ std::vector<sk_sp<sksg::GeometryNode>>&&);
+ static std::vector<sk_sp<sksg::GeometryNode>> AttachTrimGeometryEffect(
+ const skjson::ObjectValue&,
+ const AnimationBuilder*,
+ std::vector<sk_sp<sksg::GeometryNode>>&&);
+ static std::vector<sk_sp<sksg::GeometryNode>> AttachRoundGeometryEffect(
+ const skjson::ObjectValue&, const AnimationBuilder*,
+ std::vector<sk_sp<sksg::GeometryNode>>&&);
+
+ static std::vector<sk_sp<sksg::RenderNode>> AttachRepeaterDrawEffect(
+ const skjson::ObjectValue&,
+ const AnimationBuilder*,
+ std::vector<sk_sp<sksg::RenderNode>>&&);
+};
+
+} // namespace internal
+} // namespace skottie
+
+#endif // SkottieShapeLayer_DEFINED
diff --git a/modules/skottie/src/layers/shapelayer/TrimPaths.cpp b/modules/skottie/src/layers/shapelayer/TrimPaths.cpp
new file mode 100644
index 0000000..a96aecc
--- /dev/null
+++ b/modules/skottie/src/layers/shapelayer/TrimPaths.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "modules/skottie/src/Adapter.h"
+#include "modules/skottie/src/SkottieJson.h"
+#include "modules/skottie/src/SkottiePriv.h"
+#include "modules/skottie/src/SkottieValue.h"
+#include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
+#include "modules/sksg/include/SkSGMerge.h"
+#include "modules/sksg/include/SkSGTrimEffect.h"
+
+#include <vector>
+
+namespace skottie {
+namespace internal {
+
+namespace {
+
+class TrimEffectAdapter final : public DiscardableAdapterBase<TrimEffectAdapter, sksg::TrimEffect> {
+public:
+ TrimEffectAdapter(const skjson::ObjectValue& jtrim,
+ const AnimationBuilder& abuilder,
+ sk_sp<sksg::GeometryNode> child)
+ : INHERITED(sksg::TrimEffect::Make(std::move(child))) {
+ this->bind(abuilder, jtrim["s"], &fStart);
+ this->bind(abuilder, jtrim["e"], &fEnd);
+ this->bind(abuilder, jtrim["o"], &fOffset);
+ }
+
+private:
+ void onSync() override {
+ // BM semantics: start/end are percentages, offset is "degrees" (?!).
+ const auto start = fStart / 100,
+ end = fEnd / 100,
+ offset = fOffset / 360;
+
+ auto startT = SkTMin(start, end) + offset,
+ stopT = SkTMax(start, end) + offset;
+ auto mode = SkTrimPathEffect::Mode::kNormal;
+
+ if (stopT - startT < 1) {
+ startT -= SkScalarFloorToScalar(startT);
+ stopT -= SkScalarFloorToScalar(stopT);
+
+ if (startT > stopT) {
+ using std::swap;
+ swap(startT, stopT);
+ mode = SkTrimPathEffect::Mode::kInverted;
+ }
+ } else {
+ startT = 0;
+ stopT = 1;
+ }
+
+ this->node()->setStart(startT);
+ this->node()->setStop(stopT);
+ this->node()->setMode(mode);
+ }
+
+ ScalarValue fStart = 0,
+ fEnd = 100,
+ fOffset = 0;
+
+ using INHERITED = DiscardableAdapterBase<TrimEffectAdapter, sksg::TrimEffect>;
+};
+
+} // namespace
+
+std::vector<sk_sp<sksg::GeometryNode>> ShapeBuilder::AttachTrimGeometryEffect(
+ const skjson::ObjectValue& jtrim,
+ const AnimationBuilder* abuilder,
+ std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
+
+ enum class Mode {
+ kParallel, // "m": 1 (Trim Multiple Shapes: Simultaneously)
+ kSerial, // "m": 2 (Trim Multiple Shapes: Individually)
+ } gModes[] = { Mode::kParallel, Mode::kSerial};
+
+ const auto mode = gModes[SkTMin<size_t>(ParseDefault<size_t>(jtrim["m"], 1) - 1,
+ SK_ARRAY_COUNT(gModes) - 1)];
+
+ std::vector<sk_sp<sksg::GeometryNode>> inputs;
+ if (mode == Mode::kSerial) {
+ inputs.push_back(ShapeBuilder::MergeGeometry(std::move(geos), sksg::Merge::Mode::kMerge));
+ } else {
+ inputs = std::move(geos);
+ }
+
+ std::vector<sk_sp<sksg::GeometryNode>> trimmed;
+ trimmed.reserve(inputs.size());
+
+ for (const auto& i : inputs) {
+ trimmed.push_back(
+ abuilder->attachDiscardableAdapter<TrimEffectAdapter, sk_sp<sksg::TrimEffect>>
+ (jtrim, *abuilder, i));
+ }
+
+ return trimmed;
+}
+
+} // namespace internal
+} // namespace skottie
diff --git a/modules/skottie/src/text/TextAdapter.h b/modules/skottie/src/text/TextAdapter.h
index 550d1ab..501a479 100644
--- a/modules/skottie/src/text/TextAdapter.h
+++ b/modules/skottie/src/text/TextAdapter.h
@@ -33,7 +33,7 @@
~TextAdapter() override;
- const sk_sp<sksg::Group>& renderNode() const { return fRoot; }
+ const sk_sp<sksg::Group>& node() const { return fRoot; }
const TextValue& getText() const { return fText.fCurrentValue; }
void setText(const TextValue&);
diff --git a/public.bzl b/public.bzl
index 729d99e..45d43e7 100644
--- a/public.bzl
+++ b/public.bzl
@@ -738,6 +738,8 @@
"modules/skottie/src/effects/*.h",
"modules/skottie/src/layers/*.cpp",
"modules/skottie/src/layers/*.h",
+ "modules/skottie/src/layers/shapelayer/*.cpp",
+ "modules/skottie/src/layers/shapelayer/*.h",
"modules/skottie/src/text/*.cpp",
"modules/skottie/src/text/*.h",
],