PDF import: an axial or radial shading keeps its function's curve when sampled into stops

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
EricKuck
2026-10-16 03:36:20 +07:00
committed by bflatastic
co-authored by Claude Opus 5.5
parent 136810bee2
commit f3b4c3ec78
2 changed files with 26 additions and 3 deletions
+8 -3
View File
@@ -44,11 +44,16 @@ pub(crate) fn shading_gradient(sp: &ShadingPattern, colors: &mut Colors<'_>, ink
let t = i as f32 / N as f32;
pts.push((t, sample(t)?.to_rgb()));
}
// Every sample dropped since the last stop is checked against the line to the next one, not
// just its neighbours: a gentle curve (an exponential's N) looks straight between neighbours.
let mut keep = vec![0usize];
for w in 1..N {
let (Some(a), Some(p), Some(b)) = (keep.last().and_then(|&k| pts.get(k)), pts.get(w), pts.get(w + 1)) else { continue };
let u = (p.0 - a.0) / (b.0 - a.0).max(1e-6);
let off = (0..3).map(|k| (a.1[k] + (b.1[k] - a.1[k]) * u - p.1[k]).abs()).fold(0.0f32, f32::max);
let (Some(&k), Some(b)) = (keep.last(), pts.get(w + 1)) else { continue };
let Some(a) = pts.get(k) else { continue };
let off = pts.get(k + 1..=w).unwrap_or_default().iter().fold(0.0f32, |worst, p| {
let u = (p.0 - a.0) / (b.0 - a.0).max(1e-6);
(0..3).map(|c| (a.1[c] + (b.1[c] - a.1[c]) * u - p.1[c]).abs()).fold(worst, f32::max)
});
if off > 1.5 / 255.0 {
keep.push(w);
}
+18
View File
@@ -422,6 +422,24 @@ fn a_shading_that_does_not_extend_paints_only_between_its_ends() {
assert!(b.x0 <= 0.0 && (b.x1 - 80.0).abs() < 0.01, "{b:?}");
}
/// GUARDS: an exponential function with N off 1 (how a gradient midpoint is written) keeps its
/// curve: sampled stops must follow it, not collapse to the two ends because neighbouring samples
/// look straight.
#[test]
fn a_shading_with_an_exponent_keeps_its_curve() {
let shading = "/Shading << /Sh0 << /ShadingType 2 /ColorSpace /DeviceRGB /Coords [0 0 100 0] /Extend [true true] /Function << /FunctionType 2 /Domain [0 1] /C0 [0 0 0] /C1 [1 1 1] /N 1.4234 >> >> >>";
let d = open(&one_page("/Sh0 sh", shading, &[]));
let g = all(&d)
.into_iter()
.find_map(|n| n.appearance.items.iter().find_map(|i| if let Paint::Gradient(g) = i.paint() { Some(g.clone()) } else { None }))
.expect("a gradient");
assert!(g.gradient.stops.len() > 2, "{:?}", g.gradient.stops);
// Halfway along, the curve is at 0.5^1.4234 ≈ 0.373, well off the straight 0.5.
let (c, _) = g.gradient.sample(0.5);
let v = c.to_rgb()[0];
assert!((v - 0.5f32.powf(1.4234)).abs() < 0.01, "{v}");
}
/// A diamond (10–90 × 10–90) clipping `paint`, which fills its bounds: how a shape filled with a
/// gradient is written (`W n`, then `sh`).
fn clipped_diamond(paint: &str) -> Document {