1use crate::{Alpha, Matrix3, Number, RgbSpace, ToAlpha, XyzD50, XyzD65};
2use core::fmt;
3
4macro_rules! rgb_color {
5 ($(#[$meta:meta])* $ty:ident, $css:literal, $space:ident, $xyz:ident) => {
6 rgb_color!($(#[$meta])* $ty, $space, $xyz);
7
8 impl fmt::Display for $ty {
9 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
10 let Self { red, green, blue, alpha } = *self;
11 write!(f, concat!("color(", $css, " {} {} {}{})"), Number(red), Number(green), Number(blue), Alpha(alpha))
12 }
13 }
14 };
15 ($(#[$meta:meta])* $ty:ident, $space:ident, $xyz:ident) => {
16 $(#[$meta])*
17 #[derive(Debug, Clone, Copy, PartialEq)]
26 pub struct $ty {
27 pub red: f64,
28 pub green: f64,
29 pub blue: f64,
30 pub alpha: f32,
31 }
32
33 impl $ty {
34 pub const SPACE: RgbSpace = RgbSpace::$space;
36
37 pub fn new(red: f64, green: f64, blue: f64, alpha: f32) -> Self {
38 Self { red, green, blue, alpha: alpha.clamp(0.0, 100.0) }
39 }
40 }
41
42 impl ToAlpha for $ty {
43 fn to_alpha(&self) -> f32 {
44 self.alpha
45 }
46 }
47
48 impl From<$xyz> for $ty {
49 fn from(value: $xyz) -> Self {
50 let $xyz { x, y, z, alpha } = value;
51 let [red, green, blue] = Self::SPACE.encoded_from_xyz([x / 100.0, y / 100.0, z / 100.0]);
52 Self::new(red, green, blue, alpha)
53 }
54 }
55
56 impl From<$ty> for $xyz {
57 fn from(value: $ty) -> Self {
58 let $ty { red, green, blue, alpha } = value;
59 let [x, y, z] = $ty::SPACE.xyz_from_encoded([red, green, blue]);
60 $xyz::new(x * 100.0, y * 100.0, z * 100.0, alpha)
61 }
62 }
63 };
64}
65
66rgb_color!(
67 Srgb,
69 SRGB,
70 XyzD65
71);
72
73rgb_color!(
74 LinearRgb,
76 "srgb-linear",
77 SRGB_LINEAR,
78 XyzD65
79);
80
81rgb_color!(
82 A98Rgb,
84 "a98-rgb",
85 A98_RGB,
86 XyzD65
87);
88
89rgb_color!(
90 DisplayP3,
92 "display-p3",
93 DISPLAY_P3,
94 XyzD65
95);
96
97rgb_color!(
98 ProphotoRgb,
100 "prophoto-rgb",
101 PROPHOTO_RGB,
102 XyzD50
103);
104
105rgb_color!(
106 Rec2020,
108 "rec2020",
109 REC2020,
110 XyzD65
111);
112
113const P3_TO_SRGB: Matrix3 = Matrix3([
114 [3685649.0 / 3008840.0, -676809.0 / 3008840.0, 0.0],
115 [-5617931.0 / 133579120.0, 139197051.0 / 133579120.0, 0.0],
116 [-1323971.0 / 67420360.0, -1514763.0 / 19262960.0, 148092003.0 / 134840720.0],
117]);
118const SRGB_TO_P3: Matrix3 = Matrix3([
119 [2442703.0 / 2969989.0, 527286.0 / 2969989.0, 0.0],
120 [621563.0 / 18725049.0, 18103486.0 / 18725049.0, 0.0],
121 [281089.0 / 16454667.0, 10721482.0 / 148092003.0, 134840720.0 / 148092003.0],
122]);
123
124impl From<DisplayP3> for LinearRgb {
125 fn from(value: DisplayP3) -> Self {
126 let DisplayP3 { red, green, blue, alpha } = value;
127 let [red, green, blue] = P3_TO_SRGB.transform(DisplayP3::SPACE.transfer.decode_all([red, green, blue]));
128 LinearRgb::new(red, green, blue, alpha)
129 }
130}
131
132impl From<LinearRgb> for DisplayP3 {
133 fn from(value: LinearRgb) -> Self {
134 let LinearRgb { red, green, blue, alpha } = value;
135 let [red, green, blue] = DisplayP3::SPACE.transfer.encode_all(SRGB_TO_P3.transform([red, green, blue]));
136 DisplayP3::new(red, green, blue, alpha)
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn p3_srgb_matrices_are_products_of_the_css_color_4_matrices() {
146 for (matrix, product) in [
147 (P3_TO_SRGB, DisplayP3::SPACE.to_xyz.then(&LinearRgb::SPACE.from_xyz)),
148 (SRGB_TO_P3, LinearRgb::SPACE.to_xyz.then(&DisplayP3::SPACE.from_xyz)),
149 ] {
150 for (got, want) in matrix.0.iter().flatten().zip(product.0.iter().flatten()) {
151 assert!((got - want).abs() < 1e-12, "{got} != {want}");
152 }
153 }
154 }
155}