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