diff options
author | Florin Malita <fmalita@chromium.org> | 2018-05-26 09:49:28 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-05-27 02:21:33 +0000 |
commit | 3d856bdeee7fae2ff36cdb6a9807c588fc030eb1 (patch) | |
tree | b26aa52b2d2f8877bdc7a7c647e4a34fd3e96f35 /modules/skottie/src/SkottieAnimator.cpp | |
parent | d8eb7b6b12d5b155214031d4aa4d8f582ebb91a1 (diff) |
[skottie] Relocate to modules/skottie
TBR=
Change-Id: I218d251ca56578a3a7fd4fb86cba9abdc10fb3bd
Reviewed-on: https://skia-review.googlesource.com/130322
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
Diffstat (limited to 'modules/skottie/src/SkottieAnimator.cpp')
-rw-r--r-- | modules/skottie/src/SkottieAnimator.cpp | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/modules/skottie/src/SkottieAnimator.cpp b/modules/skottie/src/SkottieAnimator.cpp new file mode 100644 index 0000000000..4554409761 --- /dev/null +++ b/modules/skottie/src/SkottieAnimator.cpp @@ -0,0 +1,374 @@ +/* + * 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 |