From 2f24495ffe03a59fbe7fd79e34841ee9dfbe47ae Mon Sep 17 00:00:00 2001 From: Marco Arnulfo Date: Fri, 16 Oct 2026 13:05:37 +0200 Subject: [PATCH] Color effects recolour the points of a freeform gradient (fixes #2390) --- crates/color/src/lib.rs | 11 +++++++++-- crates/effects/src/adjust.rs | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/crates/color/src/lib.rs b/crates/color/src/lib.rs index 6fc854ff0..ff10699be 100644 --- a/crates/color/src/lib.rs +++ b/crates/color/src/lib.rs @@ -387,7 +387,9 @@ impl Paint { } /// Apply `f` to the colours of this paint that can link to a swatch (a solid colour, each /// gradient stop) with their link and tint; `f` returns true when it changed them. A colour - /// left without a link gets tint 1. Returns whether anything changed. + /// left without a link gets tint 1. A freeform gradient's points, which it draws from, go + /// through `f` too, as unlinked colours (a link `f` gives them is dropped). Returns whether + /// anything changed. pub fn map_links(&mut self, f: &mut dyn FnMut(&mut Color, &mut Option, &mut f32) -> bool) -> bool { let mut one = |c: &mut Color, link: &mut Option, tint: &mut f32| { let changed = f(c, link, tint); @@ -398,7 +400,12 @@ impl Paint { }; match self { Paint::Solid { color, swatch, tint } => one(color, swatch, tint), - Paint::Gradient(g) => g.gradient.stops.iter_mut().fold(false, |ch, s| one(&mut s.color, &mut s.swatch, &mut s.tint) | ch), + Paint::Gradient(g) => { + let stops = g.gradient.stops.iter_mut().fold(false, |ch, s| one(&mut s.color, &mut s.swatch, &mut s.tint) | ch); + let points = + g.freeform.iter_mut().flat_map(|ff| ff.points.iter_mut()).fold(false, |ch, p| one(&mut p.color, &mut None, &mut 1.0) | ch); + stops | points + } _ => false, } } diff --git a/crates/effects/src/adjust.rs b/crates/effects/src/adjust.rs index 85e8a2cd9..84ca9d3c1 100644 --- a/crates/effects/src/adjust.rs +++ b/crates/effects/src/adjust.rs @@ -490,4 +490,20 @@ mod tests { assert_eq!(out.appearance.items[0].paint(), p.appearance.items[0].paint()); assert!(out.appearance.items[1].effects().is_empty()); } + + #[test] + fn adjust_recolours_the_points_of_a_freeform_gradient() { + use vectorcraft_doc::color::{Freeform, FreeformPoint, Gradient, GradientKind, GradientPaint}; + let mut gp = GradientPaint::new(Gradient { kind: GradientKind::Freeform, ..Gradient::default() }); + let pt = |x, c| FreeformPoint::new(kurbo::Point::new(x, 5.0), c); + gp.set_freeform(Freeform { points: vec![pt(2.0, Color::rgb(1.0, 0.0, 0.0)), pt(8.0, Color::rgb(0.0, 0.0, 1.0))], ..Default::default() }); + let fill = Appearance::basic(Paint::Gradient(Box::new(gp)), Paint::None, 0.0); + let mut p = Node::path(NodeId(1), shapes::rectangle(Rect::new(0.0, 0.0, 10.0, 10.0)), fill); + p.appearance.effects.push(Effect { id: "adjust.hueSaturation".into(), params: json!({"hue": 120}), visible: true }); + let out = adjust(&p, None, &mut |_, _| None).unwrap(); + let Paint::Gradient(g) = out.appearance.fill_paint() else { panic!("a gradient fill") }; + // The points are what a freeform gradient draws from, not its stops. + let hexes: Vec = g.freeform.as_ref().unwrap().points.iter().map(|p| p.color.to_hex()).collect(); + assert_eq!(hexes, ["#00ff00", "#ff0000"]); + } }