color/chromaticity.rs
1// Copyright 2024 the Color Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use crate::{matdiagmatmul, matmatmul, matvecmul};
5
6/// CIE `xy` chromaticity, specifying a color in the XYZ color space, but not its luminosity.
7///
8/// An absolute color can be specified by adding a luminosity coordinate `Y` as in `xyY`. An `XYZ`
9/// color can be calculated from `xyY` as follows.
10///
11/// ```text
12/// X = Y/y * x
13/// Y = Y
14/// Z = Y/y * (1 - x - y)
15/// ```
16#[derive(Clone, Copy, Debug, PartialEq)]
17pub struct Chromaticity {
18 /// The x-coordinate of the CIE `xy` chromaticity.
19 pub x: f32,
20
21 /// The y-coordinate of the CIE `xy` chromaticity.
22 pub y: f32,
23}
24
25impl Chromaticity {
26 /// The CIE D65 white point under the standard 2° observer.
27 ///
28 /// This is a common white point for color spaces targeting monitors.
29 ///
30 /// The white point's chromaticities are truncated to four digits here, as specified by the
31 /// CSS Color 4 specification, and following most color spaces using this white point.
32 pub const D65: Self = Self {
33 x: 0.3127,
34 y: 0.3290,
35 };
36
37 /// The CIE D50 white point under the standard 2° observer.
38 ///
39 /// The white point's chromaticities are truncated to four digits here, as specified by the
40 /// CSS Color 4 specification, and following most color spaces using this white point.
41 pub const D50: Self = Self {
42 x: 0.3457,
43 y: 0.3585,
44 };
45
46 /// The [ACES white point][aceswp].
47 ///
48 /// This is the reference white of [ACEScg](crate::AcesCg) and [ACES2065-1](crate::Aces2065_1).
49 /// The white point is near the D60 white point under the standard 2° observer.
50 ///
51 /// [aceswp]: https://docs.acescentral.com/tb/white-point
52 pub const ACES: Self = Self {
53 x: 0.32168,
54 y: 0.33767,
55 };
56
57 /// Convert the `xy` chromaticities to XYZ, assuming `xyY` with `Y=1`.
58 pub(crate) const fn to_xyz(self) -> [f32; 3] {
59 let y_recip = 1. / self.y;
60 [self.x * y_recip, 1., (1. - self.x - self.y) * y_recip]
61 }
62
63 /// Calculate the 3x3 linear Bradford chromatic adaptation matrix from linear sRGB space.
64 ///
65 /// This calculates the matrix going from a reference white of `self` to a reference white of
66 /// `to`.
67 pub(crate) const fn linear_srgb_chromatic_adaptation_matrix(self, to: Self) -> [[f32; 3]; 3] {
68 let bradford_source = matvecmul(&Self::XYZ_TO_BRADFORD, self.to_xyz());
69 let bradford_dest = matvecmul(&Self::XYZ_TO_BRADFORD, to.to_xyz());
70
71 matmatmul(
72 &matdiagmatmul(
73 &Self::BRADFORD_TO_SRGB,
74 [
75 bradford_dest[0] / bradford_source[0],
76 bradford_dest[1] / bradford_source[1],
77 bradford_dest[2] / bradford_source[2],
78 ],
79 ),
80 &Self::SRGB_TO_BRADFORD,
81 )
82 }
83
84 /// `XYZ_to_Bradford * lin_sRGB_to_XYZ`
85 const SRGB_TO_BRADFORD: [[f32; 3]; 3] = [
86 [
87 1_298_421_353. / 3_072_037_500.,
88 172_510_403. / 351_090_000.,
89 32_024_671. / 1_170_300_000.,
90 ],
91 [
92 85_542_113. / 1_536_018_750.,
93 7_089_448_151. / 7_372_890_000.,
94 244_246_729. / 10_532_700_000.,
95 ],
96 [
97 131_355_661. / 6_144_075_000.,
98 71_798_777. / 819_210_000.,
99 3_443_292_119. / 3_510_900_000.,
100 ],
101 ];
102
103 /// `XYZ_to_lin_sRGB * Bradford_to_XYZ`
104 const BRADFORD_TO_SRGB: [[f32; 3]; 3] = [
105 [
106 3_597_831_250_055_000. / 1_417_335_035_684_489.,
107 -1_833_298_161_702_000. / 1_417_335_035_684_489.,
108 -57_038_163_791_000. / 1_417_335_035_684_489.,
109 ],
110 [
111 -4_593_417_841_453_000. / 31_461_687_363_220_151.,
112 35_130_825_086_032_200. / 31_461_687_363_220_151.,
113 -702_492_905_752_400. / 31_461_687_363_220_151.,
114 ],
115 [
116 -191_861_334_350_000. / 4_536_975_728_019_583.,
117 -324_802_409_790_000. / 4_536_975_728_019_583.,
118 4_639_090_845_380_000. / 4_536_975_728_019_583.,
119 ],
120 ];
121
122 const XYZ_TO_BRADFORD: [[f32; 3]; 3] = [
123 [0.8951, 0.2664, -0.1614],
124 [-0.7502, 1.7135, 0.0367],
125 [0.0389, -0.0685, 1.0296],
126 ];
127}