skia2/experimental/skottie/SkottieAnimator.cpp
Florin Malita 20880784fb [skottie] Json cleanup pass
Assorted Json tweaks:

  - more defensive internal object access
  - drop unneeded isObject checks
  - drop unneeded namespace
  - restrict the iterator to arrays

TBR=
Change-Id: I02f1c5d84c429cf5206bc2a0a7843097b92bac94
Reviewed-on: https://skia-review.googlesource.com/126930
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
2018-05-09 16:11:45 +00:00

375 lines
11 KiB
C++

/*
* 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