[skottie] Refactor property animators
Currently, property animators use lambda captures (std::function<>) to push values to adapters and then to the scene graph. Some downsides: * complex lambda captures are expensive in terms of object code size * adapters with multiple animated properties don't synchronize/quiesce: each individual property tick triggers a SG synchronization, possibly with inconsistent state (as animator running order is unspecified) * there is no enforced scoping, resulting in fragile constructs when SG fragments are discarded This CL introduces a simplified and more robust animator pattern: * property animators are scoped to explicit containers * instead of capturing arbitrary value functors, animators only capture a pointer to the target value Some implementation details: * keyframe/interpolation logic is pretty much unchanged (just relocated) * introduced AnimatablePropertyContainer - a base class for animatable adapters * legacy binding functions are refactored based on the new mechanism (they now/transitionally inject adapter objects) * converted a handful of effects, to exercise trivial refactoring patterns * converted the text animator goo, to exercise non-trivial refactoring: - detecting value changes is now trickier (no more lambda magic) - value adjustments must be hoisted into adapter logic (no more lambda magic) - all dependent animated values (selectors, etc) must be scoped to the text adapter to avoid lifetime issues TBR= Change-Id: Ia5821982f251de0de58fd3f87812219ff7fcc726 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/263938 Commit-Queue: Florin Malita <fmalita@chromium.org> Reviewed-by: Mike Reed <reed@google.com>
This commit is contained in:
parent
a8fa49016c
commit
d5c42c8c03
@ -13,6 +13,8 @@ skia_skottie_public = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
skia_skottie_sources = [
|
skia_skottie_sources = [
|
||||||
|
"$_src/Animator.cpp",
|
||||||
|
"$_src/Animator.h",
|
||||||
"$_src/Camera.cpp",
|
"$_src/Camera.cpp",
|
||||||
"$_src/Camera.h",
|
"$_src/Camera.h",
|
||||||
"$_src/Composition.cpp",
|
"$_src/Composition.cpp",
|
||||||
|
429
modules/skottie/src/Animator.cpp
Normal file
429
modules/skottie/src/Animator.cpp
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Google Inc.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by a BSD-style license that can be
|
||||||
|
* found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "modules/skottie/src/Animator.h"
|
||||||
|
|
||||||
|
#include "include/core/SkCubicMap.h"
|
||||||
|
#include "modules/skottie/src/SkottieJson.h"
|
||||||
|
#include "modules/skottie/src/SkottiePriv.h"
|
||||||
|
#include "modules/skottie/src/SkottieValue.h"
|
||||||
|
#include "modules/skottie/src/text/TextValue.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace skottie {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
void AnimatablePropertyContainer::onTick(float t) {
|
||||||
|
for (const auto& animator : fAnimators) {
|
||||||
|
animator->tick(t);
|
||||||
|
}
|
||||||
|
this->onSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class KeyframeAnimatorBase : public sksg::Animator {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEmpty() const { return fRecs.empty(); }
|
||||||
|
|
||||||
|
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
|
||||||
|
: fCubicMaps[SkToSizeT(rec.cmidx)].computeYFromX(lt);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int parseValue(const AnimationBuilder&, const skjson::Value&) = 0;
|
||||||
|
|
||||||
|
void parseKeyFrames(const AnimationBuilder& abuilder, const skjson::ArrayValue& jframes) {
|
||||||
|
// Logically, a keyframe is defined as a (t0, t1, v0, v1) tuple: a given value
|
||||||
|
// is interpolated in the [v0..v1] interval over the [t0..t1] time span.
|
||||||
|
//
|
||||||
|
// There are three interestingly-different keyframe formats handled here.
|
||||||
|
//
|
||||||
|
// 1) Legacy keyframe format
|
||||||
|
//
|
||||||
|
// - normal keyframes specify t0 ("t"), v0 ("s") and v1 ("e")
|
||||||
|
// - last frame only specifies a t0
|
||||||
|
// - t1[frame] == t0[frame + 1]
|
||||||
|
// - the last entry (where we cannot determine t1) is ignored
|
||||||
|
//
|
||||||
|
// 2) Regular (new) keyframe format
|
||||||
|
//
|
||||||
|
// - all keyframes specify t0 ("t") and v0 ("s")
|
||||||
|
// - t1[frame] == t0[frame + 1]
|
||||||
|
// - v1[frame] == v0[frame + 1]
|
||||||
|
// - the last entry (where we cannot determine t1/v1) is ignored
|
||||||
|
//
|
||||||
|
// 3) Text value keyframe format
|
||||||
|
//
|
||||||
|
// - similar to case #2, all keyframes specify t0 & v0
|
||||||
|
// - unlike case #2, all keyframes are assumed to be constant (v1 == v0),
|
||||||
|
// and the last frame is not discarded (its t1 is assumed -> inf)
|
||||||
|
//
|
||||||
|
|
||||||
|
SkPoint prev_c0 = { 0, 0 },
|
||||||
|
prev_c1 = prev_c0;
|
||||||
|
|
||||||
|
fRecs.reserve(SkTMax<size_t>(jframes.size(), 1) - 1);
|
||||||
|
|
||||||
|
for (const skjson::ObjectValue* jframe : jframes) {
|
||||||
|
if (!jframe) continue;
|
||||||
|
|
||||||
|
float t0;
|
||||||
|
if (!Parse<float>((*jframe)["t"], &t0))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const auto v0_idx = this->parseValue(abuilder, (*jframe)["s"]),
|
||||||
|
v1_idx = this->parseValue(abuilder, (*jframe)["e"]);
|
||||||
|
|
||||||
|
if (!fRecs.empty()) {
|
||||||
|
if (fRecs.back().t1 >= t0) {
|
||||||
|
abuilder.log(Logger::Level::kWarning, nullptr,
|
||||||
|
"Ignoring out-of-order key frame (t:%f < t:%f).",
|
||||||
|
t0, fRecs.back().t1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back-fill t1 and v1 (if needed).
|
||||||
|
auto& prev = fRecs.back();
|
||||||
|
prev.t1 = t0;
|
||||||
|
|
||||||
|
// Previous keyframe did not specify an end value (case #2, #3).
|
||||||
|
if (prev.vidx1 < 0) {
|
||||||
|
// If this frame has no v0, we're in case #3 (constant text value),
|
||||||
|
// otherwise case #2 (v0 for current frame is the same as prev frame v1).
|
||||||
|
prev.vidx1 = v0_idx < 0 ? prev.vidx0 : v0_idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start value 's' is required.
|
||||||
|
if (v0_idx < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((v1_idx < 0) && ParseDefault((*jframe)["h"], false)) {
|
||||||
|
// Constant keyframe ("h": true).
|
||||||
|
fRecs.push_back({t0, t0, v0_idx, v0_idx, -1 });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto cubic_mapper_index = [&]() -> int {
|
||||||
|
// Do we have non-linear control points?
|
||||||
|
SkPoint c0, c1;
|
||||||
|
if (!Parse((*jframe)["o"], &c0) ||
|
||||||
|
!Parse((*jframe)["i"], &c1) ||
|
||||||
|
SkCubicMap::IsLinear(c0, c1)) {
|
||||||
|
// No need for a cubic mapper.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// De-dupe sequential cubic mappers.
|
||||||
|
if (c0 != prev_c0 || c1 != prev_c1) {
|
||||||
|
fCubicMaps.emplace_back(c0, c1);
|
||||||
|
prev_c0 = c0;
|
||||||
|
prev_c1 = c1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SkASSERT(!fCubicMaps.empty());
|
||||||
|
return SkToInt(fCubicMaps.size()) - 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
fRecs.push_back({t0, t0, v0_idx, v1_idx, cubic_mapper_index()});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fRecs.empty()) {
|
||||||
|
auto& last = fRecs.back();
|
||||||
|
|
||||||
|
// If the last entry has only a v0, we're in case #3 - make it a constant frame.
|
||||||
|
if (last.vidx0 >= 0 && last.vidx1 < 0) {
|
||||||
|
last.vidx1 = last.vidx0;
|
||||||
|
last.t1 = last.t0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we couldn't determine a valid t1 for the last frame, discard it
|
||||||
|
// (most likely the last frame entry for all 3 cases).
|
||||||
|
if (!last.isValid()) {
|
||||||
|
fRecs.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fRecs.shrink_to_fit();
|
||||||
|
fCubicMaps.shrink_to_fit();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<KeyframeRec> fRecs;
|
||||||
|
std::vector<SkCubicMap> fCubicMaps;
|
||||||
|
const KeyframeRec* fCachedRec = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class KeyframeAnimator final : public KeyframeAnimatorBase {
|
||||||
|
public:
|
||||||
|
static sk_sp<KeyframeAnimator> Make(const AnimationBuilder& abuilder,
|
||||||
|
const skjson::ArrayValue* jv,
|
||||||
|
T* target_value) {
|
||||||
|
if (!jv) return nullptr;
|
||||||
|
|
||||||
|
sk_sp<KeyframeAnimator> animator(new KeyframeAnimator(abuilder, *jv, target_value));
|
||||||
|
|
||||||
|
return animator->isEmpty() ? nullptr : animator;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isConstant() const {
|
||||||
|
SkASSERT(!fValues.empty());
|
||||||
|
return fValues.size() == 1ul;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
KeyframeAnimator(const AnimationBuilder& abuilder,
|
||||||
|
const skjson::ArrayValue& jframes,
|
||||||
|
T* target_value)
|
||||||
|
: fTarget(target_value) {
|
||||||
|
// Generally, each keyframe holds two values (start, end) and a cubic mapper. Except
|
||||||
|
// the last frame, which only holds a marker timestamp. Then, the values series is
|
||||||
|
// contiguous (keyframe[i].end == keyframe[i + 1].start), and we dedupe them.
|
||||||
|
// => we'll store (keyframes.size) values and (keyframe.size - 1) recs and cubic maps.
|
||||||
|
fValues.reserve(jframes.size());
|
||||||
|
this->parseKeyFrames(abuilder, jframes);
|
||||||
|
fValues.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
|
int parseValue(const AnimationBuilder& abuilder, const skjson::Value& jv) override {
|
||||||
|
T val;
|
||||||
|
if (!ValueTraits<T>::FromJSON(jv, &abuilder, &val) ||
|
||||||
|
(!fValues.empty() && !ValueTraits<T>::CanLerp(val, fValues.back()))) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: full deduping?
|
||||||
|
if (fValues.empty() || val != fValues.back()) {
|
||||||
|
fValues.push_back(std::move(val));
|
||||||
|
}
|
||||||
|
return SkToInt(fValues.size()) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onTick(float t) override {
|
||||||
|
const auto& rec = this->frame(t);
|
||||||
|
SkASSERT(rec.isValid());
|
||||||
|
|
||||||
|
if (rec.isConstant() || t <= rec.t0) {
|
||||||
|
*fTarget = fValues[SkToSizeT(rec.vidx0)];
|
||||||
|
return;
|
||||||
|
} else if (t >= rec.t1) {
|
||||||
|
*fTarget = fValues[SkToSizeT(rec.vidx1)];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueTraits<T>::Lerp(fValues[SkToSizeT(rec.vidx0)],
|
||||||
|
fValues[SkToSizeT(rec.vidx1)],
|
||||||
|
this->localT(rec, t),
|
||||||
|
fTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<T> fValues;
|
||||||
|
T* fTarget;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool BindPropertyImpl(const AnimationBuilder& abuilder,
|
||||||
|
const skjson::ObjectValue* jprop,
|
||||||
|
AnimatorScope* ascope,
|
||||||
|
T* target_value) {
|
||||||
|
if (!jprop) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& jpropA = (*jprop)["a"];
|
||||||
|
const auto& jpropK = (*jprop)["k"];
|
||||||
|
|
||||||
|
if (!(*jprop)["x"].is<skjson::NullValue>()) {
|
||||||
|
abuilder.log(Logger::Level::kWarning, nullptr, "Unsupported expression.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Older Json versions don't have an "a" animation marker.
|
||||||
|
// For those, we attempt to parse both ways.
|
||||||
|
if (!ParseDefault<bool>(jpropA, false)) {
|
||||||
|
if (ValueTraits<T>::FromJSON(jpropK, &abuilder, target_value)) {
|
||||||
|
// Static property.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!jpropA.is<skjson::NullValue>()) {
|
||||||
|
abuilder.log(Logger::Level::kError, jprop,
|
||||||
|
"Could not parse (explicit) static property.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keyframe property.
|
||||||
|
auto animator = KeyframeAnimator<T>::Make(abuilder, jpropK, target_value);
|
||||||
|
|
||||||
|
if (!animator) {
|
||||||
|
abuilder.log(Logger::Level::kError, jprop, "Could not parse keyframed property.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animator->isConstant()) {
|
||||||
|
// If all keyframes are constant, there is no reason to treat this
|
||||||
|
// as an animated property - apply immediately and discard the animator.
|
||||||
|
animator->tick(0);
|
||||||
|
} else {
|
||||||
|
ascope->push_back(std::move(animator));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Explicit instantiations
|
||||||
|
template <>
|
||||||
|
bool AnimatablePropertyContainer::bind<ScalarValue>(const AnimationBuilder& abuilder,
|
||||||
|
const skjson::ObjectValue* jprop,
|
||||||
|
ScalarValue* v) {
|
||||||
|
return BindPropertyImpl(abuilder, jprop, &fAnimators, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
bool AnimatablePropertyContainer::bind<ShapeValue>(const AnimationBuilder& abuilder,
|
||||||
|
const skjson::ObjectValue* jprop,
|
||||||
|
ShapeValue* v) {
|
||||||
|
return BindPropertyImpl(abuilder, jprop, &fAnimators, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
bool AnimatablePropertyContainer::bind<TextValue>(const AnimationBuilder& abuilder,
|
||||||
|
const skjson::ObjectValue* jprop,
|
||||||
|
TextValue* v) {
|
||||||
|
return BindPropertyImpl(abuilder, jprop, &fAnimators, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
bool AnimatablePropertyContainer::bind<VectorValue>(const AnimationBuilder& abuilder,
|
||||||
|
const skjson::ObjectValue* jprop,
|
||||||
|
VectorValue* v) {
|
||||||
|
if (!jprop) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ParseDefault<bool>((*jprop)["s"], false)) {
|
||||||
|
// Regular (static or keyframed) vector value.
|
||||||
|
return BindPropertyImpl(abuilder, jprop, &fAnimators, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separate-dimensions vector value: each component is animated independently.
|
||||||
|
class SeparateDimensionsAnimator final : public AnimatablePropertyContainer {
|
||||||
|
public:
|
||||||
|
static sk_sp<SeparateDimensionsAnimator> Make(const AnimationBuilder& abuilder,
|
||||||
|
const skjson::ObjectValue& jprop,
|
||||||
|
VectorValue* v) {
|
||||||
|
|
||||||
|
sk_sp<SeparateDimensionsAnimator> animator(new SeparateDimensionsAnimator(v));
|
||||||
|
auto bound = animator->bind(abuilder, jprop["x"], &animator->fX);
|
||||||
|
bound |= animator->bind(abuilder, jprop["y"], &animator->fY);
|
||||||
|
bound |= animator->bind(abuilder, jprop["z"], &animator->fZ);
|
||||||
|
|
||||||
|
return bound ? animator : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit SeparateDimensionsAnimator(VectorValue* v)
|
||||||
|
: fTarget(v) {}
|
||||||
|
|
||||||
|
void onSync() override {
|
||||||
|
*fTarget = { fX, fY, fZ };
|
||||||
|
}
|
||||||
|
|
||||||
|
VectorValue* fTarget;
|
||||||
|
ScalarValue fX = 0,
|
||||||
|
fY = 0,
|
||||||
|
fZ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (auto sd_animator = SeparateDimensionsAnimator::Make(abuilder, *jprop, v)) {
|
||||||
|
if (sd_animator->isStatic()) {
|
||||||
|
sd_animator->tick(0);
|
||||||
|
} else {
|
||||||
|
fAnimators.push_back(std::move(sd_animator));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
} // namespace skottie
|
46
modules/skottie/src/Animator.h
Normal file
46
modules/skottie/src/Animator.h
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Google Inc.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by a BSD-style license that can be
|
||||||
|
* found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SkottieAnimator_DEFINED
|
||||||
|
#define SkottieAnimator_DEFINED
|
||||||
|
|
||||||
|
#include "modules/sksg/include/SkSGScene.h"
|
||||||
|
|
||||||
|
namespace skjson {
|
||||||
|
|
||||||
|
class ObjectValue;
|
||||||
|
|
||||||
|
} // namespace skjson
|
||||||
|
|
||||||
|
namespace skottie {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
class AnimationBuilder;
|
||||||
|
|
||||||
|
class AnimatablePropertyContainer : public sksg::Animator {
|
||||||
|
public:
|
||||||
|
// This is the workhorse for property binding: depending on whether the property is animated,
|
||||||
|
// it will either apply immediately or instantiate and attach a keyframe animator, scoped to
|
||||||
|
// this container.
|
||||||
|
template <typename T>
|
||||||
|
bool bind(const AnimationBuilder&, const skjson::ObjectValue*, T*);
|
||||||
|
|
||||||
|
bool isStatic() const { return fAnimators.empty(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void onSync() = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onTick(float) final;
|
||||||
|
|
||||||
|
sksg::AnimatorList fAnimators;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
} // namespace skottie
|
||||||
|
|
||||||
|
#endif // SkottieAnimator_DEFINED
|
@ -30,24 +30,6 @@
|
|||||||
|
|
||||||
namespace skottie {
|
namespace skottie {
|
||||||
|
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
DiscardableAdaptorBase::DiscardableAdaptorBase() = default;
|
|
||||||
|
|
||||||
void DiscardableAdaptorBase::setAnimators(sksg::AnimatorList&& animators) {
|
|
||||||
fAnimators = std::move(animators);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DiscardableAdaptorBase::onTick(float t) {
|
|
||||||
for (auto& animator : fAnimators) {
|
|
||||||
animator->tick(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->onSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
RRectAdapter::RRectAdapter(sk_sp<sksg::RRect> wrapped_node)
|
RRectAdapter::RRectAdapter(sk_sp<sksg::RRect> wrapped_node)
|
||||||
: fRRectNode(std::move(wrapped_node)) {}
|
: fRRectNode(std::move(wrapped_node)) {}
|
||||||
|
|
||||||
|
@ -42,27 +42,6 @@ namespace skjson {
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace skottie {
|
namespace skottie {
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
class DiscardableAdaptorBase : public sksg::Animator {
|
|
||||||
protected:
|
|
||||||
DiscardableAdaptorBase();
|
|
||||||
|
|
||||||
void onTick(float t) final;
|
|
||||||
|
|
||||||
virtual void onSync() = 0;
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend class AnimationBuilder;
|
|
||||||
void setAnimators(sksg::AnimatorList&&);
|
|
||||||
|
|
||||||
sksg::AnimatorList fAnimators;
|
|
||||||
|
|
||||||
using INHERITED = sksg::Animator;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
#define ADAPTER_PROPERTY(p_name, p_type, p_default) \
|
#define ADAPTER_PROPERTY(p_name, p_type, p_default) \
|
||||||
const p_type& get##p_name() const { \
|
const p_type& get##p_name() const { \
|
||||||
|
@ -5,468 +5,90 @@
|
|||||||
* found in the LICENSE file.
|
* found in the LICENSE file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "include/core/SkCubicMap.h"
|
#include "modules/skottie/src/Animator.h"
|
||||||
#include "include/core/SkString.h"
|
|
||||||
#include "modules/skottie/src/SkottieJson.h"
|
#include "modules/skottie/src/SkottieJson.h"
|
||||||
#include "modules/skottie/src/SkottiePriv.h"
|
#include "modules/skottie/src/SkottiePriv.h"
|
||||||
#include "modules/skottie/src/SkottieValue.h"
|
#include "modules/skottie/src/SkottieValue.h"
|
||||||
#include "modules/skottie/src/text/TextValue.h"
|
#include "modules/skottie/src/text/TextValue.h"
|
||||||
#include "modules/sksg/include/SkSGScene.h"
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace skottie {
|
namespace skottie {
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class KeyframeAnimatorBase : public sksg::Animator {
|
|
||||||
public:
|
|
||||||
size_t count() const { return fRecs.size(); }
|
|
||||||
|
|
||||||
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
|
|
||||||
: fCubicMaps[rec.cmidx].computeYFromX(lt);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual int parseValue(const skjson::Value&, const AnimationBuilder* abuilder) = 0;
|
|
||||||
|
|
||||||
void parseKeyFrames(const skjson::ArrayValue& jframes, const AnimationBuilder* abuilder) {
|
|
||||||
// Logically, a keyframe is defined as a (t0, t1, v0, v1) tuple: a given value
|
|
||||||
// is interpolated in the [v0..v1] interval over the [t0..t1] time span.
|
|
||||||
//
|
|
||||||
// There are three interestingly-different keyframe formats handled here.
|
|
||||||
//
|
|
||||||
// 1) Legacy keyframe format
|
|
||||||
//
|
|
||||||
// - normal keyframes specify t0 ("t"), v0 ("s") and v1 ("e")
|
|
||||||
// - last frame only specifies a t0
|
|
||||||
// - t1[frame] == t0[frame + 1]
|
|
||||||
// - the last entry (where we cannot determine t1) is ignored
|
|
||||||
//
|
|
||||||
// 2) Regular (new) keyframe format
|
|
||||||
//
|
|
||||||
// - all keyframes specify t0 ("t") and v0 ("s")
|
|
||||||
// - t1[frame] == t0[frame + 1]
|
|
||||||
// - v1[frame] == v0[frame + 1]
|
|
||||||
// - the last entry (where we cannot determine t1/v1) is ignored
|
|
||||||
//
|
|
||||||
// 3) Text value keyframe format
|
|
||||||
//
|
|
||||||
// - similar to case #2, all keyframes specify t0 & v0
|
|
||||||
// - unlike case #2, all keyframes are assumed to be constant (v1 == v0),
|
|
||||||
// and the last frame is not discarded (its t1 is assumed -> inf)
|
|
||||||
//
|
|
||||||
|
|
||||||
SkPoint prev_c0 = { 0, 0 },
|
|
||||||
prev_c1 = prev_c0;
|
|
||||||
|
|
||||||
for (const skjson::ObjectValue* jframe : jframes) {
|
|
||||||
if (!jframe) continue;
|
|
||||||
|
|
||||||
float t0;
|
|
||||||
if (!Parse<float>((*jframe)["t"], &t0))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const auto v0_idx = this->parseValue((*jframe)["s"], abuilder),
|
|
||||||
v1_idx = this->parseValue((*jframe)["e"], abuilder);
|
|
||||||
|
|
||||||
if (!fRecs.empty()) {
|
|
||||||
if (fRecs.back().t1 >= t0) {
|
|
||||||
abuilder->log(Logger::Level::kWarning, nullptr,
|
|
||||||
"Ignoring out-of-order key frame (t:%f < t:%f).",
|
|
||||||
t0, fRecs.back().t1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Back-fill t1 and v1 (if needed).
|
|
||||||
auto& prev = fRecs.back();
|
|
||||||
prev.t1 = t0;
|
|
||||||
|
|
||||||
// Previous keyframe did not specify an end value (case #2, #3).
|
|
||||||
if (prev.vidx1 < 0) {
|
|
||||||
// If this frame has no v0, we're in case #3 (constant text value),
|
|
||||||
// otherwise case #2 (v0 for current frame is the same as prev frame v1).
|
|
||||||
prev.vidx1 = v0_idx < 0 ? prev.vidx0 : v0_idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start value 's' is required.
|
|
||||||
if (v0_idx < 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if ((v1_idx < 0) && ParseDefault((*jframe)["h"], false)) {
|
|
||||||
// Constant keyframe ("h": true).
|
|
||||||
fRecs.push_back({t0, t0, v0_idx, v0_idx, -1 });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto cubic_mapper_index = [&]() -> int {
|
|
||||||
// Do we have non-linear control points?
|
|
||||||
SkPoint c0, c1;
|
|
||||||
if (!Parse((*jframe)["o"], &c0) ||
|
|
||||||
!Parse((*jframe)["i"], &c1) ||
|
|
||||||
SkCubicMap::IsLinear(c0, c1)) {
|
|
||||||
// No need for a cubic mapper.
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// De-dupe sequential cubic mappers.
|
|
||||||
if (c0 != prev_c0 || c1 != prev_c1) {
|
|
||||||
fCubicMaps.emplace_back(c0, c1);
|
|
||||||
prev_c0 = c0;
|
|
||||||
prev_c1 = c1;
|
|
||||||
}
|
|
||||||
|
|
||||||
SkASSERT(!fCubicMaps.empty());
|
|
||||||
return SkToInt(fCubicMaps.size()) - 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
fRecs.push_back({t0, t0, v0_idx, v1_idx, cubic_mapper_index()});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fRecs.empty()) {
|
|
||||||
auto& last = fRecs.back();
|
|
||||||
|
|
||||||
// If the last entry has only a v0, we're in case #3 - make it a constant frame.
|
|
||||||
if (last.vidx0 >= 0 && last.vidx1 < 0) {
|
|
||||||
last.vidx1 = last.vidx0;
|
|
||||||
last.t1 = last.t0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we couldn't determine a valid t1 for the last frame, discard it
|
|
||||||
// (most likely the last frame entry for all 3 cases).
|
|
||||||
if (!last.isValid()) {
|
|
||||||
fRecs.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fRecs.shrink_to_fit();
|
|
||||||
fCubicMaps.shrink_to_fit();
|
|
||||||
|
|
||||||
SkASSERT(fRecs.empty() || fRecs.back().isValid());
|
|
||||||
}
|
|
||||||
|
|
||||||
void reserve(size_t frame_count) {
|
|
||||||
fRecs.reserve(frame_count);
|
|
||||||
fCubicMaps.reserve(frame_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<KeyframeRec> fRecs;
|
|
||||||
std::vector<SkCubicMap> fCubicMaps;
|
|
||||||
const KeyframeRec* fCachedRec = nullptr;
|
|
||||||
|
|
||||||
using INHERITED = sksg::Animator;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class KeyframeAnimator final : public KeyframeAnimatorBase {
|
class LegacyAnimatorAdapter final : public AnimatablePropertyContainer {
|
||||||
public:
|
public:
|
||||||
static sk_sp<KeyframeAnimator> Make(const skjson::ArrayValue* jv,
|
LegacyAnimatorAdapter(const AnimationBuilder& abuilder,
|
||||||
const AnimationBuilder* abuilder,
|
const skjson::ObjectValue* jprop,
|
||||||
std::function<void(const T&)>&& apply) {
|
std::function<void(const T&)>&& apply)
|
||||||
if (!jv) return nullptr;
|
: fApplyFunc(std::move(apply)) {
|
||||||
|
this->bind<T>(abuilder, jprop, &fValue);
|
||||||
sk_sp<KeyframeAnimator> animator(new KeyframeAnimator(*jv, abuilder, std::move(apply)));
|
|
||||||
|
|
||||||
return animator->count() ? animator : nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isConstant() const {
|
const T& value() const { return fValue; }
|
||||||
SkASSERT(!fVs.empty());
|
|
||||||
return fVs.size() == 1ul;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onTick(float t) override {
|
|
||||||
fApplyFunc(*this->eval(this->frame(t), t, &fScratch));
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
KeyframeAnimator(const skjson::ArrayValue& jframes,
|
void onSync() override {
|
||||||
const AnimationBuilder* abuilder,
|
fApplyFunc(fValue);
|
||||||
std::function<void(const T&)>&& apply)
|
|
||||||
: fApplyFunc(std::move(apply)) {
|
|
||||||
// Generally, each keyframe holds two values (start, end) and a cubic mapper. Except
|
|
||||||
// the last frame, which only holds a marker timestamp. Then, the values series is
|
|
||||||
// contiguous (keyframe[i].end == keyframe[i + 1].start), and we dedupe them.
|
|
||||||
// => we'll store (keyframes.size) values and (keyframe.size - 1) recs and cubic maps.
|
|
||||||
fVs.reserve(jframes.size());
|
|
||||||
this->reserve(SkTMax<size_t>(jframes.size(), 1) - 1);
|
|
||||||
|
|
||||||
this->parseKeyFrames(jframes, abuilder);
|
|
||||||
|
|
||||||
fVs.shrink_to_fit();
|
|
||||||
}
|
|
||||||
|
|
||||||
int parseValue(const skjson::Value& jv, const AnimationBuilder* abuilder) override {
|
|
||||||
T val;
|
|
||||||
if (!ValueTraits<T>::FromJSON(jv, abuilder, &val) ||
|
|
||||||
(!fVs.empty() && !ValueTraits<T>::CanLerp(val, fVs.back()))) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: full deduping?
|
|
||||||
if (fVs.empty() || val != fVs.back()) {
|
|
||||||
fVs.push_back(std::move(val));
|
|
||||||
}
|
|
||||||
return SkToInt(fVs.size()) - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const T* eval(const KeyframeRec& rec, float t, T* v) const {
|
|
||||||
SkASSERT(rec.isValid());
|
|
||||||
if (rec.isConstant() || t <= rec.t0) {
|
|
||||||
return &fVs[rec.vidx0];
|
|
||||||
} else if (t >= rec.t1) {
|
|
||||||
return &fVs[rec.vidx1];
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto lt = this->localT(rec, t);
|
|
||||||
const auto& v0 = fVs[rec.vidx0];
|
|
||||||
const auto& v1 = fVs[rec.vidx1];
|
|
||||||
ValueTraits<T>::Lerp(v0, v1, lt, v);
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::function<void(const T&)> fApplyFunc;
|
const std::function<void(const T&)> fApplyFunc;
|
||||||
std::vector<T> fVs;
|
T fValue;
|
||||||
|
|
||||||
// LERP storage: we use this to temporarily store interpolation results.
|
|
||||||
// Alternatively, the temp result could live on the stack -- but for vector values that would
|
|
||||||
// involve dynamic allocations on each tick. This a trade-off to avoid allocator pressure
|
|
||||||
// during animation.
|
|
||||||
T fScratch; // lerp storage
|
|
||||||
|
|
||||||
using INHERITED = KeyframeAnimatorBase;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static inline bool BindPropertyImpl(const skjson::ObjectValue* jprop,
|
bool BindLegacyPropertyImpl(const AnimationBuilder& abuilder,
|
||||||
const AnimationBuilder* abuilder,
|
const skjson::ObjectValue* jprop,
|
||||||
AnimatorScope* ascope,
|
AnimatorScope* ascope,
|
||||||
std::function<void(const T&)>&& apply,
|
std::function<void(const T&)>&& apply,
|
||||||
const T* noop = nullptr) {
|
const T* noop) {
|
||||||
if (!jprop) return false;
|
if (!jprop) {
|
||||||
|
|
||||||
const auto& jpropA = (*jprop)["a"];
|
|
||||||
const auto& jpropK = (*jprop)["k"];
|
|
||||||
|
|
||||||
if (!(*jprop)["x"].is<skjson::NullValue>()) {
|
|
||||||
abuilder->log(Logger::Level::kWarning, nullptr, "Unsupported expression.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Older Json versions don't have an "a" animation marker.
|
|
||||||
// For those, we attempt to parse both ways.
|
|
||||||
if (!ParseDefault<bool>(jpropA, false)) {
|
|
||||||
T val;
|
|
||||||
if (ValueTraits<T>::FromJSON(jpropK, abuilder, &val)) {
|
|
||||||
// Static property.
|
|
||||||
if (noop && val == *noop)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
apply(val);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!jpropA.is<skjson::NullValue>()) {
|
|
||||||
abuilder->log(Logger::Level::kError, jprop,
|
|
||||||
"Could not parse (explicit) static property.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keyframe property.
|
|
||||||
auto animator = KeyframeAnimator<T>::Make(jpropK, abuilder, std::move(apply));
|
|
||||||
|
|
||||||
if (!animator) {
|
|
||||||
abuilder->log(Logger::Level::kError, jprop, "Could not parse keyframed property.");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (animator->isConstant()) {
|
auto adapter = sk_make_sp<LegacyAnimatorAdapter<T>>(abuilder, jprop, std::move(apply));
|
||||||
// If all keyframes are constant, there is no reason to treat this
|
|
||||||
// as an animated property - apply immediately and discard the animator.
|
if (adapter->isStatic()) {
|
||||||
animator->tick(0);
|
if (noop && *noop == adapter->value()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
adapter->tick(0);
|
||||||
} else {
|
} else {
|
||||||
ascope->push_back(std::move(animator));
|
ascope->push_back(std::move(adapter));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SplitPointAnimator final : public sksg::Animator {
|
|
||||||
public:
|
|
||||||
static sk_sp<SplitPointAnimator> Make(const skjson::ObjectValue* jprop,
|
|
||||||
const AnimationBuilder* abuilder,
|
|
||||||
std::function<void(const VectorValue&)>&& apply,
|
|
||||||
const VectorValue*) {
|
|
||||||
if (!jprop) return nullptr;
|
|
||||||
|
|
||||||
sk_sp<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"], abuilder, &split_animator->fAnimators,
|
|
||||||
[split_animator_ptr](const ScalarValue& x) { split_animator_ptr->setX(x); }) ||
|
|
||||||
!BindPropertyImpl<ScalarValue>((*jprop)["y"], abuilder, &split_animator->fAnimators,
|
|
||||||
[split_animator_ptr](const ScalarValue& y) { split_animator_ptr->setY(y); })) {
|
|
||||||
abuilder->log(Logger::Level::kError, jprop, "Could not parse split property.");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (split_animator->fAnimators.empty()) {
|
|
||||||
// Static split property: commit the (buffered) value and discard.
|
|
||||||
split_animator->onTick(0);
|
|
||||||
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 skjson::Value& jv,
|
|
||||||
const AnimationBuilder* abuilder,
|
|
||||||
AnimatorScope* ascope,
|
|
||||||
std::function<void(const VectorValue&)>&& apply,
|
|
||||||
const VectorValue* noop) {
|
|
||||||
if (auto split_animator = SplitPointAnimator::Make(jv, abuilder, std::move(apply), noop)) {
|
|
||||||
ascope->push_back(std::move(split_animator));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
bool AnimationBuilder::bindProperty(const skjson::Value& jv,
|
bool AnimationBuilder::bindProperty(const skjson::Value& jv,
|
||||||
std::function<void(const ScalarValue&)>&& apply,
|
std::function<void(const ScalarValue&)>&& apply,
|
||||||
const ScalarValue* noop) const {
|
const ScalarValue* noop) const {
|
||||||
return BindPropertyImpl(jv, this, fCurrentAnimatorScope, std::move(apply), noop);
|
return BindLegacyPropertyImpl(*this, jv, fCurrentAnimatorScope, std::move(apply), noop);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
bool AnimationBuilder::bindProperty(const skjson::Value& jv,
|
bool AnimationBuilder::bindProperty(const skjson::Value& jv,
|
||||||
std::function<void(const VectorValue&)>&& apply,
|
std::function<void(const VectorValue&)>&& apply,
|
||||||
const VectorValue* noop) const {
|
const VectorValue* noop) const {
|
||||||
if (!jv.is<skjson::ObjectValue>())
|
return BindLegacyPropertyImpl(*this, jv, fCurrentAnimatorScope, std::move(apply), noop);
|
||||||
return false;
|
|
||||||
|
|
||||||
return ParseDefault<bool>(jv.as<skjson::ObjectValue>()["s"], false)
|
|
||||||
? BindSplitPositionProperty(jv, this, fCurrentAnimatorScope, std::move(apply), noop)
|
|
||||||
: BindPropertyImpl(jv, this, fCurrentAnimatorScope, std::move(apply), noop);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
bool AnimationBuilder::bindProperty(const skjson::Value& jv,
|
bool AnimationBuilder::bindProperty(const skjson::Value& jv,
|
||||||
std::function<void(const ShapeValue&)>&& apply,
|
std::function<void(const ShapeValue&)>&& apply,
|
||||||
const ShapeValue* noop) const {
|
const ShapeValue* noop) const {
|
||||||
return BindPropertyImpl(jv, this, fCurrentAnimatorScope, std::move(apply), noop);
|
return BindLegacyPropertyImpl(*this, jv, fCurrentAnimatorScope, std::move(apply), noop);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
bool AnimationBuilder::bindProperty(const skjson::Value& jv,
|
bool AnimationBuilder::bindProperty(const skjson::Value& jv,
|
||||||
std::function<void(const TextValue&)>&& apply,
|
std::function<void(const TextValue&)>&& apply,
|
||||||
const TextValue* noop) const {
|
const TextValue* noop) const {
|
||||||
return BindPropertyImpl(jv, this, fCurrentAnimatorScope, std::move(apply), noop);
|
return BindLegacyPropertyImpl(*this, jv, fCurrentAnimatorScope, std::move(apply), noop);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
@ -63,8 +63,7 @@ public:
|
|||||||
};
|
};
|
||||||
const FontInfo* findFont(const SkString& name) const;
|
const FontInfo* findFont(const SkString& name) const;
|
||||||
|
|
||||||
// This is the workhorse for property binding: depending on whether the property is animated,
|
// DEPRECATED/TO-BE-REMOVED: use AnimatablePropertyContainer::bind<> instead.
|
||||||
// it will either apply immediately or instantiate and attach a keyframe animator.
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
bool bindProperty(const skjson::Value&,
|
bool bindProperty(const skjson::Value&,
|
||||||
std::function<void(const T&)>&&,
|
std::function<void(const T&)>&&,
|
||||||
@ -118,22 +117,18 @@ public:
|
|||||||
|
|
||||||
template <typename T, typename... Args>
|
template <typename T, typename... Args>
|
||||||
sk_sp<sksg::RenderNode> attachDiscardableAdapter(Args&&... args) const {
|
sk_sp<sksg::RenderNode> attachDiscardableAdapter(Args&&... args) const {
|
||||||
AutoScope ascope(this);
|
if (auto adapter = T::Make(std::forward<Args>(args)...)) {
|
||||||
auto adapter = T::Make(std::forward<Args>(args)...);
|
sk_sp<sksg::RenderNode> node = adapter->renderNode();
|
||||||
auto adapter_animators = ascope.release();
|
if (adapter->isStatic()) {
|
||||||
|
// Fire off a synthetic tick to force a single SG sync before discarding.
|
||||||
if (!adapter) { return nullptr; }
|
adapter->tick(0);
|
||||||
|
} else {
|
||||||
const auto& node = adapter->renderNode();
|
fCurrentAnimatorScope->push_back(std::move(adapter));
|
||||||
if (adapter_animators.empty()) {
|
}
|
||||||
// Fire off a synthetic tick to force a single SG sync before discarding the adapter.
|
return node;
|
||||||
adapter->tick(0);
|
|
||||||
} else {
|
|
||||||
adapter->setAnimators(std::move(adapter_animators));
|
|
||||||
fCurrentAnimatorScope->push_back(std::move(adapter));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AutoPropertyTracker {
|
class AutoPropertyTracker {
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#include "modules/skottie/src/effects/Effects.h"
|
#include "modules/skottie/src/effects/Effects.h"
|
||||||
|
|
||||||
#include "modules/skottie/src/SkottieAdapter.h"
|
#include "modules/skottie/src/Animator.h"
|
||||||
#include "modules/skottie/src/SkottieJson.h"
|
#include "modules/skottie/src/SkottieJson.h"
|
||||||
#include "modules/skottie/src/SkottieValue.h"
|
#include "modules/skottie/src/SkottieValue.h"
|
||||||
#include "modules/sksg/include/SkSGColorFilter.h"
|
#include "modules/sksg/include/SkSGColorFilter.h"
|
||||||
@ -18,11 +18,23 @@ namespace internal {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class HueSaturationEffectAdapter final : public DiscardableAdaptorBase {
|
class HueSaturationEffectAdapter final : public AnimatablePropertyContainer {
|
||||||
public:
|
public:
|
||||||
static sk_sp<HueSaturationEffectAdapter> Make(const skjson::ArrayValue& jprops,
|
static sk_sp<HueSaturationEffectAdapter> Make(const skjson::ArrayValue& jprops,
|
||||||
sk_sp<sksg::RenderNode> layer,
|
sk_sp<sksg::RenderNode> layer,
|
||||||
const AnimationBuilder* abuilder) {
|
const AnimationBuilder* abuilder) {
|
||||||
|
|
||||||
|
return sk_sp<HueSaturationEffectAdapter>(
|
||||||
|
new HueSaturationEffectAdapter(jprops, std::move(layer), abuilder));
|
||||||
|
}
|
||||||
|
|
||||||
|
const sk_sp<sksg::ExternalColorFilter>& renderNode() const { return fColorFilter; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
HueSaturationEffectAdapter(const skjson::ArrayValue& jprops,
|
||||||
|
sk_sp<sksg::RenderNode> layer,
|
||||||
|
const AnimationBuilder* abuilder)
|
||||||
|
: fColorFilter(sksg::ExternalColorFilter::Make(std::move(layer))) {
|
||||||
enum : size_t {
|
enum : size_t {
|
||||||
kChannelControl_Index = 0,
|
kChannelControl_Index = 0,
|
||||||
kChannelRange_Index = 1,
|
kChannelRange_Index = 1,
|
||||||
@ -33,52 +45,24 @@ public:
|
|||||||
kColorizeHue_Index = 6,
|
kColorizeHue_Index = 6,
|
||||||
kColorizeSat_Index = 7,
|
kColorizeSat_Index = 7,
|
||||||
kColorizeLightness_Index = 8,
|
kColorizeLightness_Index = 8,
|
||||||
|
|
||||||
kMax_Index = kColorizeLightness_Index
|
|
||||||
};
|
};
|
||||||
|
|
||||||
auto adapter = sk_sp<HueSaturationEffectAdapter>(
|
this->bind(*abuilder, EffectBuilder::GetPropValue(jprops, kChannelControl_Index),
|
||||||
new HueSaturationEffectAdapter(std::move(layer)));
|
&fChanCtrl);
|
||||||
|
this->bind(*abuilder, EffectBuilder::GetPropValue(jprops, kMasterHue_Index),
|
||||||
auto* raw_adapter = adapter.get();
|
&fMasterHue);
|
||||||
|
this->bind(*abuilder, EffectBuilder::GetPropValue(jprops, kMasterSat_Index),
|
||||||
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops,
|
&fMasterSat);
|
||||||
kChannelControl_Index),
|
this->bind(*abuilder, EffectBuilder::GetPropValue(jprops, kMasterLightness_Index),
|
||||||
[raw_adapter](const ScalarValue& c) {
|
&fMasterLight);
|
||||||
raw_adapter->fChanCtrl = c;
|
|
||||||
});
|
|
||||||
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops,
|
|
||||||
kMasterHue_Index),
|
|
||||||
[raw_adapter](const ScalarValue& h) {
|
|
||||||
raw_adapter->fMasterHue = h;
|
|
||||||
});
|
|
||||||
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops,
|
|
||||||
kMasterSat_Index),
|
|
||||||
[raw_adapter](const ScalarValue& s) {
|
|
||||||
raw_adapter->fMasterSat = s;
|
|
||||||
});
|
|
||||||
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops,
|
|
||||||
kMasterLightness_Index),
|
|
||||||
[raw_adapter](const ScalarValue& l) {
|
|
||||||
raw_adapter->fMasterLight = l;
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: colorize support?
|
// TODO: colorize support?
|
||||||
|
|
||||||
return adapter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sk_sp<sksg::ExternalColorFilter>& renderNode() const { return fColorFilter; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onSync() override {
|
void onSync() override {
|
||||||
fColorFilter->setColorFilter(this->makeColorFilter());
|
fColorFilter->setColorFilter(this->makeColorFilter());
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
explicit HueSaturationEffectAdapter(sk_sp<sksg::RenderNode> layer)
|
|
||||||
: fColorFilter(sksg::ExternalColorFilter::Make(std::move(layer))) {}
|
|
||||||
|
|
||||||
sk_sp<SkColorFilter> makeColorFilter() const {
|
sk_sp<SkColorFilter> makeColorFilter() const {
|
||||||
enum : uint8_t {
|
enum : uint8_t {
|
||||||
kMaster_Chan = 0x01,
|
kMaster_Chan = 0x01,
|
||||||
@ -127,8 +111,6 @@ private:
|
|||||||
fMasterHue = 0.0f,
|
fMasterHue = 0.0f,
|
||||||
fMasterSat = 0.0f,
|
fMasterSat = 0.0f,
|
||||||
fMasterLight = 0.0f;
|
fMasterLight = 0.0f;
|
||||||
|
|
||||||
using INHERITED = DiscardableAdaptorBase;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#include "modules/skottie/src/effects/Effects.h"
|
#include "modules/skottie/src/effects/Effects.h"
|
||||||
|
|
||||||
#include "modules/skottie/src/SkottieAdapter.h"
|
#include "modules/skottie/src/Animator.h"
|
||||||
#include "modules/skottie/src/SkottieValue.h"
|
#include "modules/skottie/src/SkottieValue.h"
|
||||||
#include "modules/sksg/include/SkSGColorFilter.h"
|
#include "modules/sksg/include/SkSGColorFilter.h"
|
||||||
#include "src/utils/SkJSON.h"
|
#include "src/utils/SkJSON.h"
|
||||||
@ -17,31 +17,28 @@ namespace internal {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class InvertEffectAdapter final : public DiscardableAdaptorBase {
|
class InvertEffectAdapter final : public AnimatablePropertyContainer {
|
||||||
public:
|
public:
|
||||||
static sk_sp<InvertEffectAdapter> Make(const skjson::ArrayValue& jprops,
|
static sk_sp<InvertEffectAdapter> Make(const skjson::ArrayValue& jprops,
|
||||||
sk_sp<sksg::RenderNode> layer,
|
sk_sp<sksg::RenderNode> layer,
|
||||||
const AnimationBuilder* abuilder) {
|
const AnimationBuilder* abuilder) {
|
||||||
enum : size_t {
|
return sk_sp<InvertEffectAdapter>(
|
||||||
kChannel_Index = 0,
|
new InvertEffectAdapter(jprops, std::move(layer), abuilder));
|
||||||
};
|
|
||||||
|
|
||||||
auto adapter = sk_sp<InvertEffectAdapter>(new InvertEffectAdapter(std::move(layer)));
|
|
||||||
auto* raw_adapter = adapter.get();
|
|
||||||
|
|
||||||
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops, kChannel_Index),
|
|
||||||
[raw_adapter](const ScalarValue& c) {
|
|
||||||
raw_adapter->fChannel = c;
|
|
||||||
});
|
|
||||||
|
|
||||||
return adapter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sk_sp<sksg::ExternalColorFilter>& renderNode() const { return fColorFilter; }
|
const sk_sp<sksg::ExternalColorFilter>& renderNode() const { return fColorFilter; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit InvertEffectAdapter(sk_sp<sksg::RenderNode> layer)
|
InvertEffectAdapter(const skjson::ArrayValue& jprops,
|
||||||
: fColorFilter(sksg::ExternalColorFilter::Make(std::move(layer))) {}
|
sk_sp<sksg::RenderNode> layer,
|
||||||
|
const AnimationBuilder* abuilder)
|
||||||
|
: fColorFilter(sksg::ExternalColorFilter::Make(std::move(layer))) {
|
||||||
|
enum : size_t {
|
||||||
|
kChannel_Index = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this->bind(*abuilder, EffectBuilder::GetPropValue(jprops, kChannel_Index), &fChannel);
|
||||||
|
}
|
||||||
|
|
||||||
void onSync() override {
|
void onSync() override {
|
||||||
struct STColorMatrix {
|
struct STColorMatrix {
|
||||||
|
@ -8,7 +8,8 @@
|
|||||||
#include "modules/skottie/src/effects/Effects.h"
|
#include "modules/skottie/src/effects/Effects.h"
|
||||||
|
|
||||||
#include "include/private/SkColorData.h"
|
#include "include/private/SkColorData.h"
|
||||||
#include "modules/skottie/src/SkottieAdapter.h"
|
#include "modules/skottie/src/Animator.h"
|
||||||
|
#include "modules/skottie/src/SkottieJson.h"
|
||||||
#include "modules/sksg/include/SkSGColorFilter.h"
|
#include "modules/sksg/include/SkSGColorFilter.h"
|
||||||
|
|
||||||
namespace skottie {
|
namespace skottie {
|
||||||
@ -25,47 +26,34 @@ namespace {
|
|||||||
*
|
*
|
||||||
* C.r, C.g, C.b, C.a, Luminance(C), Hue(C), Saturation(C), Lightness(C), 1 or 0.
|
* C.r, C.g, C.b, C.a, Luminance(C), Hue(C), Saturation(C), Lightness(C), 1 or 0.
|
||||||
*/
|
*/
|
||||||
class ShiftChannelsEffectAdapter final : public DiscardableAdaptorBase {
|
class ShiftChannelsEffectAdapter final : public AnimatablePropertyContainer {
|
||||||
public:
|
public:
|
||||||
static sk_sp<ShiftChannelsEffectAdapter> Make(const skjson::ArrayValue& jprops,
|
static sk_sp<ShiftChannelsEffectAdapter> Make(const skjson::ArrayValue& jprops,
|
||||||
sk_sp<sksg::RenderNode> layer,
|
sk_sp<sksg::RenderNode> layer,
|
||||||
const AnimationBuilder* abuilder) {
|
const AnimationBuilder* abuilder) {
|
||||||
enum : size_t {
|
return sk_sp<ShiftChannelsEffectAdapter>(
|
||||||
kTakeAlphaFrom_Index = 0,
|
new ShiftChannelsEffectAdapter(jprops, std::move(layer), abuilder));
|
||||||
kTakeRedFrom_Index = 1,
|
|
||||||
kTakeGreenFrom_Index = 2,
|
|
||||||
kTakeBlueFrom_Index = 3,
|
|
||||||
|
|
||||||
kMax_Index = kTakeBlueFrom_Index
|
|
||||||
};
|
|
||||||
|
|
||||||
auto adapter = sk_sp<ShiftChannelsEffectAdapter>(
|
|
||||||
new ShiftChannelsEffectAdapter(std::move(layer)));
|
|
||||||
|
|
||||||
// Use raw captures, pending TheBigRefactoringToComeReallySoonNow.
|
|
||||||
auto* raw_adapter = adapter.get();
|
|
||||||
|
|
||||||
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops,
|
|
||||||
kTakeRedFrom_Index),
|
|
||||||
[raw_adapter](const ScalarValue& r) { raw_adapter->fR = r; });
|
|
||||||
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops,
|
|
||||||
kTakeGreenFrom_Index),
|
|
||||||
[raw_adapter](const ScalarValue& g) { raw_adapter->fG = g; });
|
|
||||||
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops,
|
|
||||||
kTakeBlueFrom_Index),
|
|
||||||
[raw_adapter](const ScalarValue& b) { raw_adapter->fB = b; });
|
|
||||||
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops,
|
|
||||||
kTakeAlphaFrom_Index),
|
|
||||||
[raw_adapter](const ScalarValue& a) { raw_adapter->fA = a; });
|
|
||||||
|
|
||||||
return adapter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sk_sp<sksg::ExternalColorFilter>& renderNode() const { return fColorFilter; }
|
const sk_sp<sksg::ExternalColorFilter>& renderNode() const { return fColorFilter; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit ShiftChannelsEffectAdapter(sk_sp<sksg::RenderNode> layer)
|
ShiftChannelsEffectAdapter(const skjson::ArrayValue& jprops,
|
||||||
: fColorFilter(sksg::ExternalColorFilter::Make(std::move(layer))) {}
|
sk_sp<sksg::RenderNode> layer,
|
||||||
|
const AnimationBuilder* abuilder)
|
||||||
|
: fColorFilter(sksg::ExternalColorFilter::Make(std::move(layer))) {
|
||||||
|
enum : size_t {
|
||||||
|
kTakeAlphaFrom_Index = 0,
|
||||||
|
kTakeRedFrom_Index = 1,
|
||||||
|
kTakeGreenFrom_Index = 2,
|
||||||
|
kTakeBlueFrom_Index = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
this->bind(*abuilder, EffectBuilder::GetPropValue(jprops, kTakeRedFrom_Index), &fR);
|
||||||
|
this->bind(*abuilder, EffectBuilder::GetPropValue(jprops, kTakeGreenFrom_Index), &fG);
|
||||||
|
this->bind(*abuilder, EffectBuilder::GetPropValue(jprops, kTakeBlueFrom_Index), &fB);
|
||||||
|
this->bind(*abuilder, EffectBuilder::GetPropValue(jprops, kTakeAlphaFrom_Index), &fA);
|
||||||
|
}
|
||||||
|
|
||||||
enum class Source : uint8_t {
|
enum class Source : uint8_t {
|
||||||
kAlpha = 1,
|
kAlpha = 1,
|
||||||
@ -127,8 +115,6 @@ private:
|
|||||||
fG = static_cast<float>(Source::kGreen),
|
fG = static_cast<float>(Source::kGreen),
|
||||||
fB = static_cast<float>(Source::kBlue),
|
fB = static_cast<float>(Source::kBlue),
|
||||||
fA = static_cast<float>(Source::kAlpha);
|
fA = static_cast<float>(Source::kAlpha);
|
||||||
|
|
||||||
using INHERITED = DiscardableAdaptorBase;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include "modules/skottie/src/text/RangeSelector.h"
|
#include "modules/skottie/src/text/RangeSelector.h"
|
||||||
|
|
||||||
#include "include/core/SkCubicMap.h"
|
#include "include/core/SkCubicMap.h"
|
||||||
|
#include "modules/skottie/src/Animator.h"
|
||||||
#include "modules/skottie/src/SkottieJson.h"
|
#include "modules/skottie/src/SkottieJson.h"
|
||||||
#include "modules/skottie/src/SkottieValue.h"
|
#include "modules/skottie/src/SkottieValue.h"
|
||||||
|
|
||||||
@ -229,7 +230,8 @@ static constexpr ShapeInfo gShapeInfo[] = {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
sk_sp<RangeSelector> RangeSelector::Make(const skjson::ObjectValue* jrange,
|
sk_sp<RangeSelector> RangeSelector::Make(const skjson::ObjectValue* jrange,
|
||||||
const AnimationBuilder* abuilder) {
|
const AnimationBuilder* abuilder,
|
||||||
|
AnimatablePropertyContainer* acontainer) {
|
||||||
if (!jrange) {
|
if (!jrange) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -280,39 +282,17 @@ sk_sp<RangeSelector> RangeSelector::Make(const skjson::ObjectValue* jrange,
|
|||||||
ParseEnum<Domain>(gDomainMap, (*jrange)["b" ], abuilder, "domain"),
|
ParseEnum<Domain>(gDomainMap, (*jrange)["b" ], abuilder, "domain"),
|
||||||
ParseEnum<Mode> (gModeMap , (*jrange)["m" ], abuilder, "mode" ),
|
ParseEnum<Mode> (gModeMap , (*jrange)["m" ], abuilder, "mode" ),
|
||||||
ParseEnum<Shape> (gShapeMap , (*jrange)["sh"], abuilder, "shape" )));
|
ParseEnum<Shape> (gShapeMap , (*jrange)["sh"], abuilder, "shape" )));
|
||||||
auto* raw_selector = selector.get();
|
|
||||||
|
|
||||||
abuilder->bindProperty<ScalarValue>((*jrange)["s"],
|
acontainer->bind(*abuilder, (*jrange)["s" ], &selector->fStart );
|
||||||
[raw_selector](const ScalarValue& s) {
|
acontainer->bind(*abuilder, (*jrange)["e" ], &selector->fEnd );
|
||||||
raw_selector->fStart = s;
|
acontainer->bind(*abuilder, (*jrange)["o" ], &selector->fOffset);
|
||||||
});
|
acontainer->bind(*abuilder, (*jrange)["a" ], &selector->fAmount);
|
||||||
abuilder->bindProperty<ScalarValue>((*jrange)["e"],
|
acontainer->bind(*abuilder, (*jrange)["ne"], &selector->fEaseLo);
|
||||||
[raw_selector](const ScalarValue& e) {
|
acontainer->bind(*abuilder, (*jrange)["xe"], &selector->fEaseHi);
|
||||||
raw_selector->fEnd = e;
|
|
||||||
});
|
|
||||||
abuilder->bindProperty<ScalarValue>((*jrange)["o"],
|
|
||||||
[raw_selector](const ScalarValue& o) {
|
|
||||||
raw_selector->fOffset = o;
|
|
||||||
});
|
|
||||||
abuilder->bindProperty<ScalarValue>((*jrange)["a"],
|
|
||||||
[raw_selector](const ScalarValue& a) {
|
|
||||||
raw_selector->fAmount = a;
|
|
||||||
});
|
|
||||||
abuilder->bindProperty<ScalarValue>((*jrange)["ne"],
|
|
||||||
[raw_selector](const ScalarValue& ne) {
|
|
||||||
raw_selector->fEaseLo = ne;
|
|
||||||
});
|
|
||||||
abuilder->bindProperty<ScalarValue>((*jrange)["xe"],
|
|
||||||
[raw_selector](const ScalarValue& xe) {
|
|
||||||
raw_selector->fEaseHi = xe;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Optional square "smoothness" prop.
|
// Optional square "smoothness" prop.
|
||||||
if (selector->fShape == Shape::kSquare) {
|
if (selector->fShape == Shape::kSquare) {
|
||||||
abuilder->bindProperty<ScalarValue>((*jrange)["sm"],
|
acontainer->bind(*abuilder, (*jrange)["sm" ], &selector->fSmoothness);
|
||||||
[selector](const ScalarValue& sm) {
|
|
||||||
selector->fSmoothness = sm;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return selector;
|
return selector;
|
||||||
|
@ -21,7 +21,8 @@ namespace internal {
|
|||||||
class RangeSelector final : public SkNVRefCnt<RangeSelector> {
|
class RangeSelector final : public SkNVRefCnt<RangeSelector> {
|
||||||
public:
|
public:
|
||||||
static sk_sp<RangeSelector> Make(const skjson::ObjectValue*,
|
static sk_sp<RangeSelector> Make(const skjson::ObjectValue*,
|
||||||
const AnimationBuilder*);
|
const AnimationBuilder*,
|
||||||
|
AnimatablePropertyContainer*);
|
||||||
|
|
||||||
enum class Units : uint8_t {
|
enum class Units : uint8_t {
|
||||||
kPercentage, // values are percentages of domain size
|
kPercentage, // values are percentages of domain size
|
||||||
|
@ -60,38 +60,27 @@ sk_sp<TextAdapter> TextAdapter::Make(const skjson::ObjectValue& jlayer,
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<sk_sp<TextAnimator>> animators;
|
auto adapter = sk_sp<TextAdapter>(new TextAdapter(std::move(fontmgr), std::move(logger)));
|
||||||
|
adapter->bind(*abuilder, jd, &adapter->fText.fCurrentValue);
|
||||||
|
|
||||||
if (const skjson::ArrayValue* janimators = (*jt)["a"]) {
|
if (const skjson::ArrayValue* janimators = (*jt)["a"]) {
|
||||||
animators.reserve(janimators->size());
|
adapter->fAnimators.reserve(janimators->size());
|
||||||
|
|
||||||
for (const skjson::ObjectValue* janimator : *janimators) {
|
for (const skjson::ObjectValue* janimator : *janimators) {
|
||||||
if (auto animator = TextAnimator::Make(janimator, abuilder)) {
|
if (auto animator = TextAnimator::Make(janimator, abuilder, adapter.get())) {
|
||||||
animators.push_back(std::move(animator));
|
adapter->fAnimators.push_back(std::move(animator));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto adapter = sk_sp<TextAdapter>(new TextAdapter(std::move(fontmgr),
|
|
||||||
std::move(logger),
|
|
||||||
std::move(animators)));
|
|
||||||
auto* raw_adapter = adapter.get();
|
|
||||||
|
|
||||||
abuilder->bindProperty<TextValue>(*jd,
|
|
||||||
[raw_adapter] (const TextValue& txt) {
|
|
||||||
raw_adapter->setText(txt);
|
|
||||||
});
|
|
||||||
|
|
||||||
abuilder->dispatchTextProperty(adapter);
|
abuilder->dispatchTextProperty(adapter);
|
||||||
|
|
||||||
return adapter;
|
return adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextAdapter::TextAdapter(sk_sp<SkFontMgr> fontmgr,
|
TextAdapter::TextAdapter(sk_sp<SkFontMgr> fontmgr, sk_sp<Logger> logger)
|
||||||
sk_sp<Logger> logger,
|
|
||||||
std::vector<sk_sp<TextAnimator>>&& animators)
|
|
||||||
: fRoot(sksg::Group::Make())
|
: fRoot(sksg::Group::Make())
|
||||||
, fFontMgr(std::move(fontmgr))
|
, fFontMgr(std::move(fontmgr))
|
||||||
, fAnimators(std::move(animators))
|
|
||||||
, fLogger(std::move(logger)) {}
|
, fLogger(std::move(logger)) {}
|
||||||
|
|
||||||
TextAdapter::~TextAdapter() = default;
|
TextAdapter::~TextAdapter() = default;
|
||||||
@ -114,17 +103,17 @@ void TextAdapter::addFragment(const Shaper::Fragment& frag) {
|
|||||||
frag.fPos.y()));
|
frag.fPos.y()));
|
||||||
|
|
||||||
std::vector<sk_sp<sksg::RenderNode>> draws;
|
std::vector<sk_sp<sksg::RenderNode>> draws;
|
||||||
draws.reserve(static_cast<size_t>(fText.fHasFill) + static_cast<size_t>(fText.fHasStroke));
|
draws.reserve(static_cast<size_t>(fText->fHasFill) + static_cast<size_t>(fText->fHasStroke));
|
||||||
|
|
||||||
SkASSERT(fText.fHasFill || fText.fHasStroke);
|
SkASSERT(fText->fHasFill || fText->fHasStroke);
|
||||||
|
|
||||||
if (fText.fHasFill) {
|
if (fText->fHasFill) {
|
||||||
rec.fFillColorNode = sksg::Color::Make(fText.fFillColor);
|
rec.fFillColorNode = sksg::Color::Make(fText->fFillColor);
|
||||||
rec.fFillColorNode->setAntiAlias(true);
|
rec.fFillColorNode->setAntiAlias(true);
|
||||||
draws.push_back(sksg::Draw::Make(blob_node, rec.fFillColorNode));
|
draws.push_back(sksg::Draw::Make(blob_node, rec.fFillColorNode));
|
||||||
}
|
}
|
||||||
if (fText.fHasStroke) {
|
if (fText->fHasStroke) {
|
||||||
rec.fStrokeColorNode = sksg::Color::Make(fText.fStrokeColor);
|
rec.fStrokeColorNode = sksg::Color::Make(fText->fStrokeColor);
|
||||||
rec.fStrokeColorNode->setAntiAlias(true);
|
rec.fStrokeColorNode->setAntiAlias(true);
|
||||||
rec.fStrokeColorNode->setStyle(SkPaint::kStroke_Style);
|
rec.fStrokeColorNode->setStyle(SkPaint::kStroke_Style);
|
||||||
draws.push_back(sksg::Draw::Make(blob_node, rec.fStrokeColorNode));
|
draws.push_back(sksg::Draw::Make(blob_node, rec.fStrokeColorNode));
|
||||||
@ -187,28 +176,25 @@ void TextAdapter::buildDomainMaps(const Shaper::Result& shape_result) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TextAdapter::setText(const TextValue& txt) {
|
void TextAdapter::setText(const TextValue& txt) {
|
||||||
if (txt != fText) {
|
fText.fCurrentValue = txt;
|
||||||
fText = txt;
|
|
||||||
fTextDirty = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextAdapter::reshape() {
|
void TextAdapter::reshape() {
|
||||||
const Shaper::TextDesc text_desc = {
|
const Shaper::TextDesc text_desc = {
|
||||||
fText.fTypeface,
|
fText->fTypeface,
|
||||||
fText.fTextSize,
|
fText->fTextSize,
|
||||||
fText.fLineHeight,
|
fText->fLineHeight,
|
||||||
fText.fAscent,
|
fText->fAscent,
|
||||||
fText.fHAlign,
|
fText->fHAlign,
|
||||||
fText.fVAlign,
|
fText->fVAlign,
|
||||||
fAnimators.empty() ? Shaper::Flags::kNone : Shaper::Flags::kFragmentGlyphs,
|
fAnimators.empty() ? Shaper::Flags::kNone : Shaper::Flags::kFragmentGlyphs,
|
||||||
};
|
};
|
||||||
const auto shape_result = Shaper::Shape(fText.fText, text_desc, fText.fBox, fFontMgr);
|
const auto shape_result = Shaper::Shape(fText->fText, text_desc, fText->fBox, fFontMgr);
|
||||||
|
|
||||||
if (fLogger && shape_result.fMissingGlyphCount > 0) {
|
if (fLogger && shape_result.fMissingGlyphCount > 0) {
|
||||||
const auto msg = SkStringPrintf("Missing %zu glyphs for '%s'.",
|
const auto msg = SkStringPrintf("Missing %zu glyphs for '%s'.",
|
||||||
shape_result.fMissingGlyphCount,
|
shape_result.fMissingGlyphCount,
|
||||||
fText.fText.c_str());
|
fText->fText.c_str());
|
||||||
fLogger->log(Logger::Level::kWarning, msg.c_str());
|
fLogger->log(Logger::Level::kWarning, msg.c_str());
|
||||||
|
|
||||||
// This may trigger repeatedly when the text is animating.
|
// This may trigger repeatedly when the text is animating.
|
||||||
@ -251,13 +237,12 @@ void TextAdapter::reshape() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TextAdapter::onSync() {
|
void TextAdapter::onSync() {
|
||||||
if (!fText.fHasFill && !fText.fHasStroke) {
|
if (!fText->fHasFill && !fText->fHasStroke) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fTextDirty) {
|
if (fText.hasChanged()) {
|
||||||
this->reshape();
|
this->reshape();
|
||||||
fTextDirty = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fFragments.empty()) {
|
if (fFragments.empty()) {
|
||||||
@ -265,9 +250,9 @@ void TextAdapter::onSync() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Seed props from the current text value.
|
// Seed props from the current text value.
|
||||||
TextAnimator::AnimatedProps seed_props;
|
TextAnimator::ResolvedProps seed_props;
|
||||||
seed_props.fill_color = fText.fFillColor;
|
seed_props.fill_color = fText->fFillColor;
|
||||||
seed_props.stroke_color = fText.fStrokeColor;
|
seed_props.stroke_color = fText->fStrokeColor;
|
||||||
|
|
||||||
TextAnimator::ModulatorBuffer buf;
|
TextAnimator::ModulatorBuffer buf;
|
||||||
buf.resize(fFragments.size(), { seed_props, 0 });
|
buf.resize(fFragments.size(), { seed_props, 0 });
|
||||||
@ -299,7 +284,7 @@ void TextAdapter::onSync() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextAdapter::pushPropsToFragment(const TextAnimator::AnimatedProps& props,
|
void TextAdapter::pushPropsToFragment(const TextAnimator::ResolvedProps& props,
|
||||||
const FragmentRec& rec) const {
|
const FragmentRec& rec) const {
|
||||||
// TODO: share this with TransformAdapter2D?
|
// TODO: share this with TransformAdapter2D?
|
||||||
auto t = SkMatrix::MakeTrans(rec.fOrigin.x() + props.position.x(),
|
auto t = SkMatrix::MakeTrans(rec.fOrigin.x() + props.position.x(),
|
||||||
@ -347,7 +332,7 @@ void TextAdapter::adjustLineTracking(const TextAnimator::ModulatorBuffer& buf,
|
|||||||
return 0.0f;
|
return 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto align_offset = total_tracking * align_factor(fText.fHAlign);
|
const auto align_offset = total_tracking * align_factor(fText->fHAlign);
|
||||||
|
|
||||||
float tracking_acc = 0;
|
float tracking_acc = 0;
|
||||||
for (size_t i = line_span.fOffset; i < line_span.fOffset + line_span.fCount; ++i) {
|
for (size_t i = line_span.fOffset; i < line_span.fOffset + line_span.fCount; ++i) {
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
#ifndef SkottieTextAdapter_DEFINED
|
#ifndef SkottieTextAdapter_DEFINED
|
||||||
#define SkottieTextAdapter_DEFINED
|
#define SkottieTextAdapter_DEFINED
|
||||||
|
|
||||||
#include "modules/skottie/src/SkottieAdapter.h"
|
#include "modules/skottie/src/Animator.h"
|
||||||
#include "modules/skottie/src/text/SkottieShaper.h"
|
#include "modules/skottie/src/text/SkottieShaper.h"
|
||||||
#include "modules/skottie/src/text/TextAnimator.h"
|
#include "modules/skottie/src/text/TextAnimator.h"
|
||||||
#include "modules/skottie/src/text/TextValue.h"
|
#include "modules/skottie/src/text/TextValue.h"
|
||||||
@ -17,10 +17,16 @@
|
|||||||
|
|
||||||
class SkFontMgr;
|
class SkFontMgr;
|
||||||
|
|
||||||
|
namespace sksg {
|
||||||
|
class Group;
|
||||||
|
template <typename T>
|
||||||
|
class Matrix;
|
||||||
|
} // namespace sksg
|
||||||
|
|
||||||
namespace skottie {
|
namespace skottie {
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
class TextAdapter final : public DiscardableAdaptorBase {
|
class TextAdapter final : public AnimatablePropertyContainer {
|
||||||
public:
|
public:
|
||||||
static sk_sp<TextAdapter> Make(const skjson::ObjectValue&, const AnimationBuilder*,
|
static sk_sp<TextAdapter> Make(const skjson::ObjectValue&, const AnimationBuilder*,
|
||||||
sk_sp<SkFontMgr>, sk_sp<Logger>);
|
sk_sp<SkFontMgr>, sk_sp<Logger>);
|
||||||
@ -29,14 +35,14 @@ public:
|
|||||||
|
|
||||||
const sk_sp<sksg::Group>& renderNode() const { return fRoot; }
|
const sk_sp<sksg::Group>& renderNode() const { return fRoot; }
|
||||||
|
|
||||||
const TextValue& getText() const { return fText; }
|
const TextValue& getText() const { return fText.fCurrentValue; }
|
||||||
void setText(const TextValue&);
|
void setText(const TextValue&);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void onSync() override;
|
void onSync() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TextAdapter(sk_sp<SkFontMgr>, sk_sp<Logger>, std::vector<sk_sp<TextAnimator>>&&);
|
TextAdapter(sk_sp<SkFontMgr>, sk_sp<Logger>);
|
||||||
|
|
||||||
struct FragmentRec {
|
struct FragmentRec {
|
||||||
SkPoint fOrigin; // fragment position
|
SkPoint fOrigin; // fragment position
|
||||||
@ -50,22 +56,39 @@ private:
|
|||||||
void addFragment(const Shaper::Fragment&);
|
void addFragment(const Shaper::Fragment&);
|
||||||
void buildDomainMaps(const Shaper::Result&);
|
void buildDomainMaps(const Shaper::Result&);
|
||||||
|
|
||||||
void pushPropsToFragment(const TextAnimator::AnimatedProps&, const FragmentRec&) const;
|
void pushPropsToFragment(const TextAnimator::ResolvedProps&, const FragmentRec&) const;
|
||||||
|
|
||||||
void adjustLineTracking(const TextAnimator::ModulatorBuffer&,
|
void adjustLineTracking(const TextAnimator::ModulatorBuffer&,
|
||||||
const TextAnimator::DomainSpan&,
|
const TextAnimator::DomainSpan&,
|
||||||
float line_tracking) const;
|
float line_tracking) const;
|
||||||
|
|
||||||
const sk_sp<sksg::Group> fRoot;
|
const sk_sp<sksg::Group> fRoot;
|
||||||
const sk_sp<SkFontMgr> fFontMgr;
|
const sk_sp<SkFontMgr> fFontMgr;
|
||||||
const std::vector<sk_sp<TextAnimator>> fAnimators;
|
sk_sp<Logger> fLogger;
|
||||||
sk_sp<Logger> fLogger;
|
|
||||||
|
|
||||||
std::vector<FragmentRec> fFragments;
|
std::vector<sk_sp<TextAnimator>> fAnimators;
|
||||||
TextAnimator::DomainMaps fMaps;
|
std::vector<FragmentRec> fFragments;
|
||||||
|
TextAnimator::DomainMaps fMaps;
|
||||||
|
|
||||||
TextValue fText;
|
// Helps detect external value changes.
|
||||||
bool fTextDirty = true;
|
struct TextValueTracker {
|
||||||
|
TextValue fCurrentValue;
|
||||||
|
|
||||||
|
bool hasChanged() const {
|
||||||
|
if (fCurrentValue != fPrevValue) {
|
||||||
|
fPrevValue = fCurrentValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextValue* operator->() const { return &fCurrentValue; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable TextValue fPrevValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
TextValueTracker fText;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include "include/core/SkColor.h"
|
#include "include/core/SkColor.h"
|
||||||
#include "include/core/SkPoint.h"
|
#include "include/core/SkPoint.h"
|
||||||
#include "include/private/SkNx.h"
|
#include "include/private/SkNx.h"
|
||||||
|
#include "modules/skottie/src/Animator.h"
|
||||||
#include "modules/skottie/src/SkottieValue.h"
|
#include "modules/skottie/src/SkottieValue.h"
|
||||||
#include "modules/skottie/src/text/RangeSelector.h"
|
#include "modules/skottie/src/text/RangeSelector.h"
|
||||||
#include "src/utils/SkJSON.h"
|
#include "src/utils/SkJSON.h"
|
||||||
@ -58,7 +59,8 @@ namespace internal {
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
sk_sp<TextAnimator> TextAnimator::Make(const skjson::ObjectValue* janimator,
|
sk_sp<TextAnimator> TextAnimator::Make(const skjson::ObjectValue* janimator,
|
||||||
const AnimationBuilder* abuilder) {
|
const AnimationBuilder* abuilder,
|
||||||
|
AnimatablePropertyContainer* acontainer) {
|
||||||
if (!janimator) {
|
if (!janimator) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -75,18 +77,19 @@ sk_sp<TextAnimator> TextAnimator::Make(const skjson::ObjectValue* janimator,
|
|||||||
if (const skjson::ArrayValue* jselectors = (*janimator)["s"]) {
|
if (const skjson::ArrayValue* jselectors = (*janimator)["s"]) {
|
||||||
selectors.reserve(jselectors->size());
|
selectors.reserve(jselectors->size());
|
||||||
for (const skjson::ObjectValue* jselector : *jselectors) {
|
for (const skjson::ObjectValue* jselector : *jselectors) {
|
||||||
if (auto sel = RangeSelector::Make(*jselector, abuilder)) {
|
if (auto sel = RangeSelector::Make(*jselector, abuilder, acontainer)) {
|
||||||
selectors.push_back(std::move(sel));
|
selectors.push_back(std::move(sel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (auto sel = RangeSelector::Make((*janimator)["s"], abuilder)) {
|
if (auto sel = RangeSelector::Make((*janimator)["s"], abuilder, acontainer)) {
|
||||||
selectors.reserve(1);
|
selectors.reserve(1);
|
||||||
selectors.push_back(std::move(sel));
|
selectors.push_back(std::move(sel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sk_sp<TextAnimator>(new TextAnimator(std::move(selectors), *jprops, abuilder));
|
return sk_sp<TextAnimator>(
|
||||||
|
new TextAnimator(std::move(selectors), *jprops, abuilder, acontainer));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextAnimator::modulateProps(const DomainMaps& maps, ModulatorBuffer& buf) const {
|
void TextAnimator::modulateProps(const DomainMaps& maps, ModulatorBuffer& buf) const {
|
||||||
@ -109,15 +112,15 @@ void TextAnimator::modulateProps(const DomainMaps& maps, ModulatorBuffer& buf) c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextAnimator::AnimatedProps TextAnimator::modulateProps(const AnimatedProps& props,
|
TextAnimator::ResolvedProps TextAnimator::modulateProps(const ResolvedProps& props,
|
||||||
float amount) const {
|
float amount) const {
|
||||||
auto modulated_props = props;
|
auto modulated_props = props;
|
||||||
|
|
||||||
// Transform props compose.
|
// Transform props compose.
|
||||||
modulated_props.position += fTextProps.position * amount;
|
modulated_props.position += ValueTraits<VectorValue>::As<SkPoint>(fTextProps.position) * amount;
|
||||||
modulated_props.rotation += fTextProps.rotation * amount;
|
modulated_props.rotation += fTextProps.rotation * amount;
|
||||||
modulated_props.tracking += fTextProps.tracking * amount;
|
modulated_props.tracking += fTextProps.tracking * amount;
|
||||||
modulated_props.scale *= 1 + (fTextProps.scale - 1) * amount;
|
modulated_props.scale *= 1 + (fTextProps.scale * 0.01f - 1) * amount; // scale is 100-based
|
||||||
|
|
||||||
const auto lerp_color = [](SkColor c0, SkColor c1, float t) {
|
const auto lerp_color = [](SkColor c0, SkColor c1, float t) {
|
||||||
const auto c0_4f = SkNx_cast<float>(Sk4b::Load(&c0)),
|
const auto c0_4f = SkNx_cast<float>(Sk4b::Load(&c0)),
|
||||||
@ -132,56 +135,32 @@ TextAnimator::AnimatedProps TextAnimator::modulateProps(const AnimatedProps& pro
|
|||||||
// Colors and opacity are overridden, and use a clamped amount value.
|
// Colors and opacity are overridden, and use a clamped amount value.
|
||||||
const auto clamped_amount = std::max(amount, 0.0f);
|
const auto clamped_amount = std::max(amount, 0.0f);
|
||||||
if (fHasFillColor) {
|
if (fHasFillColor) {
|
||||||
modulated_props.fill_color = lerp_color(props.fill_color,
|
const auto fc = ValueTraits<VectorValue>::As<SkColor>(fTextProps.fill_color);
|
||||||
fTextProps.fill_color,
|
modulated_props.fill_color = lerp_color(props.fill_color, fc, clamped_amount);
|
||||||
clamped_amount);
|
|
||||||
}
|
}
|
||||||
if (fHasStrokeColor) {
|
if (fHasStrokeColor) {
|
||||||
modulated_props.stroke_color = lerp_color(props.stroke_color,
|
const auto sc = ValueTraits<VectorValue>::As<SkColor>(fTextProps.stroke_color);
|
||||||
fTextProps.stroke_color,
|
modulated_props.stroke_color = lerp_color(props.stroke_color, sc, clamped_amount);
|
||||||
clamped_amount);
|
|
||||||
}
|
}
|
||||||
modulated_props.opacity *= 1 + (fTextProps.opacity - 1) * clamped_amount;
|
modulated_props.opacity *= 1 + (fTextProps.opacity * 0.01f - 1) * clamped_amount; // 100-based
|
||||||
|
|
||||||
return modulated_props;
|
return modulated_props;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextAnimator::TextAnimator(std::vector<sk_sp<RangeSelector>>&& selectors,
|
TextAnimator::TextAnimator(std::vector<sk_sp<RangeSelector>>&& selectors,
|
||||||
const skjson::ObjectValue& jprops,
|
const skjson::ObjectValue& jprops,
|
||||||
const AnimationBuilder* abuilder)
|
const AnimationBuilder* abuilder,
|
||||||
|
AnimatablePropertyContainer* acontainer)
|
||||||
: fSelectors(std::move(selectors)) {
|
: fSelectors(std::move(selectors)) {
|
||||||
auto* animator = this;
|
|
||||||
|
|
||||||
abuilder->bindProperty<VectorValue>(jprops["p"],
|
acontainer->bind(*abuilder, jprops["p" ], &fTextProps.position);
|
||||||
[animator](const VectorValue& p) {
|
acontainer->bind(*abuilder, jprops["s" ], &fTextProps.scale );
|
||||||
animator->fTextProps.position = ValueTraits<VectorValue>::As<SkPoint>(p);
|
acontainer->bind(*abuilder, jprops["r" ], &fTextProps.rotation);
|
||||||
});
|
acontainer->bind(*abuilder, jprops["o" ], &fTextProps.opacity );
|
||||||
abuilder->bindProperty<ScalarValue>(jprops["s"],
|
acontainer->bind(*abuilder, jprops["t" ], &fTextProps.tracking);
|
||||||
[animator](const ScalarValue& s) {
|
|
||||||
// Scale is 100-based.
|
fHasFillColor = acontainer->bind(*abuilder, jprops["fc"], &fTextProps.fill_color );
|
||||||
animator->fTextProps.scale = s * 0.01f;
|
fHasStrokeColor = acontainer->bind(*abuilder, jprops["sc"], &fTextProps.stroke_color);
|
||||||
});
|
|
||||||
abuilder->bindProperty<ScalarValue>(jprops["r"],
|
|
||||||
[animator](const ScalarValue& r) {
|
|
||||||
animator->fTextProps.rotation = r;
|
|
||||||
});
|
|
||||||
fHasFillColor = abuilder->bindProperty<VectorValue>(jprops["fc"],
|
|
||||||
[animator](const VectorValue& fc) {
|
|
||||||
animator->fTextProps.fill_color = ValueTraits<VectorValue>::As<SkColor>(fc);
|
|
||||||
});
|
|
||||||
fHasStrokeColor = abuilder->bindProperty<VectorValue>(jprops["sc"],
|
|
||||||
[animator](const VectorValue& sc) {
|
|
||||||
animator->fTextProps.stroke_color = ValueTraits<VectorValue>::As<SkColor>(sc);
|
|
||||||
});
|
|
||||||
abuilder->bindProperty<ScalarValue>(jprops["o"],
|
|
||||||
[animator](const ScalarValue& o) {
|
|
||||||
// Opacity is 100-based.
|
|
||||||
animator->fTextProps.opacity = SkTPin<float>(o * 0.01f, 0, 1);
|
|
||||||
});
|
|
||||||
abuilder->bindProperty<ScalarValue>(jprops["t"],
|
|
||||||
[animator](const ScalarValue& t) {
|
|
||||||
animator->fTextProps.tracking = t;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "include/core/SkRefCnt.h"
|
#include "include/core/SkRefCnt.h"
|
||||||
#include "modules/skottie/src/SkottiePriv.h"
|
#include "modules/skottie/src/SkottiePriv.h"
|
||||||
|
#include "modules/skottie/src/SkottieValue.h"
|
||||||
#include "modules/sksg/include/SkSGScene.h"
|
#include "modules/sksg/include/SkSGScene.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -18,15 +19,28 @@
|
|||||||
namespace skottie {
|
namespace skottie {
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
|
class AnimatablePropertyContainer;
|
||||||
class AnimationBuilder;
|
class AnimationBuilder;
|
||||||
class RangeSelector;
|
class RangeSelector;
|
||||||
|
|
||||||
class TextAnimator final : public SkNVRefCnt<TextAnimator> {
|
class TextAnimator final : public SkNVRefCnt<TextAnimator> {
|
||||||
public:
|
public:
|
||||||
static sk_sp<TextAnimator> Make(const skjson::ObjectValue*,
|
static sk_sp<TextAnimator> Make(const skjson::ObjectValue*,
|
||||||
const AnimationBuilder*);
|
const AnimationBuilder*,
|
||||||
|
AnimatablePropertyContainer* acontainer);
|
||||||
|
|
||||||
|
// Direct mapping of AE properties.
|
||||||
struct AnimatedProps {
|
struct AnimatedProps {
|
||||||
|
VectorValue position,
|
||||||
|
fill_color,
|
||||||
|
stroke_color;
|
||||||
|
ScalarValue opacity = 100,
|
||||||
|
scale = 100,
|
||||||
|
rotation = 0,
|
||||||
|
tracking = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ResolvedProps {
|
||||||
SkPoint position = { 0, 0 };
|
SkPoint position = { 0, 0 };
|
||||||
float opacity = 1,
|
float opacity = 1,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
@ -37,7 +51,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct AnimatedPropsModulator {
|
struct AnimatedPropsModulator {
|
||||||
AnimatedProps props; // accumulates properties across *all* animators
|
ResolvedProps props; // accumulates properties across *all* animators
|
||||||
float coverage; // accumulates range selector coverage for a given animator
|
float coverage; // accumulates range selector coverage for a given animator
|
||||||
};
|
};
|
||||||
using ModulatorBuffer = std::vector<AnimatedPropsModulator>;
|
using ModulatorBuffer = std::vector<AnimatedPropsModulator>;
|
||||||
@ -61,11 +75,12 @@ public:
|
|||||||
void modulateProps(const DomainMaps&, ModulatorBuffer&) const;
|
void modulateProps(const DomainMaps&, ModulatorBuffer&) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TextAnimator(std::vector<sk_sp<RangeSelector>>&& selectors,
|
TextAnimator(std::vector<sk_sp<RangeSelector>>&&,
|
||||||
const skjson::ObjectValue& jprops,
|
const skjson::ObjectValue&,
|
||||||
const AnimationBuilder* abuilder);
|
const AnimationBuilder*,
|
||||||
|
AnimatablePropertyContainer*);
|
||||||
|
|
||||||
AnimatedProps modulateProps(const AnimatedProps&, float amount) const;
|
ResolvedProps modulateProps(const ResolvedProps&, float amount) const;
|
||||||
|
|
||||||
const std::vector<sk_sp<RangeSelector>> fSelectors;
|
const std::vector<sk_sp<RangeSelector>> fSelectors;
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ public:
|
|||||||
Animator(const Animator&) = delete;
|
Animator(const Animator&) = delete;
|
||||||
Animator& operator=(const Animator&) = delete;
|
Animator& operator=(const Animator&) = delete;
|
||||||
|
|
||||||
void tick(float t);
|
void tick(float t) { this->onTick(t); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Animator();
|
Animator();
|
||||||
|
@ -18,10 +18,6 @@ namespace sksg {
|
|||||||
Animator::Animator() = default;
|
Animator::Animator() = default;
|
||||||
Animator::~Animator() = default;
|
Animator::~Animator() = default;
|
||||||
|
|
||||||
void Animator::tick(float t) {
|
|
||||||
this->onTick(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
GroupAnimator::GroupAnimator(AnimatorList&& animators)
|
GroupAnimator::GroupAnimator(AnimatorList&& animators)
|
||||||
: fAnimators(std::move(animators)) {}
|
: fAnimators(std::move(animators)) {}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user