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