xref: /aosp_15_r20/external/skia/modules/skottie/src/layers/shapelayer/ShapeLayer.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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