diff options
Diffstat (limited to 'experimental/skottie/SkottieAnimator.cpp')
-rw-r--r-- | experimental/skottie/SkottieAnimator.cpp | 374 |
1 files changed, 0 insertions, 374 deletions
diff --git a/experimental/skottie/SkottieAnimator.cpp b/experimental/skottie/SkottieAnimator.cpp deleted file mode 100644 index 4554409761..0000000000 --- a/experimental/skottie/SkottieAnimator.cpp +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#include "SkottieAnimator.h" - -#include "SkCubicMap.h" -#include "SkottieJson.h" -#include "SkottieValue.h" -#include "SkString.h" -#include "SkTArray.h" - -#include <memory> - -namespace skottie { - -namespace { - -#define LOG SkDebugf - -bool LogFail(const json::ValueRef& json, const char* msg) { - const auto dump = json.toString(); - LOG("!! %s: %s\n", msg, dump.c_str()); - return false; -} - -class KeyframeAnimatorBase : public sksg::Animator { -public: - int count() const { return fRecs.count(); } - -protected: - KeyframeAnimatorBase() = default; - - struct KeyframeRec { - float t0, t1; - int vidx0, vidx1, // v0/v1 indices - cmidx; // cubic map index - - bool contains(float t) const { return t0 <= t && t <= t1; } - bool isConstant() const { return vidx0 == vidx1; } - bool isValid() const { - SkASSERT(t0 <= t1); - // Constant frames don't need/use t1 and vidx1. - return t0 < t1 || this->isConstant(); - } - }; - - const KeyframeRec& frame(float t) { - if (!fCachedRec || !fCachedRec->contains(t)) { - fCachedRec = findFrame(t); - } - return *fCachedRec; - } - - float localT(const KeyframeRec& rec, float t) const { - SkASSERT(rec.isValid()); - SkASSERT(!rec.isConstant()); - SkASSERT(t > rec.t0 && t < rec.t1); - - auto lt = (t - rec.t0) / (rec.t1 - rec.t0); - - return rec.cmidx < 0 - ? lt - : SkTPin(fCubicMaps[rec.cmidx].computeYFromX(lt), 0.0f, 1.0f); - } - - virtual int parseValue(const json::ValueRef&) = 0; - - void parseKeyFrames(const json::ValueRef& jframes) { - if (!jframes.isArray()) - return; - - for (const json::ValueRef jframe : jframes) { - float t0; - if (!jframe["t"].to(&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 < 0) - continue; - - // Defaults for constant frames. - int vidx1 = vidx0, cmidx = -1; - - if (!jframe["h"].toDefault(false)) { - // Regular frame, requires an end value. - vidx1 = this->parseValue(jframe["e"]); - if (vidx1 < 0) - continue; - - // default is linear lerp - static constexpr SkPoint kDefaultC0 = { 0, 0 }, - kDefaultC1 = { 1, 1 }; - const auto c0 = jframe["i"].toDefault(kDefaultC0), - c1 = jframe["o"].toDefault(kDefaultC1); - - if (c0 != kDefaultC0 || c1 != kDefaultC1) { - // TODO: is it worth de-duping these? - cmidx = fCubicMaps.count(); - 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 valid t1 for the last frame, discard it. - if (!fRecs.empty() && !fRecs.back().isValid()) { - fRecs.pop_back(); - } - - SkASSERT(fRecs.empty() || fRecs.back().isValid()); - } - -private: - const KeyframeRec* findFrame(float t) const { - SkASSERT(!fRecs.empty()); - - auto f0 = &fRecs.front(), - f1 = &fRecs.back(); - - 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; - } - - SkTArray<KeyframeRec> fRecs; - SkTArray<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::ValueRef& jframes, - std::function<void(const T&)>&& apply) { - std::unique_ptr<KeyframeAnimator> animator(new KeyframeAnimator(jframes, std::move(apply))); - if (!animator->count()) - 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::ValueRef& jframes, - std::function<void(const T&)>&& apply) - : fApplyFunc(std::move(apply)) { - this->parseKeyFrames(jframes); - } - - int parseValue(const json::ValueRef& jv) override { - T val; - if (!jv.to(&val) || (!fVs.empty() && - ValueTraits<T>::Cardinality(val) != ValueTraits<T>::Cardinality(fVs.back()))) { - return -1; - } - - // TODO: full deduping? - if (fVs.empty() || val != fVs.back()) { - fVs.push_back(std::move(val)); - } - return fVs.count() - 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 = ValueTraits<T>::Lerp(v0, v1, lt); - } - } - - const std::function<void(const T&)> fApplyFunc; - SkTArray<T> fVs; - - - using INHERITED = KeyframeAnimatorBase; -}; - -template <typename T> -static inline bool BindPropertyImpl(const json::ValueRef& jprop, - sksg::AnimatorList* animators, - std::function<void(const T&)>&& apply, - const T* noop = nullptr) { - 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 (!jpropA.toDefault(false)) { - T val; - if (jpropK.to<T>(&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; -} - -class SplitPointAnimator final : public sksg::Animator { -public: - static std::unique_ptr<SplitPointAnimator> Make(const json::ValueRef& jprop, - std::function<void(const VectorValue&)>&& apply, - const VectorValue*) { - if (!jprop.isObject()) - return nullptr; - - std::unique_ptr<SplitPointAnimator> split_animator( - new SplitPointAnimator(std::move(apply))); - - // This raw pointer is captured in lambdas below. But the lambdas are owned by - // the object itself, so the scope is bound to the life time of the object. - auto* split_animator_ptr = split_animator.get(); - - if (!BindPropertyImpl<ScalarValue>(jprop["x"], &split_animator->fAnimators, - [split_animator_ptr](const ScalarValue& x) { split_animator_ptr->setX(x); }) || - !BindPropertyImpl<ScalarValue>(jprop["y"], &split_animator->fAnimators, - [split_animator_ptr](const ScalarValue& y) { split_animator_ptr->setY(y); })) { - LogFail(jprop, "Could not parse split property"); - return nullptr; - } - - if (split_animator->fAnimators.empty()) { - // Static split property, no need to hold on to the split animator. - return nullptr; - } - - return split_animator; - } - - void onTick(float t) override { - for (const auto& animator : fAnimators) { - animator->tick(t); - } - - const VectorValue vec = { fX, fY }; - fApplyFunc(vec); - } - - void setX(const ScalarValue& x) { fX = x; } - void setY(const ScalarValue& y) { fY = y; } - -private: - explicit SplitPointAnimator(std::function<void(const VectorValue&)>&& apply) - : fApplyFunc(std::move(apply)) {} - - const std::function<void(const VectorValue&)> fApplyFunc; - sksg::AnimatorList fAnimators; - - ScalarValue fX = 0, - fY = 0; - - using INHERITED = sksg::Animator; -}; - -bool BindSplitPositionProperty(const json::ValueRef& jprop, - sksg::AnimatorList* animators, - std::function<void(const VectorValue&)>&& apply, - const VectorValue* noop) { - if (auto split_animator = SplitPointAnimator::Make(jprop, std::move(apply), noop)) { - animators->push_back(std::unique_ptr<sksg::Animator>(split_animator.release())); - return true; - } - - return false; -} - -} // namespace - -template <> -bool BindProperty(const json::ValueRef& jprop, - sksg::AnimatorList* animators, - std::function<void(const ScalarValue&)>&& apply, - const ScalarValue* noop) { - return BindPropertyImpl(jprop, animators, std::move(apply), noop); -} - -template <> -bool BindProperty(const json::ValueRef& jprop, - sksg::AnimatorList* animators, - std::function<void(const VectorValue&)>&& apply, - const VectorValue* noop) { - return jprop["s"].toDefault<bool>(false) - ? BindSplitPositionProperty(jprop, animators, std::move(apply), noop) - : BindPropertyImpl(jprop, animators, std::move(apply), noop); -} - -template <> -bool BindProperty(const json::ValueRef& jprop, - sksg::AnimatorList* animators, - std::function<void(const ShapeValue&)>&& apply, - const ShapeValue* noop) { - return BindPropertyImpl(jprop, animators, std::move(apply), noop); -} - -} // namespace skottie |