[skottie] KeyframeAnimator builders

Split the keyframe parsing logic into separate builder helpers.

No functional impact, this will facilitate some upcoming changes.

TBR=

Change-Id: I62a02a0ad90ddef572208f8714f22ae0a0b97023
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/276003
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
Florin Malita 2020-03-09 12:53:16 -04:00 committed by Skia Commit-Bot
parent 20baaee0f7
commit ea3ddc9aef

View File

@ -48,18 +48,7 @@ void AnimatablePropertyContainer::shrink_to_fit() {
namespace {
class KeyframeAnimatorBase : public sksg::Animator {
public:
bool isConstant() const {
SkASSERT(!fKFs.empty());
// parseKeyFrames() ensures we only keep a single frame for constant properties.
return fKFs.size() == 1;
}
protected:
KeyframeAnimatorBase() = default;
struct Keyframe {
// We can store scalar values inline; other types are stored externally,
// and we track them by index.
struct Value {
@ -75,47 +64,23 @@ protected:
bool operator!=(const Value& other) const { return !((*this) == other); }
};
struct LERPInfo {
float weight; // vrec0/vrec1 weight [0..1]
Value vrec0, vrec1;
float t;
Value v;
uint32_t mapping; // Encodes the value interpolation in [KFRec_n .. KFRec_n+1):
// 0 -> constant
// 1 -> linear
// n -> cubic: cubic_mappers[n-2]
bool isConstant() const { return vrec0 == vrec1; }
};
static constexpr uint32_t kConstantMapping = 0;
static constexpr uint32_t kLinearMapping = 1;
static constexpr uint32_t kCubicIndexOffset = 2;
};
// Main entry point: |t| -> LERPInfo
LERPInfo getLERPInfo(float t) const {
SkASSERT(!fKFs.empty());
class KeyframeBuilderBase : SkNoncopyable {
public:
virtual ~KeyframeBuilderBase() = default;
if (t <= fKFs.front().t) {
// Constant/clamped segment.
return { 0, fKFs.front().v, fKFs.front().v };
}
if (t >= fKFs.back().t) {
// Constant/clamped segment.
return { 0, fKFs.back().v, fKFs.back().v };
}
// Cache the current segment (most queries have good locality).
if (!fCurrentSegment.contains(t)) {
fCurrentSegment = this->find_segment(t);
}
SkASSERT(fCurrentSegment.contains(t));
if (fCurrentSegment.kf0->mapping == KFRec::kConstantMapping) {
// Constant/hold segment.
return { 0, fCurrentSegment.kf0->v, fCurrentSegment.kf0->v };
}
return {
this->compute_weight(fCurrentSegment, t),
fCurrentSegment.kf0->v,
fCurrentSegment.kf1->v,
};
}
virtual bool parseValue(const AnimationBuilder&, const skjson::Value&, Value*) = 0;
bool parseKeyFrames(const AnimationBuilder& abuilder, const skjson::ArrayValue& jkfs) {
bool parseKeyframes(const AnimationBuilder& abuilder, const skjson::ArrayValue& jkfs) {
// Keyframe format:
//
// [ // array of
@ -150,34 +115,7 @@ protected:
// Note: the legacy format contains duplicates, as normal frames are contiguous:
// frame(n).e == frame(n+1).s
// Tracks previous cubic map parameters (for deduping).
SkPoint prev_c0 = { 0, 0 },
prev_c1 = { 0, 0 };
const auto parse_mapping = [&](const skjson::ObjectValue& jkf) {
if (ParseDefault(jkf["h"], false)) {
return KFRec::kConstantMapping;
}
SkPoint c0, c1;
if (!Parse(jkf["o"], &c0) ||
!Parse(jkf["i"], &c1) ||
SkCubicMap::IsLinear(c0, c1)) {
return KFRec::kLinearMapping;
}
// De-dupe sequential cubic mappers.
if (c0 != prev_c0 || c1 != prev_c1 || fCMs.empty()) {
fCMs.emplace_back(c0, c1);
prev_c0 = c0;
prev_c1 = c1;
}
SkASSERT(!fCMs.empty());
return SkToU32(fCMs.size()) - 1 + KFRec::kCubicIndexOffset;
};
const auto parse_value = [&](const skjson::ObjectValue& jkf, size_t i, Value* v) {
const auto parse_value = [&](const skjson::ObjectValue& jkf, size_t i, Keyframe::Value* v) {
auto parsed = this->parseValue(abuilder, jkf["s"], v);
// A missing value is only OK for the last legacy KF
@ -206,7 +144,7 @@ protected:
return false;
}
Value v;
Keyframe::Value v;
if (!parse_value(*jkf, i, &v)) {
return false;
}
@ -221,11 +159,11 @@ protected:
// We can power-reduce the mapping of repeated values (implicitly constant).
if (v == prev_kf.v) {
prev_kf.mapping = KFRec::kConstantMapping;
prev_kf.mapping = Keyframe::kConstantMapping;
}
}
fKFs.push_back({t, v, parse_mapping(*jkf)});
fKFs.push_back({t, v, this->parseMapping(*jkf)});
constant_value = constant_value && (v == fKFs.front().v);
}
@ -249,25 +187,98 @@ protected:
return true;
}
private:
// We store one KFRec for each AE/Lottie keyframe.
struct KFRec {
float t;
Value v;
uint32_t mapping; // Encodes the value interpolation in [KFRec_n .. KFRec_n+1):
// 0 -> constant
// 1 -> linear
// n -> cubic: fCMs[n-2]
protected:
virtual bool parseValue(const AnimationBuilder&, const skjson::Value&, Keyframe::Value*) = 0;
static constexpr uint32_t kConstantMapping = 0;
static constexpr uint32_t kLinearMapping = 1;
static constexpr uint32_t kCubicIndexOffset = 2;
std::vector<Keyframe> fKFs; // Keyframe records, one per AE/Lottie keyframe.
std::vector<SkCubicMap> fCMs; // Optional cubic mappers (Bezier interpolation).
private:
uint32_t parseMapping(const skjson::ObjectValue& jkf) {
if (ParseDefault(jkf["h"], false)) {
return Keyframe::kConstantMapping;
}
SkPoint c0, c1;
if (!Parse(jkf["o"], &c0) ||
!Parse(jkf["i"], &c1) ||
SkCubicMap::IsLinear(c0, c1)) {
return Keyframe::kLinearMapping;
}
// De-dupe sequential cubic mappers.
if (c0 != prev_c0 || c1 != prev_c1 || fCMs.empty()) {
fCMs.emplace_back(c0, c1);
prev_c0 = c0;
prev_c1 = c1;
}
SkASSERT(!fCMs.empty());
return SkToU32(fCMs.size()) - 1 + Keyframe::kCubicIndexOffset;
}
// Track previous cubic map parameters (for deduping).
SkPoint prev_c0 = { 0, 0 },
prev_c1 = { 0, 0 };
};
class KeyframeAnimatorBase : public sksg::Animator {
public:
bool isConstant() const {
SkASSERT(!fKFs.empty());
// parseKeyFrames() ensures we only keep a single frame for constant properties.
return fKFs.size() == 1;
}
protected:
KeyframeAnimatorBase(std::vector<Keyframe> kfs, std::vector<SkCubicMap> cms)
: fKFs(std::move(kfs))
, fCMs(std::move(cms)) {}
struct LERPInfo {
float weight; // vrec0/vrec1 weight [0..1]
Keyframe::Value vrec0, vrec1;
bool isConstant() const { return vrec0 == vrec1; }
};
// Main entry point: |t| -> LERPInfo
LERPInfo getLERPInfo(float t) const {
SkASSERT(!fKFs.empty());
if (t <= fKFs.front().t) {
// Constant/clamped segment.
return { 0, fKFs.front().v, fKFs.front().v };
}
if (t >= fKFs.back().t) {
// Constant/clamped segment.
return { 0, fKFs.back().v, fKFs.back().v };
}
// Cache the current segment (most queries have good locality).
if (!fCurrentSegment.contains(t)) {
fCurrentSegment = this->find_segment(t);
}
SkASSERT(fCurrentSegment.contains(t));
if (fCurrentSegment.kf0->mapping == Keyframe::kConstantMapping) {
// Constant/hold segment.
return { 0, fCurrentSegment.kf0->v, fCurrentSegment.kf0->v };
}
return {
this->compute_weight(fCurrentSegment, t),
fCurrentSegment.kf0->v,
fCurrentSegment.kf1->v,
};
}
private:
// Two sequential KFRecs determine how the value varies within [kf0 .. kf1)
struct KFSegment {
const KFRec* kf0;
const KFRec* kf1;
const Keyframe* kf0;
const Keyframe* kf1;
bool contains(float t) const {
SkASSERT(!!kf0 == !!kf1);
@ -311,62 +322,74 @@ private:
auto w = (t - seg.kf0->t) / (seg.kf1->t - seg.kf0->t);
// Optional cubic mapper.
if (seg.kf0->mapping >= KFRec::kCubicIndexOffset) {
if (seg.kf0->mapping >= Keyframe::kCubicIndexOffset) {
SkASSERT(seg.kf0->v != seg.kf1->v);
const auto mapper_index = SkToSizeT(seg.kf0->mapping - KFRec::kCubicIndexOffset);
const auto mapper_index = SkToSizeT(seg.kf0->mapping - Keyframe::kCubicIndexOffset);
w = fCMs[mapper_index].computeYFromX(w);
}
return w;
}
std::vector<KFRec> fKFs; // Keyframe records, one per AE/Lottie keyframe.
std::vector<SkCubicMap> fCMs; // Optional cubic mappers (Bezier interpolation).
mutable KFSegment fCurrentSegment = { nullptr, nullptr }; // Cached segment.
const std::vector<Keyframe> fKFs; // Keyframe records, one per AE/Lottie keyframe.
const std::vector<SkCubicMap> fCMs; // Optional cubic mappers (Bezier interpolation).
mutable KFSegment fCurrentSegment = { nullptr, nullptr }; // Cached segment.
};
// Stores generic Ts in dedicated storage, and uses indices to track in keyframes.
template <typename T>
class KeyframeAnimator final : public KeyframeAnimatorBase {
public:
static sk_sp<KeyframeAnimator> Make(const AnimationBuilder& abuilder,
const skjson::ArrayValue* jkfs,
T* target_value) {
if (!jkfs || jkfs->size() < 1) {
return nullptr;
class Builder final : public KeyframeBuilderBase {
public:
sk_sp<KeyframeAnimator> make(const AnimationBuilder& abuilder,
const skjson::ArrayValue* jkfs,
T* target_value) {
if (!jkfs || jkfs->size() < 1) {
return nullptr;
}
fValues.reserve(jkfs->size());
if (!this->parseKeyframes(abuilder, *jkfs)) {
return nullptr;
}
fValues.shrink_to_fit();
return sk_sp<KeyframeAnimator>(new KeyframeAnimator(std::move(fKFs),
std::move(fCMs),
std::move(fValues),
target_value));
}
sk_sp<KeyframeAnimator> animator(new KeyframeAnimator(target_value));
private:
bool parseValue(const AnimationBuilder& abuilder,
const skjson::Value& jv,
Keyframe::Value* v) override {
T val;
if (!ValueTraits<T>::FromJSON(jv, &abuilder, &val) ||
(!fValues.empty() && !ValueTraits<T>::CanLerp(val, fValues.back()))) {
return false;
}
animator->fValues.reserve(jkfs->size());
if (!animator->parseKeyFrames(abuilder, *jkfs)) {
return nullptr;
// TODO: full deduping?
if (fValues.empty() || val != fValues.back()) {
fValues.push_back(std::move(val));
}
v->idx = SkToU32(fValues.size() - 1);
return true;
}
animator->fValues.shrink_to_fit();
return animator;
}
std::vector<T> fValues;
};
private:
explicit KeyframeAnimator(T* target_value)
: fTarget(target_value) {}
bool parseValue(const AnimationBuilder& abuilder, const skjson::Value& jv, Value* v) override {
T val;
if (!ValueTraits<T>::FromJSON(jv, &abuilder, &val) ||
(!fValues.empty() && !ValueTraits<T>::CanLerp(val, fValues.back()))) {
return false;
}
// TODO: full deduping?
if (fValues.empty() || val != fValues.back()) {
fValues.push_back(std::move(val));
}
v->idx = SkToU32(fValues.size() - 1);
return true;
}
explicit KeyframeAnimator(std::vector<Keyframe> kfs, std::vector<SkCubicMap> cms,
std::vector<T> vs, T* target_value)
: INHERITED(std::move(kfs), std::move(cms))
, fValues(std::move(vs))
, fTarget(target_value) {}
void onTick(float t) override {
const auto& lerp_info = this->getLERPInfo(t);
@ -381,34 +404,47 @@ private:
}
}
std::vector<T> fValues;
T* fTarget;
const std::vector<T> fValues;
T* fTarget;
using INHERITED = KeyframeAnimatorBase;
};
// Scalar specialization: stores scalar values (floats) inline in keyframes.
class ScalarKeyframeAnimator final : public KeyframeAnimatorBase {
public:
static sk_sp<ScalarKeyframeAnimator> Make(const AnimationBuilder& abuilder,
const skjson::ArrayValue* jkfs,
ScalarValue* target_value) {
if (!jkfs || jkfs->size() < 1) {
return nullptr;
class Builder final : public KeyframeBuilderBase {
public:
sk_sp<ScalarKeyframeAnimator> make(const AnimationBuilder& abuilder,
const skjson::ArrayValue* jkfs,
ScalarValue* target_value) {
if (!jkfs || jkfs->size() < 1) {
return nullptr;
}
if (!this->parseKeyframes(abuilder, *jkfs)) {
return nullptr;
}
return sk_sp<ScalarKeyframeAnimator>(new ScalarKeyframeAnimator(std::move(fKFs),
std::move(fCMs),
target_value));
}
sk_sp<ScalarKeyframeAnimator> animator(new ScalarKeyframeAnimator(target_value));
return animator->parseKeyFrames(abuilder, *jkfs)
? animator
: nullptr;
}
private:
bool parseValue(const AnimationBuilder&,
const skjson::Value& jv,
Keyframe::Value* v) override {
return Parse(jv, &v->flt);
}
};
private:
explicit ScalarKeyframeAnimator(ScalarValue* target_value)
: fTarget(target_value) {}
bool parseValue(const AnimationBuilder&, const skjson::Value& jv, Value* v) override {
return Parse(jv, &v->flt);
}
explicit ScalarKeyframeAnimator(std::vector<Keyframe> kfs,
std::vector<SkCubicMap> cms,
ScalarValue* target_value)
: INHERITED(std::move(kfs), std::move(cms))
, fTarget(target_value) {}
void onTick(float t) override {
const auto& lerp_info = this->getLERPInfo(t);
@ -418,19 +454,21 @@ private:
}
ScalarValue* fTarget;
using INHERITED = KeyframeAnimatorBase;
};
template <typename T>
auto make_animator(const AnimationBuilder& abuilder,
const skjson::ArrayValue* jkfs,
T* target_value) {
return KeyframeAnimator<T>::Make(abuilder, jkfs, target_value);
return typename KeyframeAnimator<T>::Builder().make(abuilder, jkfs, target_value);
}
auto make_animator(const AnimationBuilder& abuilder,
const skjson::ArrayValue* jkfs,
ScalarValue* target_value) {
return ScalarKeyframeAnimator::Make(abuilder, jkfs, target_value);
return ScalarKeyframeAnimator::Builder().make(abuilder, jkfs, target_value);
}
template <typename T>