lcms2/ext.rs
1use crate::{ffi, CIELab, CIExyY, ColorSpaceSignature, CIEXYZ};
2use ffi::PixelType;
3use std::mem::MaybeUninit;
4
5pub trait ColorSpaceSignatureExt: Sized + Copy {
6 /// Returns channel count for a given color space.
7 ///
8 /// Returns 3 on error (sic).
9 fn channels(self) -> u32;
10 /// Converts from ICC color space notation (LCMS PDF Table 10) to Little CMS color space notation (LCMS PDF Table 36).
11 fn pixel_type(self) -> PixelType;
12}
13
14impl ColorSpaceSignatureExt for ColorSpaceSignature {
15 #[inline]
16 fn channels(self) -> u32 {
17 unsafe { ffi::cmsChannelsOf(self) }
18 }
19
20 #[inline]
21 fn pixel_type(self) -> PixelType {
22 PixelType(unsafe { ffi::_cmsLCMScolorSpace(self) } as u32)
23 }
24}
25
26/// Chromatic adaptation
27pub trait CIEXYZExt: Sized {
28 /// Adapts a color to a given illuminant. Original color is expected to have
29 /// a `source_white_point` white point.
30 fn adapt_to_illuminant(&self, source_white_point: &CIEXYZ, illuminant: &CIEXYZ) -> Option<CIEXYZ>;
31
32 /// Colorimetric space conversion.
33 fn to_lab(&self, white_point: &CIEXYZ) -> CIELab;
34
35 /// Decodes a XYZ value, encoded on ICC convention
36 fn from_encoded(icc: &[u16; 3]) -> Self;
37}
38
39impl CIEXYZExt for CIEXYZ {
40 #[inline]
41 fn adapt_to_illuminant(&self, source_white_point: &CIEXYZ, illuminant: &CIEXYZ) -> Option<CIEXYZ> {
42 unsafe {
43 let mut res = MaybeUninit::<CIEXYZ>::uninit();
44 let ok = ffi::cmsAdaptToIlluminant(res.as_mut_ptr(), source_white_point, illuminant, self) != 0;
45 if ok {
46 Some(res.assume_init())
47 } else {
48 None
49 }
50 }
51 }
52
53 #[inline]
54 fn to_lab(&self, white_point: &CIEXYZ) -> CIELab {
55 unsafe {
56 let mut out = MaybeUninit::<CIELab>::uninit();
57 ffi::cmsXYZ2Lab(white_point, out.as_mut_ptr(), self);
58 out.assume_init()
59 }
60 }
61
62 #[inline]
63 fn from_encoded(icc: &[u16; 3]) -> Self {
64 unsafe {
65 let mut out = MaybeUninit::<Self>::uninit();
66 ffi::cmsXYZEncoded2Float(out.as_mut_ptr(), icc.as_ptr());
67 out.assume_init()
68 }
69 }
70}
71
72/// White point
73pub trait CIExzYExt: Sized {
74 /// Correlates a black body temperature in ºK from given chromaticity.
75 fn temp(&self) -> Option<f64>;
76}
77
78impl CIExzYExt for CIExyY {
79 #[inline]
80 fn temp(&self) -> Option<f64> {
81 let mut out = 0.;
82 if 0 != unsafe { ffi::cmsTempFromWhitePoint(&mut out, self) } {
83 Some(out)
84 } else {
85 None
86 }
87 }
88}
89
90/// Delta E
91pub trait CIELabExt: Sized {
92 /// Delta-E 2000 is the first major revision of the dE94 equation.
93 ///
94 /// Unlike dE94, which assumes that L\* correctly reflects the perceived differences in lightness, dE2000 varies the weighting of L\* depending on where in the lightness range the color falls.
95 /// dE2000 is still under consideration and does not seem to be widely supported in graphics arts applications.
96 ///
97 /// Returns:
98 /// The CIE2000 dE metric value.
99 fn cie2000_delta_e(&self, other: &CIELab, kl: f64, kc: f64, kh: f64) -> f64;
100
101 /// A technical committee of the CIE (TC1-29) published an equation in 1995 called CIE94.
102 /// The equation is similar to CMC but the weighting functions are largely based on RIT/DuPont tolerance data derived from automotive paint experiments where sample surfaces are smooth.
103 /// It also has ratios, labeled kL (lightness) and Kc (chroma) and the commercial factor (cf) but these tend to be preset in software and are not often exposed for the user (as it is the case in Little CMS).
104 /// Returns:
105 /// The CIE94 dE metric value.
106 fn cie94_delta_e(&self, other: &CIELab) -> f64;
107
108 /// BFD delta E metric.
109 fn bfd_delta_e(&self, other: &CIELab) -> f64;
110
111 /// The dE76 metric value.
112 ///
113 /// The L\*a\*b\* color space was devised in 1976 and, at the same time delta-E 1976 (dE76) came into being.
114 /// If you can imagine attaching a string to a color point in 3D Lab space, dE76 describes the sphere that is described by all the possible directions you could pull the string.
115 /// If you hear people speak of just plain 'delta-E' they are probably referring to dE76. It is also known as dE-Lab and dE-ab.
116 ///
117 /// One problem with dE76 is that Lab itself is not 'perceptually uniform' as its creators had intended.
118 /// So different amounts of visual color shift in different color areas of Lab might have the same dE76 number.
119 /// Conversely, the same amount of color shift might result in different dE76 values.
120 /// Another issue is that the eye is most sensitive to hue differences, then chroma and finally lightness and dE76 does not take this into account.
121 fn delta_e(&self, other: &CIELab) -> f64;
122
123 /// In 1984 the CMC (Colour Measurement Committee of the Society of Dyes and Colourists of Great Britain) developed and adopted an equation based on LCH numbers.
124 ///
125 /// Intended for the textiles industry, CMC l:c allows the setting of lightness (l) and chroma (c) factors. As the eye is more sensitive to chroma, the default ratio for l:c is 2:1 allowing for 2x the difference in lightness than chroma (numbers). There is also a 'commercial factor' (cf) which allows an overall varying of the size of the tolerance region according to accuracy requirements. A cf=1.0 means that a delta-E CMC value <1.0 is acceptable.
126 /// CMC l:c is designed to be used with D65 and the CIE Supplementary Observer. Commonly-used values for l:c are 2:1 for acceptability and 1:1 for the threshold of imperceptibility.
127 fn cmc_delta_e(&self, other: &CIELab, k: f64, c: f64) -> f64;
128
129 /// amin, amax, bmin, bmax: boundaries of gamut rectangle
130 fn desaturate(&mut self, amin: f64, amax: f64, bmin: f64, bmax: f64) -> bool;
131
132 /// Encodes a Lab value, from a CIELab value to ICC v4 convention.
133 fn encoded(&self) -> [u16; 3];
134
135 /// Encodes a Lab value, from a CIELab value to ICC v2 convention.
136 fn encoded_v2(&self) -> [u16; 3];
137
138 /// Decodes a Lab value, encoded on ICC v4 convention
139 fn from_encoded(icc: &[u16; 3]) -> Self;
140
141 /// Decodes a Lab value, encoded on ICC v2 convention
142 fn from_encoded_v2(icc: &[u16; 3]) -> Self;
143
144 /// Colorimetric space conversion.
145 fn to_xyz(&self, white_point: &CIEXYZ) -> CIEXYZ;
146}
147
148impl CIELabExt for CIELab {
149 #[inline]
150 fn cie2000_delta_e(&self, other: &CIELab, kl: f64, kc: f64, kh: f64) -> f64 {
151 unsafe { ffi::cmsCIE2000DeltaE(self, other, kl, kc, kh) }
152 }
153
154 #[inline]
155 fn cie94_delta_e(&self, other: &CIELab) -> f64 {
156 unsafe { ffi::cmsCIE94DeltaE(self, other) }
157 }
158
159 #[inline]
160 fn bfd_delta_e(&self, other: &CIELab) -> f64 {
161 unsafe { ffi::cmsBFDdeltaE(self, other) }
162 }
163
164 #[inline]
165 fn delta_e(&self, other: &CIELab) -> f64 {
166 unsafe { ffi::cmsDeltaE(self, other) }
167 }
168
169 #[inline]
170 fn cmc_delta_e(&self, other: &CIELab, k: f64, c: f64) -> f64 {
171 unsafe { ffi::cmsCMCdeltaE(self, other, k, c) }
172 }
173
174 #[inline]
175 fn desaturate(&mut self, amin: f64, amax: f64, bmin: f64, bmax: f64) -> bool {
176 unsafe { 0 != ffi::cmsDesaturateLab(self, amax, amin, bmax, bmin) }
177 }
178
179 #[inline]
180 fn encoded(&self) -> [u16; 3] {
181 let mut out = [0u16; 3];
182 unsafe { ffi::cmsFloat2LabEncoded(out.as_mut_ptr(), self) }
183 out
184 }
185
186 #[inline]
187 fn encoded_v2(&self) -> [u16; 3] {
188 let mut out = [0u16; 3];
189 unsafe { ffi::cmsFloat2LabEncodedV2(out.as_mut_ptr(), self) }
190 out
191 }
192
193 #[inline]
194 fn from_encoded(icc: &[u16; 3]) -> Self {
195 unsafe {
196 let mut out = MaybeUninit::<Self>::uninit();
197 ffi::cmsLabEncoded2Float(out.as_mut_ptr(), icc.as_ptr());
198 out.assume_init()
199 }
200 }
201
202 #[inline]
203 fn from_encoded_v2(icc: &[u16; 3]) -> Self {
204 unsafe {
205 let mut out = MaybeUninit::<Self>::uninit();
206 ffi::cmsLabEncoded2FloatV2(out.as_mut_ptr(), icc.as_ptr());
207 out.assume_init()
208 }
209 }
210
211 #[inline]
212 fn to_xyz(&self, white_point: &CIEXYZ) -> CIEXYZ {
213 unsafe {
214 let mut out = MaybeUninit::<CIEXYZ>::uninit();
215 ffi::cmsLab2XYZ(white_point, out.as_mut_ptr(), self);
216 out.assume_init()
217 }
218 }
219}
220
221#[test]
222fn temp() {
223 assert!(crate::white_point_from_temp(4000.).is_some());
224}