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