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