palette/chromatic_adaptation.rs
1//! Convert colors from one reference white point to another
2//!
3//! Chromatic adaptation is the ability to adjust the appearance of colors to
4//! changes in illumination. This happens naturally in our body's visual system,
5//! and can be emulated with a "chromatic adaptation transform" (CAT).
6//!
7//! This library implements a one-step adaptation transform, known as the von
8//! Kries method. It's provided as [`AdaptFromUnclamped`] or
9//! [`AdaptIntoUnclamped`] for convenience, or [`adaptation_matrix`] for control
10//! and reusability. All of them can be customized with different LMS matrices.
11//!
12//! The provided LMS matrices are:
13//!
14//! - [`Bradford`] - A "spectrally sharpened" matrix, which may improve
15//! chromatic adaptation. This is the default for [`AdaptFromUnclamped`] and
16//! [`AdaptIntoUnclamped`].
17//! - [`VonKries`][lms::matrix::VonKries] - Produces cone-describing LMS values,
18//! as opposed to many other matrices, but may perform worse than other
19//! matrices.
20//! - [`UnitMatrix`][lms::matrix::UnitMatrix] - Included for completeness, but
21//! generally considered a bad option. Also called "XYZ scaling" or "wrong von
22//! Kries".
23//!
24//! ```
25//! use palette::{
26//! Xyz, white_point::{A, C},
27//! chromatic_adaptation::AdaptIntoUnclamped,
28//! };
29//! use approx::assert_relative_eq;
30//!
31//! let input = Xyz::<A, f32>::new(0.315756, 0.162732, 0.015905);
32//!
33//! //Will convert Xyz<A, f32> to Xyz<C, f32> using Bradford chromatic adaptation;
34//! let output: Xyz<C, f32> = input.adapt_into_unclamped();
35//!
36//! let expected = Xyz::new(0.257963, 0.139776, 0.058825);
37//! assert_relative_eq!(output, expected, epsilon = 0.0001);
38//! ```
39
40use core::ops::Div;
41
42use crate::{
43 convert::{FromColorUnclamped, IntoColorUnclamped, Matrix3},
44 lms::{
45 self,
46 matrix::{Bradford, LmsToXyz, WithLmsMatrix, XyzToLms},
47 Lms,
48 },
49 matrix::{multiply_3x3, multiply_3x3_and_vec3, Mat3},
50 num::{Arithmetics, Real, Zero},
51 white_point::{Any, WhitePoint},
52 xyz::meta::HasXyzMeta,
53 Xyz,
54};
55
56/// Construct a one-step chromatic adaptation matrix.
57///
58/// The matrix uses the von Kries method to fully adapt a color from an input
59/// white point to an output white point, using a provided LMS matrix. See the
60/// [`chromatic_adaptation`][self] module for more details.
61///
62/// ## Static White Points
63///
64/// The `input_wp` and `output_wp` parameters represent the color "white" for
65/// the input and output colors, respectively. Passing `None` will make it use
66/// `I` and `O` to calculate the white points:
67///
68/// ```
69/// use palette::{
70/// chromatic_adaptation::adaptation_matrix,
71/// lms::matrix::Bradford,
72/// convert::Convert,
73/// white_point::{A, C},
74/// Xyz,
75/// };
76/// use approx::assert_relative_eq;
77///
78/// // Adapts from white point A to white point C:
79/// let matrix = adaptation_matrix::<f32, A, C, Bradford>(None, None);
80///
81/// // Explicit types added for illustration.
82/// let input: Xyz<A> = Xyz::new(0.315756, 0.162732, 0.015905);
83/// let output: Xyz<C> = matrix.convert(input);
84///
85/// let expected = Xyz::new(0.257963, 0.139776, 0.058825);
86/// assert_relative_eq!(output, expected, epsilon = 0.0001);
87/// ```
88///
89/// ## Dynamic White Points
90///
91/// It's also possible to use arbitrary colors as white points, as long as they
92/// are brighter than black. This can be useful for white balancing a photo,
93/// where we may want to use the same static white point for both the input and
94/// the output:
95///
96/// ```
97/// use palette::{
98/// chromatic_adaptation::adaptation_matrix,
99/// lms::matrix::Bradford,
100/// convert::{FromColorUnclampedMut, Convert},
101/// Srgb, Xyz,
102/// };
103/// use approx::assert_relative_eq;
104///
105/// fn simple_white_balance(image: &mut [Srgb<f32>]) {
106/// // Temporarily convert to Xyz:
107/// let mut image = <[Xyz<_, f32>]>::from_color_unclamped_mut(image);
108///
109/// // Find the average Xyz color:
110/// let sum = image.iter().fold(Xyz::new(0.0, 0.0, 0.0), |sum, &c| sum + c);
111/// let average = sum / image.len() as f32;
112///
113/// // Considering the average color to be "white", this matrix adapts from the
114/// // average to default sRGB white, D65:
115/// let matrix = adaptation_matrix::<_, _, _, Bradford>(Some(average), None);
116///
117/// for pixel in &mut *image {
118/// *pixel = matrix.convert(*pixel);
119/// }
120/// }
121///
122/// // Minimal test case. This one pixel becomes gray after white balancing:
123/// let mut image = [Srgb::new(0.8, 0.3, 0.9)];
124/// simple_white_balance(&mut image);
125///
126/// let expected = Srgb::new(0.524706, 0.524706, 0.524706);
127/// assert_relative_eq!(image[0], expected, epsilon = 0.00001);
128/// ```
129///
130/// See also [Wikipedia - Von Kries transform][wikipedia].
131///
132/// [wikipedia]:
133/// https://en.wikipedia.org/wiki/Chromatic_adaptation#Von_Kries_transform
134pub fn adaptation_matrix<T, I, O, M>(
135 input_wp: Option<Xyz<I, T>>,
136 output_wp: Option<Xyz<O, T>>,
137) -> Matrix3<Xyz<I, T>, Xyz<O, T>>
138where
139 T: Zero + Arithmetics + Clone,
140 I: WhitePoint<T> + HasXyzMeta<XyzMeta = I>,
141 O: WhitePoint<T> + HasXyzMeta<XyzMeta = O>,
142 M: XyzToLms<T> + LmsToXyz<T>,
143 Xyz<I, T>: IntoColorUnclamped<Lms<WithLmsMatrix<I, M>, T>>,
144 Xyz<O, T>: IntoColorUnclamped<Lms<WithLmsMatrix<O, M>, T>>,
145{
146 let input_to_lms = Lms::<WithLmsMatrix<I, M>, T>::matrix_from_xyz();
147 let lms_to_output = Xyz::<O, T>::matrix_from_lms::<WithLmsMatrix<O, M>>();
148
149 let input_wp = input_wp
150 .unwrap_or_else(|| I::get_xyz().with_white_point())
151 .normalize()
152 .into_color_unclamped();
153
154 let output_wp = output_wp
155 .unwrap_or_else(|| O::get_xyz().with_white_point())
156 .normalize()
157 .into_color_unclamped();
158
159 input_to_lms
160 .then(diagonal_matrix(input_wp, output_wp))
161 .then(lms_to_output)
162}
163
164/// Construct a diagonal matrix for full adaptation of [`Lms`] colors.
165///
166/// This is the core matrix in the von Kries adaptation method and is a central
167/// part of the matrix from [`adaptation_matrix`]. It's offered separately, as
168/// an option for building more advanced adaptation matrices.
169///
170/// The produced matrix is a diagonal matrix, containing the output white point
171/// divided by the input white point:
172///
173/// ```text
174/// [out.l / in.l, 0, 0]
175/// [ 0, out.m / in.m, 0]
176/// [ 0, 0, out.s / in.s]
177/// ```
178///
179/// See also [Wikipedia - Von Kries transform][wikipedia].
180///
181/// [wikipedia]:
182/// https://en.wikipedia.org/wiki/Chromatic_adaptation#Von_Kries_transform
183#[inline]
184pub fn diagonal_matrix<T, I, O>(
185 input_wp: Lms<I, T>,
186 output_wp: Lms<O, T>,
187) -> Matrix3<Lms<I, T>, Lms<O, T>>
188where
189 T: Zero + Div<Output = T>,
190{
191 let gain = output_wp / input_wp.with_meta();
192
193 #[rustfmt::skip]
194 let matrix = [
195 gain.long, T::zero(), T::zero(),
196 T::zero(), gain.medium, T::zero(),
197 T::zero(), T::zero(), gain.short,
198 ];
199
200 Matrix3::from_array(matrix)
201}
202
203/// A trait for unchecked conversion of one color from another via chromatic
204/// adaptation.
205///
206/// See [`FromColor`][crate::convert::FromColor],
207/// [`TryFromColor`][crate::convert::TryFromColor] and [`FromColorUnclamped`]
208/// for when there's no need for chromatic adaptation.
209///
210/// Some conversions require the reference white point to be changed, while
211/// maintaining the appearance of the color. This is called "chromatic
212/// adaptation" or "white balancing", and typically involves converting the
213/// color to the [`Lms`] color space. This trait defaults to using the
214/// [`Bradford`] matrix as part of the process, but other options are available
215/// in [`lms::matrix`].
216///
217/// The [`adaptation_matrix`] function offers more options and control. This
218/// trait can be a convenient alternative when the source and destination white
219/// points are statically known.
220pub trait AdaptFromUnclamped<T>: Sized {
221 /// The number type that's used as the color's components.
222 type Scalar;
223
224 /// Adapt a color of type `T` into a color of type `Self`, using the
225 /// [`Bradford`] matrix.
226 ///
227 /// ```
228 /// use palette::{
229 /// Xyz, white_point::{A, C},
230 /// chromatic_adaptation::AdaptFromUnclamped,
231 /// };
232 ///
233 /// let input = Xyz::<A, f32>::new(0.315756, 0.162732, 0.015905);
234 ///
235 /// //Will convert Xyz<A, f32> to Xyz<C, f32> using Bradford chromatic adaptation:
236 /// let output = Xyz::<C, f32>::adapt_from_unclamped(input);
237 /// ```
238 #[must_use]
239 #[inline]
240 fn adapt_from_unclamped(input: T) -> Self
241 where
242 Bradford: LmsToXyz<Self::Scalar> + XyzToLms<Self::Scalar>,
243 {
244 Self::adapt_from_unclamped_with::<Bradford>(input)
245 }
246
247 /// Adapt a color of type `T` into a color of type `Self`, using the custom
248 /// matrix `M`.
249 ///
250 /// ```
251 /// use palette::{
252 /// Xyz, white_point::{A, C}, lms::matrix::VonKries,
253 /// chromatic_adaptation::AdaptFromUnclamped,
254 /// };
255 ///
256 /// let input = Xyz::<A, f32>::new(0.315756, 0.162732, 0.015905);
257 ///
258 /// //Will convert Xyz<A, f32> to Xyz<C, f32> using von Kries chromatic adaptation:
259 /// let output = Xyz::<C, f32>::adapt_from_unclamped_with::<VonKries>(input);
260 /// ```
261 #[must_use]
262 fn adapt_from_unclamped_with<M>(input: T) -> Self
263 where
264 M: LmsToXyz<Self::Scalar> + XyzToLms<Self::Scalar>;
265}
266
267/// A trait for unchecked conversion of one color into another via chromatic
268/// adaptation.
269///
270/// See [`IntoColor`][crate::convert::IntoColor],
271/// [`TryIntoColor`][crate::convert::TryIntoColor] and [`IntoColorUnclamped`]
272/// for when there's no need for chromatic adaptation.
273///
274/// Some conversions require the reference white point to be changed, while
275/// maintaining the appearance of the color. This is called "chromatic
276/// adaptation" or "white balancing", and typically involves converting the
277/// color to the [`Lms`] color space. This trait defaults to using the
278/// [`Bradford`] matrix as part of the process, but other options are available
279/// in [`lms::matrix`].
280///
281/// The [`adaptation_matrix`] function offers more options and control. This
282/// trait can be a convenient alternative when the source and destination white
283/// points are statically known.
284pub trait AdaptIntoUnclamped<T>: Sized {
285 /// The number type that's used as the color's components.
286 type Scalar;
287
288 /// Adapt a color of type `Self` into a color of type `T`, using the
289 /// [`Bradford`] matrix.
290 ///
291 /// ```
292 /// use palette::{
293 /// Xyz, white_point::{A, C},
294 /// chromatic_adaptation::AdaptIntoUnclamped,
295 /// };
296 ///
297 /// let input = Xyz::<A, f32>::new(0.315756, 0.162732, 0.015905);
298 ///
299 /// //Will convert Xyz<A, f32> to Xyz<C, f32> using Bradford chromatic adaptation:
300 /// let output: Xyz<C, f32> = input.adapt_into_unclamped();
301 /// ```
302 #[must_use]
303 #[inline]
304 fn adapt_into_unclamped(self) -> T
305 where
306 Bradford: LmsToXyz<Self::Scalar> + XyzToLms<Self::Scalar>,
307 {
308 self.adapt_into_unclamped_with::<Bradford>()
309 }
310
311 /// Adapt a color of type `Self` into a color of type `T`, using the custom
312 /// matrix `M`.
313 ///
314 /// ```
315 /// use palette::{
316 /// Xyz, white_point::{A, C}, lms::matrix::VonKries,
317 /// chromatic_adaptation::AdaptIntoUnclamped,
318 /// };
319 ///
320 /// let input = Xyz::<A, f32>::new(0.315756, 0.162732, 0.015905);
321 ///
322 /// //Will convert Xyz<A, f32> to Xyz<C, f32> using von Kries chromatic adaptation:
323 /// let output: Xyz<C, f32> = input.adapt_into_unclamped_with::<VonKries>();
324 /// ```
325 #[must_use]
326 fn adapt_into_unclamped_with<M>(self) -> T
327 where
328 M: LmsToXyz<Self::Scalar> + XyzToLms<Self::Scalar>;
329}
330
331impl<T, C> AdaptIntoUnclamped<T> for C
332where
333 T: AdaptFromUnclamped<C>,
334{
335 type Scalar = T::Scalar;
336
337 #[inline]
338 fn adapt_into_unclamped_with<M>(self) -> T
339 where
340 M: LmsToXyz<Self::Scalar> + XyzToLms<Self::Scalar>,
341 {
342 T::adapt_from_unclamped_with::<M>(self)
343 }
344}
345
346/// Chromatic adaptation methods implemented in the library
347#[deprecated(
348 since = "0.7.7",
349 note = "use the options from `palette::lms::matrix` or a custom matrix"
350)]
351pub enum Method {
352 /// Bradford chromatic adaptation method
353 Bradford,
354 /// VonKries chromatic adaptation method
355 VonKries,
356 /// XyzScaling chromatic adaptation method
357 XyzScaling,
358}
359
360/// Holds the matrix coefficients for the chromatic adaptation methods
361#[deprecated(
362 since = "0.7.7",
363 note = "use the options from `palette::lms::matrix` or a custom matrix"
364)]
365pub struct ConeResponseMatrices<T> {
366 ///3x3 matrix for the cone response domains
367 pub ma: Mat3<T>,
368 ///3x3 matrix for the inverse of the cone response domains
369 pub inv_ma: Mat3<T>,
370}
371
372/// Generates a conversion matrix to convert the Xyz tristimulus values from
373/// one illuminant to another (`source_wp` to `destination_wp`)
374#[deprecated(
375 since = "0.7.7",
376 note = "use the options from `palette::lms::matrix` or a custom matrix"
377)]
378#[allow(deprecated)]
379pub trait TransformMatrix<T>
380where
381 T: Zero + Arithmetics + Clone,
382{
383 /// Get the cone response functions for the chromatic adaptation method
384 #[must_use]
385 fn get_cone_response(&self) -> ConeResponseMatrices<T>;
386
387 /// Generates a 3x3 transformation matrix to convert color from one
388 /// reference white point to another with the given cone_response
389 #[must_use]
390 fn generate_transform_matrix(
391 &self,
392 source_wp: Xyz<Any, T>,
393 destination_wp: Xyz<Any, T>,
394 ) -> Mat3<T> {
395 let adapt = self.get_cone_response();
396
397 let resp_src: Lms<Any, T> =
398 multiply_3x3_and_vec3(adapt.ma.clone(), source_wp.into()).into();
399 let resp_dst: Lms<Any, T> =
400 multiply_3x3_and_vec3(adapt.ma.clone(), destination_wp.into()).into();
401
402 let resp = diagonal_matrix(resp_src, resp_dst).into_array();
403
404 let tmp = multiply_3x3(resp, adapt.ma);
405 multiply_3x3(adapt.inv_ma, tmp)
406 }
407}
408
409#[allow(deprecated)]
410impl<T> TransformMatrix<T> for Method
411where
412 T: Real + Zero + Arithmetics + Clone,
413{
414 #[rustfmt::skip]
415 #[inline]
416 fn get_cone_response(&self) -> ConeResponseMatrices<T> {
417 match *self {
418 Method::Bradford => {
419 ConeResponseMatrices::<T> {
420 ma: lms::matrix::Bradford::xyz_to_lms_matrix(),
421 inv_ma: lms::matrix::Bradford::lms_to_xyz_matrix(),
422 }
423 }
424 Method::VonKries => {
425 ConeResponseMatrices::<T> {
426 ma: lms::matrix::VonKries::xyz_to_lms_matrix(),
427 inv_ma: lms::matrix::VonKries::lms_to_xyz_matrix(),
428 }
429 }
430 Method::XyzScaling => {
431 ConeResponseMatrices::<T> {
432 ma: lms::matrix::UnitMatrix::xyz_to_lms_matrix(),
433 inv_ma: lms::matrix::UnitMatrix::lms_to_xyz_matrix(),
434 }
435 }
436 }
437 }
438}
439
440/// Trait to convert color from one reference white point to another
441///
442/// Converts a color from the source white point (Swp) to the destination white
443/// point (Dwp). Uses the bradford method for conversion by default.
444#[deprecated(
445 since = "0.7.7",
446 note = "replaced by `palette::chromatic_adaptation::AdaptFromUnclamped`"
447)]
448#[allow(deprecated)]
449pub trait AdaptFrom<S, Swp, Dwp, T>: Sized
450where
451 T: Real + Zero + Arithmetics + Clone,
452 Swp: WhitePoint<T>,
453 Dwp: WhitePoint<T>,
454{
455 /// Convert the source color to the destination color using the bradford
456 /// method by default.
457 #[must_use]
458 #[inline]
459 fn adapt_from(color: S) -> Self {
460 Self::adapt_from_using(color, Method::Bradford)
461 }
462 /// Convert the source color to the destination color using the specified
463 /// method.
464 #[must_use]
465 fn adapt_from_using<M: TransformMatrix<T>>(color: S, method: M) -> Self;
466}
467
468#[allow(deprecated)]
469impl<S, D, Swp, Dwp, T> AdaptFrom<S, Swp, Dwp, T> for D
470where
471 T: Real + Zero + Arithmetics + Clone,
472 Swp: WhitePoint<T>,
473 Dwp: WhitePoint<T>,
474 S: IntoColorUnclamped<Xyz<Swp, T>>,
475 D: FromColorUnclamped<Xyz<Dwp, T>>,
476{
477 #[inline]
478 fn adapt_from_using<M: TransformMatrix<T>>(color: S, method: M) -> D {
479 let src_xyz: Xyz<Swp, T> = color.into_color_unclamped();
480 let transform_matrix = method.generate_transform_matrix(Swp::get_xyz(), Dwp::get_xyz());
481 let dst_xyz: Xyz<Dwp, T> = multiply_3x3_and_vec3(transform_matrix, src_xyz.into()).into();
482 D::from_color_unclamped(dst_xyz)
483 }
484}
485
486/// Trait to convert color with one reference white point into another
487///
488/// Converts a color with the source white point (Swp) into the destination
489/// white point (Dwp). Uses the bradford method for conversion by default.
490#[deprecated(
491 since = "0.7.7",
492 note = "replaced by `palette::chromatic_adaptation::AdaptIntoUnclamped`"
493)]
494#[allow(deprecated)]
495pub trait AdaptInto<D, Swp, Dwp, T>: Sized
496where
497 T: Real + Zero + Arithmetics + Clone,
498 Swp: WhitePoint<T>,
499 Dwp: WhitePoint<T>,
500{
501 /// Convert the source color to the destination color using the bradford
502 /// method by default.
503 #[must_use]
504 #[inline]
505 fn adapt_into(self) -> D {
506 self.adapt_into_using(Method::Bradford)
507 }
508 /// Convert the source color to the destination color using the specified
509 /// method.
510 #[must_use]
511 fn adapt_into_using<M: TransformMatrix<T>>(self, method: M) -> D;
512}
513
514#[allow(deprecated)]
515impl<S, D, Swp, Dwp, T> AdaptInto<D, Swp, Dwp, T> for S
516where
517 T: Real + Zero + Arithmetics + Clone,
518 Swp: WhitePoint<T>,
519 Dwp: WhitePoint<T>,
520 D: AdaptFrom<S, Swp, Dwp, T>,
521{
522 #[inline]
523 fn adapt_into_using<M: TransformMatrix<T>>(self, method: M) -> D {
524 D::adapt_from_using(self, method)
525 }
526}
527
528#[cfg(feature = "approx")]
529#[cfg(test)]
530mod test {
531 #![allow(deprecated)]
532
533 use super::{AdaptFrom, AdaptInto, Method, TransformMatrix};
534 use crate::{
535 encoding::{Linear, Srgb},
536 Xyz,
537 };
538 use crate::{
539 rgb::Rgb,
540 white_point::{WhitePoint, A, C, D50, D65},
541 };
542
543 #[test]
544 fn d65_to_d50_matrix_xyz_scaling() {
545 let expected = [
546 1.0144665, 0.0000000, 0.0000000, 0.0000000, 1.0000000, 0.0000000, 0.0000000, 0.0000000,
547 0.7578869,
548 ];
549 let xyz_scaling = Method::XyzScaling;
550 let computed = xyz_scaling.generate_transform_matrix(D65::get_xyz(), D50::get_xyz());
551 for (e, c) in expected.iter().zip(computed.iter()) {
552 assert_relative_eq!(e, c, epsilon = 0.0001)
553 }
554 }
555 #[test]
556 fn d65_to_d50_matrix_von_kries() {
557 let expected = [
558 1.0160803, 0.0552297, -0.0521326, 0.0060666, 0.9955661, -0.0012235, 0.0000000,
559 0.0000000, 0.7578869,
560 ];
561 let von_kries = Method::VonKries;
562 let computed = von_kries.generate_transform_matrix(D65::get_xyz(), D50::get_xyz());
563 for (e, c) in expected.iter().zip(computed.iter()) {
564 assert_relative_eq!(e, c, epsilon = 0.0001)
565 }
566 }
567 #[test]
568 fn d65_to_d50_matrix_bradford() {
569 let expected = [
570 1.0478112, 0.0228866, -0.0501270, 0.0295424, 0.9904844, -0.0170491, -0.0092345,
571 0.0150436, 0.7521316,
572 ];
573 let bradford = Method::Bradford;
574 let computed = bradford.generate_transform_matrix(D65::get_xyz(), D50::get_xyz());
575 for (e, c) in expected.iter().zip(computed.iter()) {
576 assert_relative_eq!(e, c, epsilon = 0.0001)
577 }
578 }
579
580 #[test]
581 fn chromatic_adaptation_from_a_to_c() {
582 let input_a = Xyz::<A, f32>::new(0.315756, 0.162732, 0.015905);
583
584 let expected_bradford = Xyz::<C, f32>::new(0.257963, 0.139776, 0.058825);
585 let expected_vonkries = Xyz::<C, f32>::new(0.268446, 0.159139, 0.052843);
586 let expected_xyz_scaling = Xyz::<C, f32>::new(0.281868, 0.162732, 0.052844);
587
588 let computed_bradford: Xyz<C, f32> = Xyz::adapt_from(input_a);
589 assert_relative_eq!(expected_bradford, computed_bradford, epsilon = 0.0001);
590
591 let computed_vonkries: Xyz<C, f32> = Xyz::adapt_from_using(input_a, Method::VonKries);
592 assert_relative_eq!(expected_vonkries, computed_vonkries, epsilon = 0.0001);
593
594 let computed_xyz_scaling: Xyz<C, _> = Xyz::adapt_from_using(input_a, Method::XyzScaling);
595 assert_relative_eq!(expected_xyz_scaling, computed_xyz_scaling, epsilon = 0.0001);
596 }
597
598 #[test]
599 fn chromatic_adaptation_into_a_to_c() {
600 let input_a = Xyz::<A, f32>::new(0.315756, 0.162732, 0.015905);
601
602 let expected_bradford = Xyz::<C, f32>::new(0.257963, 0.139776, 0.058825);
603 let expected_vonkries = Xyz::<C, f32>::new(0.268446, 0.159139, 0.052843);
604 let expected_xyz_scaling = Xyz::<C, f32>::new(0.281868, 0.162732, 0.052844);
605
606 let computed_bradford: Xyz<C, f32> = input_a.adapt_into();
607 assert_relative_eq!(expected_bradford, computed_bradford, epsilon = 0.0001);
608
609 let computed_vonkries: Xyz<C, f32> = input_a.adapt_into_using(Method::VonKries);
610 assert_relative_eq!(expected_vonkries, computed_vonkries, epsilon = 0.0001);
611
612 let computed_xyz_scaling: Xyz<C, _> = input_a.adapt_into_using(Method::XyzScaling);
613 assert_relative_eq!(expected_xyz_scaling, computed_xyz_scaling, epsilon = 0.0001);
614 }
615
616 #[test]
617 fn d65_to_d50() {
618 let input: Rgb<Linear<Srgb>> = Rgb::new(1.0, 1.0, 1.0);
619 let expected: Rgb<Linear<(Srgb, D50)>> = Rgb::new(1.0, 1.0, 1.0);
620
621 let computed: Rgb<Linear<(Srgb, D50)>> = input.adapt_into();
622 assert_relative_eq!(expected, computed, epsilon = 0.000001);
623 }
624}