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