[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 <reed@google.com> Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
parent
b0892888d1
commit
46a331b93f
@ -172,14 +172,6 @@ CompositionBuilder::CompositionBuilder(const AnimationBuilder& abuilder,
|
|||||||
|
|
||||||
CompositionBuilder::~CompositionBuilder() = default;
|
CompositionBuilder::~CompositionBuilder() = default;
|
||||||
|
|
||||||
void CompositionBuilder::pushMatte(sk_sp<sksg::RenderNode> matte) {
|
|
||||||
fCurrentMatte = std::move(matte);
|
|
||||||
}
|
|
||||||
|
|
||||||
sk_sp<sksg::RenderNode> CompositionBuilder::popMatte() {
|
|
||||||
return std::move(fCurrentMatte);
|
|
||||||
}
|
|
||||||
|
|
||||||
LayerBuilder* CompositionBuilder::layerBuilder(int layer_index) {
|
LayerBuilder* CompositionBuilder::layerBuilder(int layer_index) {
|
||||||
if (layer_index < 0) {
|
if (layer_index < 0) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -202,10 +194,12 @@ sk_sp<sksg::RenderNode> CompositionBuilder::build(const AnimationBuilder& abuild
|
|||||||
std::vector<sk_sp<sksg::RenderNode>> layers;
|
std::vector<sk_sp<sksg::RenderNode>> layers;
|
||||||
layers.reserve(fLayerBuilders.size());
|
layers.reserve(fLayerBuilders.size());
|
||||||
|
|
||||||
|
LayerBuilder* prev_layer = nullptr;
|
||||||
for (auto& lbuilder : fLayerBuilders) {
|
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));
|
layers.push_back(std::move(layer));
|
||||||
}
|
}
|
||||||
|
prev_layer = &lbuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (layers.empty()) {
|
if (layers.empty()) {
|
||||||
|
@ -29,9 +29,6 @@ private:
|
|||||||
|
|
||||||
const sk_sp<sksg::Transform>& getCameraTransform() const { return fCameraTransform; }
|
const sk_sp<sksg::Transform>& getCameraTransform() const { return fCameraTransform; }
|
||||||
|
|
||||||
void pushMatte(sk_sp<sksg::RenderNode>);
|
|
||||||
sk_sp<sksg::RenderNode> popMatte();
|
|
||||||
|
|
||||||
friend class LayerBuilder;
|
friend class LayerBuilder;
|
||||||
|
|
||||||
const SkSize fSize;
|
const SkSize fSize;
|
||||||
@ -40,7 +37,6 @@ private:
|
|||||||
SkTHashMap<int, size_t> fLayerIndexMap; // Maps layer "ind" to layer builder index.
|
SkTHashMap<int, size_t> fLayerIndexMap; // Maps layer "ind" to layer builder index.
|
||||||
|
|
||||||
sk_sp<sksg::Transform> fCameraTransform;
|
sk_sp<sksg::Transform> fCameraTransform;
|
||||||
sk_sp<sksg::RenderNode> fCurrentMatte; // Tracks the current/active matte.
|
|
||||||
|
|
||||||
size_t fMotionBlurSamples = 1;
|
size_t fMotionBlurSamples = 1;
|
||||||
float fMotionBlurAngle = 0,
|
float fMotionBlurAngle = 0,
|
||||||
|
@ -363,7 +363,8 @@ bool LayerBuilder::hasMotionBlur(const CompositionBuilder* cbuilder) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sk_sp<sksg::RenderNode> LayerBuilder::buildRenderTree(const AnimationBuilder& abuilder,
|
sk_sp<sksg::RenderNode> LayerBuilder::buildRenderTree(const AnimationBuilder& abuilder,
|
||||||
CompositionBuilder* cbuilder) {
|
CompositionBuilder* cbuilder,
|
||||||
|
const LayerBuilder* prev_layer) {
|
||||||
AnimationBuilder::LayerInfo layer_info = {
|
AnimationBuilder::LayerInfo layer_info = {
|
||||||
cbuilder->fSize,
|
cbuilder->fSize,
|
||||||
ParseDefault<float>(fJlayer["ip"], 0.0f),
|
ParseDefault<float>(fJlayer["ip"], 0.0f),
|
||||||
@ -475,40 +476,38 @@ sk_sp<sksg::RenderNode> LayerBuilder::buildRenderTree(const AnimationBuilder& ab
|
|||||||
|
|
||||||
abuilder.fCurrentAnimatorScope->push_back(std::move(controller));
|
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<bool>(fJlayer["td"], false)) {
|
||||||
|
// |layer| is a track matte. We apply it as a mask to the next layer.
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto matte = cbuilder->popMatte()) {
|
// Optional matte.
|
||||||
// There is a pending matte (|layer| is a matte target).
|
size_t matte_mode;
|
||||||
static constexpr sksg::MaskEffect::Mode gMaskModes[] = {
|
if (prev_layer && Parse(fJlayer["tt"], &matte_mode)) {
|
||||||
|
static constexpr sksg::MaskEffect::Mode gMatteModes[] = {
|
||||||
sksg::MaskEffect::Mode::kAlphaNormal, // tt: 1
|
sksg::MaskEffect::Mode::kAlphaNormal, // tt: 1
|
||||||
sksg::MaskEffect::Mode::kAlphaInvert, // tt: 2
|
sksg::MaskEffect::Mode::kAlphaInvert, // tt: 2
|
||||||
sksg::MaskEffect::Mode::kLumaNormal, // tt: 3
|
sksg::MaskEffect::Mode::kLumaNormal, // tt: 3
|
||||||
sksg::MaskEffect::Mode::kLumaInvert, // tt: 4
|
sksg::MaskEffect::Mode::kLumaInvert, // tt: 4
|
||||||
};
|
};
|
||||||
const auto matteType = ParseDefault<size_t>(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),
|
layer = sksg::MaskEffect::Make(std::move(layer),
|
||||||
std::move(matte),
|
prev_layer->fContentTree,
|
||||||
gMaskModes[matteType]);
|
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:
|
// Finally, attach an optional blend mode.
|
||||||
// - for mattes (mask layers), the blend mode is applied to the layer content
|
// NB: blend modes are never applied to matte sources (layer content only).
|
||||||
// - for matte targets (masked layers), the blend mode is applied post-masking
|
return abuilder.attachBlendMode(fJlayer, std::move(layer));
|
||||||
// (wrapping the MaskEffect above)
|
|
||||||
layer = abuilder.attachBlendMode(fJlayer, std::move(layer));
|
|
||||||
|
|
||||||
if (ParseDefault<bool>(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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
@ -28,7 +28,8 @@ public:
|
|||||||
sk_sp<sksg::Transform> buildTransform(const AnimationBuilder&, CompositionBuilder*);
|
sk_sp<sksg::Transform> buildTransform(const AnimationBuilder&, CompositionBuilder*);
|
||||||
|
|
||||||
// Attaches the actual layer content and finalizes its render tree. Called once per layer.
|
// Attaches the actual layer content and finalizes its render tree. Called once per layer.
|
||||||
sk_sp<sksg::RenderNode> buildRenderTree(const AnimationBuilder&, CompositionBuilder*);
|
sk_sp<sksg::RenderNode> buildRenderTree(const AnimationBuilder&, CompositionBuilder*,
|
||||||
|
const LayerBuilder* prev_layer);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum TransformType : uint8_t {
|
enum TransformType : uint8_t {
|
||||||
@ -65,6 +66,8 @@ private:
|
|||||||
|
|
||||||
sk_sp<sksg::Transform> fLayerTransform; // this layer's transform node.
|
sk_sp<sksg::Transform> fLayerTransform; // this layer's transform node.
|
||||||
sk_sp<sksg::Transform> fTransformCache[2]; // cached 2D/3D chain for the local node
|
sk_sp<sksg::Transform> fTransformCache[2]; // cached 2D/3D chain for the local node
|
||||||
|
sk_sp<sksg::RenderNode> fContentTree; // render tree for layer content,
|
||||||
|
// excluding mask/matte and blending
|
||||||
|
|
||||||
AnimatorScope fLayerScope; // layer-scoped animators
|
AnimatorScope fLayerScope; // layer-scoped animators
|
||||||
size_t fTransformAnimatorCount = 0; // transform-related animator count
|
size_t fTransformAnimatorCount = 0; // transform-related animator count
|
||||||
|
1
resources/skottie/skottie-chained-mattes.json
Normal file
1
resources/skottie/skottie-chained-mattes.json
Normal file
@ -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":[]}
|
Loading…
Reference in New Issue
Block a user