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