Skip to main content

appthere_color/
proofing.rs

1//! Soft proofing configuration and chained transform execution.
2//!
3//! Simulates how a document will appear when printed on a specific output
4//! device by chaining two transforms through an intermediate buffer.
5
6extern crate alloc;
7
8use alloc::boxed::Box;
9use alloc::vec;
10
11use crate::error::{ColorError, ColorResult};
12use crate::proofing_builder::ProofingConfigBuilder;
13use crate::rendering_intent::RenderingIntent;
14
15/// Gamut warning indicator for out-of-gamut pixels.
16///
17/// When soft proofing, pixels that fall outside the output device's gamut
18/// can be flagged with a warning color.
19///
20/// # Examples
21///
22/// ```
23/// use appthere_color::{GamutWarning, RgbColor};
24///
25/// let warning = GamutWarning::Replace(RgbColor::new(1.0, 0.0, 1.0));
26/// ```
27#[derive(Debug, Clone, Copy, PartialEq)]
28pub enum GamutWarning {
29    /// No gamut warning — out-of-gamut colors pass through unchanged.
30    None,
31    /// Replace out-of-gamut pixels with the specified RGB warning color.
32    Replace(crate::color_value::RgbColor),
33}
34
35impl Default for GamutWarning {
36    /// Returns [`GamutWarning::None`] as the default.
37    fn default() -> Self {
38        GamutWarning::None
39    }
40}
41
42/// Configuration for soft proofing transforms.
43///
44/// Built via [`ProofingConfig::builder()`], this struct captures the profiles,
45/// intents, and optional gamut warning needed for a two-stage proofing pipeline.
46///
47/// # Examples
48///
49/// ```
50/// use appthere_color::{ProofingConfig, IccProfile, RenderingIntent};
51///
52/// let config = ProofingConfig::builder()
53///     .source(&IccProfile::new_srgb())
54///     .simulation(&IccProfile::new_srgb())
55///     .display(&IccProfile::new_srgb())
56///     .simulation_intent(RenderingIntent::Perceptual)
57///     .build()
58///     .unwrap();
59/// ```
60pub struct ProofingConfig {
61    sim_executor: Box<moxcms::TransformF32BitExecutor>,
62    disp_executor: Box<moxcms::TransformF32BitExecutor>,
63    gamut_warning: GamutWarning,
64    sim_channels: usize,
65    src_channels: usize,
66    dst_channels: usize,
67}
68
69impl ProofingConfig {
70    /// Creates a new [`ProofingConfigBuilder`].
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// use appthere_color::ProofingConfig;
76    ///
77    /// let builder = ProofingConfig::builder();
78    /// ```
79    pub fn builder() -> ProofingConfigBuilder {
80        ProofingConfigBuilder {
81            source: None,
82            simulation: None,
83            display: None,
84            simulation_intent: RenderingIntent::Perceptual,
85            display_intent: RenderingIntent::RelativeColorimetric,
86            gamut_warning: GamutWarning::None,
87        }
88    }
89
90    /// Internal constructor used by the builder.
91    pub(crate) fn new(
92        sim_executor: Box<moxcms::TransformF32BitExecutor>,
93        disp_executor: Box<moxcms::TransformF32BitExecutor>,
94        gamut_warning: GamutWarning,
95        sim_channels: usize,
96        src_channels: usize,
97        dst_channels: usize,
98    ) -> ColorResult<Self> {
99        Ok(Self {
100            sim_executor,
101            disp_executor,
102            gamut_warning,
103            sim_channels,
104            src_channels,
105            dst_channels,
106        })
107    }
108
109    /// Executes the two-stage proofing transform on pixel data.
110    ///
111    /// Transform 1: source → simulation profile (simulation_intent).
112    /// Transform 2: simulation → display profile (display_intent).
113    ///
114    /// # Errors
115    ///
116    /// Returns [`ColorError::TransformExecution`] if either stage fails.
117    ///
118    /// # Examples
119    ///
120    /// ```
121    /// use appthere_color::{ProofingConfig, IccProfile, RenderingIntent};
122    ///
123    /// let config = ProofingConfig::builder()
124    ///     .source(&IccProfile::new_srgb())
125    ///     .simulation(&IccProfile::new_srgb())
126    ///     .display(&IccProfile::new_srgb())
127    ///     .build()
128    ///     .unwrap();
129    ///
130    /// let input = [0.5_f32, 0.3, 0.8];
131    /// let mut output = [0.0_f32; 3];
132    /// config.proof(&input, &mut output).unwrap();
133    /// ```
134    pub fn proof(&self, src: &[f32], dst: &mut [f32]) -> ColorResult<()> {
135        let mut intermediate = vec![0.0_f32; self.sim_channels];
136
137        self.sim_executor
138            .transform(src, &mut intermediate)
139            .map_err(|e| {
140                ColorError::TransformExecution(alloc::format!("{:?}", e))
141            })?;
142
143        self.disp_executor
144            .transform(&intermediate, dst)
145            .map_err(|e| {
146                ColorError::TransformExecution(alloc::format!("{:?}", e))
147            })?;
148
149        Ok(())
150    }
151
152    /// Returns the gamut warning mode configured for this proofing config.
153    ///
154    /// # Examples
155    ///
156    /// ```
157    /// use appthere_color::{ProofingConfig, IccProfile, GamutWarning};
158    ///
159    /// let config = ProofingConfig::builder()
160    ///     .source(&IccProfile::new_srgb())
161    ///     .simulation(&IccProfile::new_srgb())
162    ///     .display(&IccProfile::new_srgb())
163    ///     .build()
164    ///     .unwrap();
165    /// assert_eq!(config.gamut_warning(), GamutWarning::None);
166    /// ```
167    pub fn gamut_warning(&self) -> GamutWarning {
168        self.gamut_warning
169    }
170
171    /// Returns the number of channels in the source color space.
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// use appthere_color::{ProofingConfig, IccProfile};
177    ///
178    /// let config = ProofingConfig::builder()
179    ///     .source(&IccProfile::new_srgb())
180    ///     .simulation(&IccProfile::new_srgb())
181    ///     .display(&IccProfile::new_srgb())
182    ///     .build()
183    ///     .unwrap();
184    /// assert_eq!(config.source_channels(), 3);
185    /// ```
186    pub fn source_channels(&self) -> usize {
187        self.src_channels
188    }
189
190    /// Returns the number of channels in the display color space.
191    ///
192    /// # Examples
193    ///
194    /// ```
195    /// use appthere_color::{ProofingConfig, IccProfile};
196    ///
197    /// let config = ProofingConfig::builder()
198    ///     .source(&IccProfile::new_srgb())
199    ///     .simulation(&IccProfile::new_srgb())
200    ///     .display(&IccProfile::new_srgb())
201    ///     .build()
202    ///     .unwrap();
203    /// assert_eq!(config.destination_channels(), 3);
204    /// ```
205    pub fn destination_channels(&self) -> usize {
206        self.dst_channels
207    }
208}