Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
PropertyObserver.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2023 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
14#include "tests/Test.h"
15#include "tools/ToolUtils.h"
17
18using namespace skottie;
19
20namespace {
21
22TextPropertyValue make_text_prop(const char* str) {
24
26 prop.fText = SkString(str);
27
28 return prop;
29}
30
31class MockPropertyObserver final : public PropertyObserver {
32public:
33 explicit MockPropertyObserver(bool override_props = false) : fOverrideProps(override_props) {}
34
35 struct ColorInfo {
36 SkString node_name;
37 std::unique_ptr<skottie::ColorPropertyHandle> handle;
38 };
39
40 struct OpacityInfo {
41 SkString node_name;
42 std::unique_ptr<skottie::OpacityPropertyHandle> handle;
43 };
44
45 struct TextInfo {
46 SkString node_name;
47 std::unique_ptr<skottie::TextPropertyHandle> handle;
48 };
49
50 struct TransformInfo {
51 SkString node_name;
52 std::unique_ptr<skottie::TransformPropertyHandle> handle;
53 };
54
55 void onColorProperty(const char node_name[],
57 auto prop_handle = lh();
58 if (fOverrideProps) {
59 static constexpr ColorPropertyValue kOverrideColor = 0xffffff00;
60 prop_handle->set(kOverrideColor);
61 }
62 fColors.push_back({SkString(node_name), std::move(prop_handle)});
63 fColorsWithFullKeypath.push_back({SkString(fCurrentNode.c_str()), lh()});
64 }
65
66 void onOpacityProperty(const char node_name[],
68 auto prop_handle = lh();
69 if (fOverrideProps) {
70 static constexpr OpacityPropertyValue kOverrideOpacity = 0.75f;
71 prop_handle->set(kOverrideOpacity);
72 }
73 fOpacities.push_back({SkString(node_name), std::move(prop_handle)});
74 }
75
76 void onTextProperty(const char node_name[],
78 auto prop_handle = lh();
79 if (fOverrideProps) {
80 static const TextPropertyValue kOverrideText = make_text_prop("foo");
81 prop_handle->set(kOverrideText);
82 }
83 fTexts.push_back({SkString(node_name), std::move(prop_handle)});
84 }
85
86 void onTransformProperty(const char node_name[],
88 auto prop_handle = lh();
89 if (fOverrideProps) {
90 static constexpr TransformPropertyValue kOverrideTransform = {
91 { 100, 100 },
92 { 200, 200 },
93 { 2, 2 },
94 45,
95 0,
96 0,
97 };
98 prop_handle->set(kOverrideTransform);
99 }
100 fTransforms.push_back({SkString(node_name), std::move(prop_handle)});
101 }
102
103 void onEnterNode(const char node_name[], PropertyObserver::NodeType node_type) override {
104 if (node_name == nullptr) {
105 return;
106 }
107 fCurrentNode = fCurrentNode.empty() ? node_name : fCurrentNode + "." + node_name;
108 }
109
110 void onLeavingNode(const char node_name[], PropertyObserver::NodeType node_type) override {
111 if (node_name == nullptr) {
112 return;
113 }
114 auto length = strlen(node_name);
115 fCurrentNode =
116 fCurrentNode.length() > length
117 ? fCurrentNode.substr(0, fCurrentNode.length() - strlen(node_name) - 1)
118 : "";
119 }
120
121 const std::vector<ColorInfo>& colors() const { return fColors; }
122 const std::vector<OpacityInfo>& opacities() const { return fOpacities; }
123 const std::vector<TextInfo>& texts() const { return fTexts; }
124 const std::vector<TransformInfo>& transforms() const { return fTransforms; }
125 const std::vector<ColorInfo>& colorsWithFullKeypath() const {
126 return fColorsWithFullKeypath;
127 }
128
129private:
130 const bool fOverrideProps;
131 std::vector<ColorInfo> fColors;
132 std::vector<OpacityInfo> fOpacities;
133 std::vector<TextInfo> fTexts;
134 std::vector<TransformInfo> fTransforms;
135 std::string fCurrentNode;
136 std::vector<ColorInfo> fColorsWithFullKeypath;
137};
138
139// Returns a single specified typeface for all requests.
140class MockFontMgr : public SkFontMgr {
141 public:
142 MockFontMgr(sk_sp<SkTypeface> test_font) : fTestFont(std::move(test_font)) {}
143
144 int onCountFamilies() const override { return 1; }
145 void onGetFamilyName(int index, SkString* familyName) const override {}
146 sk_sp<SkFontStyleSet> onCreateStyleSet(int index) const override { return nullptr; }
147 sk_sp<SkFontStyleSet> onMatchFamily(const char familyName[]) const override {
148 return nullptr;
149 }
150 sk_sp<SkTypeface> onMatchFamilyStyle(const char familyName[],
151 const SkFontStyle& fontStyle) const override {
152 return nullptr;
153 }
154 sk_sp<SkTypeface> onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle&,
155 const char* bcp47[], int bcp47Count,
156 SkUnichar character) const override {
157 return nullptr;
158 }
159 sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int ttcIndex) const override {
160 return fTestFont;
161 }
162 sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,
163 int ttcIndex) const override {
164 return fTestFont;
165 }
166 sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
167 const SkFontArguments&) const override {
168 return fTestFont;
169 }
170 sk_sp<SkTypeface> onMakeFromFile(const char path[], int ttcIndex) const override {
171 return fTestFont;
172 }
173 sk_sp<SkTypeface> onLegacyMakeTypeface(const char familyName[], SkFontStyle) const override {
174 return fTestFont;
175 }
176 private:
177 sk_sp<SkTypeface> fTestFont;
178};
179
180static const char gTestJson[] = R"({
181 "v": "5.2.1",
182 "w": 100,
183 "h": 100,
184 "fr": 1,
185 "ip": 0,
186 "op": 1,
187 "fonts": {
188 "list": [
189 {
190 "fName": "test_font",
191 "fFamily": "test-family",
192 "fStyle": "TestFontStyle"
193 }
194 ]
195 },
196 "layers": [
197 {
198 "ty": 4,
199 "nm": "layer_0",
200 "ind": 0,
201 "ip": 0,
202 "op": 1,
203 "ks": {
204 "o": { "a": 0, "k": 50 }
205 },
206 "ef": [{
207 "ef": [
208 {},
209 {},
210 { "v": { "a": 0, "k": [ 0, 1, 0 ] }},
211 {},
212 {},
213 {},
214 { "v": { "a": 0, "k": 1 }}
215 ],
216 "nm": "fill_effect_0",
217 "mn": "ADBE Fill",
218 "ty": 21
219 }],
220 "shapes": [
221 {
222 "ty": "el",
223 "nm": "geometry_0",
224 "p": { "a": 0, "k": [ 50, 50 ] },
225 "s": { "a": 0, "k": [ 50, 50 ] }
226 },
227 {
228 "ty": "fl",
229 "nm": "fill_0",
230 "c": { "a": 0, "k": [ 1, 0, 0] }
231 },
232 {
233 "ty": "tr",
234 "nm": "shape_transform_0",
235 "o": { "a": 0, "k": 100 },
236 "s": { "a": 0, "k": [ 50, 50 ] }
237 }
238 ]
239 },
240 {
241 "ty": 5,
242 "nm": "layer_1",
243 "ip": 0,
244 "op": 1,
245 "ks": {
246 "p": { "a": 0, "k": [25, 25] }
247 },
248 "t": {
249 "d": {
250 "k": [
251 {
252 "t": 0,
253 "s": {
254 "f": "test_font",
255 "s": 100,
256 "t": "inline_text",
257 "lh": 120,
258 "ls": 12
259 }
260 }
261 ]
262 }
263 }
264 }
265 ]
266 })";
267
268} // anonymous namespace
269
270DEF_TEST(Skottie_Props, reporter) {
273 auto test_font_manager = sk_make_sp<MockFontMgr>(test_typeface);
274
275 SkMemoryStream stream(gTestJson, strlen(gTestJson));
276 auto observer = sk_make_sp<MockPropertyObserver>();
277
278 auto animation = skottie::Animation::Builder()
279 .setPropertyObserver(observer)
280 .setFontManager(test_font_manager)
281 .make(&stream);
282
283 REPORTER_ASSERT(reporter, animation);
284
285 const auto& colors = observer->colors();
286 REPORTER_ASSERT(reporter, colors.size() == 2);
287 REPORTER_ASSERT(reporter, colors[0].node_name.equals("fill_0"));
288 REPORTER_ASSERT(reporter, colors[0].handle->get() == 0xffff0000);
289 REPORTER_ASSERT(reporter, colors[1].node_name.equals("fill_effect_0"));
290 REPORTER_ASSERT(reporter, colors[1].handle->get() == 0xff00ff00);
291
292 const auto& colorsWithFullKeypath = observer->colorsWithFullKeypath();
293 REPORTER_ASSERT(reporter, colorsWithFullKeypath.size() == 2);
294 REPORTER_ASSERT(reporter, colorsWithFullKeypath[0].node_name.equals("layer_0.fill_0"));
295 REPORTER_ASSERT(reporter, colorsWithFullKeypath[0].handle->get() == 0xffff0000);
296 REPORTER_ASSERT(reporter, colorsWithFullKeypath[1].node_name.equals("layer_0.fill_effect_0"));
297 REPORTER_ASSERT(reporter, colorsWithFullKeypath[1].handle->get() == 0xff00ff00);
298
299 const auto& opacities = observer->opacities();
300 REPORTER_ASSERT(reporter, opacities.size() == 3);
301 REPORTER_ASSERT(reporter, opacities[0].node_name.equals("shape_transform_0"));
302 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(opacities[0].handle->get(), 100));
303 REPORTER_ASSERT(reporter, opacities[1].node_name.equals("layer_0"));
304 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(opacities[1].handle->get(), 50));
305
306 const auto& transforms = observer->transforms();
307 REPORTER_ASSERT(reporter, transforms.size() == 3);
308 REPORTER_ASSERT(reporter, transforms[0].node_name.equals("layer_0"));
309 REPORTER_ASSERT(reporter, transforms[0].handle->get() == skottie::TransformPropertyValue({
310 SkPoint::Make(0, 0),
311 SkPoint::Make(0, 0),
312 SkVector::Make(100, 100),
313 0,
314 0,
315 0
316 }));
317 REPORTER_ASSERT(reporter, transforms[1].node_name.equals("layer_1"));
318 REPORTER_ASSERT(reporter, transforms[1].handle->get() == skottie::TransformPropertyValue({
319 SkPoint::Make(0, 0),
320 SkPoint::Make(25, 25),
321 SkVector::Make(100, 100),
322 0,
323 0,
324 0
325 }));
326 REPORTER_ASSERT(reporter, transforms[2].node_name.equals("shape_transform_0"));
327 REPORTER_ASSERT(reporter, transforms[2].handle->get() == skottie::TransformPropertyValue({
328 SkPoint::Make(0, 0),
329 SkPoint::Make(0, 0),
330 SkVector::Make(50, 50),
331 0,
332 0,
333 0
334 }));
335
336 const auto& texts = observer->texts();
337 REPORTER_ASSERT(reporter, texts.size() == 1);
338 REPORTER_ASSERT(reporter, texts[0].node_name.equals("layer_1"));
341 SkString("inline_text"),
342 100,
343 0, 100,
344 0,
345 120,
346 12,
347 0,
348 0,
350 Shaper::VAlign::kTopBaseline,
351 Shaper::ResizePolicy::kNone,
352 Shaper::LinebreakPolicy::kExplicit,
353 Shaper::Direction::kLTR,
354 Shaper::Capitalization::kNone,
358 TextPaintOrder::kFillStroke,
360 false,
361 false,
362 nullptr,
363 SkString(),
364 SkString("test-family")
365 });
366 REPORTER_ASSERT(reporter, texts[0].handle->get() == text_prop);
367 text_prop.fLocale = "custom_lc";
368 texts[0].handle->set(text_prop);
369 REPORTER_ASSERT(reporter, texts[0].handle->get() == text_prop);
370}
371
372DEF_TEST(Skottie_Props_Revalidation, reporter) {
375 auto test_font_manager = sk_make_sp<MockFontMgr>(test_typeface);
376
377 SkMemoryStream stream(gTestJson, strlen(gTestJson));
378 auto observer = sk_make_sp<MockPropertyObserver>(true);
379
380 auto animation = skottie::Animation::Builder()
381 .setPropertyObserver(observer)
382 .setFontManager(test_font_manager)
383 .make(&stream);
384
385 REPORTER_ASSERT(reporter, animation);
386
387 SkPictureRecorder recorder;
388 // Rendering without seek() should not trigger revalidation asserts.
389 animation->render(recorder.beginRecording(100, 100));
390
391 // Mutate all props.
392 for (const auto& c : observer->colors()) {
393 c.handle->set(SK_ColorGRAY);
394 }
395 for (const auto& o : observer->opacities()) {
396 o.handle->set(0.25f);
397 }
398 for (const auto& t : observer->texts()) {
399 t.handle->set(make_text_prop("bar"));
400 }
401 for (const auto& t : observer->transforms()) {
402 t.handle->set({
403 { 1000, 1000 },
404 { 2000, 2000 },
405 { 20, 20 },
406 5,
407 0,
408 0,
409 });
410 }
411
412 // Rendering without seek() after property mutation should not trigger revalidation asserts.
413 animation->render(recorder.beginRecording(100, 100));
414}
415
reporter
static void test_typeface(skiatest::Reporter *reporter)
constexpr SkColor SK_ColorTRANSPARENT
Definition SkColor.h:99
constexpr SkColor SK_ColorGRAY
Definition SkColor.h:113
static bool SkScalarNearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:107
int32_t SkUnichar
Definition SkTypes.h:175
#define DEF_TEST(name, reporter)
Definition Test.h:312
#define REPORTER_ASSERT(r, cond,...)
Definition Test.h:286
virtual void onGetFamilyName(int index, SkString *familyName) const =0
virtual sk_sp< SkTypeface > onMakeFromData(sk_sp< SkData >, int ttcIndex) const =0
virtual sk_sp< SkFontStyleSet > onCreateStyleSet(int index) const =0
virtual sk_sp< SkTypeface > onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle &, const char *bcp47[], int bcp47Count, SkUnichar character) const =0
virtual sk_sp< SkFontStyleSet > onMatchFamily(const char familyName[]) const =0
virtual sk_sp< SkTypeface > onMakeFromStreamIndex(std::unique_ptr< SkStreamAsset >, int ttcIndex) const =0
virtual sk_sp< SkTypeface > onMakeFromStreamArgs(std::unique_ptr< SkStreamAsset >, const SkFontArguments &) const =0
virtual sk_sp< SkTypeface > onMatchFamilyStyle(const char familyName[], const SkFontStyle &) const =0
virtual sk_sp< SkTypeface > onLegacyMakeTypeface(const char familyName[], SkFontStyle) const =0
virtual sk_sp< SkTypeface > onMakeFromFile(const char path[], int ttcIndex) const =0
virtual int onCountFamilies() const =0
@ kDefault_Join
equivalent to kMiter_Join
Definition SkPaint.h:363
SkCanvas * beginRecording(const SkRect &bounds, sk_sp< SkBBoxHierarchy > bbh)
Builder & setPropertyObserver(sk_sp< PropertyObserver >)
Definition Skottie.cpp:319
Builder & setFontManager(sk_sp< SkFontMgr >)
Definition Skottie.cpp:314
sk_sp< Animation > make(SkStream *)
Definition Skottie.cpp:349
virtual void onTransformProperty(const char node_name[], const LazyHandle< TransformPropertyHandle > &)
virtual void onLeavingNode(const char node_name[], NodeType node_type)
virtual void onOpacityProperty(const char node_name[], const LazyHandle< OpacityPropertyHandle > &)
virtual void onTextProperty(const char node_name[], const LazyHandle< TextPropertyHandle > &)
virtual void onEnterNode(const char node_name[], NodeType node_type)
std::function< std::unique_ptr< T >()> LazyHandle
virtual void onColorProperty(const char node_name[], const LazyHandle< ColorPropertyHandle > &)
size_t length
PODArray< SkColor > colors
Definition SkRecords.h:276
sk_sp< SkTypeface > DefaultPortableTypeface()
float OpacityPropertyValue
SkColor ColorPropertyValue
Definition ref_ptr.h:256
static SkScalar prop(SkScalar radius, SkScalar newSize, SkScalar oldSize)
Definition rrect.cpp:83
static constexpr SkRect MakeEmpty()
Definition SkRect.h:595
sk_sp< SkTypeface > fTypeface