Skip to main content

hayro_interpret/function/
mod.rs

1//! PDF functions.
2//!
3//! PDF has the concept of functions, representing objects that take a certain number of values
4//! as input, do some processing on them and then return some output.
5
6mod type0;
7mod type2;
8mod type3;
9mod type4;
10
11use crate::function::type0::Type0;
12use crate::function::type2::Type2;
13use crate::function::type3::Type3;
14use crate::function::type4::Type4;
15use hayro_syntax::object::Dict;
16use hayro_syntax::object::dict::keys::{DOMAIN, FUNCTION_TYPE, RANGE};
17use hayro_syntax::object::{Object, dict_or_stream};
18use smallvec::SmallVec;
19use std::sync::{Arc, OnceLock};
20
21/// The input/output type of functions.
22pub(crate) type Values = SmallVec<[f32; 4]>;
23pub(crate) type StitchingBounds = SmallVec<[f32; 3]>;
24type TupleVec = SmallVec<[(f32, f32); 4]>;
25
26#[derive(Debug)]
27enum FunctionType {
28    Type0(Type0),
29    Type2(Type2),
30    Type3(Type3),
31    Type4(Type4),
32}
33
34/// A PDF function.
35#[derive(Debug, Clone)]
36pub struct Function(Arc<FunctionType>);
37
38impl Function {
39    /// Create a new function.
40    pub fn new(obj: &Object<'_>) -> Option<Self> {
41        let (dict, stream) = dict_or_stream(obj)?;
42
43        let function_type = match dict.get::<u8>(FUNCTION_TYPE)? {
44            0 => FunctionType::Type0(Type0::new(stream?)?),
45            2 => FunctionType::Type2(Type2::new(dict)?),
46            3 => FunctionType::Type3(Type3::new(dict)?),
47            4 => FunctionType::Type4(Type4::new(stream?)?),
48            _ => return None,
49        };
50
51        Some(Self(Arc::new(function_type)))
52    }
53
54    /// Evaluate the function with the given input.
55    pub fn eval(&self, input: Values) -> Option<Values> {
56        match self.0.as_ref() {
57            FunctionType::Type0(t0) => t0.eval(input),
58            FunctionType::Type2(t2) => Some(t2.eval(*input.first()?)),
59            FunctionType::Type3(t3) => t3.eval(*input.first()?),
60            FunctionType::Type4(t4) => Some(t4.eval(input)?),
61        }
62    }
63
64    pub(crate) fn stitching_bounds(&self) -> StitchingBounds {
65        match self.0.as_ref() {
66            FunctionType::Type3(t3) => t3.stitching_bounds(),
67            _ => SmallVec::new(),
68        }
69    }
70}
71
72/// A transfer function.
73#[derive(Clone, Debug)]
74pub struct TransferFunction {
75    function: Function,
76    samples: Arc<OnceLock<[u8; 256]>>,
77}
78
79impl TransferFunction {
80    pub(crate) fn new(function: Function) -> Self {
81        Self {
82            function,
83            samples: Arc::new(OnceLock::new()),
84        }
85    }
86
87    /// Apply the transfer function to a buffer of values.
88    pub fn apply_to(&self, values: &mut [u8]) {
89        self.apply_to_stride(values, 1);
90    }
91
92    pub(crate) fn apply_to_stride(&self, values: &mut [u8], stride: usize) {
93        let samples = self.samples();
94
95        for value in values.iter_mut().step_by(stride) {
96            *value = samples[*value as usize];
97        }
98    }
99
100    pub(crate) fn apply_f32(&self, value: f32) -> f32 {
101        self.samples()[(value * 255.0 + 0.5) as u8 as usize] as f32 / 255.0
102    }
103
104    fn samples(&self) -> &[u8; 256] {
105        self.samples.get_or_init(|| {
106            std::array::from_fn(|sample| {
107                self.function
108                    .eval(smallvec::smallvec![sample as f32 / 255.0])
109                    .and_then(|output| output.first().copied())
110                    .map(|output| (output * 255.0 + 0.5) as u8)
111                    .unwrap_or(sample as u8)
112            })
113        })
114    }
115}
116
117#[derive(Debug, Clone)]
118struct Clamper {
119    domain: TupleVec,
120    range: Option<TupleVec>,
121}
122
123impl Clamper {
124    fn new(dict: &Dict<'_>) -> Option<Self> {
125        let domain = dict.get::<TupleVec>(DOMAIN)?;
126        let range = dict.get::<TupleVec>(RANGE);
127
128        Some(Self { domain, range })
129    }
130
131    fn clamp_input(&self, input: &mut [f32]) {
132        if input.len() != self.domain.len() {
133            warn!("the domain of the function didn't match the input arguments");
134        }
135
136        for ((min, max), val) in self.domain.iter().zip(input.iter_mut()) {
137            *val = val.min(*max).max(*min);
138        }
139    }
140
141    fn clamp_output(&self, output: &mut [f32]) {
142        if let Some(range) = &self.range {
143            if range.len() != output.len() {
144                warn!("the range of the function didn't match the output arguments");
145            }
146
147            for ((min, max), val) in range.iter().zip(output.iter_mut()) {
148                *val = val.min(*max).max(*min);
149            }
150        }
151    }
152}
153
154/// Linearly interpolate the value `x`, assuming that it lies within the range `x_min` and `x_max`,
155/// to the range `y_min` and `y_max`.
156pub(crate) fn interpolate(x: f32, x_min: f32, x_max: f32, y_min: f32, y_max: f32) -> f32 {
157    y_min + (x - x_min) * (y_max - y_min) / (x_max - x_min)
158}