Skip to main content

appthere_color/
rendering_intent.rs

1//! Rendering intent selection for ICC color transforms.
2//!
3//! Defines the four ICC rendering intents that control how out-of-gamut colors
4//! are mapped during profile-to-profile conversion.
5
6/// ICC rendering intent controlling out-of-gamut color mapping.
7///
8/// When converting colors between profiles with different gamuts, the rendering
9/// intent determines how colors outside the destination gamut are handled.
10/// Each intent makes a different trade-off between accuracy and aesthetics.
11///
12/// # Examples
13///
14/// ```
15/// use appthere_color::RenderingIntent;
16///
17/// let intent = RenderingIntent::Perceptual;
18/// assert_eq!(intent, RenderingIntent::Perceptual);
19/// ```
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub enum RenderingIntent {
23    /// Perceptual intent — optimizes for pleasing visual appearance.
24    ///
25    /// Compresses the entire source gamut to fit within the destination gamut,
26    /// preserving the relationships between colors. Best for photographic images
27    /// where visual quality matters more than exact color matching.
28    Perceptual,
29
30    /// Relative colorimetric intent — matches in-gamut colors exactly.
31    ///
32    /// Maps the source white point to the destination white point and maps
33    /// in-gamut colors exactly. Out-of-gamut colors are clipped to the nearest
34    /// reproducible color. Best for proofing and logo colors.
35    RelativeColorimetric,
36
37    /// Saturation intent — maximizes color vividness.
38    ///
39    /// Maps saturated source colors to saturated destination colors, prioritizing
40    /// vividness over accuracy. Best for business graphics and charts where
41    /// impact matters more than fidelity.
42    Saturation,
43
44    /// Absolute colorimetric intent — preserves exact colorimetry.
45    ///
46    /// Like relative colorimetric but does not adjust for the destination
47    /// white point. Colors are reproduced with absolute fidelity, including
48    /// the media white point simulation. Best for exact color matching and
49    /// substrate simulation in proofing.
50    AbsoluteColorimetric,
51}
52
53impl RenderingIntent {
54    /// Converts this rendering intent to the corresponding `moxcms` intent.
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// use appthere_color::RenderingIntent;
60    ///
61    /// let intent = RenderingIntent::Perceptual;
62    /// let moxcms_intent = intent.to_moxcms();
63    /// ```
64    pub fn to_moxcms(self) -> moxcms::RenderingIntent {
65        match self {
66            RenderingIntent::Perceptual => moxcms::RenderingIntent::Perceptual,
67            RenderingIntent::RelativeColorimetric => {
68                moxcms::RenderingIntent::RelativeColorimetric
69            }
70            RenderingIntent::Saturation => moxcms::RenderingIntent::Saturation,
71            RenderingIntent::AbsoluteColorimetric => {
72                moxcms::RenderingIntent::AbsoluteColorimetric
73            }
74        }
75    }
76
77    /// Creates a rendering intent from the corresponding `moxcms` intent.
78    ///
79    /// # Examples
80    ///
81    /// ```
82    /// use appthere_color::RenderingIntent;
83    ///
84    /// let intent = RenderingIntent::from_moxcms(moxcms::RenderingIntent::Perceptual);
85    /// assert_eq!(intent, RenderingIntent::Perceptual);
86    /// ```
87    pub fn from_moxcms(intent: moxcms::RenderingIntent) -> Self {
88        match intent {
89            moxcms::RenderingIntent::Perceptual => RenderingIntent::Perceptual,
90            moxcms::RenderingIntent::RelativeColorimetric => {
91                RenderingIntent::RelativeColorimetric
92            }
93            moxcms::RenderingIntent::Saturation => RenderingIntent::Saturation,
94            moxcms::RenderingIntent::AbsoluteColorimetric => {
95                RenderingIntent::AbsoluteColorimetric
96            }
97        }
98    }
99}
100
101impl Default for RenderingIntent {
102    /// Returns [`RenderingIntent::Perceptual`] as the default.
103    fn default() -> Self {
104        RenderingIntent::Perceptual
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn default_is_perceptual() {
114        assert_eq!(RenderingIntent::default(), RenderingIntent::Perceptual);
115    }
116
117    #[test]
118    fn round_trip_through_moxcms() {
119        let intents = [
120            RenderingIntent::Perceptual,
121            RenderingIntent::RelativeColorimetric,
122            RenderingIntent::Saturation,
123            RenderingIntent::AbsoluteColorimetric,
124        ];
125        for intent in intents {
126            let mox = intent.to_moxcms();
127            let back = RenderingIntent::from_moxcms(mox);
128            assert_eq!(intent, back);
129        }
130    }
131}