Text animators: a later animator's Opacity can show characters an earlier one hid

Animator Opacity stacked by multiplying, so once one animator set 0 %
no animator below it could raise it again. Each animator now blends the
opacity left by the ones above it toward its own value. A single Opacity
animator gives the same result as before.

Fixes #2838.
This commit is contained in:
JonRC committed 2026-11-02 12:16:12 -03:00
1 parent 9d7baea5cc
commit 0af6d2aab8
2 files changed
+20 -1

No files matched your search

+4 -1
View File
@@ -343,7 +343,10 @@ pub fn char_transforms(ctx: &EvalCtx, layer: &Layer, text: &PropGroup, lay: &Tex
}
angles.apply(c, k);
if let Some(o) = op {
c.opacity *= 1.0 + (o / 100.0 - 1.0) * k;
// Blend toward this animator's value, so a later animator can show characters an
// earlier one hid (Lottie files rely on it: Opacity 0 % on every character, then
// animators at 100 % that sweep in). With one animator this is the same as before.
c.opacity += (o - c.opacity) * k;
}
if let Some(f) = fill {
c.fill = Some(f);
+16
View File
@@ -548,6 +548,22 @@ fn keyframed_selector_animates() {
assert_eq!(selection(&p, cid, 1.0, 1), vec![0.0; 4]);
}
#[test]
fn a_later_animator_opacity_shows_characters_an_earlier_one_hid() {
let (mut p, cid, comp) = setup(400, 200);
let mut l = text_layer(&mut p, &comp, doc("ABCD", 40.0));
animator(&mut p, &mut l, &["opacity"], &["range"]);
set(&mut l, "text/animators/#1/properties/opacity", Value::Scalar(0.0));
animator(&mut p, &mut l, &["opacity"], &["range"]);
set(&mut l, "text/animators/#2/properties/opacity", Value::Scalar(60.0));
set(&mut l, "text/animators/#2/selectors/#1/end", Value::Scalar(50.0));
p.comp_mut(cid).unwrap().layers.push(l);
let glyphs = glyph_paths(&ctx(&p, cid, 0.0), &p.comp(cid).unwrap().layers[0]);
let opacity: Vec<f64> = glyphs.iter().map(|g| (g.1.opacity * 1000.0).round() / 1000.0).collect();
// The second animator selects "AB" only: they come back at 60 %, "CD" stay hidden.
assert_eq!(opacity, [60.0, 60.0, 0.0, 0.0]);
}
#[test]
fn caret_follows_animators_and_path_text() {
let (mut p, cid, comp) = setup(400, 400);