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