1use core::marker::PhantomData;
4
5use crate::{
6 bool_mask::{HasBoolMask, LazySelect},
7 convert::{FromColorUnclamped, IntoColorUnclamped},
8 encoding::IntoLinear,
9 luma::LumaStandard,
10 num::{Arithmetics, IsValidDivisor, One, PartialCmp, Real, Zero},
11 white_point::{WhitePoint, D65},
12 Alpha, Luma, Xyz,
13};
14
15pub type Yxya<Wp = D65, T = f32> = Alpha<Yxy<Wp, T>, T>;
18
19#[derive(Debug, ArrayCast, FromColorUnclamped, WithAlpha)]
27#[cfg_attr(feature = "serializing", derive(Serialize, Deserialize))]
28#[palette(
29 palette_internal,
30 white_point = "Wp",
31 component = "T",
32 skip_derives(Xyz, Yxy, Luma)
33)]
34#[repr(C)]
35#[doc(alias = "xyY")]
36pub struct Yxy<Wp = D65, T = f32> {
37 pub x: T,
40
41 pub y: T,
44
45 pub luma: T,
49
50 #[cfg_attr(feature = "serializing", serde(skip))]
53 #[palette(unsafe_zero_sized)]
54 pub white_point: PhantomData<Wp>,
55}
56
57impl<Wp, T> Yxy<Wp, T> {
58 pub const fn new(x: T, y: T, luma: T) -> Yxy<Wp, T> {
60 Yxy {
61 x,
62 y,
63 luma,
64 white_point: PhantomData,
65 }
66 }
67
68 pub fn into_components(self) -> (T, T, T) {
70 (self.x, self.y, self.luma)
71 }
72
73 pub fn from_components((x, y, luma): (T, T, T)) -> Self {
75 Self::new(x, y, luma)
76 }
77
78 #[inline]
88 pub fn with_white_point<NewWp>(self) -> Yxy<NewWp, T> {
89 Yxy::new(self.x, self.y, self.luma)
90 }
91}
92
93impl<Wp, T> Yxy<Wp, T>
94where
95 T: Zero + One,
96{
97 pub fn min_x() -> T {
99 T::zero()
100 }
101
102 pub fn max_x() -> T {
104 T::one()
105 }
106
107 pub fn min_y() -> T {
109 T::zero()
110 }
111
112 pub fn max_y() -> T {
114 T::one()
115 }
116
117 pub fn min_luma() -> T {
119 T::zero()
120 }
121
122 pub fn max_luma() -> T {
124 T::one()
125 }
126}
127
128impl<Wp, T, A> Alpha<Yxy<Wp, T>, A> {
130 pub const fn new(x: T, y: T, luma: T, alpha: A) -> Self {
132 Alpha {
133 color: Yxy::new(x, y, luma),
134 alpha,
135 }
136 }
137
138 pub fn into_components(self) -> (T, T, T, A) {
140 (self.color.x, self.color.y, self.color.luma, self.alpha)
141 }
142
143 pub fn from_components((x, y, luma, alpha): (T, T, T, A)) -> Self {
145 Self::new(x, y, luma, alpha)
146 }
147
148 #[inline]
158 pub fn with_white_point<NewWp>(self) -> Alpha<Yxy<NewWp, T>, A> {
159 Alpha::<Yxy<NewWp, T>, A>::new(self.color.x, self.color.y, self.color.luma, self.alpha)
160 }
161}
162
163impl_reference_component_methods!(Yxy<Wp>, [x, y, luma], white_point);
164impl_struct_of_arrays_methods!(Yxy<Wp>, [x, y, luma], white_point);
165
166impl_tuple_conversion!(Yxy<Wp> as (T, T, T));
167
168impl<Wp, T> FromColorUnclamped<Yxy<Wp, T>> for Yxy<Wp, T> {
169 fn from_color_unclamped(color: Yxy<Wp, T>) -> Self {
170 color
171 }
172}
173
174impl<Wp, T> FromColorUnclamped<Xyz<Wp, T>> for Yxy<Wp, T>
175where
176 T: Zero + IsValidDivisor + Arithmetics + Clone,
177 T::Mask: LazySelect<T> + Clone,
178{
179 fn from_color_unclamped(xyz: Xyz<Wp, T>) -> Self {
180 let Xyz { x, y, z, .. } = xyz;
181
182 let sum = x.clone() + &y + z;
183
184 let mask = sum.is_valid_divisor();
186 Yxy {
187 x: lazy_select! {
188 if mask.clone() => x / &sum,
189 else => T::zero(),
190 },
191 y: lazy_select! {
192 if mask => y.clone() / sum,
193 else => T::zero()
194 },
195 luma: y,
196 white_point: PhantomData,
197 }
198 }
199}
200
201impl<T, S> FromColorUnclamped<Luma<S, T>> for Yxy<S::WhitePoint, T>
202where
203 S: LumaStandard,
204 S::TransferFn: IntoLinear<T, T>,
205 Self: Default,
206{
207 fn from_color_unclamped(luma: Luma<S, T>) -> Self {
208 Yxy {
209 luma: luma.into_linear().luma,
210 ..Default::default()
211 }
212 }
213}
214
215impl_is_within_bounds! {
216 Yxy<Wp> {
217 x => [Self::min_x(), Self::max_x()],
218 y => [Self::min_y(), Self::max_y()],
219 luma => [Self::min_luma(), Self::max_luma()]
220 }
221 where T: Zero + One
222}
223impl_clamp! {
224 Yxy<Wp> {
225 x => [Self::min_x(), Self::max_x()],
226 y => [Self::min_y(), Self::max_y()],
227 luma => [Self::min_luma(), Self::max_luma()]
228 }
229 other {white_point}
230 where T: Zero + One
231}
232
233impl_mix!(Yxy<Wp>);
234impl_lighten!(Yxy<Wp> increase {luma => [Self::min_luma(), Self::max_luma()]} other {x, y} phantom: white_point where T: One);
235impl_premultiply!(Yxy<Wp> {x, y, luma} phantom: white_point);
236impl_euclidean_distance!(Yxy<Wp> {x, y, luma});
237
238impl<Wp, T> HasBoolMask for Yxy<Wp, T>
239where
240 T: HasBoolMask,
241{
242 type Mask = T::Mask;
243}
244
245impl<Wp, T> Default for Yxy<Wp, T>
246where
247 T: Zero,
248 Wp: WhitePoint<T>,
249 Xyz<Wp, T>: IntoColorUnclamped<Self>,
250{
251 fn default() -> Yxy<Wp, T> {
252 Yxy {
257 luma: T::zero(),
258 ..Wp::get_xyz().with_white_point().into_color_unclamped()
259 }
260 }
261}
262
263impl_color_add!(Yxy<Wp>, [x, y, luma], white_point);
264impl_color_sub!(Yxy<Wp>, [x, y, luma], white_point);
265impl_color_mul!(Yxy<Wp>, [x, y, luma], white_point);
266impl_color_div!(Yxy<Wp>, [x, y, luma], white_point);
267
268impl_array_casts!(Yxy<Wp, T>, [T; 3]);
269impl_simd_array_conversion!(Yxy<Wp>, [x, y, luma], white_point);
270impl_struct_of_array_traits!(Yxy<Wp>, [x, y, luma], white_point);
271
272impl_eq!(Yxy<Wp>, [x, y, luma]);
273impl_copy_clone!(Yxy<Wp>, [x, y, luma], white_point);
274
275#[allow(deprecated)]
276impl<Wp, T> crate::RelativeContrast for Yxy<Wp, T>
277where
278 T: Real + Arithmetics + PartialCmp,
279 T::Mask: LazySelect<T>,
280{
281 type Scalar = T;
282
283 #[inline]
284 fn get_contrast_ratio(self, other: Self) -> T {
285 crate::contrast_ratio(self.luma, other.luma)
286 }
287}
288
289impl_rand_traits_cartesian!(UniformYxy, Yxy<Wp> {x, y, luma} phantom: white_point: PhantomData<Wp>);
290
291#[cfg(feature = "bytemuck")]
292unsafe impl<Wp, T> bytemuck::Zeroable for Yxy<Wp, T> where T: bytemuck::Zeroable {}
293
294#[cfg(feature = "bytemuck")]
295unsafe impl<Wp: 'static, T> bytemuck::Pod for Yxy<Wp, T> where T: bytemuck::Pod {}
296
297#[cfg(test)]
298mod test {
299 use super::Yxy;
300 use crate::white_point::D65;
301
302 test_convert_into_from_xyz!(Yxy);
303
304 #[cfg(feature = "approx")]
305 mod conversion {
306 use crate::{white_point::D65, FromColor, LinLuma, LinSrgb, Yxy};
307
308 #[test]
309 fn luma() {
310 let a = Yxy::<D65>::from_color(LinLuma::new(0.5));
311 let b = Yxy::new(0.312727, 0.329023, 0.5);
312 assert_relative_eq!(a, b, epsilon = 0.000001);
313 }
314
315 #[test]
316 fn red() {
317 let a = Yxy::from_color(LinSrgb::new(1.0, 0.0, 0.0));
318 let b = Yxy::new(0.64, 0.33, 0.212673);
319 assert_relative_eq!(a, b, epsilon = 0.000001);
320 }
321
322 #[test]
323 fn green() {
324 let a = Yxy::from_color(LinSrgb::new(0.0, 1.0, 0.0));
325 let b = Yxy::new(0.3, 0.6, 0.715152);
326 assert_relative_eq!(a, b, epsilon = 0.000001);
327 }
328
329 #[test]
330 fn blue() {
331 let a = Yxy::from_color(LinSrgb::new(0.0, 0.0, 1.0));
332 let b = Yxy::new(0.15, 0.06, 0.072175);
333 assert_relative_eq!(a, b, epsilon = 0.000001);
334 }
335 }
336
337 #[test]
338 fn ranges() {
339 assert_ranges! {
340 Yxy<D65, f64>;
341 clamped {
342 x: 0.0 => 1.0,
343 y: 0.0 => 1.0,
344 luma: 0.0 => 1.0
345 }
346 clamped_min {}
347 unclamped {}
348 }
349 }
350
351 raw_pixel_conversion_tests!(Yxy<D65>: x, y, luma);
352 raw_pixel_conversion_fail_tests!(Yxy<D65>: x, y, luma);
353
354 #[test]
355 fn check_min_max_components() {
356 assert_eq!(Yxy::<D65>::min_x(), 0.0);
357 assert_eq!(Yxy::<D65>::min_y(), 0.0);
358 assert_eq!(Yxy::<D65>::min_luma(), 0.0);
359 assert_eq!(Yxy::<D65>::max_x(), 1.0);
360 assert_eq!(Yxy::<D65>::max_y(), 1.0);
361 assert_eq!(Yxy::<D65>::max_luma(), 1.0);
362 }
363
364 struct_of_arrays_tests!(
365 Yxy<D65>[x, y, luma] phantom: white_point,
366 super::Yxya::new(0.1f32, 0.2, 0.3, 0.4),
367 super::Yxya::new(0.2, 0.3, 0.4, 0.5),
368 super::Yxya::new(0.3, 0.4, 0.5, 0.6)
369 );
370
371 #[cfg(feature = "serializing")]
372 #[test]
373 fn serialize() {
374 let serialized = ::serde_json::to_string(&Yxy::<D65>::new(0.3, 0.8, 0.1)).unwrap();
375
376 assert_eq!(serialized, r#"{"x":0.3,"y":0.8,"luma":0.1}"#);
377 }
378
379 #[cfg(feature = "serializing")]
380 #[test]
381 fn deserialize() {
382 let deserialized: Yxy = ::serde_json::from_str(r#"{"x":0.3,"y":0.8,"luma":0.1}"#).unwrap();
383
384 assert_eq!(deserialized, Yxy::new(0.3, 0.8, 0.1));
385 }
386
387 test_uniform_distribution! {
388 Yxy<D65, f32> {
389 x: (0.0, 1.0),
390 y: (0.0, 1.0),
391 luma: (0.0, 1.0)
392 },
393 min: Yxy::new(0.0f32, 0.0, 0.0),
394 max: Yxy::new(1.0, 1.0, 1.0),
395 }
396}