From 46a331b93f54d8b3bce88792dd8679beef11a751 Mon Sep 17 00:00:00 2001 From: Florin Malita Date: Thu, 12 Dec 2019 15:43:53 -0500 Subject: [PATCH] [skottie] Cascading track matte support Currently, we treat track matte source layers (tagged with td:1) as single-shot mask triggers: we apply once to the following layer, then move on. But track mattes can cascade: a layer with a matte can itself be applied as a track matte for the following layer. Also, for matte/masking purposes, only the layer content is being considered (ignoring blend mode and any masks applied to the matte itself). To support this, refactor the layer attachment code: - instead of tracking the presence of a single-shot matte source, always track previous layer content trees - instead of triggering matte attachment in the presence of a matte source, trigger based on the matte *target* property (tt: X) - log errors on unknown matte modes Change-Id: I6c71d4007e1e27d3f3a139344bbf367d7bc6e29d Reviewed-on: https://skia-review.googlesource.com/c/skia/+/259820 Reviewed-by: Mike Reed Commit-Queue: Florin Malita --- modules/skottie/src/Composition.cpp | 12 ++---- modules/skottie/src/Composition.h | 4 -- modules/skottie/src/Layer.cpp | 43 +++++++++---------- modules/skottie/src/Layer.h | 5 ++- resources/skottie/skottie-chained-mattes.json | 1 + 5 files changed, 29 insertions(+), 36 deletions(-) create mode 100644 resources/skottie/skottie-chained-mattes.json diff --git a/modules/skottie/src/Composition.cpp b/modules/skottie/src/Composition.cpp index abb82ce092..ec68341399 100644 --- a/modules/skottie/src/Composition.cpp +++ b/modules/skottie/src/Composition.cpp @@ -172,14 +172,6 @@ CompositionBuilder::CompositionBuilder(const AnimationBuilder& abuilder, CompositionBuilder::~CompositionBuilder() = default; -void CompositionBuilder::pushMatte(sk_sp matte) { - fCurrentMatte = std::move(matte); -} - -sk_sp CompositionBuilder::popMatte() { - return std::move(fCurrentMatte); -} - LayerBuilder* CompositionBuilder::layerBuilder(int layer_index) { if (layer_index < 0) { return nullptr; @@ -202,10 +194,12 @@ sk_sp CompositionBuilder::build(const AnimationBuilder& abuild std::vector> layers; layers.reserve(fLayerBuilders.size()); + LayerBuilder* prev_layer = nullptr; for (auto& lbuilder : fLayerBuilders) { - if (auto layer = lbuilder.buildRenderTree(abuilder, this)) { + if (auto layer = lbuilder.buildRenderTree(abuilder, this, prev_layer)) { layers.push_back(std::move(layer)); } + prev_layer = &lbuilder; } if (layers.empty()) { diff --git a/modules/skottie/src/Composition.h b/modules/skottie/src/Composition.h index 461778bc05..99fb396aed 100644 --- a/modules/skottie/src/Composition.h +++ b/modules/skottie/src/Composition.h @@ -29,9 +29,6 @@ private: const sk_sp& getCameraTransform() const { return fCameraTransform; } - void pushMatte(sk_sp); - sk_sp popMatte(); - friend class LayerBuilder; const SkSize fSize; @@ -40,7 +37,6 @@ private: SkTHashMap fLayerIndexMap; // Maps layer "ind" to layer builder index. sk_sp fCameraTransform; - sk_sp fCurrentMatte; // Tracks the current/active matte. size_t fMotionBlurSamples = 1; float fMotionBlurAngle = 0, diff --git a/modules/skottie/src/Layer.cpp b/modules/skottie/src/Layer.cpp index cfdea6f688..4ba0a9cd6e 100644 --- a/modules/skottie/src/Layer.cpp +++ b/modules/skottie/src/Layer.cpp @@ -363,7 +363,8 @@ bool LayerBuilder::hasMotionBlur(const CompositionBuilder* cbuilder) const { } sk_sp LayerBuilder::buildRenderTree(const AnimationBuilder& abuilder, - CompositionBuilder* cbuilder) { + CompositionBuilder* cbuilder, + const LayerBuilder* prev_layer) { AnimationBuilder::LayerInfo layer_info = { cbuilder->fSize, ParseDefault(fJlayer["ip"], 0.0f), @@ -475,40 +476,38 @@ sk_sp LayerBuilder::buildRenderTree(const AnimationBuilder& ab abuilder.fCurrentAnimatorScope->push_back(std::move(controller)); - if (!layer) { + // Stash the content tree in case it is needed for later mattes. + fContentTree = layer; + + if (ParseDefault(fJlayer["td"], false)) { + // |layer| is a track matte. We apply it as a mask to the next layer. return nullptr; } - if (auto matte = cbuilder->popMatte()) { - // There is a pending matte (|layer| is a matte target). - static constexpr sksg::MaskEffect::Mode gMaskModes[] = { + // Optional matte. + size_t matte_mode; + if (prev_layer && Parse(fJlayer["tt"], &matte_mode)) { + static constexpr sksg::MaskEffect::Mode gMatteModes[] = { sksg::MaskEffect::Mode::kAlphaNormal, // tt: 1 sksg::MaskEffect::Mode::kAlphaInvert, // tt: 2 sksg::MaskEffect::Mode::kLumaNormal, // tt: 3 sksg::MaskEffect::Mode::kLumaInvert, // tt: 4 }; - const auto matteType = ParseDefault(fJlayer["tt"], 1) - 1; - if (matteType < SK_ARRAY_COUNT(gMaskModes)) { + if (matte_mode > 0 && matte_mode <= SK_ARRAY_COUNT(gMatteModes)) { + // The current layer is masked with the previous layer *content*. layer = sksg::MaskEffect::Make(std::move(layer), - std::move(matte), - gMaskModes[matteType]); + prev_layer->fContentTree, + gMatteModes[matte_mode - 1]); + } else { + abuilder.log(Logger::Level::kError, nullptr, + "Unknown track matte mode: %zu\n", matte_mode); } } - // Optional blend mode. The attachment point is important for matte interactions: - // - for mattes (mask layers), the blend mode is applied to the layer content - // - for matte targets (masked layers), the blend mode is applied post-masking - // (wrapping the MaskEffect above) - layer = abuilder.attachBlendMode(fJlayer, std::move(layer)); - - if (ParseDefault(fJlayer["td"], false)) { - // |layer| is a matte. We apply it as a mask to the next layer. - cbuilder->pushMatte(std::move(layer)); - return nullptr; - } - - return layer; + // Finally, attach an optional blend mode. + // NB: blend modes are never applied to matte sources (layer content only). + return abuilder.attachBlendMode(fJlayer, std::move(layer)); } } // namespace internal diff --git a/modules/skottie/src/Layer.h b/modules/skottie/src/Layer.h index d85a52b9e8..3a1f6f2bf6 100644 --- a/modules/skottie/src/Layer.h +++ b/modules/skottie/src/Layer.h @@ -28,7 +28,8 @@ public: sk_sp buildTransform(const AnimationBuilder&, CompositionBuilder*); // Attaches the actual layer content and finalizes its render tree. Called once per layer. - sk_sp buildRenderTree(const AnimationBuilder&, CompositionBuilder*); + sk_sp buildRenderTree(const AnimationBuilder&, CompositionBuilder*, + const LayerBuilder* prev_layer); private: enum TransformType : uint8_t { @@ -65,6 +66,8 @@ private: sk_sp fLayerTransform; // this layer's transform node. sk_sp fTransformCache[2]; // cached 2D/3D chain for the local node + sk_sp fContentTree; // render tree for layer content, + // excluding mask/matte and blending AnimatorScope fLayerScope; // layer-scoped animators size_t fTransformAnimatorCount = 0; // transform-related animator count diff --git a/resources/skottie/skottie-chained-mattes.json b/resources/skottie/skottie-chained-mattes.json new file mode 100644 index 0000000000..c4a34669a7 --- /dev/null +++ b/resources/skottie/skottie-chained-mattes.json @@ -0,0 +1 @@ +{"v":"5.5.10","fr":60,"ip":0,"op":601,"w":500,"h":500,"nm":"chained mattes","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"matte 1","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":600,"s":[720]}],"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"sr","sy":1,"d":1,"pt":{"a":0,"k":5,"ix":3},"p":{"a":0,"k":[0,0],"ix":4},"r":{"a":0,"k":0,"ix":5},"ir":{"a":0,"k":50,"ix":6},"is":{"a":0,"k":0,"ix":8},"or":{"a":0,"k":140,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":601,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"matte2","tt":2,"sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[150,150],"ix":2},"p":{"a":0,"k":[75,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.342968761921,0.597441792488,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":26,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":600,"s":[-360]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[400,400],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.520817995071,0.698636651039,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":601,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":1,"nm":"Medium Purple Solid 1","tt":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"sw":500,"sh":500,"sc":"#d14eff","ip":0,"op":601,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":1,"nm":"bg","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"sw":500,"sh":500,"sc":"#000000","ip":0,"op":601,"st":0,"bm":0}],"markers":[]} \ No newline at end of file