mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
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:
committed by
bflatastic
co-authored by
Claude Opus 5.5
parent
136810bee2
commit
f3b4c3ec78
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user