aboutsummaryrefslogtreecommitdiffhomepage
path: root/experimental/skottie
diff options
context:
space:
mode:
authorGravatar Florin Malita <fmalita@chromium.org>2018-01-25 22:35:09 -0500
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2018-01-26 13:13:24 +0000
commitfc807c885b3cce6252cc1d057098d96f8e14eadc (patch)
tree0cd2093335fa7d8b2fe7a1122f90c2c1c87449e0 /experimental/skottie
parentfb33355c36fbc24f5ed6a89a4459e9e59fed0990 (diff)
[skottie] Refactor animators
Separate storage for values, cubic maps, repeated values deduplication. TBR= Change-Id: Ibfbcea91ef1d7b1da937b4af44079e7612d410cb Reviewed-on: https://skia-review.googlesource.com/99981 Reviewed-by: Florin Malita <fmalita@chromium.org> Commit-Queue: Florin Malita <fmalita@chromium.org>
Diffstat (limited to 'experimental/skottie')
-rw-r--r--experimental/skottie/Skottie.cpp101
-rw-r--r--experimental/skottie/SkottieAnimator.cpp350
-rw-r--r--experimental/skottie/SkottieAnimator.h183
3 files changed, 337 insertions, 297 deletions
diff --git a/experimental/skottie/Skottie.cpp b/experimental/skottie/Skottie.cpp
index 53f1c88037..7e56106fc5 100644
--- a/experimental/skottie/Skottie.cpp
+++ b/experimental/skottie/Skottie.cpp
@@ -8,6 +8,7 @@
#include "Skottie.h"
#include "SkCanvas.h"
+#include "SkJSONCPP.h"
#include "SkottieAnimator.h"
#include "SkottieParser.h"
#include "SkottieProperties.h"
@@ -64,44 +65,6 @@ bool LogFail(const Json::Value& json, const char* msg) {
return false;
}
-// This is the workhorse for binding properties: depending on whether the property is animated,
-// it will either apply immediately or instantiate and attach a keyframe animator.
-template <typename T>
-bool BindProperty(const Json::Value& jprop, AttachContext* ctx,
- typename Animator<T>::ApplyFuncT&& apply) {
- if (!jprop.isObject())
- return false;
-
- const auto& jpropA = jprop["a"];
- const auto& jpropK = jprop["k"];
-
- // Older Json versions don't have an "a" animation marker.
- // For those, we attempt to parse both ways.
- if (!ParseDefault(jpropA, false)) {
- T val;
- if (Parse<T>(jpropK, &val)) {
- // Static property.
- apply(val);
- return true;
- }
-
- if (!jpropA.isNull()) {
- return LogFail(jprop, "Could not parse (explicit) static property");
- }
- }
-
- // Keyframe property.
- auto animator = Animator<T>::Make(jpropK, std::move(apply));
-
- if (!animator) {
- return LogFail(jprop, "Could not parse keyframed property");
- }
-
- ctx->fAnimators.push_back(std::move(animator));
-
- return true;
-}
-
sk_sp<sksg::Matrix> AttachMatrix(const Json::Value& t, AttachContext* ctx,
sk_sp<sksg::Matrix> parentMatrix) {
if (!t.isObject())
@@ -109,27 +72,27 @@ sk_sp<sksg::Matrix> AttachMatrix(const Json::Value& t, AttachContext* ctx,
auto matrix = sksg::Matrix::Make(SkMatrix::I(), std::move(parentMatrix));
auto composite = sk_make_sp<CompositeTransform>(matrix);
- auto anchor_attached = BindProperty<VectorValue>(t["a"], ctx,
+ auto anchor_attached = BindProperty<VectorValue>(t["a"], &ctx->fAnimators,
[composite](const VectorValue& a) {
composite->setAnchorPoint(ValueTraits<VectorValue>::As<SkPoint>(a));
});
- auto position_attached = BindProperty<VectorValue>(t["p"], ctx,
+ auto position_attached = BindProperty<VectorValue>(t["p"], &ctx->fAnimators,
[composite](const VectorValue& p) {
composite->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
});
- auto scale_attached = BindProperty<VectorValue>(t["s"], ctx,
+ auto scale_attached = BindProperty<VectorValue>(t["s"], &ctx->fAnimators,
[composite](const VectorValue& s) {
composite->setScale(ValueTraits<VectorValue>::As<SkVector>(s));
});
- auto rotation_attached = BindProperty<ScalarValue>(t["r"], ctx,
+ auto rotation_attached = BindProperty<ScalarValue>(t["r"], &ctx->fAnimators,
[composite](const ScalarValue& r) {
composite->setRotation(r);
});
- auto skew_attached = BindProperty<ScalarValue>(t["sk"], ctx,
+ auto skew_attached = BindProperty<ScalarValue>(t["sk"], &ctx->fAnimators,
[composite](const ScalarValue& sk) {
composite->setSkew(sk);
});
- auto skewaxis_attached = BindProperty<ScalarValue>(t["sa"], ctx,
+ auto skewaxis_attached = BindProperty<ScalarValue>(t["sa"], &ctx->fAnimators,
[composite](const ScalarValue& sa) {
composite->setSkewAxis(sa);
});
@@ -163,7 +126,7 @@ sk_sp<sksg::RenderNode> AttachOpacity(const Json::Value& jtransform, AttachConte
}
auto opacityNode = sksg::OpacityEffect::Make(childNode);
- BindProperty<ScalarValue>(opacity, ctx,
+ BindProperty<ScalarValue>(opacity, &ctx->fAnimators,
[opacityNode](const ScalarValue& o) {
// BM opacity is [0..100]
opacityNode->setOpacity(o * 0.01f);
@@ -176,8 +139,8 @@ sk_sp<sksg::RenderNode> AttachComposition(const Json::Value&, AttachContext* ctx
sk_sp<sksg::Path> AttachPath(const Json::Value& jpath, AttachContext* ctx) {
auto path_node = sksg::Path::Make();
- return BindProperty<ShapeValue>(jpath, ctx,
- [path_node](const ShapeValue& p) { path_node->setPath(p); })
+ return BindProperty<ShapeValue>(jpath, &ctx->fAnimators,
+ [path_node](const ShapeValue& p) { path_node->setPath(p); })
? path_node
: nullptr;
}
@@ -194,15 +157,15 @@ sk_sp<sksg::GeometryNode> AttachRRectGeometry(const Json::Value& jrect, AttachCo
auto rect_node = sksg::RRect::Make();
auto composite = sk_make_sp<CompositeRRect>(rect_node);
- auto p_attached = BindProperty<VectorValue>(jrect["p"], ctx,
+ auto p_attached = BindProperty<VectorValue>(jrect["p"], &ctx->fAnimators,
[composite](const VectorValue& p) {
composite->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
});
- auto s_attached = BindProperty<VectorValue>(jrect["s"], ctx,
+ auto s_attached = BindProperty<VectorValue>(jrect["s"], &ctx->fAnimators,
[composite](const VectorValue& s) {
composite->setSize(ValueTraits<VectorValue>::As<SkSize>(s));
});
- auto r_attached = BindProperty<ScalarValue>(jrect["r"], ctx,
+ auto r_attached = BindProperty<ScalarValue>(jrect["r"], &ctx->fAnimators,
[composite](const ScalarValue& r) {
composite->setRadius(SkSize::Make(r, r));
});
@@ -222,11 +185,11 @@ sk_sp<sksg::GeometryNode> AttachEllipseGeometry(const Json::Value& jellipse, Att
auto rect_node = sksg::RRect::Make();
auto composite = sk_make_sp<CompositeRRect>(rect_node);
- auto p_attached = BindProperty<VectorValue>(jellipse["p"], ctx,
+ auto p_attached = BindProperty<VectorValue>(jellipse["p"], &ctx->fAnimators,
[composite](const VectorValue& p) {
composite->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
});
- auto s_attached = BindProperty<VectorValue>(jellipse["s"], ctx,
+ auto s_attached = BindProperty<VectorValue>(jellipse["s"], &ctx->fAnimators,
[composite](const VectorValue& s) {
const auto sz = ValueTraits<VectorValue>::As<SkSize>(s);
composite->setSize(sz);
@@ -259,31 +222,31 @@ sk_sp<sksg::GeometryNode> AttachPolystarGeometry(const Json::Value& jstar, Attac
auto path_node = sksg::Path::Make();
auto composite = sk_make_sp<CompositePolyStar>(path_node, gTypes[type]);
- BindProperty<VectorValue>(jstar["p"], ctx,
+ BindProperty<VectorValue>(jstar["p"], &ctx->fAnimators,
[composite](const VectorValue& p) {
composite->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
});
- BindProperty<ScalarValue>(jstar["pt"], ctx,
+ BindProperty<ScalarValue>(jstar["pt"], &ctx->fAnimators,
[composite](const ScalarValue& pt) {
composite->setPointCount(pt);
});
- BindProperty<ScalarValue>(jstar["ir"], ctx,
+ BindProperty<ScalarValue>(jstar["ir"], &ctx->fAnimators,
[composite](const ScalarValue& ir) {
composite->setInnerRadius(ir);
});
- BindProperty<ScalarValue>(jstar["or"], ctx,
+ BindProperty<ScalarValue>(jstar["or"], &ctx->fAnimators,
[composite](const ScalarValue& otr) {
composite->setOuterRadius(otr);
});
- BindProperty<ScalarValue>(jstar["is"], ctx,
+ BindProperty<ScalarValue>(jstar["is"], &ctx->fAnimators,
[composite](const ScalarValue& is) {
composite->setInnerRoundness(is);
});
- BindProperty<ScalarValue>(jstar["os"], ctx,
+ BindProperty<ScalarValue>(jstar["os"], &ctx->fAnimators,
[composite](const ScalarValue& os) {
composite->setOuterRoundness(os);
});
- BindProperty<ScalarValue>(jstar["r"], ctx,
+ BindProperty<ScalarValue>(jstar["r"], &ctx->fAnimators,
[composite](const ScalarValue& r) {
composite->setRotation(r);
});
@@ -295,7 +258,7 @@ sk_sp<sksg::Color> AttachColor(const Json::Value& obj, AttachContext* ctx) {
SkASSERT(obj.isObject());
auto color_node = sksg::Color::Make(SK_ColorBLACK);
- auto color_attached = BindProperty<VectorValue>(obj["c"], ctx,
+ auto color_attached = BindProperty<VectorValue>(obj["c"], &ctx->fAnimators,
[color_node](const VectorValue& c) {
color_node->setColor(ValueTraits<VectorValue>::As<SkColor>(c));
});
@@ -329,15 +292,15 @@ sk_sp<sksg::Gradient> AttachGradient(const Json::Value& obj, AttachContext* ctx)
gradient_node = std::move(radial_node);
}
- BindProperty<VectorValue>(stops["k"], ctx,
+ BindProperty<VectorValue>(stops["k"], &ctx->fAnimators,
[composite](const VectorValue& stops) {
composite->setColorStops(stops);
});
- BindProperty<VectorValue>(obj["s"], ctx,
+ BindProperty<VectorValue>(obj["s"], &ctx->fAnimators,
[composite](const VectorValue& s) {
composite->setStartPoint(ValueTraits<VectorValue>::As<SkPoint>(s));
});
- BindProperty<VectorValue>(obj["e"], ctx,
+ BindProperty<VectorValue>(obj["e"], &ctx->fAnimators,
[composite](const VectorValue& e) {
composite->setEndPoint(ValueTraits<VectorValue>::As<SkPoint>(e));
});
@@ -350,7 +313,7 @@ sk_sp<sksg::PaintNode> AttachPaint(const Json::Value& jpaint, AttachContext* ctx
if (paint_node) {
paint_node->setAntiAlias(true);
- BindProperty<ScalarValue>(jpaint["o"], ctx,
+ BindProperty<ScalarValue>(jpaint["o"], &ctx->fAnimators,
[paint_node](const ScalarValue& o) {
// BM opacity is [0..100]
paint_node->setOpacity(o * 0.01f);
@@ -369,7 +332,7 @@ sk_sp<sksg::PaintNode> AttachStroke(const Json::Value& jstroke, AttachContext* c
stroke_node->setStyle(SkPaint::kStroke_Style);
- auto width_attached = BindProperty<ScalarValue>(jstroke["w"], ctx,
+ auto width_attached = BindProperty<ScalarValue>(jstroke["w"], &ctx->fAnimators,
[stroke_node](const ScalarValue& w) {
stroke_node->setStrokeWidth(w);
});
@@ -465,15 +428,15 @@ std::vector<sk_sp<sksg::GeometryNode>> AttachTrimGeometryEffect(
for (const auto& i : inputs) {
const auto trim = sksg::TrimEffect::Make(i);
trimmed.push_back(trim);
- BindProperty<ScalarValue>(jtrim["s"], ctx,
+ BindProperty<ScalarValue>(jtrim["s"], &ctx->fAnimators,
[trim](const ScalarValue& s) {
trim->setStart(s * 0.01f);
});
- BindProperty<ScalarValue>(jtrim["e"], ctx,
+ BindProperty<ScalarValue>(jtrim["e"], &ctx->fAnimators,
[trim](const ScalarValue& e) {
trim->setEnd(e * 0.01f);
});
- BindProperty<ScalarValue>(jtrim["o"], ctx,
+ BindProperty<ScalarValue>(jtrim["o"], &ctx->fAnimators,
[trim](const ScalarValue& o) {
trim->setOffset(o / 360);
});
@@ -929,7 +892,7 @@ sk_sp<sksg::RenderNode> AttachMask(const Json::Value& jmask,
auto mask_paint = sksg::Color::Make(SK_ColorBLACK);
mask_paint->setBlendMode(MaskBlendMode(mode.c_str()[0]));
- BindProperty<ScalarValue>(m["o"], ctx,
+ BindProperty<ScalarValue>(m["o"], &ctx->fAnimators,
[mask_paint](const ScalarValue& o) { mask_paint->setOpacity(o * 0.01f); });
mask_group->addChild(sksg::Draw::Make(std::move(mask_path), std::move(mask_paint)));
diff --git a/experimental/skottie/SkottieAnimator.cpp b/experimental/skottie/SkottieAnimator.cpp
index 5190341f27..fd0705ab28 100644
--- a/experimental/skottie/SkottieAnimator.cpp
+++ b/experimental/skottie/SkottieAnimator.cpp
@@ -7,91 +7,335 @@
#include "SkottieAnimator.h"
+#include "SkCubicMap.h"
+#include "SkJSONCPP.h"
+#include "SkottieProperties.h"
+#include "SkottieParser.h"
+
+#include <memory>
+
namespace skottie {
namespace {
-SkScalar lerp_scalar(float v0, float v1, float t) {
+#define LOG SkDebugf
+
+bool LogFail(const Json::Value& json, const char* msg) {
+ const auto dump = json.toStyledString();
+ LOG("!! %s: %s", msg, dump.c_str());
+ return false;
+}
+
+template <typename T>
+static inline T lerp(const T&, const T&, float);
+
+template <>
+ScalarValue lerp(const ScalarValue& v0, const ScalarValue& v1, float t) {
SkASSERT(t >= 0 && t <= 1);
return v0 * (1 - t) + v1 * t;
}
-} // namespace
+template <>
+VectorValue lerp(const VectorValue& v0, const VectorValue& v1, float t) {
+ SkASSERT(v0.size() == v1.size());
-bool KeyframeIntervalBase::parse(const Json::Value& k, KeyframeIntervalBase* prev) {
- SkASSERT(k.isObject());
+ VectorValue v;
+ v.reserve(v0.size());
- fT0 = fT1 = ParseDefault(k["t"], SK_ScalarMin);
- if (fT0 == SK_ScalarMin) {
- return false;
+ for (size_t i = 0; i < v0.size(); ++i) {
+ v.push_back(lerp(v0[i], v1[i], t));
}
- if (prev) {
- if (prev->fT1 >= fT0) {
- SkDebugf("!! Dropping out-of-order key frame (t: %f < t: %f)\n", fT0, prev->fT1);
- return false;
+ return v;
+}
+
+template <>
+ShapeValue lerp(const ShapeValue& v0, const ShapeValue& v1, float t) {
+ SkASSERT(t >= 0 && t <= 1);
+ SkASSERT(v1.isInterpolatable(v0));
+
+ ShapeValue v;
+ SkAssertResult(v1.interpolate(v0, t, &v));
+ v.setIsVolatile(true);
+
+ return v;
+}
+
+class KeyframeAnimatorBase : public sksg::Animator {
+public:
+ size_t size() const { return fRecs.size(); }
+
+protected:
+ KeyframeAnimatorBase() = default;
+
+ struct KeyframeRec {
+ float t0, t1;
+ size_t vidx0, vidx1, cmidx;
+
+ bool contains(float t) const { return t0 <= t && t <= t1; }
+ bool isConstant() const { return vidx1 == kInvalidIndex; }
+ bool isValid() const { return t0 < t1 || this->isConstant(); }
+ };
+
+ static constexpr size_t kInvalidIndex = std::numeric_limits<size_t>::max();
+
+ const KeyframeRec& frame(float t) {
+ if (!fCachedRec || !fCachedRec->contains(t)) {
+ fCachedRec = findFrame(t);
}
- // Back-fill t1 in prev interval. Note: we do this even if we end up discarding
- // the current interval (to support "t"-only final frames).
- prev->fT1 = fT0;
+ return *fCachedRec;
}
- fHold = ParseDefault(k["h"], false);
+ float localT(const KeyframeRec& rec, float t) const {
+ SkASSERT(rec.isValid());
+ SkASSERT(!rec.isConstant());
+ SkASSERT(t > rec.t0 && t < rec.t1);
- if (!fHold) {
- // default is linear lerp
- static constexpr SkPoint kDefaultC0 = { 0, 0 },
- kDefaultC1 = { 1, 1 };
- const auto c0 = ParseDefault(k["i"], kDefaultC0),
- c1 = ParseDefault(k["o"], kDefaultC1);
+ auto lt = (t -rec.t0) / (rec.t1 - rec.t0);
- if (c0 != kDefaultC0 || c1 != kDefaultC1) {
- fCubicMap = skstd::make_unique<SkCubicMap>();
- // TODO: why do we have to plug these inverted?
- fCubicMap->setPts(c1, c0);
+ return rec.cmidx != kInvalidIndex
+ ? fCubicMaps[rec.cmidx].computeYFromX(lt)
+ : lt;
+ }
+
+ virtual size_t parseValue(const Json::Value&) = 0;
+
+ void parseKeyFrames(const Json::Value& jframes) {
+ if (!jframes.isArray())
+ return;
+
+ for (const auto& jframe : jframes) {
+ if (!jframe.isObject())
+ continue;
+
+ float t0;
+ if (!Parse(jframe["t"], &t0)) {
+ continue;
+ }
+
+ if (!fRecs.empty()) {
+ if (fRecs.back().t1 >= t0) {
+ LOG("!! Ignoring out-of-order key frame (t:%f < t:%f)\n", t0, fRecs.back().t1);
+ continue;
+ }
+ // Back-fill t1 in prev interval. Note: we do this even if we end up discarding
+ // the current interval (to support "t"-only final frames).
+ fRecs.back().t1 = t0;
+ }
+
+ const auto vidx0 = this->parseValue(jframe["s"]);
+ if (vidx0 == kInvalidIndex) {
+ continue;
+ }
+
+ // Defaults for "hold" frames.
+ size_t vidx1 = kInvalidIndex, cmidx = kInvalidIndex;
+
+ if (!ParseDefault(jframe["h"], false)) {
+ // Regular frame, requires and end value.
+ vidx1 = this->parseValue(jframe["e"]);
+ if (vidx1 == kInvalidIndex) {
+ continue;
+ }
+
+ // default is linear lerp
+ static constexpr SkPoint kDefaultC0 = { 0, 0 },
+ kDefaultC1 = { 1, 1 };
+ const auto c0 = ParseDefault(jframe["i"], kDefaultC0),
+ c1 = ParseDefault(jframe["o"], kDefaultC1);
+
+ if (c0 != kDefaultC0 || c1 != kDefaultC1) {
+ // TODO: is it worth de-duping these?
+ cmidx = fCubicMaps.size();
+ fCubicMaps.emplace_back();
+ // TODO: why do we have to plug these inverted?
+ fCubicMaps.back().setPts(c1, c0);
+ }
+ }
+
+ fRecs.push_back({t0, t0, vidx0, vidx1, cmidx });
}
+
+ // If we couldn't determine a t1 for the last frame, discard it.
+ if (!fRecs.empty() && !fRecs.back().isValid()) {
+ fRecs.pop_back();
+ }
+
+ SkASSERT(fRecs.empty() || fRecs.back().isValid());
}
- return true;
-}
+private:
+ const KeyframeRec* findFrame(float t) const {
+ SkASSERT(!fRecs.empty());
-float KeyframeIntervalBase::localT(float t) const {
- SkASSERT(this->isValid());
- SkASSERT(!this->isHold());
- SkASSERT(t > fT0 && t < fT1);
+ auto f0 = &fRecs.front(),
+ f1 = &fRecs.back();
- auto lt = (t - fT0) / (fT1 - fT0);
+ SkASSERT(f0->isValid());
+ SkASSERT(f1->isValid());
- return fCubicMap ? fCubicMap->computeYFromX(lt) : lt;
-}
+ if (t < f0->t0) {
+ return f0;
+ }
-template <>
-void KeyframeInterval<ScalarValue>::lerp(float t, ScalarValue* v) const {
- const auto lt = this->localT(t);
- *v = lerp_scalar(fV0, fV1, lt);
-}
+ if (t > f1->t1) {
+ return f1;
+ }
-template <>
-void KeyframeInterval<VectorValue>::lerp(float t, VectorValue* v) const {
- SkASSERT(fV0.size() == fV1.size());
- SkASSERT(v->size() == 0);
+ while (f0 != f1) {
+ SkASSERT(f0 < f1);
+ SkASSERT(t >= f0->t0 && t <= f1->t1);
- const auto lt = this->localT(t);
+ const auto f = f0 + (f1 - f0) / 2;
+ SkASSERT(f->isValid());
- v->reserve(fV0.size());
- for (size_t i = 0; i < fV0.size(); ++i) {
- v->push_back(lerp_scalar(fV0[i], fV1[i], lt));
+ if (t > f->t1) {
+ f0 = f + 1;
+ } else {
+ f1 = f;
+ }
+ }
+
+ SkASSERT(f0 == f1);
+ SkASSERT(f0->contains(t));
+
+ return f0;
+ }
+
+ std::vector<KeyframeRec> fRecs;
+ std::vector<SkCubicMap> fCubicMaps;
+ const KeyframeRec* fCachedRec = nullptr;
+
+ using INHERITED = sksg::Animator;
+};
+
+template <typename T>
+class KeyframeAnimator final : public KeyframeAnimatorBase {
+public:
+ static std::unique_ptr<KeyframeAnimator> Make(const Json::Value& jframes,
+ std::function<void(const T&)>&& apply) {
+ std::unique_ptr<KeyframeAnimator> animator(new KeyframeAnimator(jframes, std::move(apply)));
+ if (!animator->size())
+ return nullptr;
+
+ return animator;
+ }
+
+protected:
+ void onTick(float t) override {
+ T val;
+ this->eval(this->frame(t), t, &val);
+
+ fApplyFunc(val);
+ }
+
+private:
+ KeyframeAnimator(const Json::Value& jframes,
+ std::function<void(const T&)>&& apply)
+ : fApplyFunc(std::move(apply)) {
+ this->parseKeyFrames(jframes);
+ }
+
+ size_t parseValue(const Json::Value& jv) override {
+ T val;
+ if (!Parse(jv, &val) || (!fVs.empty() &&
+ ValueTraits<T>::Cardinality(val) != ValueTraits<T>::Cardinality(fVs.back()))) {
+ return kInvalidIndex;
+ }
+
+ // TODO: full deduping?
+ if (fVs.empty() || val != fVs.back()) {
+ fVs.push_back(std::move(val));
+ }
+ return fVs.size() - 1;
+ }
+
+ void eval(const KeyframeRec& rec, float t, T* v) const {
+ SkASSERT(rec.isValid());
+ if (rec.isConstant() || t <= rec.t0) {
+ *v = fVs[rec.vidx0];
+ } else if (t >= rec.t1) {
+ *v = fVs[rec.vidx1];
+ } else {
+ const auto lt = this->localT(rec, t);
+ const auto& v0 = fVs[rec.vidx0];
+ const auto& v1 = fVs[rec.vidx1];
+ *v = lerp(v0, v1, lt);
+ }
+ }
+
+ const std::function<void(const T&)> fApplyFunc;
+ std::vector<T> fVs;
+
+
+ using INHERITED = KeyframeAnimatorBase;
+};
+
+template <typename T>
+static inline bool BindPropertyImpl(const Json::Value& jprop,
+ sksg::Scene::AnimatorList* animators,
+ std::function<void(const T&)>&& apply,
+ const T* noop) {
+ if (!jprop.isObject())
+ return false;
+
+ const auto& jpropA = jprop["a"];
+ const auto& jpropK = jprop["k"];
+
+ // Older Json versions don't have an "a" animation marker.
+ // For those, we attempt to parse both ways.
+ if (!ParseDefault(jpropA, false)) {
+ T val;
+ if (Parse<T>(jpropK, &val)) {
+ // Static property.
+ if (noop && val == *noop)
+ return false;
+
+ apply(val);
+ return true;
+ }
+
+ if (!jpropA.isNull()) {
+ return LogFail(jprop, "Could not parse (explicit) static property");
+ }
}
+
+ // Keyframe property.
+ auto animator = KeyframeAnimator<T>::Make(jpropK, std::move(apply));
+
+ if (!animator) {
+ return LogFail(jprop, "Could not parse keyframed property");
+ }
+
+ animators->push_back(std::move(animator));
+
+ return true;
}
+} // namespace
+
template <>
-void KeyframeInterval<ShapeValue>::lerp(float t, ShapeValue* v) const {
- SkASSERT(fV0.countVerbs() == fV1.countVerbs());
- SkASSERT(v->isEmpty());
+bool BindProperty(const Json::Value& jprop,
+ sksg::Scene::AnimatorList* animators,
+ std::function<void(const ScalarValue&)>&& apply,
+ const ScalarValue* noop) {
+ return BindPropertyImpl(jprop, animators, std::move(apply), noop);
+}
- const auto lt = this->localT(t);
- SkAssertResult(fV1.interpolate(fV0, lt, v));
- v->setIsVolatile(true);
+template <>
+bool BindProperty(const Json::Value& jprop,
+ sksg::Scene::AnimatorList* animators,
+ std::function<void(const VectorValue&)>&& apply,
+ const VectorValue* noop) {
+ return BindPropertyImpl(jprop, animators, std::move(apply), noop);
+}
+
+template <>
+bool BindProperty(const Json::Value& jprop,
+ sksg::Scene::AnimatorList* animators,
+ std::function<void(const ShapeValue&)>&& apply,
+ const ShapeValue* noop) {
+ return BindPropertyImpl(jprop, animators, std::move(apply), noop);
}
} // namespace skottie
diff --git a/experimental/skottie/SkottieAnimator.h b/experimental/skottie/SkottieAnimator.h
index 6e44ebe873..c32b4f8207 100644
--- a/experimental/skottie/SkottieAnimator.h
+++ b/experimental/skottie/SkottieAnimator.h
@@ -8,188 +8,21 @@
#ifndef SkottieAnimator_DEFINED
#define SkottieAnimator_DEFINED
-#include "SkCubicMap.h"
-#include "SkJSONCPP.h"
-#include "SkMakeUnique.h"
-#include "SkottieParser.h"
-#include "SkottieProperties.h"
#include "SkSGScene.h"
-#include "SkTArray.h"
-#include "SkTypes.h"
#include <functional>
-#include <memory>
-namespace skottie {
-
-class KeyframeIntervalBase : public SkNoncopyable {
-public:
- KeyframeIntervalBase() = default;
- KeyframeIntervalBase(KeyframeIntervalBase&&) = default;
- KeyframeIntervalBase& operator=(KeyframeIntervalBase&&) = default;
-
- float t0() const { return fT0; }
- float t1() const { return fT1; }
-
- bool isValid() const { return fT0 < fT1 || fHold; }
- bool contains(float t) const { return t >= fT0 && t <= fT1; }
-
-protected:
- // Parse the current interval AND back-fill prev interval t1.
- bool parse(const Json::Value&, KeyframeIntervalBase* prev);
-
- // Computes a "local" t (relative to [fT0..fT1]), and mapped
- // through the cubic (if applicable).
- float localT(float t) const;
-
- bool isHold() const { return fHold; }
-
-private:
- // Initialized for non-linear interpolation.
- std::unique_ptr<SkCubicMap> fCubicMap;
-
- // Start/end times.
- float fT0 = 0,
- fT1 = 0;
-
- bool fHold = false;
-};
-
-// Describes a keyframe interpolation interval (v0@t0) -> (v1@t1).
-template <typename T>
-class KeyframeInterval final : public KeyframeIntervalBase {
-public:
- bool parse(const Json::Value& k, KeyframeInterval* prev) {
- SkASSERT(k.isObject());
-
- if (!this->INHERITED::parse(k, prev) ||
- !Parse<T>(k["s"], &fV0)) {
- return false;
- }
-
- if (!this->isHold() &&
- (!Parse<T>(k["e"], &fV1) ||
- ValueTraits<T>::Cardinality(fV0) != ValueTraits<T>::Cardinality(fV1))) {
- return false;
- }
-
- return !prev || ValueTraits<T>::Cardinality(fV0) == ValueTraits<T>::Cardinality(prev->fV0);
- }
-
- void eval(float t, T* v) const {
- if (this->isHold() || t <= this->t0()) {
- *v = fV0;
- } else if (t >= this->t1()) {
- *v = fV1;
- } else {
- this->lerp(t, v);
- }
- }
-
-private:
- void lerp(float t, T*) const;
-
- // Start/end values.
- T fV0,
- fV1;
-
- using INHERITED = KeyframeIntervalBase;
-};
-
-// Binds an animated/keyframed property to a node attribute setter.
-template <typename T>
-class Animator final : public sksg::Animator {
-public:
- using ApplyFuncT = std::function<void(const T&)>;
+namespace Json { class Value; }
- static std::unique_ptr<Animator> Make(const Json::Value& jframes, ApplyFuncT&& applyFunc) {
- if (!jframes.isArray())
- return nullptr;
-
- SkTArray<KeyframeInterval<T>, true> frames(jframes.size());
-
- KeyframeInterval<T>* prev_frame = nullptr;
- for (const auto& jframe : jframes) {
- if (!jframe.isObject())
- continue;
-
- KeyframeInterval<T> frame;
- if (frame.parse(jframe, prev_frame)) {
- frames.push_back(std::move(frame));
- prev_frame = &frames.back();
- }
- }
-
- // If we couldn't determine a t1 for the last frame, discard it.
- if (!frames.empty() && !frames.back().isValid()) {
- frames.pop_back();
- }
-
- return frames.empty()
- ? nullptr
- : std::unique_ptr<Animator>(new Animator(std::move(frames), std::move(applyFunc)));
- }
-
- void onTick(float t) override {
- if (!fCurrentFrame || !fCurrentFrame->contains(t)) {
- fCurrentFrame = this->findFrame(t);
- }
-
- T val;
- fCurrentFrame->eval(t, &val);
-
- fFunc(val);
- }
-
-private:
- Animator(SkTArray<KeyframeInterval<T>, true>&& frames, ApplyFuncT&& applyFunc)
- : fFrames(std::move(frames))
- , fFunc(std::move(applyFunc)) {}
-
- const KeyframeInterval<T>* findFrame(float t) const;
-
- const SkTArray<KeyframeInterval<T>, true> fFrames;
- const ApplyFuncT fFunc;
- const KeyframeInterval<T>* fCurrentFrame = nullptr;
-};
+namespace skottie {
+// This is the workhorse for property binding: depending on whether the property is animated,
+// it will either apply immediately or instantiate and attach a keyframe animator.
template <typename T>
-const KeyframeInterval<T>* Animator<T>::findFrame(float t) const {
- SkASSERT(!fFrames.empty());
-
- auto f0 = fFrames.begin(),
- f1 = fFrames.end() - 1;
-
- SkASSERT(f0->isValid());
- SkASSERT(f1->isValid());
-
- if (t < f0->t0()) {
- return f0;
- }
-
- if (t > f1->t1()) {
- return f1;
- }
-
- while (f0 != f1) {
- SkASSERT(f0 < f1);
- SkASSERT(t >= f0->t0() && t <= f1->t1());
-
- const auto f = f0 + (f1 - f0) / 2;
- SkASSERT(f->isValid());
-
- if (t > f->t1()) {
- f0 = f + 1;
- } else {
- f1 = f;
- }
- }
-
- SkASSERT(f0 == f1);
- SkASSERT(f0->contains(t));
-
- return f0;
-}
+bool BindProperty(const Json::Value&,
+ sksg::Scene::AnimatorList*,
+ std::function<void(const T&)>&&,
+ const T* noop = nullptr);
} // namespace skottie