colorutils_rs/
xyz.rs

1/*
2 * // Copyright 2024 (c) the Radzivon Bartoshyk. All rights reserved.
3 * //
4 * // Use of this source code is governed by a BSD-style
5 * // license that can be found in the LICENSE file.
6 */
7use 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/// A CIE 1931 XYZ color.
18#[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    /// Scales XYZ to absolute luminance against display
60    #[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    /// Scales XYZ to absolute luminance against display
71    #[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
84/// This class avoid to scale by 100 for a reason: in common there are no need to scale by 100 in digital image processing,
85/// Normalized values are speeding up computing.
86/// if you need this multiply by yourself or use `scaled`
87impl Xyz {
88    /// Converts into *Xyz*
89    #[inline]
90    pub fn to_jzazbz(&self) -> Jzazbz {
91        Jzazbz::from_xyz(*self)
92    }
93
94    /// This functions always use sRGB transfer function and Rec.601 primaries with D65 White point
95    #[inline]
96    pub fn from_srgb(rgb: Rgb<u8>) -> Self {
97        Xyz::from_rgb(rgb, &SRGB_TO_XYZ_D65, TransferFunction::Srgb)
98    }
99
100    /// This function converts from non-linear RGB components to XYZ
101    /// # Arguments
102    /// * `matrix` - Transformation matrix from RGB to XYZ, for example `SRGB_TO_XYZ_D65`
103    /// * `transfer_function` - Transfer functions for current colorspace
104    #[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    /// This function converts from non-linear RGB components to XYZ
147    /// # Arguments
148    /// * `matrix` - Transformation matrix from RGB to XYZ, for example `SRGB_TO_XYZ_D65`
149    /// * `transfer_function` - Transfer functions for current colorspace
150    #[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    /// This functions always use sRGB transfer function and Rec.601 primaries with D65 White point
198    pub fn to_srgb(&self) -> Rgb<u8> {
199        self.to_rgb(&XYZ_TO_SRGB_D65, TransferFunction::Srgb)
200    }
201
202    /// This functions always use sRGB transfer function and Rec.601 primaries with D65 White point
203    /// # Arguments
204    /// * `matrix` - Transformation matrix from RGB to XYZ, for example `SRGB_TO_XYZ_D65`
205    /// * `transfer_function` - Transfer functions for current colorspace
206    #[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    /// This function converts XYZ to linear RGB
249    /// # Arguments
250    /// * `matrix` - Transformation matrix from RGB to XYZ, for example `SRGB_TO_XYZ_D65`
251    #[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}