Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
ShapeLayer.cpp
Go to the documentation of this file.
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
9
23#include "modules/sksg/include/SkSGPath.h" // IWYU pragma: keep
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
35namespace skottie {
36namespace internal {
37
38namespace {
39
40using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const skjson::ObjectValue&,
41 const AnimationBuilder*);
42static constexpr GeometryAttacherT gGeometryAttachers[] = {
47};
48
49using GeometryEffectAttacherT =
50 std::vector<sk_sp<sksg::GeometryNode>> (*)(const skjson::ObjectValue&,
51 const AnimationBuilder*,
52 std::vector<sk_sp<sksg::GeometryNode>>&&);
53static constexpr GeometryEffectAttacherT gGeometryEffectAttachers[] = {
59};
60
61using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const skjson::ObjectValue&,
62 const AnimationBuilder*);
63static constexpr PaintAttacherT gPaintAttachers[] = {
68};
69
70// Some paint types (looking at you dashed-stroke) mess with the local geometry.
71static constexpr GeometryEffectAttacherT gPaintGeometryAdjusters[] = {
72 nullptr, // color fill
74 nullptr, // gradient fill
75 ShapeBuilder::AdjustStrokeGeometry, // gradient stroke
76};
77static_assert(std::size(gPaintGeometryAdjusters) == std::size(gPaintAttachers), "");
78
79using DrawEffectAttacherT =
80 std::vector<sk_sp<sksg::RenderNode>> (*)(const skjson::ObjectValue&,
81 const AnimationBuilder*,
82 std::vector<sk_sp<sksg::RenderNode>>&&);
83
84static constexpr DrawEffectAttacherT gDrawEffectAttachers[] = {
86};
87
88enum class ShapeType {
89 kGeometry,
90 kGeometryEffect,
91 kPaint,
92 kGroup,
94 kDrawEffect,
95};
96
97enum ShapeFlags : uint16_t {
98 kNone = 0x00,
99 kSuppressDraws = 0x01,
100};
101
102struct ShapeInfo {
103 const char* fTypeString;
104 ShapeType fShapeType;
105 uint16_t fAttacherIndex; // index into respective attacher tables
106 uint16_t fFlags;
107};
108
109const 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
146struct GeometryEffectRec {
148 GeometryEffectAttacherT fAttach;
149};
150
151} // namespace
152
154 const AnimationBuilder* abuilder) {
155 return abuilder->attachPath(jpath["ks"]);
156}
157
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;
169};
170
171sk_sp<sksg::RenderNode> AnimationBuilder::attachShape(const skjson::ArrayValue* jshape,
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 {
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,
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
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
367sk_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
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition DM.cpp:213
ShapeType fShapeType
uint16_t fAttacherIndex
const skjson::ObjectValue & fJson
GeometryEffectAttacherT fAttach
const char * fTypeString
uint16_t fFlags
#define SkASSERT(cond)
Definition SkAssert.h:116
#define SkDEBUGCODE(...)
Definition SkDebug.h:23
static void draw(SkCanvas *canvas, SkRect &target, int x, int y)
Definition aaclip.cpp:27
size_t size() const
Definition SkJSON.h:262
sk_sp< sksg::Path > attachPath(const skjson::Value &) const
Definition Path.cpp:50
void sk_sp< sksg::Transform > attachMatrix2D(const skjson::ObjectValue &, sk_sp< sksg::Transform >, bool auto_orient=false) const
sk_sp< sksg::RenderNode > attachOpacity(const skjson::ObjectValue &, sk_sp< sksg::RenderNode >) const
Definition Skottie.cpp:118
void log(Logger::Level, const skjson::Value *, const char fmt[],...) const SK_PRINTF_LIKE(4
Definition Skottie.cpp:71
static sk_sp< sksg::GeometryNode > AttachPathGeometry(const skjson::ObjectValue &, const AnimationBuilder *)
static sk_sp< sksg::GeometryNode > AttachPolystarGeometry(const skjson::ObjectValue &, const AnimationBuilder *)
Definition Polystar.cpp:101
static std::vector< sk_sp< sksg::RenderNode > > AttachRepeaterDrawEffect(const skjson::ObjectValue &, const AnimationBuilder *, std::vector< sk_sp< sksg::RenderNode > > &&)
Definition Repeater.cpp:184
static std::vector< sk_sp< sksg::GeometryNode > > AttachOffsetGeometryEffect(const skjson::ObjectValue &, const AnimationBuilder *, std::vector< sk_sp< sksg::GeometryNode > > &&)
static sk_sp< sksg::PaintNode > AttachColorStroke(const skjson::ObjectValue &, const AnimationBuilder *)
static sk_sp< sksg::PaintNode > AttachColorFill(const skjson::ObjectValue &, const AnimationBuilder *)
static std::vector< sk_sp< sksg::GeometryNode > > AdjustStrokeGeometry(const skjson::ObjectValue &, const AnimationBuilder *, std::vector< sk_sp< sksg::GeometryNode > > &&)
static std::vector< sk_sp< sksg::GeometryNode > > AttachRoundGeometryEffect(const skjson::ObjectValue &, const AnimationBuilder *, std::vector< sk_sp< sksg::GeometryNode > > &&)
static sk_sp< sksg::PaintNode > AttachGradientStroke(const skjson::ObjectValue &, const AnimationBuilder *)
Definition Gradient.cpp:230
static sk_sp< sksg::GeometryNode > AttachEllipseGeometry(const skjson::ObjectValue &, const AnimationBuilder *)
Definition Ellipse.cpp:57
static std::vector< sk_sp< sksg::GeometryNode > > AttachMergeGeometryEffect(const skjson::ObjectValue &, const AnimationBuilder *, std::vector< sk_sp< sksg::GeometryNode > > &&)
static sk_sp< sksg::GeometryNode > AttachRRectGeometry(const skjson::ObjectValue &, const AnimationBuilder *)
Definition Rectangle.cpp:59
static sk_sp< sksg::PaintNode > AttachGradientFill(const skjson::ObjectValue &, const AnimationBuilder *)
Definition Gradient.cpp:221
static sk_sp< sksg::Merge > MergeGeometry(std::vector< sk_sp< sksg::GeometryNode > > &&, sksg::Merge::Mode)
static std::vector< sk_sp< sksg::GeometryNode > > AttachPuckerBloatGeometryEffect(const skjson::ObjectValue &, const AnimationBuilder *, std::vector< sk_sp< sksg::GeometryNode > > &&)
static std::vector< sk_sp< sksg::GeometryNode > > AttachTrimGeometryEffect(const skjson::ObjectValue &, const AnimationBuilder *, std::vector< sk_sp< sksg::GeometryNode > > &&)
Definition TrimPaths.cpp:81
static sk_sp< Draw > Make(sk_sp< GeometryNode > geo, sk_sp< PaintNode > paint)
Definition SkSGDraw.h:35
static sk_sp< GeometryTransform > Make(sk_sp< GeometryNode > child, sk_sp< Transform > transform)
static sk_sp< Group > Make()
Definition SkSGGroup.h:31
static sk_sp< TransformEffect > Make(sk_sp< RenderNode > child, sk_sp< Transform > transform)
const Paint & paint
static constexpr char kTransform[]
Definition ref_ptr.h:256
std::vector< sk_sp< sksg::GeometryNode > > * fGeometryStack
AttachShapeContext(std::vector< sk_sp< sksg::GeometryNode > > *geos, std::vector< GeometryEffectRec > *effects, size_t committedAnimators)
std::vector< GeometryEffectRec > * fGeometryEffectStack