Skip to main content

chromashift/
rgb_space.rs

1use crate::{Chromaticity, Matrix3, Transfer};
2
3/// A description of an RGB colour space: its white point, and how to move between its encoded channels and CIE XYZ.
4///
5/// The matrices are relative to the space's own [`white`](RgbSpace::white) point, with `Y = 1` for that white.
6///
7/// The constants are the predefined RGB colour spaces of CSS Color 4, with the matrices from its sample code:
8/// <https://drafts.csswg.org/css-color-4/#color-conversion-code>
9///
10/// ```
11/// # use chromashift::RgbSpace;
12/// let white = RgbSpace::SRGB.xyz_from_encoded([1.0, 1.0, 1.0]);
13/// assert!(white.iter().zip(RgbSpace::SRGB.white.xyz()).all(|(a, b)| (a - b).abs() < 1e-12));
14/// ```
15#[derive(Debug, Clone, Copy, PartialEq)]
16pub struct RgbSpace {
17	/// Chromaticity of the white point.
18	pub white: Chromaticity,
19	/// Linear RGB -> CIE XYZ, at this space's white point.
20	pub to_xyz: Matrix3,
21	/// CIE XYZ -> linear RGB, at this space's white point.
22	pub from_xyz: Matrix3,
23	/// The transfer function between linear light and this space's encoded channels.
24	pub transfer: Transfer,
25}
26
27impl RgbSpace {
28	/// sRGB (IEC 61966-2-1), D65.
29	///
30	/// <https://drafts.csswg.org/css-color-4/#predefined-sRGB>
31	pub const SRGB: Self = Self { transfer: Transfer::Srgb, ..Self::SRGB_LINEAR };
32
33	/// sRGB primaries with no transfer function ("srgb-linear"), D65.
34	///
35	/// <https://drafts.csswg.org/css-color-4/#predefined-sRGB-linear>
36	pub const SRGB_LINEAR: Self = Self {
37		white: Chromaticity::D65,
38		to_xyz: Matrix3([
39			[506752.0 / 1228815.0, 87881.0 / 245763.0, 12673.0 / 70218.0],
40			[87098.0 / 409605.0, 175762.0 / 245763.0, 12673.0 / 175545.0],
41			[7918.0 / 409605.0, 87881.0 / 737289.0, 1001167.0 / 1053270.0],
42		]),
43		from_xyz: Matrix3([
44			[12831.0 / 3959.0, -329.0 / 214.0, -1974.0 / 3959.0],
45			[-851781.0 / 878810.0, 1648619.0 / 878810.0, 36519.0 / 878810.0],
46			[705.0 / 12673.0, -2585.0 / 12673.0, 705.0 / 667.0],
47		]),
48		transfer: Transfer::Linear,
49	};
50
51	/// Display P3 (DCI-P3 primaries, sRGB transfer), D65.
52	///
53	/// <https://drafts.csswg.org/css-color-4/#predefined-display-p3>
54	pub const DISPLAY_P3: Self = Self {
55		white: Chromaticity::D65,
56		to_xyz: Matrix3([
57			[608311.0 / 1250200.0, 189793.0 / 714400.0, 198249.0 / 1000160.0],
58			[35783.0 / 156275.0, 247089.0 / 357200.0, 198249.0 / 2500400.0],
59			[0.0, 32229.0 / 714400.0, 5220557.0 / 5000800.0],
60		]),
61		from_xyz: Matrix3([
62			[446124.0 / 178915.0, -333277.0 / 357830.0, -72051.0 / 178915.0],
63			[-14852.0 / 17905.0, 63121.0 / 35810.0, 423.0 / 17905.0],
64			[11844.0 / 330415.0, -50337.0 / 660830.0, 316169.0 / 330415.0],
65		]),
66		transfer: Transfer::Srgb,
67	};
68
69	/// Adobe RGB (1998), D65.
70	///
71	/// <https://drafts.csswg.org/css-color-4/#predefined-a98-rgb>
72	pub const A98_RGB: Self = Self {
73		white: Chromaticity::D65,
74		to_xyz: Matrix3([
75			[573536.0 / 994567.0, 263643.0 / 1420810.0, 187206.0 / 994567.0],
76			[591459.0 / 1989134.0, 6239551.0 / 9945670.0, 374412.0 / 4972835.0],
77			[53769.0 / 1989134.0, 351524.0 / 4972835.0, 4929758.0 / 4972835.0],
78		]),
79		from_xyz: Matrix3([
80			[1829569.0 / 896150.0, -506331.0 / 896150.0, -308931.0 / 896150.0],
81			[-851781.0 / 878810.0, 1648619.0 / 878810.0, 36519.0 / 878810.0],
82			[16779.0 / 1248040.0, -147721.0 / 1248040.0, 1266979.0 / 1248040.0],
83		]),
84		transfer: Transfer::A98Rgb,
85	};
86
87	/// ProPhoto RGB (ROMM RGB), D50.
88	///
89	/// <https://drafts.csswg.org/css-color-4/#predefined-prophoto-rgb>
90	pub const PROPHOTO_RGB: Self = Self {
91		white: Chromaticity::D50,
92		to_xyz: Matrix3([
93			[0.7977666449006423, 0.13518129740053308, 0.0313477341283922],
94			[0.2880748288194013, 0.711835234241873, 0.00008993693872564],
95			[0.0, 0.0, 0.8251046025104602],
96		]),
97		from_xyz: Matrix3([
98			[1.3457868816471583, -0.25557208737979464, -0.05110186497554526],
99			[-0.5446307051249019, 1.5082477428451468, 0.02052744743642139],
100			[0.0, 0.0, 1.2119675456389452],
101		]),
102		transfer: Transfer::ProphotoRgb,
103	};
104
105	/// ITU-R BT.2020 primaries, D65, with the transfer function described at [`Transfer::Rec2020`].
106	///
107	/// <https://drafts.csswg.org/css-color-4/#predefined-rec2020>
108	pub const REC2020: Self = Self {
109		white: Chromaticity::D65,
110		to_xyz: Matrix3([
111			[63426534.0 / 99577255.0, 20160776.0 / 139408157.0, 47086771.0 / 278816314.0],
112			[26158966.0 / 99577255.0, 472592308.0 / 697040785.0, 8267143.0 / 139408157.0],
113			[0.0, 19567812.0 / 697040785.0, 295819943.0 / 278816314.0],
114		]),
115		from_xyz: Matrix3([
116			[30757411.0 / 17917100.0, -6372589.0 / 17917100.0, -4539589.0 / 17917100.0],
117			[-19765991.0 / 29648200.0, 47925759.0 / 29648200.0, 467509.0 / 29648200.0],
118			[792561.0 / 44930125.0, -1921689.0 / 44930125.0, 42328811.0 / 44930125.0],
119		]),
120		transfer: Transfer::Rec2020,
121	};
122
123	/// Converts linear channels of this space to CIE XYZ at this space's white point.
124	pub const fn xyz_from_linear(&self, rgb: [f64; 3]) -> [f64; 3] {
125		self.to_xyz.transform(rgb)
126	}
127
128	/// Converts CIE XYZ at this space's white point to this space's linear channels.
129	pub const fn linear_from_xyz(&self, xyz: [f64; 3]) -> [f64; 3] {
130		self.from_xyz.transform(xyz)
131	}
132
133	/// Converts this space's encoded channels to CIE XYZ at this space's white point.
134	pub fn xyz_from_encoded(&self, rgb: [f64; 3]) -> [f64; 3] {
135		self.xyz_from_linear(self.transfer.decode_all(rgb))
136	}
137
138	/// Converts CIE XYZ at this space's white point to this space's encoded channels.
139	pub fn encoded_from_xyz(&self, xyz: [f64; 3]) -> [f64; 3] {
140		self.transfer.encode_all(self.linear_from_xyz(xyz))
141	}
142}