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(
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 pub x: f32,
28 pub y: f32,
30 pub z: f32,
32 pub alpha: f32,
34}
35
36impl StandardColor for Xyza {}
37
38impl_componentwise_vector_space!(Xyza, [x, y, z, alpha]);
39
40impl Xyza {
41 pub const fn new(x: f32, y: f32, z: f32, alpha: f32) -> Self {
50 Self { x, y, z, alpha }
51 }
52
53 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 pub const fn with_x(self, x: f32) -> Self {
71 Self { x, ..self }
72 }
73
74 pub const fn with_y(self, y: f32) -> Self {
76 Self { y, ..self }
77 }
78
79 pub const fn with_z(self, z: f32) -> Self {
81 Self { z, ..self }
82 }
83
84 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 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 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}