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