From fc807c885b3cce6252cc1d057098d96f8e14eadc Mon Sep 17 00:00:00 2001 From: Florin Malita Date: Thu, 25 Jan 2018 22:35:09 -0500 Subject: [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 Commit-Queue: Florin Malita --- experimental/skottie/Skottie.cpp | 101 +++------ experimental/skottie/SkottieAnimator.cpp | 350 ++++++++++++++++++++++++++----- experimental/skottie/SkottieAnimator.h | 183 +--------------- 3 files changed, 337 insertions(+), 297 deletions(-) (limited to 'experimental') 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 -bool BindProperty(const Json::Value& jprop, AttachContext* ctx, - typename Animator::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(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::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 AttachMatrix(const Json::Value& t, AttachContext* ctx, sk_sp parentMatrix) { if (!t.isObject()) @@ -109,27 +72,27 @@ sk_sp AttachMatrix(const Json::Value& t, AttachContext* ctx, auto matrix = sksg::Matrix::Make(SkMatrix::I(), std::move(parentMatrix)); auto composite = sk_make_sp(matrix); - auto anchor_attached = BindProperty(t["a"], ctx, + auto anchor_attached = BindProperty(t["a"], &ctx->fAnimators, [composite](const VectorValue& a) { composite->setAnchorPoint(ValueTraits::As(a)); }); - auto position_attached = BindProperty(t["p"], ctx, + auto position_attached = BindProperty(t["p"], &ctx->fAnimators, [composite](const VectorValue& p) { composite->setPosition(ValueTraits::As(p)); }); - auto scale_attached = BindProperty(t["s"], ctx, + auto scale_attached = BindProperty(t["s"], &ctx->fAnimators, [composite](const VectorValue& s) { composite->setScale(ValueTraits::As(s)); }); - auto rotation_attached = BindProperty(t["r"], ctx, + auto rotation_attached = BindProperty(t["r"], &ctx->fAnimators, [composite](const ScalarValue& r) { composite->setRotation(r); }); - auto skew_attached = BindProperty(t["sk"], ctx, + auto skew_attached = BindProperty(t["sk"], &ctx->fAnimators, [composite](const ScalarValue& sk) { composite->setSkew(sk); }); - auto skewaxis_attached = BindProperty(t["sa"], ctx, + auto skewaxis_attached = BindProperty(t["sa"], &ctx->fAnimators, [composite](const ScalarValue& sa) { composite->setSkewAxis(sa); }); @@ -163,7 +126,7 @@ sk_sp AttachOpacity(const Json::Value& jtransform, AttachConte } auto opacityNode = sksg::OpacityEffect::Make(childNode); - BindProperty(opacity, ctx, + BindProperty(opacity, &ctx->fAnimators, [opacityNode](const ScalarValue& o) { // BM opacity is [0..100] opacityNode->setOpacity(o * 0.01f); @@ -176,8 +139,8 @@ sk_sp AttachComposition(const Json::Value&, AttachContext* ctx sk_sp AttachPath(const Json::Value& jpath, AttachContext* ctx) { auto path_node = sksg::Path::Make(); - return BindProperty(jpath, ctx, - [path_node](const ShapeValue& p) { path_node->setPath(p); }) + return BindProperty(jpath, &ctx->fAnimators, + [path_node](const ShapeValue& p) { path_node->setPath(p); }) ? path_node : nullptr; } @@ -194,15 +157,15 @@ sk_sp AttachRRectGeometry(const Json::Value& jrect, AttachCo auto rect_node = sksg::RRect::Make(); auto composite = sk_make_sp(rect_node); - auto p_attached = BindProperty(jrect["p"], ctx, + auto p_attached = BindProperty(jrect["p"], &ctx->fAnimators, [composite](const VectorValue& p) { composite->setPosition(ValueTraits::As(p)); }); - auto s_attached = BindProperty(jrect["s"], ctx, + auto s_attached = BindProperty(jrect["s"], &ctx->fAnimators, [composite](const VectorValue& s) { composite->setSize(ValueTraits::As(s)); }); - auto r_attached = BindProperty(jrect["r"], ctx, + auto r_attached = BindProperty(jrect["r"], &ctx->fAnimators, [composite](const ScalarValue& r) { composite->setRadius(SkSize::Make(r, r)); }); @@ -222,11 +185,11 @@ sk_sp AttachEllipseGeometry(const Json::Value& jellipse, Att auto rect_node = sksg::RRect::Make(); auto composite = sk_make_sp(rect_node); - auto p_attached = BindProperty(jellipse["p"], ctx, + auto p_attached = BindProperty(jellipse["p"], &ctx->fAnimators, [composite](const VectorValue& p) { composite->setPosition(ValueTraits::As(p)); }); - auto s_attached = BindProperty(jellipse["s"], ctx, + auto s_attached = BindProperty(jellipse["s"], &ctx->fAnimators, [composite](const VectorValue& s) { const auto sz = ValueTraits::As(s); composite->setSize(sz); @@ -259,31 +222,31 @@ sk_sp AttachPolystarGeometry(const Json::Value& jstar, Attac auto path_node = sksg::Path::Make(); auto composite = sk_make_sp(path_node, gTypes[type]); - BindProperty(jstar["p"], ctx, + BindProperty(jstar["p"], &ctx->fAnimators, [composite](const VectorValue& p) { composite->setPosition(ValueTraits::As(p)); }); - BindProperty(jstar["pt"], ctx, + BindProperty(jstar["pt"], &ctx->fAnimators, [composite](const ScalarValue& pt) { composite->setPointCount(pt); }); - BindProperty(jstar["ir"], ctx, + BindProperty(jstar["ir"], &ctx->fAnimators, [composite](const ScalarValue& ir) { composite->setInnerRadius(ir); }); - BindProperty(jstar["or"], ctx, + BindProperty(jstar["or"], &ctx->fAnimators, [composite](const ScalarValue& otr) { composite->setOuterRadius(otr); }); - BindProperty(jstar["is"], ctx, + BindProperty(jstar["is"], &ctx->fAnimators, [composite](const ScalarValue& is) { composite->setInnerRoundness(is); }); - BindProperty(jstar["os"], ctx, + BindProperty(jstar["os"], &ctx->fAnimators, [composite](const ScalarValue& os) { composite->setOuterRoundness(os); }); - BindProperty(jstar["r"], ctx, + BindProperty(jstar["r"], &ctx->fAnimators, [composite](const ScalarValue& r) { composite->setRotation(r); }); @@ -295,7 +258,7 @@ sk_sp AttachColor(const Json::Value& obj, AttachContext* ctx) { SkASSERT(obj.isObject()); auto color_node = sksg::Color::Make(SK_ColorBLACK); - auto color_attached = BindProperty(obj["c"], ctx, + auto color_attached = BindProperty(obj["c"], &ctx->fAnimators, [color_node](const VectorValue& c) { color_node->setColor(ValueTraits::As(c)); }); @@ -329,15 +292,15 @@ sk_sp AttachGradient(const Json::Value& obj, AttachContext* ctx) gradient_node = std::move(radial_node); } - BindProperty(stops["k"], ctx, + BindProperty(stops["k"], &ctx->fAnimators, [composite](const VectorValue& stops) { composite->setColorStops(stops); }); - BindProperty(obj["s"], ctx, + BindProperty(obj["s"], &ctx->fAnimators, [composite](const VectorValue& s) { composite->setStartPoint(ValueTraits::As(s)); }); - BindProperty(obj["e"], ctx, + BindProperty(obj["e"], &ctx->fAnimators, [composite](const VectorValue& e) { composite->setEndPoint(ValueTraits::As(e)); }); @@ -350,7 +313,7 @@ sk_sp AttachPaint(const Json::Value& jpaint, AttachContext* ctx if (paint_node) { paint_node->setAntiAlias(true); - BindProperty(jpaint["o"], ctx, + BindProperty(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 AttachStroke(const Json::Value& jstroke, AttachContext* c stroke_node->setStyle(SkPaint::kStroke_Style); - auto width_attached = BindProperty(jstroke["w"], ctx, + auto width_attached = BindProperty(jstroke["w"], &ctx->fAnimators, [stroke_node](const ScalarValue& w) { stroke_node->setStrokeWidth(w); }); @@ -465,15 +428,15 @@ std::vector> AttachTrimGeometryEffect( for (const auto& i : inputs) { const auto trim = sksg::TrimEffect::Make(i); trimmed.push_back(trim); - BindProperty(jtrim["s"], ctx, + BindProperty(jtrim["s"], &ctx->fAnimators, [trim](const ScalarValue& s) { trim->setStart(s * 0.01f); }); - BindProperty(jtrim["e"], ctx, + BindProperty(jtrim["e"], &ctx->fAnimators, [trim](const ScalarValue& e) { trim->setEnd(e * 0.01f); }); - BindProperty(jtrim["o"], ctx, + BindProperty(jtrim["o"], &ctx->fAnimators, [trim](const ScalarValue& o) { trim->setOffset(o / 360); }); @@ -929,7 +892,7 @@ sk_sp AttachMask(const Json::Value& jmask, auto mask_paint = sksg::Color::Make(SK_ColorBLACK); mask_paint->setBlendMode(MaskBlendMode(mode.c_str()[0])); - BindProperty(m["o"], ctx, + BindProperty(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 + 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 +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::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(); - // 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::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::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 fRecs; + std::vector fCubicMaps; + const KeyframeRec* fCachedRec = nullptr; + + using INHERITED = sksg::Animator; +}; + +template +class KeyframeAnimator final : public KeyframeAnimatorBase { +public: + static std::unique_ptr Make(const Json::Value& jframes, + std::function&& apply) { + std::unique_ptr 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&& 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::Cardinality(val) != ValueTraits::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 fApplyFunc; + std::vector fVs; + + + using INHERITED = KeyframeAnimatorBase; +}; + +template +static inline bool BindPropertyImpl(const Json::Value& jprop, + sksg::Scene::AnimatorList* animators, + std::function&& 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(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::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::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&& 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&& 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&& 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 -#include -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 fCubicMap; - - // Start/end times. - float fT0 = 0, - fT1 = 0; - - bool fHold = false; -}; - -// Describes a keyframe interpolation interval (v0@t0) -> (v1@t1). -template -class KeyframeInterval final : public KeyframeIntervalBase { -public: - bool parse(const Json::Value& k, KeyframeInterval* prev) { - SkASSERT(k.isObject()); - - if (!this->INHERITED::parse(k, prev) || - !Parse(k["s"], &fV0)) { - return false; - } - - if (!this->isHold() && - (!Parse(k["e"], &fV1) || - ValueTraits::Cardinality(fV0) != ValueTraits::Cardinality(fV1))) { - return false; - } - - return !prev || ValueTraits::Cardinality(fV0) == ValueTraits::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 -class Animator final : public sksg::Animator { -public: - using ApplyFuncT = std::function; +namespace Json { class Value; } - static std::unique_ptr Make(const Json::Value& jframes, ApplyFuncT&& applyFunc) { - if (!jframes.isArray()) - return nullptr; - - SkTArray, true> frames(jframes.size()); - - KeyframeInterval* prev_frame = nullptr; - for (const auto& jframe : jframes) { - if (!jframe.isObject()) - continue; - - KeyframeInterval 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(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, true>&& frames, ApplyFuncT&& applyFunc) - : fFrames(std::move(frames)) - , fFunc(std::move(applyFunc)) {} - - const KeyframeInterval* findFrame(float t) const; - - const SkTArray, true> fFrames; - const ApplyFuncT fFunc; - const KeyframeInterval* 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 -const KeyframeInterval* Animator::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&&, + const T* noop = nullptr); } // namespace skottie -- cgit v1.2.3