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