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 | |
28 | namespace skottie { |
29 | namespace internal { |
30 | |
31 | namespace { |
32 | |
33 | using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const skjson::ObjectValue&, |
34 | const AnimationBuilder*); |
35 | static constexpr GeometryAttacherT gGeometryAttachers[] = { |
36 | ShapeBuilder::AttachPathGeometry, |
37 | ShapeBuilder::AttachRRectGeometry, |
38 | ShapeBuilder::AttachEllipseGeometry, |
39 | ShapeBuilder::AttachPolystarGeometry, |
40 | }; |
41 | |
42 | using GeometryEffectAttacherT = |
43 | std::vector<sk_sp<sksg::GeometryNode>> (*)(const skjson::ObjectValue&, |
44 | const AnimationBuilder*, |
45 | std::vector<sk_sp<sksg::GeometryNode>>&&); |
46 | static constexpr GeometryEffectAttacherT gGeometryEffectAttachers[] = { |
47 | ShapeBuilder::AttachMergeGeometryEffect, |
48 | ShapeBuilder::AttachTrimGeometryEffect, |
49 | ShapeBuilder::AttachRoundGeometryEffect, |
50 | ShapeBuilder::AttachOffsetGeometryEffect, |
51 | ShapeBuilder::AttachPuckerBloatGeometryEffect, |
52 | }; |
53 | |
54 | using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const skjson::ObjectValue&, |
55 | const AnimationBuilder*); |
56 | static 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. |
64 | static constexpr GeometryEffectAttacherT gPaintGeometryAdjusters[] = { |
65 | nullptr, // color fill |
66 | ShapeBuilder::AdjustStrokeGeometry, // color stroke |
67 | nullptr, // gradient fill |
68 | ShapeBuilder::AdjustStrokeGeometry, // gradient stroke |
69 | }; |
70 | static_assert(SK_ARRAY_COUNT(gPaintGeometryAdjusters) == SK_ARRAY_COUNT(gPaintAttachers), "" ); |
71 | |
72 | using DrawEffectAttacherT = |
73 | std::vector<sk_sp<sksg::RenderNode>> (*)(const skjson::ObjectValue&, |
74 | const AnimationBuilder*, |
75 | std::vector<sk_sp<sksg::RenderNode>>&&); |
76 | |
77 | static constexpr DrawEffectAttacherT gDrawEffectAttachers[] = { |
78 | ShapeBuilder::AttachRepeaterDrawEffect, |
79 | }; |
80 | |
81 | enum class ShapeType { |
82 | kGeometry, |
83 | kGeometryEffect, |
84 | kPaint, |
85 | kGroup, |
86 | kTransform, |
87 | kDrawEffect, |
88 | }; |
89 | |
90 | enum ShapeFlags : uint16_t { |
91 | kNone = 0x00, |
92 | kSuppressDraws = 0x01, |
93 | }; |
94 | |
95 | struct ShapeInfo { |
96 | const char* fTypeString; |
97 | ShapeType fShapeType; |
98 | uint16_t fAttacherIndex; // index into respective attacher tables |
99 | uint16_t fFlags; |
100 | }; |
101 | |
102 | const 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 | |
139 | struct GeometryEffectRec { |
140 | const skjson::ObjectValue& fJson; |
141 | GeometryEffectAttacherT fAttach; |
142 | }; |
143 | |
144 | } // namespace |
145 | |
146 | sk_sp<sksg::GeometryNode> ShapeBuilder::AttachPathGeometry(const skjson::ObjectValue& jpath, |
147 | const AnimationBuilder* abuilder) { |
148 | return abuilder->attachPath(jpath["ks" ]); |
149 | } |
150 | |
151 | struct 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 | |
164 | sk_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 | |
360 | sk_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 | |