• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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/src/SkottiePriv.h"
9 
10 #include "include/core/SkPath.h"
11 #include "modules/skottie/src/SkottieAdapter.h"
12 #include "modules/skottie/src/SkottieJson.h"
13 #include "modules/skottie/src/SkottieValue.h"
14 #include "modules/sksg/include/SkSGDraw.h"
15 #include "modules/sksg/include/SkSGGeometryTransform.h"
16 #include "modules/sksg/include/SkSGGradient.h"
17 #include "modules/sksg/include/SkSGGroup.h"
18 #include "modules/sksg/include/SkSGMerge.h"
19 #include "modules/sksg/include/SkSGPaint.h"
20 #include "modules/sksg/include/SkSGPath.h"
21 #include "modules/sksg/include/SkSGRect.h"
22 #include "modules/sksg/include/SkSGRenderEffect.h"
23 #include "modules/sksg/include/SkSGRoundEffect.h"
24 #include "modules/sksg/include/SkSGTransform.h"
25 #include "modules/sksg/include/SkSGTrimEffect.h"
26 #include "src/utils/SkJSON.h"
27 
28 #include <algorithm>
29 #include <iterator>
30 
31 namespace skottie {
32 namespace internal {
33 
34 namespace {
35 
AttachPathGeometry(const skjson::ObjectValue & jpath,const AnimationBuilder * abuilder)36 sk_sp<sksg::GeometryNode> AttachPathGeometry(const skjson::ObjectValue& jpath,
37                                              const AnimationBuilder* abuilder) {
38     return abuilder->attachPath(jpath["ks"]);
39 }
40 
AttachRRectGeometry(const skjson::ObjectValue & jrect,const AnimationBuilder * abuilder)41 sk_sp<sksg::GeometryNode> AttachRRectGeometry(const skjson::ObjectValue& jrect,
42                                               const AnimationBuilder* abuilder) {
43     auto rect_node = sksg::RRect::Make();
44     rect_node->setDirection(ParseDefault(jrect["d"], -1) == 3 ? SkPath::kCCW_Direction
45                                                               : SkPath::kCW_Direction);
46     rect_node->setInitialPointIndex(2); // starting point: (Right, Top - radius.y)
47 
48     auto adapter = sk_make_sp<RRectAdapter>(rect_node);
49 
50     auto p_attached = abuilder->bindProperty<VectorValue>(jrect["p"],
51         [adapter](const VectorValue& p) {
52             adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
53         });
54     auto s_attached = abuilder->bindProperty<VectorValue>(jrect["s"],
55         [adapter](const VectorValue& s) {
56             adapter->setSize(ValueTraits<VectorValue>::As<SkSize>(s));
57         });
58     auto r_attached = abuilder->bindProperty<ScalarValue>(jrect["r"],
59         [adapter](const ScalarValue& r) {
60             adapter->setRadius(SkSize::Make(r, r));
61         });
62 
63     if (!p_attached && !s_attached && !r_attached) {
64         return nullptr;
65     }
66 
67     return rect_node;
68 }
69 
AttachEllipseGeometry(const skjson::ObjectValue & jellipse,const AnimationBuilder * abuilder)70 sk_sp<sksg::GeometryNode> AttachEllipseGeometry(const skjson::ObjectValue& jellipse,
71                                                 const AnimationBuilder* abuilder) {
72     auto rect_node = sksg::RRect::Make();
73     rect_node->setDirection(ParseDefault(jellipse["d"], -1) == 3 ? SkPath::kCCW_Direction
74                                                                  : SkPath::kCW_Direction);
75     rect_node->setInitialPointIndex(1); // starting point: (Center, Top)
76 
77     auto adapter = sk_make_sp<RRectAdapter>(rect_node);
78 
79     auto p_attached = abuilder->bindProperty<VectorValue>(jellipse["p"],
80         [adapter](const VectorValue& p) {
81             adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
82         });
83     auto s_attached = abuilder->bindProperty<VectorValue>(jellipse["s"],
84         [adapter](const VectorValue& s) {
85             const auto sz = ValueTraits<VectorValue>::As<SkSize>(s);
86             adapter->setSize(sz);
87             adapter->setRadius(SkSize::Make(sz.width() / 2, sz.height() / 2));
88         });
89 
90     if (!p_attached && !s_attached) {
91         return nullptr;
92     }
93 
94     return rect_node;
95 }
96 
AttachPolystarGeometry(const skjson::ObjectValue & jstar,const AnimationBuilder * abuilder)97 sk_sp<sksg::GeometryNode> AttachPolystarGeometry(const skjson::ObjectValue& jstar,
98                                                  const AnimationBuilder* abuilder) {
99     static constexpr PolyStarAdapter::Type gTypes[] = {
100         PolyStarAdapter::Type::kStar, // "sy": 1
101         PolyStarAdapter::Type::kPoly, // "sy": 2
102     };
103 
104     const auto type = ParseDefault<size_t>(jstar["sy"], 0) - 1;
105     if (type >= SK_ARRAY_COUNT(gTypes)) {
106         abuilder->log(Logger::Level::kError, &jstar, "Unknown polystar type.");
107         return nullptr;
108     }
109 
110     auto path_node = sksg::Path::Make();
111     auto adapter = sk_make_sp<PolyStarAdapter>(path_node, gTypes[type]);
112 
113     abuilder->bindProperty<VectorValue>(jstar["p"],
114         [adapter](const VectorValue& p) {
115             adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
116         });
117     abuilder->bindProperty<ScalarValue>(jstar["pt"],
118         [adapter](const ScalarValue& pt) {
119             adapter->setPointCount(pt);
120         });
121     abuilder->bindProperty<ScalarValue>(jstar["ir"],
122         [adapter](const ScalarValue& ir) {
123             adapter->setInnerRadius(ir);
124         });
125     abuilder->bindProperty<ScalarValue>(jstar["or"],
126         [adapter](const ScalarValue& otr) {
127             adapter->setOuterRadius(otr);
128         });
129     abuilder->bindProperty<ScalarValue>(jstar["is"],
130         [adapter](const ScalarValue& is) {
131             adapter->setInnerRoundness(is);
132         });
133     abuilder->bindProperty<ScalarValue>(jstar["os"],
134         [adapter](const ScalarValue& os) {
135             adapter->setOuterRoundness(os);
136         });
137     abuilder->bindProperty<ScalarValue>(jstar["r"],
138         [adapter](const ScalarValue& r) {
139             adapter->setRotation(r);
140         });
141 
142     return path_node;
143 }
144 
AttachGradient(const skjson::ObjectValue & jgrad,const AnimationBuilder * abuilder)145 sk_sp<sksg::ShaderPaint> AttachGradient(const skjson::ObjectValue& jgrad,
146                                         const AnimationBuilder* abuilder) {
147     const skjson::ObjectValue* stops = jgrad["g"];
148     if (!stops)
149         return nullptr;
150 
151     const auto stopCount = ParseDefault<int>((*stops)["p"], -1);
152     if (stopCount < 0)
153         return nullptr;
154 
155     sk_sp<sksg::Gradient> gradient_node;
156     sk_sp<GradientAdapter> adapter;
157 
158     if (ParseDefault<int>(jgrad["t"], 1) == 1) {
159         auto linear_node = sksg::LinearGradient::Make();
160         adapter = sk_make_sp<LinearGradientAdapter>(linear_node, stopCount);
161         gradient_node = std::move(linear_node);
162     } else {
163         auto radial_node = sksg::RadialGradient::Make();
164         adapter = sk_make_sp<RadialGradientAdapter>(radial_node, stopCount);
165 
166         // TODO: highlight, angle
167         gradient_node = std::move(radial_node);
168     }
169 
170     abuilder->bindProperty<VectorValue>((*stops)["k"],
171         [adapter](const VectorValue& stops) {
172             adapter->setColorStops(stops);
173         });
174     abuilder->bindProperty<VectorValue>(jgrad["s"],
175         [adapter](const VectorValue& s) {
176             adapter->setStartPoint(ValueTraits<VectorValue>::As<SkPoint>(s));
177         });
178     abuilder->bindProperty<VectorValue>(jgrad["e"],
179         [adapter](const VectorValue& e) {
180             adapter->setEndPoint(ValueTraits<VectorValue>::As<SkPoint>(e));
181         });
182 
183     return sksg::ShaderPaint::Make(std::move(gradient_node));
184 }
185 
AttachPaint(const skjson::ObjectValue & jpaint,const AnimationBuilder * abuilder,sk_sp<sksg::PaintNode> paint_node)186 sk_sp<sksg::PaintNode> AttachPaint(const skjson::ObjectValue& jpaint,
187                                    const AnimationBuilder* abuilder,
188                                    sk_sp<sksg::PaintNode> paint_node) {
189     if (paint_node) {
190         paint_node->setAntiAlias(true);
191 
192         abuilder->bindProperty<ScalarValue>(jpaint["o"],
193             [paint_node](const ScalarValue& o) {
194                 // BM opacity is [0..100]
195                 paint_node->setOpacity(o * 0.01f);
196             });
197     }
198 
199     return paint_node;
200 }
201 
AttachStroke(const skjson::ObjectValue & jstroke,const AnimationBuilder * abuilder,sk_sp<sksg::PaintNode> stroke_node)202 sk_sp<sksg::PaintNode> AttachStroke(const skjson::ObjectValue& jstroke,
203                                     const AnimationBuilder* abuilder,
204                                     sk_sp<sksg::PaintNode> stroke_node) {
205     if (!stroke_node)
206         return nullptr;
207 
208     stroke_node->setStyle(SkPaint::kStroke_Style);
209 
210     abuilder->bindProperty<ScalarValue>(jstroke["w"],
211         [stroke_node](const ScalarValue& w) {
212             stroke_node->setStrokeWidth(w);
213         });
214 
215     stroke_node->setStrokeMiter(ParseDefault<SkScalar>(jstroke["ml"], 4.0f));
216 
217     static constexpr SkPaint::Join gJoins[] = {
218         SkPaint::kMiter_Join,
219         SkPaint::kRound_Join,
220         SkPaint::kBevel_Join,
221     };
222     stroke_node->setStrokeJoin(gJoins[SkTMin<size_t>(ParseDefault<size_t>(jstroke["lj"], 1) - 1,
223                                                      SK_ARRAY_COUNT(gJoins) - 1)]);
224 
225     static constexpr SkPaint::Cap gCaps[] = {
226         SkPaint::kButt_Cap,
227         SkPaint::kRound_Cap,
228         SkPaint::kSquare_Cap,
229     };
230     stroke_node->setStrokeCap(gCaps[SkTMin<size_t>(ParseDefault<size_t>(jstroke["lc"], 1) - 1,
231                                                    SK_ARRAY_COUNT(gCaps) - 1)]);
232 
233     return stroke_node;
234 }
235 
AttachColorFill(const skjson::ObjectValue & jfill,const AnimationBuilder * abuilder)236 sk_sp<sksg::PaintNode> AttachColorFill(const skjson::ObjectValue& jfill,
237                                        const AnimationBuilder* abuilder) {
238     return AttachPaint(jfill, abuilder, abuilder->attachColor(jfill, "c"));
239 }
240 
AttachGradientFill(const skjson::ObjectValue & jfill,const AnimationBuilder * abuilder)241 sk_sp<sksg::PaintNode> AttachGradientFill(const skjson::ObjectValue& jfill,
242                                           const AnimationBuilder* abuilder) {
243     return AttachPaint(jfill, abuilder, AttachGradient(jfill, abuilder));
244 }
245 
AttachColorStroke(const skjson::ObjectValue & jstroke,const AnimationBuilder * abuilder)246 sk_sp<sksg::PaintNode> AttachColorStroke(const skjson::ObjectValue& jstroke,
247                                          const AnimationBuilder* abuilder) {
248     return AttachStroke(jstroke, abuilder, AttachPaint(jstroke, abuilder,
249                                                        abuilder->attachColor(jstroke, "c")));
250 }
251 
AttachGradientStroke(const skjson::ObjectValue & jstroke,const AnimationBuilder * abuilder)252 sk_sp<sksg::PaintNode> AttachGradientStroke(const skjson::ObjectValue& jstroke,
253                                             const AnimationBuilder* abuilder) {
254     return AttachStroke(jstroke, abuilder, AttachPaint(jstroke, abuilder,
255                                                        AttachGradient(jstroke, abuilder)));
256 }
257 
Merge(std::vector<sk_sp<sksg::GeometryNode>> && geos,sksg::Merge::Mode mode)258 sk_sp<sksg::Merge> Merge(std::vector<sk_sp<sksg::GeometryNode>>&& geos, sksg::Merge::Mode mode) {
259     std::vector<sksg::Merge::Rec> merge_recs;
260     merge_recs.reserve(geos.size());
261 
262     for (auto& geo : geos) {
263         merge_recs.push_back(
264             {std::move(geo), merge_recs.empty() ? sksg::Merge::Mode::kMerge : mode});
265     }
266 
267     return sksg::Merge::Make(std::move(merge_recs));
268 }
269 
AttachMergeGeometryEffect(const skjson::ObjectValue & jmerge,const AnimationBuilder *,std::vector<sk_sp<sksg::GeometryNode>> && geos)270 std::vector<sk_sp<sksg::GeometryNode>> AttachMergeGeometryEffect(
271         const skjson::ObjectValue& jmerge, const AnimationBuilder*,
272         std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
273     static constexpr sksg::Merge::Mode gModes[] = {
274         sksg::Merge::Mode::kMerge,      // "mm": 1
275         sksg::Merge::Mode::kUnion,      // "mm": 2
276         sksg::Merge::Mode::kDifference, // "mm": 3
277         sksg::Merge::Mode::kIntersect,  // "mm": 4
278         sksg::Merge::Mode::kXOR      ,  // "mm": 5
279     };
280 
281     const auto mode = gModes[SkTMin<size_t>(ParseDefault<size_t>(jmerge["mm"], 1) - 1,
282                                             SK_ARRAY_COUNT(gModes) - 1)];
283 
284     std::vector<sk_sp<sksg::GeometryNode>> merged;
285     merged.push_back(Merge(std::move(geos), mode));
286 
287     return merged;
288 }
289 
AttachTrimGeometryEffect(const skjson::ObjectValue & jtrim,const AnimationBuilder * abuilder,std::vector<sk_sp<sksg::GeometryNode>> && geos)290 std::vector<sk_sp<sksg::GeometryNode>> AttachTrimGeometryEffect(
291         const skjson::ObjectValue& jtrim, const AnimationBuilder* abuilder,
292         std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
293 
294     enum class Mode {
295         kMerged,   // "m": 1
296         kSeparate, // "m": 2
297     } gModes[] = { Mode::kMerged, Mode::kSeparate };
298 
299     const auto mode = gModes[SkTMin<size_t>(ParseDefault<size_t>(jtrim["m"], 1) - 1,
300                                             SK_ARRAY_COUNT(gModes) - 1)];
301 
302     std::vector<sk_sp<sksg::GeometryNode>> inputs;
303     if (mode == Mode::kMerged) {
304         inputs.push_back(Merge(std::move(geos), sksg::Merge::Mode::kMerge));
305     } else {
306         inputs = std::move(geos);
307     }
308 
309     std::vector<sk_sp<sksg::GeometryNode>> trimmed;
310     trimmed.reserve(inputs.size());
311     for (const auto& i : inputs) {
312         auto trimEffect = sksg::TrimEffect::Make(i);
313         trimmed.push_back(trimEffect);
314 
315         const auto adapter = sk_make_sp<TrimEffectAdapter>(std::move(trimEffect));
316         abuilder->bindProperty<ScalarValue>(jtrim["s"],
317             [adapter](const ScalarValue& s) {
318                 adapter->setStart(s);
319             });
320         abuilder->bindProperty<ScalarValue>(jtrim["e"],
321             [adapter](const ScalarValue& e) {
322                 adapter->setEnd(e);
323             });
324         abuilder->bindProperty<ScalarValue>(jtrim["o"],
325             [adapter](const ScalarValue& o) {
326                 adapter->setOffset(o);
327             });
328     }
329 
330     return trimmed;
331 }
332 
AttachRoundGeometryEffect(const skjson::ObjectValue & jtrim,const AnimationBuilder * abuilder,std::vector<sk_sp<sksg::GeometryNode>> && geos)333 std::vector<sk_sp<sksg::GeometryNode>> AttachRoundGeometryEffect(
334         const skjson::ObjectValue& jtrim, const AnimationBuilder* abuilder,
335         std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
336 
337     std::vector<sk_sp<sksg::GeometryNode>> rounded;
338     rounded.reserve(geos.size());
339 
340     for (auto& g : geos) {
341         const auto roundEffect = sksg::RoundEffect::Make(std::move(g));
342         rounded.push_back(roundEffect);
343 
344         abuilder->bindProperty<ScalarValue>(jtrim["r"],
345             [roundEffect](const ScalarValue& r) {
346                 roundEffect->setRadius(r);
347             });
348     }
349 
350     return rounded;
351 }
352 
AttachRepeaterDrawEffect(const skjson::ObjectValue & jrepeater,const AnimationBuilder * abuilder,std::vector<sk_sp<sksg::RenderNode>> && draws)353 std::vector<sk_sp<sksg::RenderNode>> AttachRepeaterDrawEffect(
354         const skjson::ObjectValue& jrepeater,
355         const AnimationBuilder* abuilder,
356         std::vector<sk_sp<sksg::RenderNode>>&& draws) {
357 
358     std::vector<sk_sp<sksg::RenderNode>> repeater_draws;
359 
360     if (const skjson::ObjectValue* jtransform = jrepeater["tr"]) {
361         sk_sp<sksg::RenderNode> repeater_node;
362         if (draws.size() > 1) {
363             repeater_node = sksg::Group::Make(std::move(draws));
364         } else {
365             repeater_node = std::move(draws[0]);
366         }
367 
368         const auto repeater_composite = (ParseDefault(jrepeater["m"], 1) == 1)
369                 ? RepeaterAdapter::Composite::kAbove
370                 : RepeaterAdapter::Composite::kBelow;
371 
372         auto adapter = sk_make_sp<RepeaterAdapter>(std::move(repeater_node),
373                                                    repeater_composite);
374 
375         abuilder->bindProperty<ScalarValue>(jrepeater["c"],
376             [adapter](const ScalarValue& c) {
377                 adapter->setCount(c);
378             });
379         abuilder->bindProperty<ScalarValue>(jrepeater["o"],
380             [adapter](const ScalarValue& o) {
381                 adapter->setOffset(o);
382             });
383         abuilder->bindProperty<VectorValue>((*jtransform)["a"],
384             [adapter](const VectorValue& a) {
385                 adapter->setAnchorPoint(ValueTraits<VectorValue>::As<SkPoint>(a));
386             });
387         abuilder->bindProperty<VectorValue>((*jtransform)["p"],
388             [adapter](const VectorValue& p) {
389                 adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
390             });
391         abuilder->bindProperty<VectorValue>((*jtransform)["s"],
392             [adapter](const VectorValue& s) {
393                 adapter->setScale(ValueTraits<VectorValue>::As<SkVector>(s));
394             });
395         abuilder->bindProperty<ScalarValue>((*jtransform)["r"],
396             [adapter](const ScalarValue& r) {
397                 adapter->setRotation(r);
398             });
399         abuilder->bindProperty<ScalarValue>((*jtransform)["so"],
400             [adapter](const ScalarValue& so) {
401                 adapter->setStartOpacity(so);
402             });
403         abuilder->bindProperty<ScalarValue>((*jtransform)["eo"],
404             [adapter](const ScalarValue& eo) {
405                 adapter->setEndOpacity(eo);
406             });
407 
408         repeater_draws.reserve(1);
409         repeater_draws.push_back(adapter->root());
410     } else {
411         repeater_draws = std::move(draws);
412     }
413 
414     return repeater_draws;
415 }
416 
417 using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const skjson::ObjectValue&,
418                                                         const AnimationBuilder*);
419 static constexpr GeometryAttacherT gGeometryAttachers[] = {
420     AttachPathGeometry,
421     AttachRRectGeometry,
422     AttachEllipseGeometry,
423     AttachPolystarGeometry,
424 };
425 
426 using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const skjson::ObjectValue&,
427                                                   const AnimationBuilder*);
428 static constexpr PaintAttacherT gPaintAttachers[] = {
429     AttachColorFill,
430     AttachColorStroke,
431     AttachGradientFill,
432     AttachGradientStroke,
433 };
434 
435 using GeometryEffectAttacherT =
436     std::vector<sk_sp<sksg::GeometryNode>> (*)(const skjson::ObjectValue&,
437                                                const AnimationBuilder*,
438                                                std::vector<sk_sp<sksg::GeometryNode>>&&);
439 static constexpr GeometryEffectAttacherT gGeometryEffectAttachers[] = {
440     AttachMergeGeometryEffect,
441     AttachTrimGeometryEffect,
442     AttachRoundGeometryEffect,
443 };
444 
445 using DrawEffectAttacherT =
446     std::vector<sk_sp<sksg::RenderNode>> (*)(const skjson::ObjectValue&,
447                                              const AnimationBuilder*,
448                                              std::vector<sk_sp<sksg::RenderNode>>&&);
449 
450 static constexpr DrawEffectAttacherT gDrawEffectAttachers[] = {
451     AttachRepeaterDrawEffect,
452 };
453 
454 enum class ShapeType {
455     kGeometry,
456     kGeometryEffect,
457     kPaint,
458     kGroup,
459     kTransform,
460     kDrawEffect,
461 };
462 
463 struct ShapeInfo {
464     const char* fTypeString;
465     ShapeType   fShapeType;
466     uint32_t    fAttacherIndex; // index into respective attacher tables
467 };
468 
FindShapeInfo(const skjson::ObjectValue & jshape)469 const ShapeInfo* FindShapeInfo(const skjson::ObjectValue& jshape) {
470     static constexpr ShapeInfo gShapeInfo[] = {
471         { "el", ShapeType::kGeometry      , 2 }, // ellipse   -> AttachEllipseGeometry
472         { "fl", ShapeType::kPaint         , 0 }, // fill      -> AttachColorFill
473         { "gf", ShapeType::kPaint         , 2 }, // gfill     -> AttachGradientFill
474         { "gr", ShapeType::kGroup         , 0 }, // group     -> Inline handler
475         { "gs", ShapeType::kPaint         , 3 }, // gstroke   -> AttachGradientStroke
476         { "mm", ShapeType::kGeometryEffect, 0 }, // merge     -> AttachMergeGeometryEffect
477         { "rc", ShapeType::kGeometry      , 1 }, // rrect     -> AttachRRectGeometry
478         { "rd", ShapeType::kGeometryEffect, 2 }, // round     -> AttachRoundGeometryEffect
479         { "rp", ShapeType::kDrawEffect    , 0 }, // repeater  -> AttachRepeaterDrawEffect
480         { "sh", ShapeType::kGeometry      , 0 }, // shape     -> AttachPathGeometry
481         { "sr", ShapeType::kGeometry      , 3 }, // polystar  -> AttachPolyStarGeometry
482         { "st", ShapeType::kPaint         , 1 }, // stroke    -> AttachColorStroke
483         { "tm", ShapeType::kGeometryEffect, 1 }, // trim      -> AttachTrimGeometryEffect
484         { "tr", ShapeType::kTransform     , 0 }, // transform -> Inline handler
485     };
486 
487     const skjson::StringValue* type = jshape["ty"];
488     if (!type) {
489         return nullptr;
490     }
491 
492     const auto* info = bsearch(type->begin(),
493                                gShapeInfo,
494                                SK_ARRAY_COUNT(gShapeInfo),
495                                sizeof(ShapeInfo),
496                                [](const void* key, const void* info) {
497                                   return strcmp(static_cast<const char*>(key),
498                                                 static_cast<const ShapeInfo*>(info)->fTypeString);
499                                });
500 
501     return static_cast<const ShapeInfo*>(info);
502 }
503 
504 struct GeometryEffectRec {
505     const skjson::ObjectValue& fJson;
506     GeometryEffectAttacherT    fAttach;
507 };
508 
509 } // namespace
510 
511 struct AnimationBuilder::AttachShapeContext {
AttachShapeContextskottie::internal::AnimationBuilder::AttachShapeContext512     AttachShapeContext(std::vector<sk_sp<sksg::GeometryNode>>* geos,
513                        std::vector<GeometryEffectRec>* effects,
514                        size_t committedAnimators)
515         : fGeometryStack(geos)
516         , fGeometryEffectStack(effects)
517         , fCommittedAnimators(committedAnimators) {}
518 
519     std::vector<sk_sp<sksg::GeometryNode>>* fGeometryStack;
520     std::vector<GeometryEffectRec>*         fGeometryEffectStack;
521     size_t                                  fCommittedAnimators;
522 };
523 
attachShape(const skjson::ArrayValue * jshape,AttachShapeContext * ctx) const524 sk_sp<sksg::RenderNode> AnimationBuilder::attachShape(const skjson::ArrayValue* jshape,
525                                                       AttachShapeContext* ctx) const {
526     if (!jshape)
527         return nullptr;
528 
529     SkDEBUGCODE(const auto initialGeometryEffects = ctx->fGeometryEffectStack->size();)
530 
531     const skjson::ObjectValue* jtransform = nullptr;
532 
533     struct ShapeRec {
534         const skjson::ObjectValue& fJson;
535         const ShapeInfo&           fInfo;
536     };
537 
538     // First pass (bottom->top):
539     //
540     //   * pick up the group transform and opacity
541     //   * push local geometry effects onto the stack
542     //   * store recs for next pass
543     //
544     std::vector<ShapeRec> recs;
545     for (size_t i = 0; i < jshape->size(); ++i) {
546         const skjson::ObjectValue* shape = (*jshape)[jshape->size() - 1 - i];
547         if (!shape) continue;
548 
549         const auto* info = FindShapeInfo(*shape);
550         if (!info) {
551             this->log(Logger::Level::kError, &(*shape)["ty"], "Unknown shape.");
552             continue;
553         }
554 
555         if (ParseDefault<bool>((*shape)["hd"], false)) {
556             // Ignore hidden shapes.
557             continue;
558         }
559 
560         recs.push_back({ *shape, *info });
561 
562         switch (info->fShapeType) {
563         case ShapeType::kTransform:
564             // Just track the transform property for now -- we'll deal with it later.
565             jtransform = shape;
566             break;
567         case ShapeType::kGeometryEffect:
568             SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
569             ctx->fGeometryEffectStack->push_back(
570                 { *shape, gGeometryEffectAttachers[info->fAttacherIndex] });
571             break;
572         default:
573             break;
574         }
575     }
576 
577     // Second pass (top -> bottom, after 2x reverse):
578     //
579     //   * track local geometry
580     //   * emit local paints
581     //
582     std::vector<sk_sp<sksg::GeometryNode>> geos;
583     std::vector<sk_sp<sksg::RenderNode  >> draws;
584 
585     const auto add_draw = [this, &draws](sk_sp<sksg::RenderNode> draw, const ShapeRec& rec) {
586         // All draws can have an optional blend mode.
587         draws.push_back(this->attachBlendMode(rec.fJson, std::move(draw)));
588     };
589 
590     for (auto rec = recs.rbegin(); rec != recs.rend(); ++rec) {
591         const AutoPropertyTracker apt(this, rec->fJson);
592 
593         switch (rec->fInfo.fShapeType) {
594         case ShapeType::kGeometry: {
595             SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gGeometryAttachers));
596             if (auto geo = gGeometryAttachers[rec->fInfo.fAttacherIndex](rec->fJson, this)) {
597                 geos.push_back(std::move(geo));
598             }
599         } break;
600         case ShapeType::kGeometryEffect: {
601             // Apply the current effect and pop from the stack.
602             SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
603             if (!geos.empty()) {
604                 geos = gGeometryEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
605                                                                            this,
606                                                                            std::move(geos));
607             }
608 
609             SkASSERT(&ctx->fGeometryEffectStack->back().fJson == &rec->fJson);
610             SkASSERT(ctx->fGeometryEffectStack->back().fAttach ==
611                      gGeometryEffectAttachers[rec->fInfo.fAttacherIndex]);
612             ctx->fGeometryEffectStack->pop_back();
613         } break;
614         case ShapeType::kGroup: {
615             AttachShapeContext groupShapeCtx(&geos,
616                                              ctx->fGeometryEffectStack,
617                                              ctx->fCommittedAnimators);
618             if (auto subgroup = this->attachShape(rec->fJson["it"], &groupShapeCtx)) {
619                 add_draw(std::move(subgroup), *rec);
620                 SkASSERT(groupShapeCtx.fCommittedAnimators >= ctx->fCommittedAnimators);
621                 ctx->fCommittedAnimators = groupShapeCtx.fCommittedAnimators;
622             }
623         } break;
624         case ShapeType::kPaint: {
625             SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gPaintAttachers));
626             auto paint = gPaintAttachers[rec->fInfo.fAttacherIndex](rec->fJson, this);
627             if (!paint || geos.empty())
628                 break;
629 
630             auto drawGeos = geos;
631 
632             // Apply all pending effects from the stack.
633             for (auto it = ctx->fGeometryEffectStack->rbegin();
634                  it != ctx->fGeometryEffectStack->rend(); ++it) {
635                 drawGeos = it->fAttach(it->fJson, this, std::move(drawGeos));
636             }
637 
638             // If we still have multiple geos, reduce using 'merge'.
639             auto geo = drawGeos.size() > 1
640                 ? Merge(std::move(drawGeos), sksg::Merge::Mode::kMerge)
641                 : drawGeos[0];
642 
643             SkASSERT(geo);
644             add_draw(sksg::Draw::Make(std::move(geo), std::move(paint)), *rec);
645             ctx->fCommittedAnimators = fCurrentAnimatorScope->size();
646         } break;
647         case ShapeType::kDrawEffect: {
648             SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gDrawEffectAttachers));
649             if (!draws.empty()) {
650                 draws = gDrawEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
651                                                                         this,
652                                                                         std::move(draws));
653                 ctx->fCommittedAnimators = fCurrentAnimatorScope->size();
654             }
655         } break;
656         default:
657             break;
658         }
659     }
660 
661     // By now we should have popped all local geometry effects.
662     SkASSERT(ctx->fGeometryEffectStack->size() == initialGeometryEffects);
663 
664     sk_sp<sksg::RenderNode> shape_wrapper;
665     if (draws.size() == 1) {
666         // For a single draw, we don't need a group.
667         shape_wrapper = std::move(draws.front());
668     } else if (!draws.empty()) {
669         // Emit local draws reversed (bottom->top, per spec).
670         std::reverse(draws.begin(), draws.end());
671         draws.shrink_to_fit();
672 
673         // We need a group to dispatch multiple draws.
674         shape_wrapper = sksg::Group::Make(std::move(draws));
675     }
676 
677     sk_sp<sksg::Transform> shape_transform;
678     if (jtransform) {
679         const AutoPropertyTracker apt(this, *jtransform);
680 
681         // This is tricky due to the interaction with ctx->fCommittedAnimators: we want any
682         // animators related to tranform/opacity to be committed => they must be inserted in front
683         // of the dangling/uncommitted ones.
684         AutoScope ascope(this);
685 
686         if ((shape_transform = this->attachMatrix2D(*jtransform, nullptr))) {
687             shape_wrapper = sksg::TransformEffect::Make(std::move(shape_wrapper), shape_transform);
688         }
689         shape_wrapper = this->attachOpacity(*jtransform, std::move(shape_wrapper));
690 
691         auto local_scope = ascope.release();
692         fCurrentAnimatorScope->insert(fCurrentAnimatorScope->begin() + ctx->fCommittedAnimators,
693                                       std::make_move_iterator(local_scope.begin()),
694                                       std::make_move_iterator(local_scope.end()));
695         ctx->fCommittedAnimators += local_scope.size();
696     }
697 
698     // Push transformed local geometries to parent list, for subsequent paints.
699     for (auto& geo : geos) {
700         ctx->fGeometryStack->push_back(shape_transform
701             ? sksg::GeometryTransform::Make(std::move(geo), shape_transform)
702             : std::move(geo));
703     }
704 
705     return shape_wrapper;
706 }
707 
attachShapeLayer(const skjson::ObjectValue & layer,LayerInfo *) const708 sk_sp<sksg::RenderNode> AnimationBuilder::attachShapeLayer(const skjson::ObjectValue& layer,
709                                                            LayerInfo*) const {
710     std::vector<sk_sp<sksg::GeometryNode>> geometryStack;
711     std::vector<GeometryEffectRec> geometryEffectStack;
712     AttachShapeContext shapeCtx(&geometryStack, &geometryEffectStack,
713                                 fCurrentAnimatorScope->size());
714     auto shapeNode = this->attachShape(layer["shapes"], &shapeCtx);
715 
716     // Trim uncommitted animators: AttachShape consumes effects on the fly, and greedily attaches
717     // geometries => at the end, we can end up with unused geometries, which are nevertheless alive
718     // due to attached animators.  To avoid this, we track committed animators and discard the
719     // orphans here.
720     SkASSERT(shapeCtx.fCommittedAnimators <= fCurrentAnimatorScope->size());
721     fCurrentAnimatorScope->resize(shapeCtx.fCommittedAnimators);
722 
723     return shapeNode;
724 }
725 
726 } // namespace internal
727 } // namespace skottie
728