bevy_color/
xyza.rs

1use crate::{
2    impl_componentwise_vector_space, Alpha, ColorToComponents, Gray, LinearRgba, Luminance, Mix,
3    StandardColor,
4};
5use bevy_math::{Vec3, Vec4};
6#[cfg(feature = "bevy_reflect")]
7use bevy_reflect::prelude::*;
8
9/// [CIE 1931](https://en.wikipedia.org/wiki/CIE_1931_color_space) color space, also known as XYZ, with an alpha channel.
10#[doc = include_str!("../docs/conversion.md")]
11/// <div>
12#[doc = include_str!("../docs/diagrams/model_graph.svg")]
13/// </div>
14#[derive(Debug, Clone, Copy, PartialEq)]
15#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
16#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
17#[cfg_attr(
18    all(feature = "serialize", feature = "bevy_reflect"),
19    reflect(Serialize, Deserialize)
20)]
21pub struct Xyza {
22    /// The x-axis. [0.0, 1.0]
23    pub x: f32,
24    /// The y-axis, intended to represent luminance. [0.0, 1.0]
25    pub y: f32,
26    /// The z-axis. [0.0, 1.0]
27    pub z: f32,
28    /// The alpha channel. [0.0, 1.0]
29    pub alpha: f32,
30}
31
32impl StandardColor for Xyza {}
33
34impl_componentwise_vector_space!(Xyza, [x, y, z, alpha]);
35
36impl Xyza {
37    /// Construct a new [`Xyza`] color from components.
38    ///
39    /// # Arguments
40    ///
41    /// * `x` - x-axis. [0.0, 1.0]
42    /// * `y` - y-axis. [0.0, 1.0]
43    /// * `z` - z-axis. [0.0, 1.0]
44    /// * `alpha` - Alpha channel. [0.0, 1.0]
45    pub const fn new(x: f32, y: f32, z: f32, alpha: f32) -> Self {
46        Self { x, y, z, alpha }
47    }
48
49    /// Construct a new [`Xyza`] color from (x, y, z) components, with the default alpha (1.0).
50    ///
51    /// # Arguments
52    ///
53    /// * `x` - x-axis. [0.0, 1.0]
54    /// * `y` - y-axis. [0.0, 1.0]
55    /// * `z` - z-axis. [0.0, 1.0]
56    pub const fn xyz(x: f32, y: f32, z: f32) -> Self {
57        Self {
58            x,
59            y,
60            z,
61            alpha: 1.0,
62        }
63    }
64
65    /// Return a copy of this color with the 'x' channel set to the given value.
66    pub const fn with_x(self, x: f32) -> Self {
67        Self { x, ..self }
68    }
69
70    /// Return a copy of this color with the 'y' channel set to the given value.
71    pub const fn with_y(self, y: f32) -> Self {
72        Self { y, ..self }
73    }
74
75    /// Return a copy of this color with the 'z' channel set to the given value.
76    pub const fn with_z(self, z: f32) -> Self {
77        Self { z, ..self }
78    }
79
80    /// [D65 White Point](https://en.wikipedia.org/wiki/Illuminant_D65#Definition)
81    pub const D65_WHITE: Self = Self::xyz(0.95047, 1.0, 1.08883);
82}
83
84impl Default for Xyza {
85    fn default() -> Self {
86        Self::new(0., 0., 0., 1.)
87    }
88}
89
90impl Alpha for Xyza {
91    #[inline]
92    fn with_alpha(&self, alpha: f32) -> Self {
93        Self { alpha, ..*self }
94    }
95
96    #[inline]
97    fn alpha(&self) -> f32 {
98        self.alpha
99    }
100
101    #[inline]
102    fn set_alpha(&mut self, alpha: f32) {
103        self.alpha = alpha;
104    }
105}
106
107impl Luminance for Xyza {
108    #[inline]
109    fn with_luminance(&self, lightness: f32) -> Self {
110        Self {
111            y: lightness,
112            ..*self
113        }
114    }
115
116    fn luminance(&self) -> f32 {
117        self.y
118    }
119
120    fn darker(&self, amount: f32) -> Self {
121        Self {
122            y: (self.y - amount).clamp(0., 1.),
123            ..*self
124        }
125    }
126
127    fn lighter(&self, amount: f32) -> Self {
128        Self {
129            y: (self.y + amount).min(1.),
130            ..*self
131        }
132    }
133}
134
135impl Mix for Xyza {
136    #[inline]
137    fn mix(&self, other: &Self, factor: f32) -> Self {
138        let n_factor = 1.0 - factor;
139        Self {
140            x: self.x * n_factor + other.x * factor,
141            y: self.y * n_factor + other.y * factor,
142            z: self.z * n_factor + other.z * factor,
143            alpha: self.alpha * n_factor + other.alpha * factor,
144        }
145    }
146}
147
148impl Gray for Xyza {
149    const BLACK: Self = Self::new(0., 0., 0., 1.);
150    const WHITE: Self = Self::new(0.95047, 1.0, 1.08883, 1.0);
151}
152
153impl ColorToComponents for Xyza {
154    fn to_f32_array(self) -> [f32; 4] {
155        [self.x, self.y, self.z, self.alpha]
156    }
157
158    fn to_f32_array_no_alpha(self) -> [f32; 3] {
159        [self.x, self.y, self.z]
160    }
161
162    fn to_vec4(self) -> Vec4 {
163        Vec4::new(self.x, self.y, self.z, self.alpha)
164    }
165
166    fn to_vec3(self) -> Vec3 {
167        Vec3::new(self.x, self.y, self.z)
168    }
169
170    fn from_f32_array(color: [f32; 4]) -> Self {
171        Self {
172            x: color[0],
173            y: color[1],
174            z: color[2],
175            alpha: color[3],
176        }
177    }
178
179    fn from_f32_array_no_alpha(color: [f32; 3]) -> Self {
180        Self {
181            x: color[0],
182            y: color[1],
183            z: color[2],
184            alpha: 1.0,
185        }
186    }
187
188    fn from_vec4(color: Vec4) -> Self {
189        Self {
190            x: color[0],
191            y: color[1],
192            z: color[2],
193            alpha: color[3],
194        }
195    }
196
197    fn from_vec3(color: Vec3) -> Self {
198        Self {
199            x: color[0],
200            y: color[1],
201            z: color[2],
202            alpha: 1.0,
203        }
204    }
205}
206
207impl From<LinearRgba> for Xyza {
208    fn from(
209        LinearRgba {
210            red,
211            green,
212            blue,
213            alpha,
214        }: LinearRgba,
215    ) -> Self {
216        // Linear sRGB to XYZ
217        // http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
218        // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html (sRGB, RGB to XYZ [M])
219        let r = red;
220        let g = green;
221        let b = blue;
222
223        let x = r * 0.4124564 + g * 0.3575761 + b * 0.1804375;
224        let y = r * 0.2126729 + g * 0.7151522 + b * 0.072175;
225        let z = r * 0.0193339 + g * 0.119192 + b * 0.9503041;
226
227        Xyza::new(x, y, z, alpha)
228    }
229}
230
231impl From<Xyza> for LinearRgba {
232    fn from(Xyza { x, y, z, alpha }: Xyza) -> Self {
233        // XYZ to Linear sRGB
234        // http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
235        // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html (sRGB, XYZ to RGB [M]-1)
236        let r = x * 3.2404542 + y * -1.5371385 + z * -0.4985314;
237        let g = x * -0.969266 + y * 1.8760108 + z * 0.041556;
238        let b = x * 0.0556434 + y * -0.2040259 + z * 1.0572252;
239
240        LinearRgba::new(r, g, b, alpha)
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247    use crate::{
248        color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq,
249        Srgba,
250    };
251
252    #[test]
253    fn test_to_from_srgba() {
254        let xyza = Xyza::new(0.5, 0.5, 0.5, 1.0);
255        let srgba: Srgba = xyza.into();
256        let xyza2: Xyza = srgba.into();
257        assert_approx_eq!(xyza.x, xyza2.x, 0.001);
258        assert_approx_eq!(xyza.y, xyza2.y, 0.001);
259        assert_approx_eq!(xyza.z, xyza2.z, 0.001);
260        assert_approx_eq!(xyza.alpha, xyza2.alpha, 0.001);
261    }
262
263    #[test]
264    fn test_to_from_srgba_2() {
265        for color in TEST_COLORS.iter() {
266            let rgb2: Srgba = (color.xyz).into();
267            let xyz2: Xyza = (color.rgb).into();
268            assert!(
269                color.rgb.distance(&rgb2) < 0.00001,
270                "{}: {:?} != {:?}",
271                color.name,
272                color.rgb,
273                rgb2
274            );
275            assert_approx_eq!(color.xyz.x, xyz2.x, 0.001);
276            assert_approx_eq!(color.xyz.y, xyz2.y, 0.001);
277            assert_approx_eq!(color.xyz.z, xyz2.z, 0.001);
278            assert_approx_eq!(color.xyz.alpha, xyz2.alpha, 0.001);
279        }
280    }
281}