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/layers/shapelayer/ShapeLayer.h"
9
10 #include "include/private/base/SkAssert.h"
11 #include "include/private/base/SkDebug.h"
12 #include "modules/skottie/include/Skottie.h"
13 #include "modules/skottie/include/SkottieProperty.h"
14 #include "modules/skottie/src/SkottieJson.h"
15 #include "modules/skottie/src/SkottiePriv.h"
16 #include "modules/skottie/src/animator/Animator.h"
17 #include "modules/sksg/include/SkSGDraw.h"
18 #include "modules/sksg/include/SkSGGeometryEffect.h"
19 #include "modules/sksg/include/SkSGGeometryNode.h"
20 #include "modules/sksg/include/SkSGGroup.h"
21 #include "modules/sksg/include/SkSGMerge.h"
22 #include "modules/sksg/include/SkSGPaint.h"
23 #include "modules/sksg/include/SkSGPath.h" // IWYU pragma: keep
24 #include "modules/sksg/include/SkSGRenderNode.h"
25 #include "modules/sksg/include/SkSGTransform.h"
26 #include "src/utils/SkJSON.h"
27
28 #include <string.h>
29 #include <algorithm>
30 #include <cstdint>
31 #include <cstdlib>
32 #include <iterator>
33 #include <utility>
34
35 namespace skottie {
36 namespace internal {
37
38 namespace {
39
40 using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const skjson::ObjectValue&,
41 const AnimationBuilder*);
42 static constexpr GeometryAttacherT gGeometryAttachers[] = {
43 ShapeBuilder::AttachPathGeometry,
44 ShapeBuilder::AttachRRectGeometry,
45 ShapeBuilder::AttachEllipseGeometry,
46 ShapeBuilder::AttachPolystarGeometry,
47 };
48
49 using GeometryEffectAttacherT =
50 std::vector<sk_sp<sksg::GeometryNode>> (*)(const skjson::ObjectValue&,
51 const AnimationBuilder*,
52 std::vector<sk_sp<sksg::GeometryNode>>&&);
53 static constexpr GeometryEffectAttacherT gGeometryEffectAttachers[] = {
54 ShapeBuilder::AttachMergeGeometryEffect,
55 ShapeBuilder::AttachTrimGeometryEffect,
56 ShapeBuilder::AttachRoundGeometryEffect,
57 ShapeBuilder::AttachOffsetGeometryEffect,
58 ShapeBuilder::AttachPuckerBloatGeometryEffect,
59 };
60
61 using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const skjson::ObjectValue&,
62 const AnimationBuilder*);
63 static constexpr PaintAttacherT gPaintAttachers[] = {
64 ShapeBuilder::AttachColorFill,
65 ShapeBuilder::AttachColorStroke,
66 ShapeBuilder::AttachGradientFill,
67 ShapeBuilder::AttachGradientStroke,
68 };
69
70 // Some paint types (looking at you dashed-stroke) mess with the local geometry.
71 static constexpr GeometryEffectAttacherT gPaintGeometryAdjusters[] = {
72 nullptr, // color fill
73 ShapeBuilder::AdjustStrokeGeometry, // color stroke
74 nullptr, // gradient fill
75 ShapeBuilder::AdjustStrokeGeometry, // gradient stroke
76 };
77 static_assert(std::size(gPaintGeometryAdjusters) == std::size(gPaintAttachers), "");
78
79 using DrawEffectAttacherT =
80 std::vector<sk_sp<sksg::RenderNode>> (*)(const skjson::ObjectValue&,
81 const AnimationBuilder*,
82 std::vector<sk_sp<sksg::RenderNode>>&&);
83
84 static constexpr DrawEffectAttacherT gDrawEffectAttachers[] = {
85 ShapeBuilder::AttachRepeaterDrawEffect,
86 };
87
88 enum class ShapeType {
89 kGeometry,
90 kGeometryEffect,
91 kPaint,
92 kGroup,
93 kTransform,
94 kDrawEffect,
95 };
96
97 enum ShapeFlags : uint16_t {
98 kNone = 0x00,
99 kSuppressDraws = 0x01,
100 };
101
102 struct ShapeInfo {
103 const char* fTypeString;
104 ShapeType fShapeType;
105 uint16_t fAttacherIndex; // index into respective attacher tables
106 uint16_t fFlags;
107 };
108
FindShapeInfo(const skjson::ObjectValue & jshape)109 const ShapeInfo* FindShapeInfo(const skjson::ObjectValue& jshape) {
110 static constexpr ShapeInfo gShapeInfo[] = {
111 { "el", ShapeType::kGeometry , 2, kNone }, // ellipse
112 { "fl", ShapeType::kPaint , 0, kNone }, // fill
113 { "gf", ShapeType::kPaint , 2, kNone }, // gfill
114 { "gr", ShapeType::kGroup , 0, kNone }, // group
115 { "gs", ShapeType::kPaint , 3, kNone }, // gstroke
116 { "mm", ShapeType::kGeometryEffect, 0, kSuppressDraws }, // merge
117 { "op", ShapeType::kGeometryEffect, 3, kNone }, // offset
118 { "pb", ShapeType::kGeometryEffect, 4, kNone }, // pucker/bloat
119 { "rc", ShapeType::kGeometry , 1, kNone }, // rrect
120 { "rd", ShapeType::kGeometryEffect, 2, kNone }, // round
121 { "rp", ShapeType::kDrawEffect , 0, kNone }, // repeater
122 { "sh", ShapeType::kGeometry , 0, kNone }, // shape
123 { "sr", ShapeType::kGeometry , 3, kNone }, // polystar
124 { "st", ShapeType::kPaint , 1, kNone }, // stroke
125 { "tm", ShapeType::kGeometryEffect, 1, kNone }, // trim
126 { "tr", ShapeType::kTransform , 0, kNone }, // transform
127 };
128
129 const skjson::StringValue* type = jshape["ty"];
130 if (!type) {
131 return nullptr;
132 }
133
134 const auto* info = bsearch(type->begin(),
135 gShapeInfo,
136 std::size(gShapeInfo),
137 sizeof(ShapeInfo),
138 [](const void* key, const void* info) {
139 return strcmp(static_cast<const char*>(key),
140 static_cast<const ShapeInfo*>(info)->fTypeString);
141 });
142
143 return static_cast<const ShapeInfo*>(info);
144 }
145
146 struct GeometryEffectRec {
147 const skjson::ObjectValue& fJson;
148 GeometryEffectAttacherT fAttach;
149 };
150
151 } // namespace
152
AttachPathGeometry(const skjson::ObjectValue & jpath,const AnimationBuilder * abuilder)153 sk_sp<sksg::GeometryNode> ShapeBuilder::AttachPathGeometry(const skjson::ObjectValue& jpath,
154 const AnimationBuilder* abuilder) {
155 return abuilder->attachPath(jpath["ks"]);
156 }
157
158 struct AnimationBuilder::AttachShapeContext {
AttachShapeContextskottie::internal::AnimationBuilder::AttachShapeContext159 AttachShapeContext(std::vector<sk_sp<sksg::GeometryNode>>* geos,
160 std::vector<GeometryEffectRec>* effects,
161 size_t committedAnimators)
162 : fGeometryStack(geos)
163 , fGeometryEffectStack(effects)
164 , fCommittedAnimators(committedAnimators) {}
165
166 std::vector<sk_sp<sksg::GeometryNode>>* fGeometryStack;
167 std::vector<GeometryEffectRec>* fGeometryEffectStack;
168 size_t fCommittedAnimators;
169 };
170
attachShape(const skjson::ArrayValue * jshape,AttachShapeContext * ctx,bool suppress_draws) const171 sk_sp<sksg::RenderNode> AnimationBuilder::attachShape(const skjson::ArrayValue* jshape,
172 AttachShapeContext* ctx,
173 bool suppress_draws) const {
174 if (!jshape)
175 return nullptr;
176
177 SkDEBUGCODE(const auto initialGeometryEffects = ctx->fGeometryEffectStack->size();)
178
179 const skjson::ObjectValue* jtransform = nullptr;
180
181 struct ShapeRec {
182 const skjson::ObjectValue& fJson;
183 const ShapeInfo& fInfo;
184 bool fSuppressed;
185 };
186
187 // First pass (bottom->top):
188 //
189 // * pick up the group transform and opacity
190 // * push local geometry effects onto the stack
191 // * store recs for next pass
192 //
193 std::vector<ShapeRec> recs;
194 for (size_t i = 0; i < jshape->size(); ++i) {
195 const skjson::ObjectValue* shape = (*jshape)[jshape->size() - 1 - i];
196 if (!shape) continue;
197
198 const auto* info = FindShapeInfo(*shape);
199 if (!info) {
200 this->log(Logger::Level::kError, &(*shape)["ty"], "Unknown shape.");
201 continue;
202 }
203
204 if (ParseDefault<bool>((*shape)["hd"], false)) {
205 // Ignore hidden shapes.
206 continue;
207 }
208
209 recs.push_back({ *shape, *info, suppress_draws });
210
211 // Some effects (merge) suppress any paints above them.
212 suppress_draws |= (info->fFlags & kSuppressDraws) != 0;
213
214 switch (info->fShapeType) {
215 case ShapeType::kTransform:
216 // Just track the transform property for now -- we'll deal with it later.
217 jtransform = shape;
218 break;
219 case ShapeType::kGeometryEffect:
220 SkASSERT(info->fAttacherIndex < std::size(gGeometryEffectAttachers));
221 ctx->fGeometryEffectStack->push_back(
222 { *shape, gGeometryEffectAttachers[info->fAttacherIndex] });
223 break;
224 default:
225 break;
226 }
227 }
228
229 // Second pass (top -> bottom, after 2x reverse):
230 //
231 // * track local geometry
232 // * emit local paints
233 //
234 std::vector<sk_sp<sksg::GeometryNode>> geos;
235 std::vector<sk_sp<sksg::RenderNode >> draws;
236
237 const auto add_draw = [this, &draws](sk_sp<sksg::RenderNode> draw, const ShapeRec& rec) {
238 // All draws can have an optional blend mode.
239 draws.push_back(this->attachBlendMode(rec.fJson, std::move(draw)));
240 };
241
242 for (auto rec = recs.rbegin(); rec != recs.rend(); ++rec) {
243 const AutoPropertyTracker apt(this, rec->fJson, PropertyObserver::NodeType::OTHER);
244
245 switch (rec->fInfo.fShapeType) {
246 case ShapeType::kGeometry: {
247 SkASSERT(rec->fInfo.fAttacherIndex < std::size(gGeometryAttachers));
248 if (auto geo = gGeometryAttachers[rec->fInfo.fAttacherIndex](rec->fJson, this)) {
249 geos.push_back(std::move(geo));
250 }
251 } break;
252 case ShapeType::kGeometryEffect: {
253 // Apply the current effect and pop from the stack.
254 SkASSERT(rec->fInfo.fAttacherIndex < std::size(gGeometryEffectAttachers));
255 if (!geos.empty()) {
256 geos = gGeometryEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
257 this,
258 std::move(geos));
259 }
260
261 SkASSERT(&ctx->fGeometryEffectStack->back().fJson == &rec->fJson);
262 SkASSERT(ctx->fGeometryEffectStack->back().fAttach ==
263 gGeometryEffectAttachers[rec->fInfo.fAttacherIndex]);
264 ctx->fGeometryEffectStack->pop_back();
265 } break;
266 case ShapeType::kGroup: {
267 AttachShapeContext groupShapeCtx(&geos,
268 ctx->fGeometryEffectStack,
269 ctx->fCommittedAnimators);
270 if (auto subgroup =
271 this->attachShape(rec->fJson["it"], &groupShapeCtx, rec->fSuppressed)) {
272 add_draw(std::move(subgroup), *rec);
273 SkASSERT(groupShapeCtx.fCommittedAnimators >= ctx->fCommittedAnimators);
274 ctx->fCommittedAnimators = groupShapeCtx.fCommittedAnimators;
275 }
276 } break;
277 case ShapeType::kPaint: {
278 SkASSERT(rec->fInfo.fAttacherIndex < std::size(gPaintAttachers));
279 auto paint = gPaintAttachers[rec->fInfo.fAttacherIndex](rec->fJson, this);
280 if (!paint || geos.empty() || rec->fSuppressed)
281 break;
282
283 auto drawGeos = geos;
284
285 // Apply all pending effects from the stack.
286 for (auto it = ctx->fGeometryEffectStack->rbegin();
287 it != ctx->fGeometryEffectStack->rend(); ++it) {
288 drawGeos = it->fAttach(it->fJson, this, std::move(drawGeos));
289 }
290
291 // Apply local paint geometry adjustments (e.g. dashing).
292 SkASSERT(rec->fInfo.fAttacherIndex < std::size(gPaintGeometryAdjusters));
293 if (const auto adjuster = gPaintGeometryAdjusters[rec->fInfo.fAttacherIndex]) {
294 drawGeos = adjuster(rec->fJson, this, std::move(drawGeos));
295 }
296
297 // If we still have multiple geos, reduce using 'merge'.
298 auto geo = drawGeos.size() > 1
299 ? ShapeBuilder::MergeGeometry(std::move(drawGeos), sksg::Merge::Mode::kMerge)
300 : drawGeos[0];
301
302 SkASSERT(geo);
303 add_draw(sksg::Draw::Make(std::move(geo), std::move(paint)), *rec);
304 ctx->fCommittedAnimators = fCurrentAnimatorScope->size();
305 } break;
306 case ShapeType::kDrawEffect: {
307 SkASSERT(rec->fInfo.fAttacherIndex < std::size(gDrawEffectAttachers));
308 if (!draws.empty()) {
309 draws = gDrawEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
310 this,
311 std::move(draws));
312 ctx->fCommittedAnimators = fCurrentAnimatorScope->size();
313 }
314 } break;
315 default:
316 break;
317 }
318 }
319
320 // By now we should have popped all local geometry effects.
321 SkASSERT(ctx->fGeometryEffectStack->size() == initialGeometryEffects);
322
323 sk_sp<sksg::RenderNode> shape_wrapper;
324 if (draws.size() == 1) {
325 // For a single draw, we don't need a group.
326 shape_wrapper = std::move(draws.front());
327 } else if (!draws.empty()) {
328 // Emit local draws reversed (bottom->top, per spec).
329 std::reverse(draws.begin(), draws.end());
330 draws.shrink_to_fit();
331
332 // We need a group to dispatch multiple draws.
333 shape_wrapper = sksg::Group::Make(std::move(draws));
334 }
335
336 sk_sp<sksg::Transform> shape_transform;
337 if (jtransform) {
338 const AutoPropertyTracker apt(this, *jtransform, PropertyObserver::NodeType::OTHER);
339
340 // This is tricky due to the interaction with ctx->fCommittedAnimators: we want any
341 // animators related to tranform/opacity to be committed => they must be inserted in front
342 // of the dangling/uncommitted ones.
343 AutoScope ascope(this);
344
345 if ((shape_transform = this->attachMatrix2D(*jtransform, nullptr))) {
346 shape_wrapper = sksg::TransformEffect::Make(std::move(shape_wrapper), shape_transform);
347 }
348 shape_wrapper = this->attachOpacity(*jtransform, std::move(shape_wrapper));
349
350 auto local_scope = ascope.release();
351 fCurrentAnimatorScope->insert(fCurrentAnimatorScope->begin() + ctx->fCommittedAnimators,
352 std::make_move_iterator(local_scope.begin()),
353 std::make_move_iterator(local_scope.end()));
354 ctx->fCommittedAnimators += local_scope.size();
355 }
356
357 // Push transformed local geometries to parent list, for subsequent paints.
358 for (auto& geo : geos) {
359 ctx->fGeometryStack->push_back(shape_transform
360 ? sksg::GeometryTransform::Make(std::move(geo), shape_transform)
361 : std::move(geo));
362 }
363
364 return shape_wrapper;
365 }
366
attachShapeLayer(const skjson::ObjectValue & layer,LayerInfo *) const367 sk_sp<sksg::RenderNode> AnimationBuilder::attachShapeLayer(const skjson::ObjectValue& layer,
368 LayerInfo*) const {
369 std::vector<sk_sp<sksg::GeometryNode>> geometryStack;
370 std::vector<GeometryEffectRec> geometryEffectStack;
371 AttachShapeContext shapeCtx(&geometryStack, &geometryEffectStack,
372 fCurrentAnimatorScope->size());
373 auto shapeNode = this->attachShape(layer["shapes"], &shapeCtx);
374
375 // Trim uncommitted animators: AttachShape consumes effects on the fly, and greedily attaches
376 // geometries => at the end, we can end up with unused geometries, which are nevertheless alive
377 // due to attached animators. To avoid this, we track committed animators and discard the
378 // orphans here.
379 SkASSERT(shapeCtx.fCommittedAnimators <= fCurrentAnimatorScope->size());
380 fCurrentAnimatorScope->resize(shapeCtx.fCommittedAnimators);
381
382 return shapeNode;
383 }
384
385 } // namespace internal
386 } // namespace skottie
387