Support expressions for scalar values
Change-Id: I87c1ca4dd6d6f2e1e591eedc97ee7cb088039eab
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/436117
Commit-Queue: Avinash Parchuri <aparchur@google.com>
Auto-Submit: Kyle Carlstrom <kylecarlstrom@google.com>
Reviewed-by: Avinash Parchuri <aparchur@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skottie/BUILD.gn b/modules/skottie/BUILD.gn
index de79d0a..0f67f3f 100644
--- a/modules/skottie/BUILD.gn
+++ b/modules/skottie/BUILD.gn
@@ -55,6 +55,7 @@
sources = [
"src/SkottieTest.cpp",
"tests/AudioLayer.cpp",
+ "tests/Expression.cpp",
"tests/Image.cpp",
"tests/Keyframe.cpp",
"tests/Text.cpp",
diff --git a/modules/skottie/include/Skottie.h b/modules/skottie/include/Skottie.h
index eae0277..cc4c92a 100644
--- a/modules/skottie/include/Skottie.h
+++ b/modules/skottie/include/Skottie.h
@@ -53,6 +53,29 @@
virtual void log(Level, const char message[], const char* json = nullptr);
};
+// Evaluates AE expressions.
+template <class T>
+class SK_API ExpressionEvaluator : public SkRefCnt {
+public:
+ // Evaluate the expression at the current time.
+ virtual T evaluate(float t) = 0;
+};
+
+/**
+ * Creates ExpressionEvaluators to evaluate AE expressions and return the results.
+ */
+class SK_API ExpressionManager : public SkRefCnt {
+public:
+ virtual sk_sp<ExpressionEvaluator<float>> createNumberExpressionEvaluator(
+ const char expression[]) = 0;
+
+ virtual sk_sp<ExpressionEvaluator<SkString>> createStringExpressionEvaluator(
+ const char expression[]) = 0;
+
+ virtual sk_sp<ExpressionEvaluator<std::vector<float>>> createArrayExpressionEvaluator(
+ const char expression[]) = 0;
+};
+
/**
* Interface for receiving AE composition markers at Animation build time.
*/
@@ -127,6 +150,12 @@
Builder& setPrecompInterceptor(sk_sp<PrecompInterceptor>);
/**
+ * Registers an ExpressionManager to evaluate AE expressions.
+ * If unspecified, expressions in the animation JSON will be ignored.
+ */
+ Builder& setExpressionManager(sk_sp<ExpressionManager>);
+
+ /**
* Animation factories.
*/
sk_sp<Animation> make(SkStream*);
@@ -142,6 +171,7 @@
sk_sp<Logger> fLogger;
sk_sp<MarkerObserver > fMarkerObserver;
sk_sp<PrecompInterceptor> fPrecompInterceptor;
+ sk_sp<ExpressionManager> fExpressionManager;
Stats fStats;
};
diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp
index 68b50e3..f1e62dd 100644
--- a/modules/skottie/src/Skottie.cpp
+++ b/modules/skottie/src/Skottie.cpp
@@ -161,6 +161,7 @@
AnimationBuilder::AnimationBuilder(sk_sp<ResourceProvider> rp, sk_sp<SkFontMgr> fontmgr,
sk_sp<PropertyObserver> pobserver, sk_sp<Logger> logger,
sk_sp<MarkerObserver> mobserver, sk_sp<PrecompInterceptor> pi,
+ sk_sp<ExpressionManager> expressionmgr,
Animation::Builder::Stats* stats,
const SkSize& comp_size, float duration, float framerate,
uint32_t flags)
@@ -170,6 +171,7 @@
, fLogger(std::move(logger))
, fMarkerObserver(std::move(mobserver))
, fPrecompInterceptor(std::move(pi))
+ , fExpressionManager(std::move(expressionmgr))
, fStats(stats)
, fCompSize(comp_size)
, fDuration(duration)
@@ -290,6 +292,10 @@
return dispatched;
}
+sk_sp<ExpressionManager> AnimationBuilder::expression_manager() const {
+ return fExpressionManager;
+}
+
void AnimationBuilder::AutoPropertyTracker::updateContext(PropertyObserver* observer,
const skjson::ObjectValue& obj) {
@@ -335,6 +341,11 @@
return *this;
}
+Animation::Builder& Animation::Builder::setExpressionManager(sk_sp<ExpressionManager> em) {
+ fExpressionManager = std::move(em);
+ return *this;
+}
+
sk_sp<Animation> Animation::Builder::make(SkStream* stream) {
if (!stream->hasLength()) {
// TODO: handle explicit buffering?
@@ -409,6 +420,7 @@
std::move(fLogger),
std::move(fMarkerObserver),
std::move(fPrecompInterceptor),
+ std::move(fExpressionManager),
&fStats, size, duration, fps, fFlags);
auto ainfo = builder.parse(json);
diff --git a/modules/skottie/src/SkottiePriv.h b/modules/skottie/src/SkottiePriv.h
index 9b5036a..4e94383 100644
--- a/modules/skottie/src/SkottiePriv.h
+++ b/modules/skottie/src/SkottiePriv.h
@@ -53,6 +53,7 @@
public:
AnimationBuilder(sk_sp<ResourceProvider>, sk_sp<SkFontMgr>, sk_sp<PropertyObserver>,
sk_sp<Logger>, sk_sp<MarkerObserver>, sk_sp<PrecompInterceptor>,
+ sk_sp<ExpressionManager>,
Animation::Builder::Stats*, const SkSize& comp_size,
float duration, float framerate, uint32_t flags);
@@ -175,6 +176,8 @@
bool dispatchTextProperty(const sk_sp<TextAdapter>&) const;
bool dispatchTransformProperty(const sk_sp<TransformAdapter2D>&) const;
+ sk_sp<ExpressionManager> expression_manager() const;
+
private:
friend class CompositionBuilder;
friend class LayerBuilder;
@@ -237,6 +240,7 @@
sk_sp<Logger> fLogger;
sk_sp<MarkerObserver> fMarkerObserver;
sk_sp<PrecompInterceptor> fPrecompInterceptor;
+ sk_sp<ExpressionManager> fExpressionManager;
Animation::Builder::Stats* fStats;
const SkSize fCompSize;
const float fDuration,
diff --git a/modules/skottie/src/animator/Animator.cpp b/modules/skottie/src/animator/Animator.cpp
index dc6ff08..8fdd5da 100644
--- a/modules/skottie/src/animator/Animator.cpp
+++ b/modules/skottie/src/animator/Animator.cpp
@@ -54,13 +54,25 @@
return false;
}
+ // Handle expressions on the property.
+ if (const skjson::StringValue* expr = (*jprop)["x"]) {
+ if (!abuilder.expression_manager()) {
+ abuilder.log(Logger::Level::kWarning, jprop,
+ "Expression encountered but ExpressionManager not provided.");
+ } else {
+ sk_sp<Animator> expression_animator = builder.makeFromExpression(
+ *abuilder.expression_manager(),
+ expr->begin());
+ if (expression_animator) {
+ fAnimators.push_back(std::move(expression_animator));
+ return true;
+ }
+ }
+ }
+
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)) {
diff --git a/modules/skottie/src/animator/KeyframeAnimator.h b/modules/skottie/src/animator/KeyframeAnimator.h
index 6196f7c..1fbac4c 100644
--- a/modules/skottie/src/animator/KeyframeAnimator.h
+++ b/modules/skottie/src/animator/KeyframeAnimator.h
@@ -11,6 +11,7 @@
#include "include/core/SkCubicMap.h"
#include "include/core/SkPoint.h"
#include "include/private/SkNoncopyable.h"
+#include "modules/skottie/include/Skottie.h"
#include "modules/skottie/src/animator/Animator.h"
#include <vector>
@@ -108,7 +109,10 @@
public:
virtual ~AnimatorBuilder();
- virtual sk_sp<KeyframeAnimator> makeFromKeyframes(const AnimationBuilder&, const skjson::ArrayValue&) = 0;
+ virtual sk_sp<KeyframeAnimator> makeFromKeyframes(const AnimationBuilder&,
+ const skjson::ArrayValue&) = 0;
+
+ virtual sk_sp<Animator> makeFromExpression(ExpressionManager&, const char*) = 0;
virtual bool parseValue(const AnimationBuilder&, const skjson::Value&) const = 0;
diff --git a/modules/skottie/src/animator/ScalarKeyframeAnimator.cpp b/modules/skottie/src/animator/ScalarKeyframeAnimator.cpp
index 5e4dbe7..f98fbf3 100644
--- a/modules/skottie/src/animator/ScalarKeyframeAnimator.cpp
+++ b/modules/skottie/src/animator/ScalarKeyframeAnimator.cpp
@@ -39,6 +39,28 @@
using INHERITED = KeyframeAnimator;
};
+ // Scalar specialization: stores scalar values (floats).
+class ScalarExpressionAnimator final : public Animator {
+public:
+ ScalarExpressionAnimator(sk_sp<ExpressionEvaluator<ScalarValue>> expression_evaluator,
+ ScalarValue* target_value)
+ : fExpressionEvaluator(std::move(expression_evaluator))
+ , fTarget(target_value) {}
+
+private:
+
+ StateChanged onSeek(float t) override {
+ auto old_value = *fTarget;
+
+ *fTarget = fExpressionEvaluator->evaluate(t);
+
+ return *fTarget != old_value;
+ }
+
+ sk_sp<ExpressionEvaluator<ScalarValue>> fExpressionEvaluator;
+ ScalarValue* fTarget;
+};
+
class ScalarAnimatorBuilder final : public AnimatorBuilder {
public:
explicit ScalarAnimatorBuilder(ScalarValue* target) : fTarget(target) {}
@@ -54,6 +76,13 @@
new ScalarKeyframeAnimator(std::move(fKFs), std::move(fCMs), fTarget));
}
+ sk_sp<Animator> makeFromExpression(ExpressionManager& em, const char* expr) override {
+ sk_sp<ExpressionEvaluator<ScalarValue>> expression_evaluator =
+ em.createNumberExpressionEvaluator(expr);
+ return sk_make_sp<ScalarExpressionAnimator>(expression_evaluator, fTarget);
+ }
+
+
bool parseValue(const AnimationBuilder&, const skjson::Value& jv) const override {
return Parse(jv, fTarget);
}
diff --git a/modules/skottie/src/animator/TextKeyframeAnimator.cpp b/modules/skottie/src/animator/TextKeyframeAnimator.cpp
index 6c031da..47582ae 100644
--- a/modules/skottie/src/animator/TextKeyframeAnimator.cpp
+++ b/modules/skottie/src/animator/TextKeyframeAnimator.cpp
@@ -61,6 +61,10 @@
fTarget));
}
+ sk_sp<Animator> makeFromExpression(ExpressionManager&, const char*) override {
+ return nullptr;
+ }
+
bool parseValue(const AnimationBuilder& abuilder, const skjson::Value& jv) const override {
return Parse(jv, abuilder, fTarget);
}
diff --git a/modules/skottie/src/animator/Vec2KeyframeAnimator.cpp b/modules/skottie/src/animator/Vec2KeyframeAnimator.cpp
index f328dda..0652ce6 100644
--- a/modules/skottie/src/animator/Vec2KeyframeAnimator.cpp
+++ b/modules/skottie/src/animator/Vec2KeyframeAnimator.cpp
@@ -125,6 +125,10 @@
fRotTarget));
}
+ sk_sp<Animator> makeFromExpression(ExpressionManager&, const char*) override {
+ return nullptr;
+ }
+
bool parseValue(const AnimationBuilder&, const skjson::Value& jv) const override {
return Parse(jv, fVecTarget);
}
diff --git a/modules/skottie/src/animator/VectorKeyframeAnimator.cpp b/modules/skottie/src/animator/VectorKeyframeAnimator.cpp
index 56f7d79..2fc197a 100644
--- a/modules/skottie/src/animator/VectorKeyframeAnimator.cpp
+++ b/modules/skottie/src/animator/VectorKeyframeAnimator.cpp
@@ -186,6 +186,10 @@
fTarget));
}
+sk_sp<Animator> VectorAnimatorBuilder::makeFromExpression(ExpressionManager&, const char*) {
+ return nullptr;
+}
+
bool VectorAnimatorBuilder::parseValue(const AnimationBuilder&,
const skjson::Value& jv) const {
size_t vec_len;
diff --git a/modules/skottie/src/animator/VectorKeyframeAnimator.h b/modules/skottie/src/animator/VectorKeyframeAnimator.h
index d253134..d4ba2d6 100644
--- a/modules/skottie/src/animator/VectorKeyframeAnimator.h
+++ b/modules/skottie/src/animator/VectorKeyframeAnimator.h
@@ -21,7 +21,10 @@
VectorAnimatorBuilder(std::vector<float>*, VectorLenParser, VectorDataParser);
- sk_sp<KeyframeAnimator> makeFromKeyframes(const AnimationBuilder&, const skjson::ArrayValue&) override;
+ sk_sp<KeyframeAnimator> makeFromKeyframes(const AnimationBuilder&,
+ const skjson::ArrayValue&) override;
+
+ sk_sp<Animator> makeFromExpression(ExpressionManager&, const char*) override;
private:
bool parseValue(const AnimationBuilder&, const skjson::Value&) const override;
diff --git a/modules/skottie/tests/Expression.cpp b/modules/skottie/tests/Expression.cpp
new file mode 100644
index 0000000..29d2177
--- /dev/null
+++ b/modules/skottie/tests/Expression.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2021 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/include/Skottie.h"
+#include "modules/skottie/include/SkottieProperty.h"
+#include "tests/Test.h"
+#include <iostream>
+
+using namespace skottie;
+
+namespace {
+
+class FakeScalarExpressionEvaluator : public ExpressionEvaluator<float> {
+public:
+ float evaluate(float t) override {
+ return 7.0f;
+ }
+};
+
+class FakeExpressionManager : public ExpressionManager {
+public:
+ sk_sp<ExpressionEvaluator<float>> createNumberExpressionEvaluator(
+ const char expression[]) override {
+ return sk_make_sp<FakeScalarExpressionEvaluator>();
+ }
+
+ sk_sp<ExpressionEvaluator<SkString>> createStringExpressionEvaluator(
+ const char expression[]) override {
+ return nullptr;
+ }
+
+ sk_sp<ExpressionEvaluator<std::vector<float>>> createArrayExpressionEvaluator(
+ const char expression[]) override {
+ return nullptr;
+ }
+};
+
+class FakePropertyObserver : public PropertyObserver {
+ public:
+ void onOpacityProperty(const char node_name[],
+ const LazyHandle<OpacityPropertyHandle>& opacity_handle) override {
+ opacity_handle_.reset(opacity_handle().release());
+ }
+
+ std::unique_ptr<OpacityPropertyHandle> opacity_handle_;
+};
+} // namespace
+
+DEF_TEST(Skottie_ScalarExpression, r) {
+ static constexpr char json[] =
+ R"({
+ "v": "5.2.1",
+ "w": 100,
+ "h": 100,
+ "fr": 10,
+ "ip": 0,
+ "op": 100,
+ "layers": [
+ {
+ "ip": 0,
+ "op": 100,
+ "ty": 1,
+ "nm": "My Layer",
+ "sr": 1,
+ "ks": {
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 11,
+ "x": "fake; return value is specified by the FakeScalarExpressionEvaluator."
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 10
+ },
+ "p": {
+ "a": 0,
+ "k": [
+ 50,
+ 50,
+ 0
+ ],
+ "ix": 2,
+ "l": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [
+ 50,
+ 50,
+ 0
+ ],
+ "ix": 1,
+ "l": 2
+ },
+ "s": {
+ "a": 0,
+ "k": [
+ 100,
+ 100,
+ 100
+ ],
+ "ix": 6,
+ "l": 2
+ }
+ },
+ "ao": 0,
+ "sw": 100,
+ "sh": 100,
+ "sc": "#51251d",
+ "st": 0,
+ "bm": 0
+ }
+ ]
+ })";
+
+ SkMemoryStream stream(json, strlen(json));
+
+ auto em = sk_make_sp<FakeExpressionManager>();
+ auto observer = sk_make_sp<FakePropertyObserver>();
+
+ auto anim = Animation::Builder()
+ .setExpressionManager(em)
+ .setPropertyObserver(observer)
+ .make(&stream);
+
+ REPORTER_ASSERT(r, anim);
+
+ anim->seekFrameTime(0);
+
+ REPORTER_ASSERT(r, SkScalarNearlyEqual(observer->opacity_handle_->get(), 7.0f));
+}
diff --git a/modules/skottie/tests/Keyframe.cpp b/modules/skottie/tests/Keyframe.cpp
index abd98c0..b534fc8 100644
--- a/modules/skottie/tests/Keyframe.cpp
+++ b/modules/skottie/tests/Keyframe.cpp
@@ -23,7 +23,7 @@
class MockProperty final : public AnimatablePropertyContainer {
public:
explicit MockProperty(const char* jprop) {
- AnimationBuilder abuilder(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+ AnimationBuilder abuilder(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
{100, 100}, 10, 1, 0);
skjson::DOM json_dom(jprop, strlen(jprop));