1 /*
2 * Copyright 2020 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "modules/skottie/include/ExternalLayer.h"
9 #include "modules/skottie/src/SkottiePriv.h"
10 #include "modules/skottie/src/SkottieValue.h"
11 #include "modules/skottie/src/animator/Animator.h"
12 #include "src/utils/SkJSON.h"
13 #include "tests/Test.h"
14
15 #include <cmath>
16
17 using namespace skottie;
18 using namespace skottie::internal;
19
20 namespace {
21
22 template <typename T>
23 class MockProperty final : public AnimatablePropertyContainer {
24 public:
MockProperty(const char * jprop)25 explicit MockProperty(const char* jprop) {
26 AnimationBuilder abuilder(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
27 nullptr, nullptr,
28 {100, 100}, 10, 1, 0);
29 skjson::DOM json_dom(jprop, strlen(jprop));
30
31 fDidBind = this->bind(abuilder, json_dom.root(), &fValue);
32 }
33
operator bool() const34 explicit operator bool() const { return fDidBind; }
35
operator ()(float t)36 const T& operator()(float t) { this->seek(t); return fValue; }
37
38 private:
onSync()39 void onSync() override {}
40
41 T fValue = T();
42 bool fDidBind;
43 };
44
45 } // namespace
46
DEF_TEST(Skottie_Keyframe,reporter)47 DEF_TEST(Skottie_Keyframe, reporter) {
48 {
49 MockProperty<ScalarValue> prop(R"({})");
50 REPORTER_ASSERT(reporter, !prop);
51 }
52 {
53 MockProperty<ScalarValue> prop(R"({ "a": 1, "k": [] })");
54 REPORTER_ASSERT(reporter, !prop);
55 }
56 {
57 // New style
58 MockProperty<ScalarValue> prop(R"({
59 "a": 1,
60 "k": [
61 { "t": 1, "s": 1 },
62 { "t": 2, "s": 2 },
63 { "t": 3, "s": 4 }
64 ]
65 })");
66 REPORTER_ASSERT(reporter, prop);
67 REPORTER_ASSERT(reporter, !prop.isStatic());
68 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( -1), 1));
69 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 0), 1));
70 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 1), 1));
71 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(1.5), 1.5f));
72 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 2), 2));
73 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(2.5), 3));
74 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 3), 4));
75 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 4), 4));
76 }
77 {
78 // New style hold (hard stops)
79 MockProperty<ScalarValue> prop(R"({
80 "a": 1,
81 "k": [
82 { "t": 1, "s": 1, "h": true },
83 { "t": 2, "s": 2, "h": true },
84 { "t": 3, "s": 4, "h": true }
85 ]
86 })");
87 REPORTER_ASSERT(reporter, prop);
88 REPORTER_ASSERT(reporter, !prop.isStatic());
89 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(0 ), 1));
90 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(1 ), 1));
91 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(1.5), 1));
92 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(std::nextafter(2.f, 0.f)), 1));
93 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(2 ), 2));
94 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(2.5), 2));
95 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(std::nextafter(3.f, 0.f)), 2));
96 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(3 ), 4));
97 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(4 ), 4));
98 }
99 {
100 // Legacy style
101 MockProperty<ScalarValue> prop(R"({
102 "a": 1,
103 "k": [
104 { "t": 1, "s": 1, "e": 2 },
105 { "t": 2, "s": 2, "e": 4 },
106 { "t": 3 }
107 ]
108 })");
109 REPORTER_ASSERT(reporter, prop);
110 REPORTER_ASSERT(reporter, !prop.isStatic());
111 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(-1), 1));
112 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 0), 1));
113 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 1 ), 1));
114 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 1.5), 1.5f));
115 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 2 ), 2));
116 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 2.5), 3));
117 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 3 ), 4));
118 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 4 ), 4));
119 }
120 {
121 // Legacy style hold (hard stops)
122 MockProperty<ScalarValue> prop(R"({
123 "a": 1,
124 "k": [
125 { "t": 1, "s": 1, "e": 2, "h": true },
126 { "t": 2, "s": 2, "e": 4, "h": true },
127 { "t": 3 }
128 ]
129 })");
130 REPORTER_ASSERT(reporter, prop);
131 REPORTER_ASSERT(reporter, !prop.isStatic());
132 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(0 ), 1));
133 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(1 ), 1));
134 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(1.5), 1));
135 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(std::nextafter(2.f, 0.f)), 1));
136 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(2 ), 2));
137 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(2.5), 2));
138 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(std::nextafter(3.f, 0.f)), 2));
139 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(3 ), 4));
140 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(4 ), 4));
141 }
142 {
143 // Static scalar prop (all equal keyframes, using float kf Value)
144 MockProperty<ScalarValue> prop(R"({
145 "a": 1,
146 "k": [
147 { "t": 1, "s": 42, "e": 42 },
148 { "t": 2, "s": 42, "e": 42 },
149 { "t": 3 }
150 ]
151 })");
152 REPORTER_ASSERT(reporter, prop);
153 REPORTER_ASSERT(reporter, prop.isStatic());
154 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(0), 42));
155 }
156 {
157 // Static vector prop (all equal keyframes, using uint32 kf Value)
158 MockProperty<Vec2Value> prop(R"({
159 "a": 1,
160 "k": [
161 { "t": 1, "s": [4,2], "e": [4,2] },
162 { "t": 2, "s": [4,2], "e": [4,2] },
163 { "t": 3 }
164 ]
165 })");
166 REPORTER_ASSERT(reporter, prop);
167 REPORTER_ASSERT(reporter, prop.isStatic());
168 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(0).x, 4));
169 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(0).y, 2));
170 }
171 {
172 // Spatial interpolation [100,100]->[200,200], with supernormal easing:
173 // https://cubic-bezier.com/#.5,-0.5,.5,1.5
174 MockProperty<Vec2Value> prop(R"({
175 "a": 1,
176 "k": [
177 { "t": 0, "s": [100,100],
178 "o":{"x":[0.5], "y":[-0.5]}, "i":{"x":[0.5], "y":[1.5]},
179 "to": [10,15], "ti": [-10,-5]
180 },
181 { "t": 1, "s": [200,200]
182 }
183 ]
184 })");
185 REPORTER_ASSERT(reporter, prop);
186 REPORTER_ASSERT(reporter, !prop.isStatic());
187
188 // Not linear.
189 REPORTER_ASSERT(reporter, !SkScalarNearlyEqual(prop(0.5f).x, 150.f));
190 REPORTER_ASSERT(reporter, !SkScalarNearlyEqual(prop(0.5f).y, 150.f));
191
192 // Subnormal region triggers extrapolation.
193 REPORTER_ASSERT(reporter, prop(0.15f).x < 100);
194 REPORTER_ASSERT(reporter, prop(0.15f).y < 100);
195
196 // Supernormal region triggers extrapolation.
197 REPORTER_ASSERT(reporter, prop(0.85f).x > 200);
198 REPORTER_ASSERT(reporter, prop(0.85f).y > 200);
199 }
200 {
201 // Coincident keyframes (t == 1)
202 //
203 // Effective interpolation intervals:
204 // [0 .. 1) -> [100 .. 200)
205 // [1 .. 2) -> [300 .. 400)
206 //
207 // When more than 2 concident keyframes are present, only the first and last one count.
208 MockProperty<ScalarValue> prop(R"({
209 "a": 1,
210 "k": [
211 { "t": 0, "s": [100] },
212 { "t": 1, "s": [200] },
213 { "t": 1, "s": [1000] },
214 { "t": 1, "s": [300] },
215 { "t": 2, "s": [400] }
216 ]
217 })");
218 REPORTER_ASSERT(reporter, prop);
219 REPORTER_ASSERT(reporter, !prop.isStatic());
220
221 REPORTER_ASSERT(reporter, prop(0.9999f) > 100);
222 REPORTER_ASSERT(reporter, prop(0.9999f) < 200);
223 REPORTER_ASSERT(reporter, prop(1) == 300);
224 REPORTER_ASSERT(reporter, prop(1.0001f) > 300);
225 REPORTER_ASSERT(reporter, prop(1.0001f) < 400);
226 }
227 }
228