use super::{ColorSpace, Rgb};
use crate::function::Function;
use std::sync::Arc;
const SCRATCH_FLOOR: usize = 16;
pub const MAX_PATTERN_COMPONENTS: usize = 16;
#[derive(Debug, Clone, PartialEq)]
pub struct Separation {
pub none: bool,
pub alternate: Option<Box<ColorSpace>>,
pub tint: Option<Arc<Function>>,
}
impl Separation {
#[must_use]
pub fn to_rgb(&self, comps: &[f32]) -> Option<Rgb> {
if self.none {
return None;
}
let alternate = self.alternate.as_ref()?;
let tint = comps.first().copied().unwrap_or(0.0);
let Some(func) = &self.tint else {
let broadcast = vec![tint; alternate.n_components()];
return Some(alternate.to_rgb(&broadcast));
};
let mut results = vec![0.0f32; func.output_count().max(SCRATCH_FLOOR)];
let produced = func.eval_into(&[tint], &mut results);
if produced == 0 {
return None;
}
Some(alternate.to_rgb(&results))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DeviceN {
pub names: Box<[pdfrum_object::Name]>,
pub alternate: Box<ColorSpace>,
pub tint: Arc<Function>,
}
impl DeviceN {
#[must_use]
pub fn to_rgb(&self, comps: &[f32]) -> Option<Rgb> {
let n = self.names.len();
let inputs: Vec<f32> = (0..n)
.map(|i| comps.get(i).copied().unwrap_or(0.0))
.collect();
let mut results = vec![0.0f32; self.tint.output_count().max(SCRATCH_FLOOR)];
let produced = self.tint.eval_into(&inputs, &mut results);
if produced == 0 {
return None;
}
Some(self.alternate.to_rgb(&results))
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct PatternSpace {
pub base: Option<Box<ColorSpace>>,
}
impl PatternSpace {
#[must_use]
pub fn n_components(&self) -> usize {
self.base.as_ref().map_or(1, |b| b.n_components() + 1)
}
#[must_use]
pub fn to_rgb(&self, comps: &[f32]) -> Option<Rgb> {
let base = self.base.as_ref()?;
Some(base.to_rgb(comps))
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unreadable_literal,
clippy::float_cmp,
clippy::indexing_slicing,
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
reason = "test fixtures quote oracle vectors verbatim and compare exactly"
)]
use super::{DeviceN, PatternSpace, Separation};
use crate::color::ColorSpace;
use crate::function::{Exponential, Function};
use std::sync::Arc;
fn ramp3() -> Arc<Function> {
Arc::new(Function::Exponential(Exponential {
domain: Box::from(&[0.0f32, 1.0][..]),
range: Box::from(&[0.0f32, 1.0, 0.0, 1.0, 0.0, 1.0][..]),
c0: Box::from(&[0.0f32, 0.0, 0.0][..]),
c1: Box::from(&[1.0f32, 1.0, 1.0][..]),
exponent: 1.0,
orig_outputs: 3,
outputs: 3,
}))
}
#[test]
fn none_separations_paint_nothing() {
let sep = Separation {
none: true,
alternate: None,
tint: None,
};
assert!(sep.to_rgb(&[1.0]).is_none());
}
#[test]
fn a_separation_without_a_transform_broadcasts_its_tint() {
let sep = Separation {
none: false,
alternate: Some(Box::new(ColorSpace::DeviceRgb)),
tint: None,
};
let rgb = sep.to_rgb(&[0.25]).expect("colour");
assert!((rgb.r - 0.25).abs() < 1e-6);
assert!((rgb.g - 0.25).abs() < 1e-6);
assert!((rgb.b - 0.25).abs() < 1e-6);
}
#[test]
fn a_separation_with_a_transform_uses_it() {
let sep = Separation {
none: false,
alternate: Some(Box::new(ColorSpace::DeviceRgb)),
tint: Some(ramp3()),
};
let rgb = sep.to_rgb(&[0.5]).expect("colour");
assert!((rgb.r - 0.5).abs() < 1e-5);
}
#[test]
fn device_n_reads_exactly_its_name_count() {
let cs = DeviceN {
names: Box::from(&[pdfrum_object::Name::from("A")][..]),
alternate: Box::new(ColorSpace::DeviceRgb),
tint: ramp3(),
};
assert_eq!(cs.names.len(), 1);
let a = cs.to_rgb(&[0.5, 9.0, 9.0]).expect("colour");
let b = cs.to_rgb(&[0.5]).expect("colour");
assert!((a.r - b.r).abs() < 1e-6);
}
#[test]
fn a_pattern_space_without_a_base_has_one_component_and_no_colour() {
let cs = PatternSpace::default();
assert_eq!(cs.n_components(), 1);
assert!(cs.to_rgb(&[0.5]).is_none());
}
#[test]
fn a_pattern_space_with_a_base_adds_one_component() {
let cs = PatternSpace {
base: Some(Box::new(ColorSpace::DeviceCmyk)),
};
assert_eq!(cs.n_components(), 5);
assert!(cs.to_rgb(&[0.0, 0.0, 0.0, 0.0]).is_some());
}
}