[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;
|
||||
|
||||
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) {
|
||||
if (layer_index < 0) {
|
||||
return nullptr;
|
||||
@ -202,10 +194,12 @@ sk_sp<sksg::RenderNode> CompositionBuilder::build(const AnimationBuilder& abuild
|
||||
std::vector<sk_sp<sksg::RenderNode>> 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()) {
|
||||
|
@ -29,9 +29,6 @@ private:
|
||||
|
||||
const sk_sp<sksg::Transform>& getCameraTransform() const { return fCameraTransform; }
|
||||
|
||||
void pushMatte(sk_sp<sksg::RenderNode>);
|
||||
sk_sp<sksg::RenderNode> popMatte();
|
||||
|
||||
friend class LayerBuilder;
|
||||
|
||||
const SkSize fSize;
|
||||
@ -40,7 +37,6 @@ private:
|
||||
SkTHashMap<int, size_t> fLayerIndexMap; // Maps layer "ind" to layer builder index.
|
||||
|
||||
sk_sp<sksg::Transform> fCameraTransform;
|
||||
sk_sp<sksg::RenderNode> fCurrentMatte; // Tracks the current/active matte.
|
||||
|
||||
size_t fMotionBlurSamples = 1;
|
||||
float fMotionBlurAngle = 0,
|
||||
|
@ -363,7 +363,8 @@ bool LayerBuilder::hasMotionBlur(const CompositionBuilder* cbuilder) const {
|
||||
}
|
||||
|
||||
sk_sp<sksg::RenderNode> LayerBuilder::buildRenderTree(const AnimationBuilder& abuilder,
|
||||
CompositionBuilder* cbuilder) {
|
||||
CompositionBuilder* cbuilder,
|
||||
const LayerBuilder* prev_layer) {
|
||||
AnimationBuilder::LayerInfo layer_info = {
|
||||
cbuilder->fSize,
|
||||
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));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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<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),
|
||||
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<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;
|
||||
// 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
|
||||
|
@ -28,7 +28,8 @@ public:
|
||||
sk_sp<sksg::Transform> buildTransform(const AnimationBuilder&, CompositionBuilder*);
|
||||
|
||||
// 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:
|
||||
enum TransformType : uint8_t {
|
||||
@ -65,6 +66,8 @@ private:
|
||||
|
||||
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::RenderNode> fContentTree; // render tree for layer content,
|
||||
// excluding mask/matte and blending
|
||||
|
||||
AnimatorScope fLayerScope; // layer-scoped animators
|
||||
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