1 | /* |
2 | * Copyright 2019 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/Layer.h" |
9 | |
10 | #include "modules/skottie/src/Camera.h" |
11 | #include "modules/skottie/src/Composition.h" |
12 | #include "modules/skottie/src/SkottieJson.h" |
13 | #include "modules/skottie/src/effects/Effects.h" |
14 | #include "modules/skottie/src/effects/MotionBlurEffect.h" |
15 | #include "modules/sksg/include/SkSGClipEffect.h" |
16 | #include "modules/sksg/include/SkSGDraw.h" |
17 | #include "modules/sksg/include/SkSGGroup.h" |
18 | #include "modules/sksg/include/SkSGMaskEffect.h" |
19 | #include "modules/sksg/include/SkSGMerge.h" |
20 | #include "modules/sksg/include/SkSGPaint.h" |
21 | #include "modules/sksg/include/SkSGPath.h" |
22 | #include "modules/sksg/include/SkSGRect.h" |
23 | #include "modules/sksg/include/SkSGRenderEffect.h" |
24 | #include "modules/sksg/include/SkSGRenderNode.h" |
25 | #include "modules/sksg/include/SkSGTransform.h" |
26 | |
27 | namespace skottie { |
28 | namespace internal { |
29 | |
30 | namespace { |
31 | |
32 | static constexpr size_t kNullLayerType = 3; |
33 | |
34 | struct MaskInfo { |
35 | SkBlendMode fBlendMode; // used when masking with layers/blending |
36 | sksg::Merge::Mode fMergeMode; // used when clipping |
37 | bool fInvertGeometry; |
38 | }; |
39 | |
40 | const MaskInfo* GetMaskInfo(char mode) { |
41 | static constexpr MaskInfo k_add_info = |
42 | { SkBlendMode::kSrcOver , sksg::Merge::Mode::kUnion , false }; |
43 | static constexpr MaskInfo k_int_info = |
44 | { SkBlendMode::kSrcIn , sksg::Merge::Mode::kIntersect , false }; |
45 | static constexpr MaskInfo k_sub_info = |
46 | { SkBlendMode::kDstOut , sksg::Merge::Mode::kDifference, true }; |
47 | static constexpr MaskInfo k_dif_info = |
48 | { SkBlendMode::kXor , sksg::Merge::Mode::kXOR , false }; |
49 | |
50 | switch (mode) { |
51 | case 'a': return &k_add_info; |
52 | case 'f': return &k_dif_info; |
53 | case 'i': return &k_int_info; |
54 | case 's': return &k_sub_info; |
55 | default: break; |
56 | } |
57 | |
58 | return nullptr; |
59 | } |
60 | |
61 | class MaskAdapter final : public AnimatablePropertyContainer { |
62 | public: |
63 | MaskAdapter(const skjson::ObjectValue& jmask, const AnimationBuilder& abuilder, SkBlendMode bm) |
64 | : fMaskPaint(sksg::Color::Make(SK_ColorBLACK)) |
65 | , fBlendMode(bm) |
66 | { |
67 | fMaskPaint->setAntiAlias(true); |
68 | if (!this->requires_isolation()) { |
69 | // We can mask at draw time. |
70 | fMaskPaint->setBlendMode(bm); |
71 | } |
72 | |
73 | this->bind(abuilder, jmask["o" ], fOpacity); |
74 | |
75 | if (this->bind(abuilder, jmask["f" ], fFeather)) { |
76 | fMaskFilter = sksg::BlurImageFilter::Make(); |
77 | } |
78 | } |
79 | |
80 | bool hasEffect() const { |
81 | return !this->isStatic() |
82 | || fOpacity < 100 |
83 | || fFeather != SkV2{0,0}; |
84 | } |
85 | |
86 | sk_sp<sksg::RenderNode> makeMask(sk_sp<sksg::Path> mask_path) const { |
87 | sk_sp<sksg::RenderNode> mask = sksg::Draw::Make(std::move(mask_path), fMaskPaint); |
88 | |
89 | // Optional mask blur (feather). |
90 | mask = sksg::ImageFilterEffect::Make(std::move(mask), fMaskFilter); |
91 | |
92 | if (this->requires_isolation()) { |
93 | mask = sksg::LayerEffect::Make(std::move(mask), fBlendMode); |
94 | } |
95 | |
96 | return mask; |
97 | } |
98 | |
99 | private: |
100 | void onSync() override { |
101 | fMaskPaint->setOpacity(fOpacity * 0.01f); |
102 | if (fMaskFilter) { |
103 | // Close enough to AE. |
104 | static constexpr SkScalar kFeatherToSigma = 0.38f; |
105 | fMaskFilter->setSigma({fFeather.x * kFeatherToSigma, |
106 | fFeather.y * kFeatherToSigma}); |
107 | } |
108 | } |
109 | |
110 | bool requires_isolation() const { |
111 | SkASSERT(fBlendMode == SkBlendMode::kSrc || |
112 | fBlendMode == SkBlendMode::kSrcOver || |
113 | fBlendMode == SkBlendMode::kSrcIn || |
114 | fBlendMode == SkBlendMode::kDstOut || |
115 | fBlendMode == SkBlendMode::kXor); |
116 | |
117 | // Some mask modes touch pixels outside the immediate draw geometry. |
118 | // These require a layer. |
119 | switch (fBlendMode) { |
120 | case (SkBlendMode::kSrcIn): return true; |
121 | default : return false; |
122 | } |
123 | SkUNREACHABLE; |
124 | } |
125 | |
126 | const sk_sp<sksg::PaintNode> fMaskPaint; |
127 | const SkBlendMode fBlendMode; |
128 | sk_sp<sksg::BlurImageFilter> fMaskFilter; // optional "feather" |
129 | |
130 | Vec2Value fFeather = {0,0}; |
131 | ScalarValue fOpacity = 100; |
132 | }; |
133 | |
134 | sk_sp<sksg::RenderNode> AttachMask(const skjson::ArrayValue* jmask, |
135 | const AnimationBuilder* abuilder, |
136 | sk_sp<sksg::RenderNode> childNode) { |
137 | if (!jmask) return childNode; |
138 | |
139 | struct MaskRecord { |
140 | sk_sp<sksg::Path> mask_path; // for clipping and masking |
141 | sk_sp<MaskAdapter> mask_adapter; // for masking |
142 | sksg::Merge::Mode merge_mode; // for clipping |
143 | }; |
144 | |
145 | SkSTArray<4, MaskRecord, true> mask_stack; |
146 | bool has_effect = false; |
147 | |
148 | for (const skjson::ObjectValue* m : *jmask) { |
149 | if (!m) continue; |
150 | |
151 | const skjson::StringValue* jmode = (*m)["mode" ]; |
152 | if (!jmode || jmode->size() != 1) { |
153 | abuilder->log(Logger::Level::kError, &(*m)["mode" ], "Invalid mask mode." ); |
154 | continue; |
155 | } |
156 | |
157 | const auto mode = *jmode->begin(); |
158 | if (mode == 'n') { |
159 | // "None" masks have no effect. |
160 | continue; |
161 | } |
162 | |
163 | const auto* mask_info = GetMaskInfo(mode); |
164 | if (!mask_info) { |
165 | abuilder->log(Logger::Level::kWarning, nullptr, "Unsupported mask mode: '%c'." , mode); |
166 | continue; |
167 | } |
168 | |
169 | auto mask_path = abuilder->attachPath((*m)["pt" ]); |
170 | if (!mask_path) { |
171 | abuilder->log(Logger::Level::kError, m, "Could not parse mask path." ); |
172 | continue; |
173 | } |
174 | |
175 | auto mask_blend_mode = mask_info->fBlendMode; |
176 | auto mask_merge_mode = mask_info->fMergeMode; |
177 | auto mask_inverted = ParseDefault<bool>((*m)["inv" ], false); |
178 | |
179 | if (mask_stack.empty()) { |
180 | // First mask adjustments: |
181 | // - always draw in source mode |
182 | // - invert geometry if needed |
183 | mask_blend_mode = SkBlendMode::kSrc; |
184 | mask_merge_mode = sksg::Merge::Mode::kMerge; |
185 | mask_inverted = mask_inverted != mask_info->fInvertGeometry; |
186 | } |
187 | |
188 | mask_path->setFillType(mask_inverted ? SkPathFillType::kInverseWinding |
189 | : SkPathFillType::kWinding); |
190 | |
191 | auto mask_adapter = sk_make_sp<MaskAdapter>(*m, *abuilder, mask_blend_mode); |
192 | abuilder->attachDiscardableAdapter(mask_adapter); |
193 | |
194 | has_effect |= mask_adapter->hasEffect(); |
195 | |
196 | mask_stack.push_back({ std::move(mask_path), |
197 | std::move(mask_adapter), |
198 | mask_merge_mode }); |
199 | } |
200 | |
201 | |
202 | if (mask_stack.empty()) |
203 | return childNode; |
204 | |
205 | // If the masks are fully opaque, we can clip. |
206 | if (!has_effect) { |
207 | sk_sp<sksg::GeometryNode> clip_node; |
208 | |
209 | if (mask_stack.count() == 1) { |
210 | // Single path -> just clip. |
211 | clip_node = std::move(mask_stack.front().mask_path); |
212 | } else { |
213 | // Multiple clip paths -> merge. |
214 | std::vector<sksg::Merge::Rec> merge_recs; |
215 | merge_recs.reserve(SkToSizeT(mask_stack.count())); |
216 | |
217 | for (auto& mask : mask_stack) { |
218 | merge_recs.push_back({std::move(mask.mask_path), mask.merge_mode }); |
219 | } |
220 | clip_node = sksg::Merge::Make(std::move(merge_recs)); |
221 | } |
222 | |
223 | return sksg::ClipEffect::Make(std::move(childNode), std::move(clip_node), true); |
224 | } |
225 | |
226 | // Complex masks (non-opaque or blurred) turn into a mask node stack. |
227 | sk_sp<sksg::RenderNode> maskNode; |
228 | if (mask_stack.count() == 1) { |
229 | // no group needed for single mask |
230 | const auto rec = mask_stack.front(); |
231 | maskNode = rec.mask_adapter->makeMask(std::move(rec.mask_path)); |
232 | } else { |
233 | std::vector<sk_sp<sksg::RenderNode>> masks; |
234 | masks.reserve(SkToSizeT(mask_stack.count())); |
235 | for (auto& rec : mask_stack) { |
236 | masks.push_back(rec.mask_adapter->makeMask(std::move(rec.mask_path))); |
237 | } |
238 | |
239 | maskNode = sksg::Group::Make(std::move(masks)); |
240 | } |
241 | |
242 | return sksg::MaskEffect::Make(std::move(childNode), std::move(maskNode)); |
243 | } |
244 | |
245 | class LayerController final : public Animator { |
246 | public: |
247 | LayerController(AnimatorScope&& layer_animators, |
248 | sk_sp<sksg::RenderNode> layer, |
249 | size_t tanim_count, float in, float out) |
250 | : fLayerAnimators(std::move(layer_animators)) |
251 | , fLayerNode(std::move(layer)) |
252 | , fTransformAnimatorsCount(tanim_count) |
253 | , fIn(in) |
254 | , fOut(out) {} |
255 | |
256 | protected: |
257 | StateChanged onSeek(float t) override { |
258 | // in/out may be inverted for time-reversed layers |
259 | const auto active = (t >= fIn && t < fOut) || (t > fOut && t <= fIn); |
260 | |
261 | bool changed = false; |
262 | if (fLayerNode) { |
263 | changed |= (fLayerNode->isVisible() != active); |
264 | fLayerNode->setVisible(active); |
265 | } |
266 | |
267 | // When active, dispatch ticks to all layer animators. |
268 | // When inactive, we must still dispatch ticks to the layer transform animators |
269 | // (active child layers depend on transforms being updated). |
270 | const auto dispatch_count = active ? fLayerAnimators.size() |
271 | : fTransformAnimatorsCount; |
272 | for (size_t i = 0; i < dispatch_count; ++i) { |
273 | changed |= fLayerAnimators[i]->seek(t); |
274 | } |
275 | |
276 | return changed; |
277 | } |
278 | |
279 | private: |
280 | const AnimatorScope fLayerAnimators; |
281 | const sk_sp<sksg::RenderNode> fLayerNode; |
282 | const size_t fTransformAnimatorsCount; |
283 | const float fIn, |
284 | fOut; |
285 | }; |
286 | |
287 | class MotionBlurController final : public Animator { |
288 | public: |
289 | explicit MotionBlurController(sk_sp<MotionBlurEffect> mbe) |
290 | : fMotionBlurEffect(std::move(mbe)) {} |
291 | |
292 | protected: |
293 | // When motion blur is present, time ticks are not passed to layer animators |
294 | // but to the motion blur effect. The effect then drives the animators/scene-graph |
295 | // during reval and render phases. |
296 | StateChanged onSeek(float t) override { |
297 | fMotionBlurEffect->setT(t); |
298 | return true; |
299 | } |
300 | |
301 | private: |
302 | const sk_sp<MotionBlurEffect> fMotionBlurEffect; |
303 | }; |
304 | |
305 | } // namespace |
306 | |
307 | LayerBuilder::LayerBuilder(const skjson::ObjectValue& jlayer) |
308 | : fJlayer(jlayer) |
309 | , fIndex (ParseDefault<int>(jlayer["ind" ], -1)) |
310 | , fParentIndex(ParseDefault<int>(jlayer["parent" ], -1)) |
311 | , fType (ParseDefault<int>(jlayer["ty" ], -1)) |
312 | , fAutoOrient (ParseDefault<int>(jlayer["ao" ], 0)) { |
313 | |
314 | if (this->isCamera() || ParseDefault<int>(jlayer["ddd" ], 0)) { |
315 | fFlags |= Flags::kIs3D; |
316 | } |
317 | } |
318 | |
319 | LayerBuilder::~LayerBuilder() = default; |
320 | |
321 | bool LayerBuilder::isCamera() const { |
322 | static constexpr int kCameraLayerType = 13; |
323 | |
324 | return fType == kCameraLayerType; |
325 | } |
326 | |
327 | sk_sp<sksg::Transform> LayerBuilder::buildTransform(const AnimationBuilder& abuilder, |
328 | CompositionBuilder* cbuilder) { |
329 | // Depending on the leaf node type, we treat the whole transform chain as either 2D or 3D. |
330 | const auto transform_chain_type = this->is3D() ? TransformType::k3D |
331 | : TransformType::k2D; |
332 | fLayerTransform = this->getTransform(abuilder, cbuilder, transform_chain_type); |
333 | |
334 | return fLayerTransform; |
335 | } |
336 | |
337 | sk_sp<sksg::Transform> LayerBuilder::getTransform(const AnimationBuilder& abuilder, |
338 | CompositionBuilder* cbuilder, |
339 | TransformType ttype) { |
340 | const auto cache_valid_mask = (1ul << ttype); |
341 | if (!(fFlags & cache_valid_mask)) { |
342 | // Set valid flag upfront to break cycles. |
343 | fFlags |= cache_valid_mask; |
344 | |
345 | const AnimationBuilder::AutoPropertyTracker apt(&abuilder, fJlayer); |
346 | AnimationBuilder::AutoScope ascope(&abuilder, std::move(fLayerScope)); |
347 | fTransformCache[ttype] = this->doAttachTransform(abuilder, cbuilder, ttype); |
348 | fLayerScope = ascope.release(); |
349 | fTransformAnimatorCount = fLayerScope.size(); |
350 | } |
351 | |
352 | return fTransformCache[ttype]; |
353 | } |
354 | |
355 | sk_sp<sksg::Transform> LayerBuilder::getParentTransform(const AnimationBuilder& abuilder, |
356 | CompositionBuilder* cbuilder, |
357 | TransformType ttype) { |
358 | if (auto* parent_builder = cbuilder->layerBuilder(fParentIndex)) { |
359 | // Explicit parent layer. |
360 | return parent_builder->getTransform(abuilder, cbuilder, ttype); |
361 | } |
362 | |
363 | if (ttype == TransformType::k3D) { |
364 | // During camera transform attachment, cbuilder->getCameraTransform() is null. |
365 | // This prevents camera->camera transform chain cycles. |
366 | SkASSERT(!this->isCamera() || !cbuilder->getCameraTransform()); |
367 | |
368 | // 3D transform chains are implicitly rooted onto the camera. |
369 | return cbuilder->getCameraTransform(); |
370 | } |
371 | |
372 | return nullptr; |
373 | } |
374 | |
375 | sk_sp<sksg::Transform> LayerBuilder::doAttachTransform(const AnimationBuilder& abuilder, |
376 | CompositionBuilder* cbuilder, |
377 | TransformType ttype) { |
378 | const skjson::ObjectValue* jtransform = fJlayer["ks" ]; |
379 | if (!jtransform) { |
380 | return nullptr; |
381 | } |
382 | |
383 | auto parent_transform = this->getParentTransform(abuilder, cbuilder, ttype); |
384 | |
385 | if (this->isCamera()) { |
386 | // parent_transform applies to the camera itself => it pre-composes inverted to the |
387 | // camera/view/adapter transform. |
388 | // |
389 | // T_camera' = T_camera x Inv(parent_transform) |
390 | // |
391 | return abuilder.attachCamera(fJlayer, |
392 | *jtransform, |
393 | sksg::Transform::MakeInverse(std::move(parent_transform)), |
394 | cbuilder->fSize); |
395 | } |
396 | |
397 | return this->is3D() |
398 | ? abuilder.attachMatrix3D(*jtransform, std::move(parent_transform), fAutoOrient) |
399 | : abuilder.attachMatrix2D(*jtransform, std::move(parent_transform), fAutoOrient); |
400 | } |
401 | |
402 | bool LayerBuilder::hasMotionBlur(const CompositionBuilder* cbuilder) const { |
403 | return cbuilder->fMotionBlurSamples > 1 |
404 | && cbuilder->fMotionBlurAngle > 0 |
405 | && ParseDefault(fJlayer["mb" ], false); |
406 | } |
407 | |
408 | sk_sp<sksg::RenderNode> LayerBuilder::buildRenderTree(const AnimationBuilder& abuilder, |
409 | CompositionBuilder* cbuilder, |
410 | const LayerBuilder* prev_layer) { |
411 | AnimationBuilder::LayerInfo layer_info = { |
412 | cbuilder->fSize, |
413 | ParseDefault<float>(fJlayer["ip" ], 0.0f), |
414 | ParseDefault<float>(fJlayer["op" ], 0.0f), |
415 | }; |
416 | if (SkScalarNearlyEqual(layer_info.fInPoint, layer_info.fOutPoint)) { |
417 | abuilder.log(Logger::Level::kError, nullptr, |
418 | "Invalid layer in/out points: %f/%f." , |
419 | layer_info.fInPoint, layer_info.fOutPoint); |
420 | return nullptr; |
421 | } |
422 | |
423 | const AnimationBuilder::AutoPropertyTracker apt(&abuilder, fJlayer); |
424 | |
425 | using LayerBuilder = |
426 | sk_sp<sksg::RenderNode> (AnimationBuilder::*)(const skjson::ObjectValue&, |
427 | AnimationBuilder::LayerInfo*) const; |
428 | |
429 | // AE is annoyingly inconsistent in how effects interact with layer transforms: depending on |
430 | // the layer type, effects are applied before or after the content is transformed. |
431 | // |
432 | // Empirically, pre-rendered layers (for some loose meaning of "pre-rendered") are in the |
433 | // former category (effects are subject to transformation), while the remaining types are in |
434 | // the latter. |
435 | enum : uint32_t { |
436 | kTransformEffects = 0x01, // The layer transform also applies to its effects. |
437 | kForceSeek = 0x02, // Dispatch all seek() events even when the layer is inactive. |
438 | }; |
439 | |
440 | static constexpr struct { |
441 | LayerBuilder fBuilder; |
442 | uint32_t fFlags; |
443 | } gLayerBuildInfo[] = { |
444 | { &AnimationBuilder::attachPrecompLayer, kTransformEffects }, // 'ty': 0 -> precomp |
445 | { &AnimationBuilder::attachSolidLayer , kTransformEffects }, // 'ty': 1 -> solid |
446 | { &AnimationBuilder::attachFootageLayer, kTransformEffects }, // 'ty': 2 -> image |
447 | { &AnimationBuilder::attachNullLayer , 0 }, // 'ty': 3 -> null |
448 | { &AnimationBuilder::attachShapeLayer , 0 }, // 'ty': 4 -> shape |
449 | { &AnimationBuilder::attachTextLayer , 0 }, // 'ty': 5 -> text |
450 | { &AnimationBuilder::attachAudioLayer , kForceSeek }, // 'ty': 6 -> audio |
451 | { nullptr , 0 }, // 'ty': 7 -> pholderVideo |
452 | { nullptr , 0 }, // 'ty': 8 -> imageSeq |
453 | { &AnimationBuilder::attachFootageLayer, kTransformEffects }, // 'ty': 9 -> video |
454 | { nullptr , 0 }, // 'ty': 10 -> pholderStill |
455 | { nullptr , 0 }, // 'ty': 11 -> guide |
456 | { nullptr , 0 }, // 'ty': 12 -> adjustment |
457 | { &AnimationBuilder::attachNullLayer , 0 }, // 'ty': 13 -> camera |
458 | { nullptr , 0 }, // 'ty': 14 -> light |
459 | }; |
460 | |
461 | // Treat all hidden layers as null. |
462 | const auto type = ParseDefault<bool>(fJlayer["hd" ], false) |
463 | ? kNullLayerType |
464 | : SkToSizeT(fType); |
465 | |
466 | if (type >= SK_ARRAY_COUNT(gLayerBuildInfo)) { |
467 | return nullptr; |
468 | } |
469 | |
470 | const auto& build_info = gLayerBuildInfo[type]; |
471 | |
472 | // Switch to the layer animator scope (which at this point holds transform-only animators). |
473 | AnimationBuilder::AutoScope ascope(&abuilder, std::move(fLayerScope)); |
474 | |
475 | // Potentially null. |
476 | sk_sp<sksg::RenderNode> layer; |
477 | |
478 | // Build the layer content fragment. |
479 | if (build_info.fBuilder) { |
480 | layer = (abuilder.*(build_info.fBuilder))(fJlayer, &layer_info); |
481 | } |
482 | |
483 | // Clip layers with explicit dimensions. |
484 | float w = 0, h = 0; |
485 | if (Parse<float>(fJlayer["w" ], &w) && Parse<float>(fJlayer["h" ], &h)) { |
486 | layer = sksg::ClipEffect::Make(std::move(layer), |
487 | sksg::Rect::Make(SkRect::MakeWH(w, h)), |
488 | true); |
489 | } |
490 | |
491 | // Optional layer mask. |
492 | layer = AttachMask(fJlayer["masksProperties" ], &abuilder, std::move(layer)); |
493 | |
494 | // Does the transform apply to effects also? |
495 | // (AE quirk: it doesn't - except for solid layers) |
496 | const auto transform_effects = (build_info.fFlags & kTransformEffects); |
497 | |
498 | // Attach the transform before effects, when needed. |
499 | if (fLayerTransform && !transform_effects) { |
500 | layer = sksg::TransformEffect::Make(std::move(layer), fLayerTransform); |
501 | } |
502 | |
503 | // Optional layer effects. |
504 | if (const skjson::ArrayValue* jeffects = fJlayer["ef" ]) { |
505 | layer = EffectBuilder(&abuilder, layer_info.fSize).attachEffects(*jeffects, |
506 | std::move(layer)); |
507 | } |
508 | |
509 | // Attach the transform after effects, when needed. |
510 | if (fLayerTransform && transform_effects) { |
511 | layer = sksg::TransformEffect::Make(std::move(layer), std::move(fLayerTransform)); |
512 | } |
513 | |
514 | // Optional layer styles. |
515 | if (const skjson::ArrayValue* jstyles = fJlayer["sy" ]) { |
516 | layer = EffectBuilder(&abuilder, layer_info.fSize).attachStyles(*jstyles, std::move(layer)); |
517 | } |
518 | |
519 | // Optional layer opacity. |
520 | // TODO: de-dupe this "ks" lookup with matrix above. |
521 | if (const skjson::ObjectValue* jtransform = fJlayer["ks" ]) { |
522 | layer = abuilder.attachOpacity(*jtransform, std::move(layer)); |
523 | } |
524 | |
525 | const auto has_animators = !abuilder.fCurrentAnimatorScope->empty(); |
526 | const auto force_seek_count = build_info.fFlags & kForceSeek |
527 | ? abuilder.fCurrentAnimatorScope->size() |
528 | : fTransformAnimatorCount; |
529 | |
530 | sk_sp<Animator> controller = sk_make_sp<LayerController>(ascope.release(), |
531 | layer, |
532 | force_seek_count, |
533 | layer_info.fInPoint, |
534 | layer_info.fOutPoint); |
535 | |
536 | // Optional motion blur. |
537 | if (layer && has_animators && this->hasMotionBlur(cbuilder)) { |
538 | // Wrap both the layer node and the controller. |
539 | auto motion_blur = MotionBlurEffect::Make(std::move(controller), std::move(layer), |
540 | cbuilder->fMotionBlurSamples, |
541 | cbuilder->fMotionBlurAngle, |
542 | cbuilder->fMotionBlurPhase); |
543 | controller = sk_make_sp<MotionBlurController>(motion_blur); |
544 | layer = std::move(motion_blur); |
545 | } |
546 | |
547 | abuilder.fCurrentAnimatorScope->push_back(std::move(controller)); |
548 | |
549 | // Stash the content tree in case it is needed for later mattes. |
550 | fContentTree = layer; |
551 | |
552 | if (ParseDefault<bool>(fJlayer["td" ], false)) { |
553 | // |layer| is a track matte. We apply it as a mask to the next layer. |
554 | return nullptr; |
555 | } |
556 | |
557 | // Optional matte. |
558 | size_t matte_mode; |
559 | if (prev_layer && Parse(fJlayer["tt" ], &matte_mode)) { |
560 | static constexpr sksg::MaskEffect::Mode gMatteModes[] = { |
561 | sksg::MaskEffect::Mode::kAlphaNormal, // tt: 1 |
562 | sksg::MaskEffect::Mode::kAlphaInvert, // tt: 2 |
563 | sksg::MaskEffect::Mode::kLumaNormal, // tt: 3 |
564 | sksg::MaskEffect::Mode::kLumaInvert, // tt: 4 |
565 | }; |
566 | |
567 | if (matte_mode > 0 && matte_mode <= SK_ARRAY_COUNT(gMatteModes)) { |
568 | // The current layer is masked with the previous layer *content*. |
569 | layer = sksg::MaskEffect::Make(std::move(layer), |
570 | prev_layer->fContentTree, |
571 | gMatteModes[matte_mode - 1]); |
572 | } else { |
573 | abuilder.log(Logger::Level::kError, nullptr, |
574 | "Unknown track matte mode: %zu\n" , matte_mode); |
575 | } |
576 | } |
577 | |
578 | // Finally, attach an optional blend mode. |
579 | // NB: blend modes are never applied to matte sources (layer content only). |
580 | return abuilder.attachBlendMode(fJlayer, std::move(layer)); |
581 | } |
582 | |
583 | } // namespace internal |
584 | } // namespace skottie |
585 | |