From 8924b24e9464f2b5d271d42cf2aff398e569c148 Mon Sep 17 00:00:00 2001 From: Taha-Mahmoodi <85902429+Taha-Mahmoodi@users.noreply.github.com> Date: Thu, 22 Oct 2026 23:22:30 +0430 Subject: [PATCH] Gradients: Expand to a gradient mesh keeps a Perceptual gradient's colour curve Review follow-up (CodeRabbit): a linear gradient expands to a mesh with a column at each stop, and the mesh mixes linearly between columns, so a Perceptual blue -> yellow came out as the grey RGB mix in the middle. `breaks` now also takes a column at each stop the Perceptual expansion adds (Gradient::expanded), so the mesh follows the OKLab curve; Classic is unchanged. the_mesh_option_makes_a_mesh_that_paints_the_gradient gains a Perceptual case; without the change 1812 pixels differ from the gradient. --- crates/engine/src/cmd/expand.rs | 5 +++++ crates/engine/src/tests_objexpand.rs | 2 ++ 2 files changed, 7 insertions(+) diff --git a/crates/engine/src/cmd/expand.rs b/crates/engine/src/cmd/expand.rs index ee9b582dd..512501d8d 100644 --- a/crates/engine/src/cmd/expand.rs +++ b/crates/engine/src/cmd/expand.rs @@ -747,6 +747,11 @@ pub(crate) fn gradient_sampler(g: &GradientPaint, bounds: Rect, doc_model: Model fn breaks(g: &Gradient, t0: f64, t1: f64, max_gap: f64) -> Vec<(f64, (Color, f32))> { let at = |t: f64| (t, g.sample_keep_model(t as f32)); let mut v = vec![at(t0)]; + // A Perceptual gradient curves between its stops: the mesh takes a column at each of the stops + // its expansion adds, so the linear mix between columns follows that curve. + if !g.method.is_classic() { + v.extend(g.expanded().map(|(o, ..)| f64::from(o)).filter(|o| *o > t0 && *o < t1).map(at)); + } for (i, s) in g.stops.iter().enumerate() { let o = s.offset as f64; if o > t0 && o < t1 { diff --git a/crates/engine/src/tests_objexpand.rs b/crates/engine/src/tests_objexpand.rs index ea92bca1f..48435adbb 100644 --- a/crates/engine/src/tests_objexpand.rs +++ b/crates/engine/src/tests_objexpand.rs @@ -197,6 +197,8 @@ fn the_mesh_option_makes_a_mesh_that_paints_the_gradient() { for gradient in [ json!({"kind": "linear", "angle": 30, "stops": [{"offset": 0, "color": "#ff0000"}, {"offset": 0.4, "color": "#00ff00", "midpoint": 0.3}, {"offset": 1, "color": "#0000ff"}]}), json!({"kind": "radial", "stops": [{"offset": 0, "color": "#ffff00"}, {"offset": 0.5, "color": "#ff00ff"}, {"offset": 1, "color": "#004080"}]}), + // Perceptual: the mesh follows the OKLab curve between the stops, not a straight RGB mix. + json!({"kind": "linear", "method": "perceptual", "stops": [{"offset": 0, "color": "#2050f0"}, {"offset": 1, "color": "#f5d020"}]}), ] { let mut s = session(); gradient_rect(&mut s, Rect::new(30.0, 40.0, 170.0, 160.0), gradient.clone(), false);