aboutsummaryrefslogtreecommitdiffhomepage
path: root/experimental/skottie/SkottieAnimator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'experimental/skottie/SkottieAnimator.cpp')
-rw-r--r--experimental/skottie/SkottieAnimator.cpp350
1 files changed, 297 insertions, 53 deletions
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