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#[doc = include_str!("../docs/conversion.md")]
11#[doc = include_str!("../docs/diagrams/model_graph.svg")]
13#[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 pub x: f32,
24 pub y: f32,
26 pub z: f32,
28 pub alpha: f32,
30}
31
32impl StandardColor for Xyza {}
33
34impl_componentwise_vector_space!(Xyza, [x, y, z, alpha]);
35
36impl Xyza {
37 pub const fn new(x: f32, y: f32, z: f32, alpha: f32) -> Self {
46 Self { x, y, z, alpha }
47 }
48
49 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 pub const fn with_x(self, x: f32) -> Self {
67 Self { x, ..self }
68 }
69
70 pub const fn with_y(self, y: f32) -> Self {
72 Self { y, ..self }
73 }
74
75 pub const fn with_z(self, z: f32) -> Self {
77 Self { z, ..self }
78 }
79
80 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 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 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}