From c703cc56503e816a5990df453403770fc130e75f Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Mon, 9 Nov 2026 21:37:10 -0800 Subject: [PATCH] Revolve: a face drawn on its own keeps its fill or stroke's opacity and blending mode A face of a group member that paints nothing (its fill or stroke at 0% opacity) was drawn without a group, so its zero opacity was lost and the member drew fully opaque. Such a face now keeps a group with the opacity and blending mode of its fill or stroke. --- crates/effects/src/revolve.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/crates/effects/src/revolve.rs b/crates/effects/src/revolve.rs index d737a0af9e..89f3bba867 100644 --- a/crates/effects/src/revolve.rs +++ b/crates/effects/src/revolve.rs @@ -657,7 +657,8 @@ fn group_art(n: &Node, expand: bool) -> Option { } } let runs = runs.into_iter().filter_map(|mut r| { - if r.member.is_none() { + // A face on its own needs a group only for the opacity or blending mode of its fill or stroke. + if r.member.is_none() && r.opacity >= 1.0 && r.blend == Default::default() { return r.faces.pop().map(|face| (r.scopes, face)); } let mut group = Node::group(n.id, r.faces); @@ -949,6 +950,36 @@ mod tests { assert_eq!(groups(&art), 2, "one group for each faded ring"); } + #[test] + fn a_member_whose_fill_or_stroke_paints_nothing_stays_invisible() { + let clear_fill = { + let mut n = (*member(3, 20.0)).clone(); + if let Some(AppearanceItem::Fill(f)) = n.appearance.items.iter_mut().find(|i| matches!(i, AppearanceItem::Fill(_))) { + f.opacity = 0.0; + } + n + }; + let clear_stroke = { + let mut n = (*member(4, 20.0)).clone(); + n.appearance = Appearance::basic(Paint::None, Paint::solid(Color::BLACK), 1.0).into(); + for item in n.appearance.items.iter_mut() { + if let AppearanceItem::Stroke(s) = item { + s.opacity = 0.0; + } + } + n + }; + for clear in [clear_fill, clear_stroke] { + let art = revolve_art(&group_with(json!({"segments":16}), vec![member(1, 0.0), member(2, 50.0), Arc::new(clear)])).unwrap(); + let faces = faces_in_order(&art); + let alone = faces_in_order(&revolve_art(&group_with(json!({"segments":16}), vec![member(1, 0.0), member(2, 50.0)])).unwrap()); + let opaque: Vec<_> = faces.iter().filter(|(_, s)| s.is_empty()).map(|(f, _)| f).collect(); + assert_eq!(opaque, alone.iter().map(|(f, _)| f).collect::>(), "only the opaque members' faces draw without transparency"); + assert!(faces.len() > alone.len(), "the clear member's faces stay in the art"); + assert!(faces.iter().all(|(_, s)| s.is_empty() || s.iter().any(|(o, _)| *o == 0.0)), "each clear face is drawn at 0% opacity"); + } + } + fn star(id: u64) -> Arc { let anchors = (0..1200) .map(|i| {