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