use kurbo::{Affine, Point};
use pdfrum_page::FunctionBased;
use pdfrum_page::Shading;
use crate::color::Argb;
use crate::pixmap::Pixmap;
pub fn draw(
dest: &mut Pixmap,
shading: &Shading,
geometry: &FunctionBased,
alpha: u8,
to_bitmap: Affine,
) {
let combined = to_bitmap * geometry.matrix;
let determinant = combined.determinant();
if determinant == 0.0 || !determinant.is_finite() {
return;
}
let inverse = combined.inverse();
let inputs = shading
.functions
.iter()
.map(|f| f.output_count())
.sum::<usize>();
if inputs == 0 && shading.space.n_components() == 0 {
return;
}
let mut outputs = vec![0.0f32; inputs.max(shading.space.n_components()).max(1)];
for row in 0..dest.height() {
for col in 0..dest.width() {
let pos = inverse * Point::new(f64::from(col), f64::from(row));
if !geometry.contains(pos) {
continue;
}
let mut written = 0usize;
for f in &shading.functions {
#[expect(
clippy::cast_possible_truncation,
reason = "shading space is f32 throughout, matching the oracle"
)]
let input = [pos.x as f32, pos.y as f32];
let Some(slice) = outputs.get_mut(written..) else {
break;
};
let Some(used) = input.get(..f.input_count().clamp(1, 2)) else {
break;
};
let n = f.eval_into(used, slice);
written = written.saturating_add(n);
}
let rgb = shading.space.to_rgb(&outputs);
let [r, g, b] = rgb.to_bytes_truncating();
let color = Argb { a: alpha, r, g, b };
dest.set_pixel(col, row, crate::pixmap::premultiply(color.to_peniko()));
}
}
}
#[cfg(test)]
mod tests {
use pdfrum_page::Rgb;
use super::*;
#[test]
fn domain_is_paired_per_axis_and_inclusive() {
let g = FunctionBased {
domain: [0.0, 10.0, 2.0, 4.0],
matrix: Affine::IDENTITY,
};
assert!(g.contains(Point::new(0.0, 2.0)), "inclusive at the low end");
assert!(
g.contains(Point::new(10.0, 4.0)),
"inclusive at the high end"
);
assert!(!g.contains(Point::new(10.1, 3.0)));
assert!(
!g.contains(Point::new(5.0, 1.9)),
"domain[2..4] is the y range"
);
}
#[test]
fn truncating_conversion_differs_from_the_ramp_rounding() {
let c = Rgb {
r: 0.5,
g: 0.5,
b: 0.5,
};
assert_eq!(c.to_bytes(), [128, 128, 128]);
assert_eq!(c.to_bytes_truncating(), [127, 127, 127]);
}
#[test]
fn a_singular_matrix_is_declined() {
let mut p = Pixmap::filled(2, 2, peniko::Color::from_rgba8(9, 9, 9, 255));
let shading = Shading {
geometry: pdfrum_page::Geometry::FunctionBased(FunctionBased {
domain: [0.0, 1.0, 0.0, 1.0],
matrix: Affine::IDENTITY,
}),
space: std::sync::Arc::new(pdfrum_page::ColorSpace::DeviceRgb),
functions: Box::new([]),
background: None,
bbox: None,
};
let g = FunctionBased {
domain: [0.0, 1.0, 0.0, 1.0],
matrix: Affine::new([0.0; 6]),
};
draw(&mut p, &shading, &g, 255, Affine::IDENTITY);
assert_eq!(p.pixel(0, 0).map(|px| px[0]), Some(9));
}
}