xref: /aosp_15_r20/external/skia/modules/skottie/tests/PropertyObserver.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 
8 #include "include/core/SkColor.h"
9 #include "include/core/SkFontMgr.h"
10 #include "include/core/SkPictureRecorder.h"
11 #include "include/core/SkStream.h"
12 #include "modules/skottie/include/Skottie.h"
13 #include "modules/skottie/include/SkottieProperty.h"
14 #include "tests/Test.h"
15 #include "tools/ToolUtils.h"
16 #include "tools/fonts/FontToolUtils.h"
17 
18 using namespace skottie;
19 
20 namespace {
21 
make_text_prop(const char * str)22 TextPropertyValue make_text_prop(const char* str) {
23     TextPropertyValue prop;
24 
25     prop.fTypeface = ToolUtils::DefaultPortableTypeface();
26     prop.fText     = SkString(str);
27 
28     return prop;
29 }
30 
31 class MockPropertyObserver final : public PropertyObserver {
32 public:
MockPropertyObserver(bool override_props=false)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 
onColorProperty(const char node_name[],const PropertyObserver::LazyHandle<ColorPropertyHandle> & lh)55     void onColorProperty(const char node_name[],
56             const PropertyObserver::LazyHandle<ColorPropertyHandle>& lh) override {
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 
onOpacityProperty(const char node_name[],const PropertyObserver::LazyHandle<OpacityPropertyHandle> & lh)66     void onOpacityProperty(const char node_name[],
67             const PropertyObserver::LazyHandle<OpacityPropertyHandle>& lh) override {
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 
onTextProperty(const char node_name[],const PropertyObserver::LazyHandle<TextPropertyHandle> & lh)76     void onTextProperty(const char node_name[],
77                         const PropertyObserver::LazyHandle<TextPropertyHandle>& lh) override {
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 
onTransformProperty(const char node_name[],const PropertyObserver::LazyHandle<TransformPropertyHandle> & lh)86     void onTransformProperty(const char node_name[],
87             const PropertyObserver::LazyHandle<TransformPropertyHandle>& lh) override {
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 
onEnterNode(const char node_name[],PropertyObserver::NodeType node_type)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 
onLeavingNode(const char node_name[],PropertyObserver::NodeType node_type)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 
colors() const121     const std::vector<ColorInfo>& colors() const { return fColors; }
opacities() const122     const std::vector<OpacityInfo>& opacities() const { return fOpacities; }
texts() const123     const std::vector<TextInfo>& texts() const { return fTexts; }
transforms() const124     const std::vector<TransformInfo>& transforms() const { return fTransforms; }
colorsWithFullKeypath() const125     const std::vector<ColorInfo>& colorsWithFullKeypath() const {
126         return fColorsWithFullKeypath;
127     }
128 
129 private:
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.
140 class MockFontMgr : public SkFontMgr {
141  public:
MockFontMgr(sk_sp<SkTypeface> test_font)142     MockFontMgr(sk_sp<SkTypeface> test_font) : fTestFont(std::move(test_font)) {}
143 
onCountFamilies() const144     int onCountFamilies() const override { return 1; }
onGetFamilyName(int index,SkString * familyName) const145     void onGetFamilyName(int index, SkString* familyName) const override {}
onCreateStyleSet(int index) const146     sk_sp<SkFontStyleSet> onCreateStyleSet(int index) const override { return nullptr; }
onMatchFamily(const char familyName[]) const147     sk_sp<SkFontStyleSet> onMatchFamily(const char familyName[]) const override {
148         return nullptr;
149     }
onMatchFamilyStyle(const char familyName[],const SkFontStyle & fontStyle) const150     sk_sp<SkTypeface> onMatchFamilyStyle(const char familyName[],
151                                          const SkFontStyle& fontStyle) const override {
152         return nullptr;
153     }
onMatchFamilyStyleCharacter(const char familyName[],const SkFontStyle &,const char * bcp47[],int bcp47Count,SkUnichar character) const154     sk_sp<SkTypeface> onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle&,
155                                                   const char* bcp47[], int bcp47Count,
156                                                   SkUnichar character) const override {
157         return nullptr;
158     }
onMakeFromData(sk_sp<SkData>,int ttcIndex) const159     sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int ttcIndex) const override {
160         return fTestFont;
161     }
onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,int ttcIndex) const162     sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,
163                                                 int ttcIndex) const override {
164         return fTestFont;
165     }
onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,const SkFontArguments &) const166     sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
167                                                const SkFontArguments&) const override {
168         return fTestFont;
169     }
onMakeFromFile(const char path[],int ttcIndex) const170     sk_sp<SkTypeface> onMakeFromFile(const char path[], int ttcIndex) const override {
171         return fTestFont;
172     }
onLegacyMakeTypeface(const char familyName[],SkFontStyle) const173     sk_sp<SkTypeface> onLegacyMakeTypeface(const char familyName[], SkFontStyle) const override {
174         return fTestFont;
175     }
176  private:
177     sk_sp<SkTypeface> fTestFont;
178 };
179 
180 static 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 
DEF_TEST(Skottie_Props,reporter)270 DEF_TEST(Skottie_Props, reporter) {
271     auto test_typeface = ToolUtils::DefaultPortableTypeface();
272     REPORTER_ASSERT(reporter, test_typeface);
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"));
339     skottie::TextPropertyValue text_prop({
340       test_typeface,
341       SkString("inline_text"),
342       100,
343       0, 100,
344       0,
345       120,
346       12,
347       0,
348       0,
349       SkTextUtils::kLeft_Align,
350       Shaper::VAlign::kTopBaseline,
351       Shaper::ResizePolicy::kNone,
352       Shaper::LinebreakPolicy::kExplicit,
353       Shaper::Direction::kLTR,
354       Shaper::Capitalization::kNone,
355       SkRect::MakeEmpty(),
356       SK_ColorTRANSPARENT,
357       SK_ColorTRANSPARENT,
358       TextPaintOrder::kFillStroke,
359       SkPaint::Join::kDefault_Join,
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 
DEF_TEST(Skottie_Props_Revalidation,reporter)372 DEF_TEST(Skottie_Props_Revalidation, reporter) {
373     auto test_typeface = ToolUtils::DefaultPortableTypeface();
374     REPORTER_ASSERT(reporter, test_typeface);
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 
416