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
27namespace skottie {
28namespace internal {
29
30namespace {
31
32static constexpr size_t kNullLayerType = 3;
33
34struct MaskInfo {
35 SkBlendMode fBlendMode; // used when masking with layers/blending
36 sksg::Merge::Mode fMergeMode; // used when clipping
37 bool fInvertGeometry;
38};
39
40const 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
61class MaskAdapter final : public AnimatablePropertyContainer {
62public:
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
99private:
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
134sk_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
245class LayerController final : public Animator {
246public:
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
256protected:
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
279private:
280 const AnimatorScope fLayerAnimators;
281 const sk_sp<sksg::RenderNode> fLayerNode;
282 const size_t fTransformAnimatorsCount;
283 const float fIn,
284 fOut;
285};
286
287class MotionBlurController final : public Animator {
288public:
289 explicit MotionBlurController(sk_sp<MotionBlurEffect> mbe)
290 : fMotionBlurEffect(std::move(mbe)) {}
291
292protected:
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
301private:
302 const sk_sp<MotionBlurEffect> fMotionBlurEffect;
303};
304
305} // namespace
306
307LayerBuilder::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
319LayerBuilder::~LayerBuilder() = default;
320
321bool LayerBuilder::isCamera() const {
322 static constexpr int kCameraLayerType = 13;
323
324 return fType == kCameraLayerType;
325}
326
327sk_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
337sk_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
355sk_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
375sk_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
402bool LayerBuilder::hasMotionBlur(const CompositionBuilder* cbuilder) const {
403 return cbuilder->fMotionBlurSamples > 1
404 && cbuilder->fMotionBlurAngle > 0
405 && ParseDefault(fJlayer["mb"], false);
406}
407
408sk_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