• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
FindShapeInfo(const skjson::ObjectValue & jshape)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 
AttachPathGeometry(const skjson::ObjectValue & jpath,const AnimationBuilder * abuilder)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 {
AttachShapeContextskottie::internal::AnimationBuilder::AttachShapeContext152     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 
attachShape(const skjson::ArrayValue * jshape,AttachShapeContext * ctx,bool suppress_draws) const164 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, PropertyObserver::NodeType::OTHER);
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, PropertyObserver::NodeType::OTHER);
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 
attachShapeLayer(const skjson::ObjectValue & layer,LayerInfo *) const360 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