1use crate::{
28 convert::{FromColorUnclamped, IntoColorUnclamped},
29 matrix::{multiply_3x3, multiply_xyz, Mat3},
30 num::{Arithmetics, Real, Zero},
31 white_point::{Any, WhitePoint},
32 Xyz,
33};
34
35pub enum Method {
37 Bradford,
39 VonKries,
41 XyzScaling,
43}
44
45pub struct ConeResponseMatrices<T> {
47 pub ma: Mat3<T>,
49 pub inv_ma: Mat3<T>,
51}
52
53pub trait TransformMatrix<T>
56where
57 T: Zero + Arithmetics + Clone,
58{
59 #[must_use]
61 fn get_cone_response(&self) -> ConeResponseMatrices<T>;
62
63 #[must_use]
66 fn generate_transform_matrix(
67 &self,
68 source_wp: Xyz<Any, T>,
69 destination_wp: Xyz<Any, T>,
70 ) -> Mat3<T> {
71 let adapt = self.get_cone_response();
72
73 let resp_src = multiply_xyz(adapt.ma.clone(), source_wp);
74 let resp_dst = multiply_xyz(adapt.ma.clone(), destination_wp);
75
76 #[rustfmt::skip]
77 let resp = [
78 resp_dst.x / resp_src.x, T::zero(), T::zero(),
79 T::zero(), resp_dst.y / resp_src.y, T::zero(),
80 T::zero(), T::zero(), resp_dst.z / resp_src.z,
81 ];
82
83 let tmp = multiply_3x3(resp, adapt.ma);
84 multiply_3x3(adapt.inv_ma, tmp)
85 }
86}
87
88impl<T> TransformMatrix<T> for Method
89where
90 T: Real + Zero + Arithmetics + Clone,
91{
92 #[rustfmt::skip]
93 #[inline]
94 fn get_cone_response(&self) -> ConeResponseMatrices<T> {
95 match *self {
96 Method::Bradford => {
97 ConeResponseMatrices::<T> {
98 ma: [
99 T::from_f64(0.8951000), T::from_f64(0.2664000), T::from_f64(-0.1614000),
100 T::from_f64(-0.7502000), T::from_f64(1.7135000), T::from_f64(0.0367000),
101 T::from_f64(0.0389000), T::from_f64(-0.0685000), T::from_f64(1.0296000)
102 ],
103 inv_ma: [
104 T::from_f64(0.9869929), T::from_f64(-0.1470543), T::from_f64(0.1599627),
105 T::from_f64(0.4323053), T::from_f64(0.5183603), T::from_f64(0.0492912),
106 T::from_f64(-0.0085287), T::from_f64(0.0400428), T::from_f64(0.9684867)
107 ],
108 }
109 }
110 Method::VonKries => {
111 ConeResponseMatrices::<T> {
112 ma: [
113 T::from_f64(0.4002400), T::from_f64(0.7076000), T::from_f64(-0.0808100),
114 T::from_f64(-0.2263000), T::from_f64(1.1653200), T::from_f64(0.0457000),
115 T::from_f64(0.0000000), T::from_f64(0.0000000), T::from_f64(0.9182200)
116 ],
117 inv_ma: [
118 T::from_f64(1.8599364), T::from_f64(-1.1293816), T::from_f64(0.2198974),
119 T::from_f64(0.3611914), T::from_f64(0.6388125), T::from_f64(-0.0000064),
120 T::from_f64(0.0000000), T::from_f64(0.0000000), T::from_f64(1.0890636)
121 ],
122 }
123 }
124 Method::XyzScaling => {
125 ConeResponseMatrices::<T> {
126 ma: [
127 T::from_f64(1.0000000), T::from_f64(0.0000000), T::from_f64(0.0000000),
128 T::from_f64(0.0000000), T::from_f64(1.0000000), T::from_f64(0.0000000),
129 T::from_f64(0.0000000), T::from_f64(0.0000000), T::from_f64(1.0000000)
130 ],
131 inv_ma: [
132 T::from_f64(1.0000000), T::from_f64(0.0000000), T::from_f64(0.0000000),
133 T::from_f64(0.0000000), T::from_f64(1.0000000), T::from_f64(0.0000000),
134 T::from_f64(0.0000000), T::from_f64(0.0000000), T::from_f64(1.0000000)
135 ],
136 }
137 }
138 }
139 }
140}
141
142pub trait AdaptFrom<S, Swp, Dwp, T>: Sized
147where
148 T: Real + Zero + Arithmetics + Clone,
149 Swp: WhitePoint<T>,
150 Dwp: WhitePoint<T>,
151{
152 #[must_use]
155 #[inline]
156 fn adapt_from(color: S) -> Self {
157 Self::adapt_from_using(color, Method::Bradford)
158 }
159 #[must_use]
162 fn adapt_from_using<M: TransformMatrix<T>>(color: S, method: M) -> Self;
163}
164
165impl<S, D, Swp, Dwp, T> AdaptFrom<S, Swp, Dwp, T> for D
166where
167 T: Real + Zero + Arithmetics + Clone,
168 Swp: WhitePoint<T>,
169 Dwp: WhitePoint<T>,
170 S: IntoColorUnclamped<Xyz<Swp, T>>,
171 D: FromColorUnclamped<Xyz<Dwp, T>>,
172{
173 #[inline]
174 fn adapt_from_using<M: TransformMatrix<T>>(color: S, method: M) -> D {
175 let src_xyz = color.into_color_unclamped().with_white_point();
176 let transform_matrix = method.generate_transform_matrix(Swp::get_xyz(), Dwp::get_xyz());
177 let dst_xyz = multiply_xyz(transform_matrix, src_xyz);
178 D::from_color_unclamped(dst_xyz.with_white_point())
179 }
180}
181
182pub trait AdaptInto<D, Swp, Dwp, T>: Sized
187where
188 T: Real + Zero + Arithmetics + Clone,
189 Swp: WhitePoint<T>,
190 Dwp: WhitePoint<T>,
191{
192 #[must_use]
195 #[inline]
196 fn adapt_into(self) -> D {
197 self.adapt_into_using(Method::Bradford)
198 }
199 #[must_use]
202 fn adapt_into_using<M: TransformMatrix<T>>(self, method: M) -> D;
203}
204
205impl<S, D, Swp, Dwp, T> AdaptInto<D, Swp, Dwp, T> for S
206where
207 T: Real + Zero + Arithmetics + Clone,
208 Swp: WhitePoint<T>,
209 Dwp: WhitePoint<T>,
210 D: AdaptFrom<S, Swp, Dwp, T>,
211{
212 #[inline]
213 fn adapt_into_using<M: TransformMatrix<T>>(self, method: M) -> D {
214 D::adapt_from_using(self, method)
215 }
216}
217
218#[cfg(feature = "approx")]
219#[cfg(test)]
220mod test {
221 use super::{AdaptFrom, AdaptInto, Method, TransformMatrix};
222 use crate::white_point::{WhitePoint, A, C, D50, D65};
223 use crate::Xyz;
224
225 #[test]
226 fn d65_to_d50_matrix_xyz_scaling() {
227 let expected = [
228 1.0144665, 0.0000000, 0.0000000, 0.0000000, 1.0000000, 0.0000000, 0.0000000, 0.0000000,
229 0.7578869,
230 ];
231 let xyz_scaling = Method::XyzScaling;
232 let computed = xyz_scaling.generate_transform_matrix(D65::get_xyz(), D50::get_xyz());
233 for (e, c) in expected.iter().zip(computed.iter()) {
234 assert_relative_eq!(e, c, epsilon = 0.0001)
235 }
236 }
237 #[test]
238 fn d65_to_d50_matrix_von_kries() {
239 let expected = [
240 1.0160803, 0.0552297, -0.0521326, 0.0060666, 0.9955661, -0.0012235, 0.0000000,
241 0.0000000, 0.7578869,
242 ];
243 let von_kries = Method::VonKries;
244 let computed = von_kries.generate_transform_matrix(D65::get_xyz(), D50::get_xyz());
245 for (e, c) in expected.iter().zip(computed.iter()) {
246 assert_relative_eq!(e, c, epsilon = 0.0001)
247 }
248 }
249 #[test]
250 fn d65_to_d50_matrix_bradford() {
251 let expected = [
252 1.0478112, 0.0228866, -0.0501270, 0.0295424, 0.9904844, -0.0170491, -0.0092345,
253 0.0150436, 0.7521316,
254 ];
255 let bradford = Method::Bradford;
256 let computed = bradford.generate_transform_matrix(D65::get_xyz(), D50::get_xyz());
257 for (e, c) in expected.iter().zip(computed.iter()) {
258 assert_relative_eq!(e, c, epsilon = 0.0001)
259 }
260 }
261
262 #[test]
263 fn chromatic_adaptation_from_a_to_c() {
264 let input_a = Xyz::<A, f32>::new(0.315756, 0.162732, 0.015905);
265
266 let expected_bradford = Xyz::<C, f32>::new(0.257963, 0.139776, 0.058825);
267 let expected_vonkries = Xyz::<C, f32>::new(0.268446, 0.159139, 0.052843);
268 let expected_xyz_scaling = Xyz::<C, f32>::new(0.281868, 0.162732, 0.052844);
269
270 let computed_bradford: Xyz<C, f32> = Xyz::adapt_from(input_a);
271 assert_relative_eq!(expected_bradford, computed_bradford, epsilon = 0.0001);
272
273 let computed_vonkries: Xyz<C, f32> = Xyz::adapt_from_using(input_a, Method::VonKries);
274 assert_relative_eq!(expected_vonkries, computed_vonkries, epsilon = 0.0001);
275
276 let computed_xyz_scaling: Xyz<C, _> = Xyz::adapt_from_using(input_a, Method::XyzScaling);
277 assert_relative_eq!(expected_xyz_scaling, computed_xyz_scaling, epsilon = 0.0001);
278 }
279
280 #[test]
281 fn chromatic_adaptation_into_a_to_c() {
282 let input_a = Xyz::<A, f32>::new(0.315756, 0.162732, 0.015905);
283
284 let expected_bradford = Xyz::<C, f32>::new(0.257963, 0.139776, 0.058825);
285 let expected_vonkries = Xyz::<C, f32>::new(0.268446, 0.159139, 0.052843);
286 let expected_xyz_scaling = Xyz::<C, f32>::new(0.281868, 0.162732, 0.052844);
287
288 let computed_bradford: Xyz<C, f32> = input_a.adapt_into();
289 assert_relative_eq!(expected_bradford, computed_bradford, epsilon = 0.0001);
290
291 let computed_vonkries: Xyz<C, f32> = input_a.adapt_into_using(Method::VonKries);
292 assert_relative_eq!(expected_vonkries, computed_vonkries, epsilon = 0.0001);
293
294 let computed_xyz_scaling: Xyz<C, _> = input_a.adapt_into_using(Method::XyzScaling);
295 assert_relative_eq!(expected_xyz_scaling, computed_xyz_scaling, epsilon = 0.0001);
296 }
297}