1use crate::gamma_curves::TransferFunction;
8use crate::rgb::Rgb;
9use crate::utils::mlaf;
10use crate::{EuclideanDistance, Jzazbz, SRGB_TO_XYZ_D65, XYZ_TO_SRGB_D65};
11use erydanos::Euclidean3DDistance;
12use num_traits::Pow;
13use std::ops::{
14 Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
15};
16
17#[repr(C)]
19#[derive(Copy, Clone, Debug, Default)]
20pub struct Xyz {
21 pub x: f32,
22 pub y: f32,
23 pub z: f32,
24}
25
26impl Xyz {
27 #[inline]
28 pub fn new(x: f32, y: f32, z: f32) -> Self {
29 Self { x, y, z }
30 }
31
32 #[inline]
33 pub fn saturate_x(x: f32) -> f32 {
34 #[allow(clippy::manual_clamp)]
35 x.max(0f32).min(95.047f32)
36 }
37
38 #[inline]
39 pub fn saturate_y(y: f32) -> f32 {
40 #[allow(clippy::manual_clamp)]
41 y.max(0f32).min(100f32)
42 }
43
44 #[inline]
45 pub fn saturate_z(z: f32) -> f32 {
46 #[allow(clippy::manual_clamp)]
47 z.max(0f32).min(108.883f32)
48 }
49
50 #[inline]
51 pub fn scale(&self, by: f32) -> Xyz {
52 Xyz {
53 x: self.x * by,
54 y: self.y * by,
55 z: self.z * by,
56 }
57 }
58
59 #[inline]
61 pub fn to_absolute_luminance(&self, display_nits: f32) -> Xyz {
62 let multiplier = display_nits;
63 Xyz::new(
64 multiplier * self.x,
65 multiplier * self.y,
66 multiplier * self.z,
67 )
68 }
69
70 #[inline]
72 pub fn to_relative_luminance(&self, display_nits: f32) -> Xyz {
73 let multiplier = 1. / display_nits;
74 Xyz::new(
75 multiplier * self.x,
76 multiplier * self.y,
77 multiplier * self.z,
78 )
79 }
80}
81
82static XYZ_SCALE_U8: f32 = 1f32 / 255f32;
83
84impl Xyz {
88 #[inline]
90 pub fn to_jzazbz(&self) -> Jzazbz {
91 Jzazbz::from_xyz(*self)
92 }
93
94 #[inline]
96 pub fn from_srgb(rgb: Rgb<u8>) -> Self {
97 Xyz::from_rgb(rgb, &SRGB_TO_XYZ_D65, TransferFunction::Srgb)
98 }
99
100 #[inline]
105 pub fn from_rgb(
106 rgb: Rgb<u8>,
107 matrix: &[[f32; 3]; 3],
108 transfer_function: TransferFunction,
109 ) -> Self {
110 unsafe {
111 let r = transfer_function.linearize(rgb.r as f32 * XYZ_SCALE_U8);
112 let g = transfer_function.linearize(rgb.g as f32 * XYZ_SCALE_U8);
113 let b = transfer_function.linearize(rgb.b as f32 * XYZ_SCALE_U8);
114 Self::new(
115 mlaf(
116 mlaf(
117 (*(*matrix.get_unchecked(0)).get_unchecked(0)) * r,
118 *(*matrix.get_unchecked(0)).get_unchecked(1),
119 g,
120 ),
121 *(*matrix.get_unchecked(0)).get_unchecked(2),
122 b,
123 ),
124 mlaf(
125 mlaf(
126 (*(*matrix.get_unchecked(1)).get_unchecked(0)) * r,
127 *(*matrix.get_unchecked(1)).get_unchecked(1),
128 g,
129 ),
130 *(*matrix.get_unchecked(1)).get_unchecked(2),
131 b,
132 ),
133 mlaf(
134 mlaf(
135 (*(*matrix.get_unchecked(2)).get_unchecked(0)) * r,
136 *(*matrix.get_unchecked(2)).get_unchecked(1),
137 g,
138 ),
139 *(*matrix.get_unchecked(2)).get_unchecked(2),
140 b,
141 ),
142 )
143 }
144 }
145
146 #[inline]
151 pub fn from_linear_rgb(rgb: Rgb<f32>, matrix: &[[f32; 3]; 3]) -> Self {
152 unsafe {
153 Self::new(
154 mlaf(
155 mlaf(
156 (*(*matrix.get_unchecked(0)).get_unchecked(0)) * rgb.r,
157 *(*matrix.get_unchecked(0)).get_unchecked(1),
158 rgb.g,
159 ),
160 *(*matrix.get_unchecked(0)).get_unchecked(2),
161 rgb.b,
162 ),
163 mlaf(
164 mlaf(
165 (*(*matrix.get_unchecked(1)).get_unchecked(0)) * rgb.r,
166 *(*matrix.get_unchecked(1)).get_unchecked(1),
167 rgb.g,
168 ),
169 *(*matrix.get_unchecked(1)).get_unchecked(2),
170 rgb.b,
171 ),
172 mlaf(
173 mlaf(
174 (*(*matrix.get_unchecked(2)).get_unchecked(0)) * rgb.r,
175 *(*matrix.get_unchecked(2)).get_unchecked(1),
176 rgb.g,
177 ),
178 *(*matrix.get_unchecked(2)).get_unchecked(2),
179 rgb.b,
180 ),
181 )
182 }
183 }
184
185 #[inline]
186 pub fn scaled(&self) -> (f32, f32, f32) {
187 (self.x * 100f32, self.y * 100f32, self.z * 100f32)
188 }
189
190 #[inline]
191 pub fn scaled_by(&self, by: f32) -> Xyz {
192 Xyz::new(self.x * by, self.y * by, self.z * by)
193 }
194}
195
196impl Xyz {
197 pub fn to_srgb(&self) -> Rgb<u8> {
199 self.to_rgb(&XYZ_TO_SRGB_D65, TransferFunction::Srgb)
200 }
201
202 #[inline]
207 pub fn to_rgb(&self, matrix: &[[f32; 3]; 3], transfer_function: TransferFunction) -> Rgb<u8> {
208 let x = self.x;
209 let y = self.y;
210 let z = self.z;
211 unsafe {
212 let r = mlaf(
213 mlaf(
214 x * (*(*matrix.get_unchecked(0)).get_unchecked(0)),
215 y,
216 *(*matrix.get_unchecked(0)).get_unchecked(1),
217 ),
218 z,
219 *(*matrix.get_unchecked(0)).get_unchecked(2),
220 );
221 let g = mlaf(
222 mlaf(
223 x * (*(*matrix.get_unchecked(1)).get_unchecked(0)),
224 y,
225 *(*matrix.get_unchecked(1)).get_unchecked(1),
226 ),
227 z,
228 *(*matrix.get_unchecked(1)).get_unchecked(2),
229 );
230 let b = mlaf(
231 mlaf(
232 x * (*(*matrix.get_unchecked(2)).get_unchecked(0)),
233 y,
234 *(*matrix.get_unchecked(2)).get_unchecked(1),
235 ),
236 z,
237 *(*matrix.get_unchecked(2)).get_unchecked(2),
238 );
239 Rgb::<f32>::new(
240 transfer_function.gamma(r),
241 transfer_function.gamma(g),
242 transfer_function.gamma(b),
243 )
244 .to_u8()
245 }
246 }
247
248 #[inline]
252 pub fn to_linear_rgb(&self, matrix: &[[f32; 3]; 3]) -> Rgb<f32> {
253 let x = self.x;
254 let y = self.y;
255 let z = self.z;
256 unsafe {
257 let r = mlaf(
258 mlaf(
259 x * (*(*matrix.get_unchecked(0)).get_unchecked(0)),
260 y,
261 *(*matrix.get_unchecked(0)).get_unchecked(1),
262 ),
263 z,
264 *(*matrix.get_unchecked(0)).get_unchecked(2),
265 );
266 let g = mlaf(
267 mlaf(
268 x * (*(*matrix.get_unchecked(1)).get_unchecked(0)),
269 y,
270 *(*matrix.get_unchecked(1)).get_unchecked(1),
271 ),
272 z,
273 *(*matrix.get_unchecked(1)).get_unchecked(2),
274 );
275 let b = mlaf(
276 mlaf(
277 x * (*(*matrix.get_unchecked(2)).get_unchecked(0)),
278 y,
279 *(*matrix.get_unchecked(2)).get_unchecked(1),
280 ),
281 z,
282 *(*matrix.get_unchecked(2)).get_unchecked(2),
283 );
284 Rgb::<f32>::new(r, g, b)
285 }
286 }
287}
288
289impl EuclideanDistance for Xyz {
290 fn euclidean_distance(&self, other: Xyz) -> f32 {
291 (self.x - other.x).hypot3(self.y - other.y, self.z - other.z)
292 }
293}
294
295impl Index<usize> for Xyz {
296 type Output = f32;
297
298 fn index(&self, index: usize) -> &f32 {
299 match index {
300 0 => &self.x,
301 1 => &self.y,
302 2 => &self.z,
303 _ => panic!("Index out of bounds for Xyz"),
304 }
305 }
306}
307
308impl IndexMut<usize> for Xyz {
309 fn index_mut(&mut self, index: usize) -> &mut f32 {
310 match index {
311 0 => &mut self.x,
312 1 => &mut self.y,
313 2 => &mut self.z,
314 _ => panic!("Index out of bounds for Xyz"),
315 }
316 }
317}
318
319impl Add<Xyz> for Xyz {
320 type Output = Xyz;
321
322 #[inline]
323 fn add(self, rhs: Self) -> Xyz {
324 Xyz::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
325 }
326}
327
328impl Add<f32> for Xyz {
329 type Output = Xyz;
330
331 #[inline]
332 fn add(self, rhs: f32) -> Xyz {
333 Xyz::new(self.x + rhs, self.y + rhs, self.z + rhs)
334 }
335}
336
337impl AddAssign<Xyz> for Xyz {
338 #[inline]
339 fn add_assign(&mut self, rhs: Xyz) {
340 self.x += rhs.x;
341 self.y += rhs.y;
342 self.z += rhs.z;
343 }
344}
345
346impl AddAssign<f32> for Xyz {
347 #[inline]
348 fn add_assign(&mut self, rhs: f32) {
349 self.x += rhs;
350 self.y += rhs;
351 self.z += rhs;
352 }
353}
354
355impl Mul<f32> for Xyz {
356 type Output = Xyz;
357
358 #[inline]
359 fn mul(self, rhs: f32) -> Self::Output {
360 Xyz::new(self.x * rhs, self.y * rhs, self.z * rhs)
361 }
362}
363
364impl Mul<Xyz> for Xyz {
365 type Output = Xyz;
366
367 #[inline]
368 fn mul(self, rhs: Xyz) -> Self::Output {
369 Xyz::new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z)
370 }
371}
372
373impl MulAssign<Xyz> for Xyz {
374 #[inline]
375 fn mul_assign(&mut self, rhs: Xyz) {
376 self.x *= rhs.x;
377 self.y *= rhs.y;
378 self.z *= rhs.z;
379 }
380}
381
382impl MulAssign<f32> for Xyz {
383 #[inline]
384 fn mul_assign(&mut self, rhs: f32) {
385 self.x *= rhs;
386 self.y *= rhs;
387 self.z *= rhs;
388 }
389}
390
391impl Sub<f32> for Xyz {
392 type Output = Xyz;
393
394 #[inline]
395 fn sub(self, rhs: f32) -> Self::Output {
396 Xyz::new(self.x - rhs, self.y - rhs, self.z - rhs)
397 }
398}
399
400impl Sub<Xyz> for Xyz {
401 type Output = Xyz;
402
403 #[inline]
404 fn sub(self, rhs: Xyz) -> Self::Output {
405 Xyz::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
406 }
407}
408
409impl SubAssign<f32> for Xyz {
410 #[inline]
411 fn sub_assign(&mut self, rhs: f32) {
412 self.x -= rhs;
413 self.y -= rhs;
414 self.z -= rhs;
415 }
416}
417
418impl SubAssign<Xyz> for Xyz {
419 #[inline]
420 fn sub_assign(&mut self, rhs: Xyz) {
421 self.x -= rhs.x;
422 self.y -= rhs.y;
423 self.z -= rhs.z;
424 }
425}
426
427impl Div<f32> for Xyz {
428 type Output = Xyz;
429
430 #[inline]
431 fn div(self, rhs: f32) -> Self::Output {
432 Xyz::new(self.x / rhs, self.y / rhs, self.z / rhs)
433 }
434}
435
436impl Div<Xyz> for Xyz {
437 type Output = Xyz;
438
439 #[inline]
440 fn div(self, rhs: Xyz) -> Self::Output {
441 Xyz::new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z)
442 }
443}
444
445impl DivAssign<f32> for Xyz {
446 #[inline]
447 fn div_assign(&mut self, rhs: f32) {
448 self.x /= rhs;
449 self.y /= rhs;
450 self.z /= rhs;
451 }
452}
453
454impl DivAssign<Xyz> for Xyz {
455 #[inline]
456 fn div_assign(&mut self, rhs: Xyz) {
457 self.x /= rhs.x;
458 self.y /= rhs.y;
459 self.z /= rhs.z;
460 }
461}
462
463impl Neg for Xyz {
464 type Output = Xyz;
465
466 #[inline]
467 fn neg(self) -> Self::Output {
468 Xyz::new(-self.x, -self.y, -self.z)
469 }
470}
471
472impl Xyz {
473 #[inline]
474 pub fn sqrt(&self) -> Xyz {
475 Xyz::new(
476 if self.x < 0. { 0. } else { self.x.sqrt() },
477 if self.y < 0. { 0. } else { self.y.sqrt() },
478 if self.z < 0. { 0. } else { self.z.sqrt() },
479 )
480 }
481
482 #[inline]
483 pub fn cbrt(&self) -> Xyz {
484 Xyz::new(self.x.cbrt(), self.y.cbrt(), self.z.cbrt())
485 }
486}
487
488impl Pow<f32> for Xyz {
489 type Output = Xyz;
490
491 #[inline]
492 fn pow(self, rhs: f32) -> Self::Output {
493 Xyz::new(self.x.powf(rhs), self.y.powf(rhs), self.z.powf(rhs))
494 }
495}
496
497impl Pow<Xyz> for Xyz {
498 type Output = Xyz;
499
500 #[inline]
501 fn pow(self, rhs: Xyz) -> Self::Output {
502 Xyz::new(self.x.powf(rhs.x), self.y.powf(rhs.y), self.z.powf(rhs.z))
503 }
504}