colorutils_rs/
jzazbz.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::utils::mlaf;
8use crate::{
9    EuclideanDistance, Jzczhz, Rgb, TaxicabDistance, TransferFunction, Xyz, SRGB_TO_XYZ_D65,
10    XYZ_TO_SRGB_D65,
11};
12use num_traits::Pow;
13use std::ops::{
14    Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
15};
16
17#[inline]
18fn perceptual_quantizer(x: f32) -> f32 {
19    if x <= 0. {
20        return 0.;
21    }
22    let xx = f32::powf(x * 1e-4, 0.1593017578125);
23    let rs = f32::powf(
24        (0.8359375 + 18.8515625 * xx) / (1. + 18.6875 * xx),
25        134.034375,
26    );
27    if rs.is_nan() {
28        return 0.;
29    }
30    rs
31}
32
33#[inline]
34fn perceptual_quantizer_inverse(x: f32) -> f32 {
35    if x <= 0. {
36        return 0.;
37    }
38    let xx = f32::powf(x, 7.460772656268214e-03);
39    let rs = 1e4
40        * f32::powf(
41            (0.8359375 - xx) / (18.6875 * xx - 18.8515625),
42            6.277394636015326,
43        );
44    if rs.is_nan() {
45        return 0.;
46    }
47    rs
48}
49
50#[repr(C)]
51#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
52/// Represents Jzazbz
53pub struct Jzazbz {
54    /// Jz(lightness) generally expects to be between [0;1]
55    pub jz: f32,
56    /// Az generally expects to be between [-0.5;0.5]
57    pub az: f32,
58    /// Bz generally expects to be between [-0.5;0.5]
59    pub bz: f32,
60    /// Display luminance, default 200 nits
61    pub display_luminance: f32,
62}
63
64impl Jzazbz {
65    /// Constructs new instance
66    #[inline]
67    pub fn new(jz: f32, az: f32, bz: f32) -> Jzazbz {
68        Jzazbz {
69            jz,
70            az,
71            bz,
72            display_luminance: 200f32,
73        }
74    }
75
76    /// Constructs new instance
77    #[inline]
78    pub fn new_with_luminance(jz: f32, az: f32, bz: f32, display_luminance: f32) -> Jzazbz {
79        Jzazbz {
80            jz,
81            az,
82            bz,
83            display_luminance,
84        }
85    }
86
87    #[inline]
88    pub fn from_xyz(xyz: Xyz) -> Jzazbz {
89        Self::from_xyz_with_display_luminance(xyz, 200f32)
90    }
91
92    #[inline]
93    pub fn from_xyz_with_display_luminance(xyz: Xyz, display_luminance: f32) -> Jzazbz {
94        let abs_xyz = xyz.to_absolute_luminance(display_luminance);
95        let lp = perceptual_quantizer(mlaf(
96            mlaf(0.674207838 * abs_xyz.x, 0.382799340, abs_xyz.y),
97            -0.047570458,
98            abs_xyz.z,
99        ));
100        let mp = perceptual_quantizer(mlaf(
101            mlaf(0.149284160 * abs_xyz.x, 0.739628340, abs_xyz.y),
102            0.083327300,
103            abs_xyz.z,
104        ));
105        let sp = perceptual_quantizer(mlaf(
106            mlaf(0.070941080 * abs_xyz.x, 0.174768000, abs_xyz.y),
107            0.670970020,
108            abs_xyz.z,
109        ));
110        let iz = 0.5 * (lp + mp);
111        let az = mlaf(mlaf(3.524000 * lp, -4.066708, mp), 0.542708, sp);
112        let bz = mlaf(mlaf(0.199076 * lp, 1.096799, mp), -1.295875, sp);
113        let jz = (0.44 * iz) / (1. - 0.56 * iz) - 1.6295499532821566e-11;
114        Jzazbz::new_with_luminance(jz, az, bz, display_luminance)
115    }
116
117    /// Converts Rgb to Jzazbz
118    /// Here is display luminance always considered as 200 nits
119    ///
120    /// # Arguments
121    /// `transfer_function` - transfer function into linear color space and it's inverse
122    #[inline]
123    pub fn from_rgb(rgb: Rgb<u8>, transfer_function: TransferFunction) -> Jzazbz {
124        let xyz = rgb.to_xyz(&SRGB_TO_XYZ_D65, transfer_function);
125        Self::from_xyz_with_display_luminance(xyz, 200.)
126    }
127
128    /// Converts Rgb to Jzazbz
129    ///
130    /// # Arguments
131    /// `transfer_function` - transfer function into linear color space and it's inverse
132    /// `display_luminance` - display luminance
133    #[inline]
134    pub fn from_rgb_with_luminance(
135        rgb: Rgb<u8>,
136        display_luminance: f32,
137        transfer_function: TransferFunction,
138    ) -> Jzazbz {
139        let xyz = rgb.to_xyz(&SRGB_TO_XYZ_D65, transfer_function);
140        Self::from_xyz_with_display_luminance(xyz, display_luminance)
141    }
142
143    /// Converts Jzazbz to *Xyz*
144    #[inline]
145    pub fn to_xyz(&self) -> Xyz {
146        let jz = self.jz + 1.6295499532821566e-11;
147
148        let iz = jz / (0.44f32 + 0.56f32 * jz);
149        let l = perceptual_quantizer_inverse(mlaf(
150            mlaf(iz, 1.386050432715393e-1, self.az),
151            5.804731615611869e-2,
152            self.bz,
153        ));
154        let m = perceptual_quantizer_inverse(mlaf(
155            mlaf(iz, -1.386050432715393e-1, self.az),
156            -5.804731615611891e-2,
157            self.bz,
158        ));
159        let s = perceptual_quantizer_inverse(mlaf(
160            mlaf(iz, -9.601924202631895e-2, self.az),
161            -8.118918960560390e-1,
162            self.bz,
163        ));
164        let x = mlaf(
165            mlaf(1.661373055774069e+00 * l, -9.145230923250668e-01, m),
166            2.313620767186147e-01,
167            s,
168        );
169        let y = mlaf(
170            mlaf(-3.250758740427037e-01 * l, 1.571847038366936e+00, m),
171            -2.182538318672940e-01,
172            s,
173        );
174        let z = mlaf(
175            mlaf(-9.098281098284756e-02 * l, -3.127282905230740e-01, m),
176            1.522766561305260e+00,
177            s,
178        );
179        Xyz::new(x, y, z).to_relative_luminance(self.display_luminance)
180    }
181
182    /// Converts to Linear RGB
183    #[inline]
184    pub fn to_linear_rgb(&self) -> Rgb<f32> {
185        let xyz = self.to_xyz();
186        xyz.to_linear_rgb(&XYZ_TO_SRGB_D65)
187    }
188
189    /// Converts Linear to RGB with requested transfer function
190    #[inline]
191    pub fn to_rgb(&self, transfer_function: TransferFunction) -> Rgb<u8> {
192        let linear_rgb = self.to_linear_rgb().gamma(transfer_function);
193        linear_rgb.to_u8()
194    }
195
196    /// Converts into *Jzczhz*
197    #[inline]
198    pub fn to_jzczhz(&self) -> Jzczhz {
199        Jzczhz::from_jzazbz(*self)
200    }
201}
202
203impl EuclideanDistance for Jzazbz {
204    #[inline]
205    fn euclidean_distance(&self, other: Self) -> f32 {
206        let djz = self.jz - other.jz;
207        let daz = self.az - other.az;
208        let dbz = self.bz - other.bz;
209        (djz * djz + daz * daz + dbz * dbz).sqrt()
210    }
211}
212
213impl TaxicabDistance for Jzazbz {
214    #[inline]
215    fn taxicab_distance(&self, other: Self) -> f32 {
216        let djz = self.jz - other.jz;
217        let daz = self.az - other.az;
218        let dbz = self.bz - other.bz;
219        djz.abs() + daz.abs() + dbz.abs()
220    }
221}
222
223impl Index<usize> for Jzazbz {
224    type Output = f32;
225
226    #[inline]
227    fn index(&self, index: usize) -> &f32 {
228        match index {
229            0 => &self.jz,
230            1 => &self.az,
231            2 => &self.bz,
232            _ => panic!("Index out of bounds for Jzazbz"),
233        }
234    }
235}
236
237impl IndexMut<usize> for Jzazbz {
238    #[inline]
239    fn index_mut(&mut self, index: usize) -> &mut f32 {
240        match index {
241            0 => &mut self.jz,
242            1 => &mut self.az,
243            2 => &mut self.bz,
244            _ => panic!("Index out of bounds for Jzazbz"),
245        }
246    }
247}
248
249impl Add<f32> for Jzazbz {
250    type Output = Jzazbz;
251
252    #[inline]
253    fn add(self, rhs: f32) -> Self::Output {
254        Jzazbz::new(self.jz + rhs, self.az + rhs, self.bz + rhs)
255    }
256}
257
258impl Sub<f32> for Jzazbz {
259    type Output = Jzazbz;
260
261    #[inline]
262    fn sub(self, rhs: f32) -> Self::Output {
263        Jzazbz::new(self.jz - rhs, self.az - rhs, self.bz - rhs)
264    }
265}
266
267impl Mul<f32> for Jzazbz {
268    type Output = Jzazbz;
269
270    #[inline]
271    fn mul(self, rhs: f32) -> Self::Output {
272        Jzazbz::new(self.jz * rhs, self.az * rhs, self.bz * rhs)
273    }
274}
275
276impl Div<f32> for Jzazbz {
277    type Output = Jzazbz;
278
279    #[inline]
280    fn div(self, rhs: f32) -> Self::Output {
281        Jzazbz::new(self.jz / rhs, self.az / rhs, self.bz / rhs)
282    }
283}
284
285impl Add<Jzazbz> for Jzazbz {
286    type Output = Jzazbz;
287
288    #[inline]
289    fn add(self, rhs: Jzazbz) -> Self::Output {
290        Jzazbz::new(self.jz + rhs.jz, self.az + rhs.az, self.bz + rhs.bz)
291    }
292}
293
294impl Sub<Jzazbz> for Jzazbz {
295    type Output = Jzazbz;
296
297    #[inline]
298    fn sub(self, rhs: Jzazbz) -> Self::Output {
299        Jzazbz::new(self.jz - rhs.jz, self.az - rhs.az, self.bz - rhs.bz)
300    }
301}
302
303impl Mul<Jzazbz> for Jzazbz {
304    type Output = Jzazbz;
305
306    #[inline]
307    fn mul(self, rhs: Jzazbz) -> Self::Output {
308        Jzazbz::new(self.jz * rhs.jz, self.az * rhs.az, self.bz * rhs.bz)
309    }
310}
311
312impl Div<Jzazbz> for Jzazbz {
313    type Output = Jzazbz;
314
315    #[inline]
316    fn div(self, rhs: Jzazbz) -> Self::Output {
317        Jzazbz::new(self.jz / rhs.jz, self.az / rhs.az, self.bz / rhs.bz)
318    }
319}
320
321impl AddAssign<Jzazbz> for Jzazbz {
322    #[inline]
323    fn add_assign(&mut self, rhs: Jzazbz) {
324        self.jz += rhs.jz;
325        self.az += rhs.az;
326        self.bz += rhs.bz;
327    }
328}
329
330impl SubAssign<Jzazbz> for Jzazbz {
331    #[inline]
332    fn sub_assign(&mut self, rhs: Jzazbz) {
333        self.jz -= rhs.jz;
334        self.az -= rhs.az;
335        self.bz -= rhs.bz;
336    }
337}
338
339impl MulAssign<Jzazbz> for Jzazbz {
340    #[inline]
341    fn mul_assign(&mut self, rhs: Jzazbz) {
342        self.jz *= rhs.jz;
343        self.az *= rhs.az;
344        self.bz *= rhs.bz;
345    }
346}
347
348impl DivAssign<Jzazbz> for Jzazbz {
349    #[inline]
350    fn div_assign(&mut self, rhs: Jzazbz) {
351        self.jz /= rhs.jz;
352        self.az /= rhs.az;
353        self.bz /= rhs.bz;
354    }
355}
356
357impl AddAssign<f32> for Jzazbz {
358    #[inline]
359    fn add_assign(&mut self, rhs: f32) {
360        self.jz += rhs;
361        self.az += rhs;
362        self.bz += rhs;
363    }
364}
365
366impl SubAssign<f32> for Jzazbz {
367    #[inline]
368    fn sub_assign(&mut self, rhs: f32) {
369        self.jz -= rhs;
370        self.az -= rhs;
371        self.bz -= rhs;
372    }
373}
374
375impl MulAssign<f32> for Jzazbz {
376    #[inline]
377    fn mul_assign(&mut self, rhs: f32) {
378        self.jz *= rhs;
379        self.az *= rhs;
380        self.bz *= rhs;
381    }
382}
383
384impl DivAssign<f32> for Jzazbz {
385    #[inline]
386    fn div_assign(&mut self, rhs: f32) {
387        self.jz /= rhs;
388        self.az /= rhs;
389        self.bz /= rhs;
390    }
391}
392
393impl Neg for Jzazbz {
394    type Output = Jzazbz;
395
396    #[inline]
397    fn neg(self) -> Self::Output {
398        Jzazbz::new(-self.jz, -self.az, -self.bz)
399    }
400}
401
402impl Jzazbz {
403    #[inline]
404    pub fn sqrt(&self) -> Jzazbz {
405        Jzazbz::new(
406            if self.jz < 0. { 0. } else { self.jz.sqrt() },
407            if self.az < 0. { 0. } else { self.az.sqrt() },
408            if self.bz < 0. { 0. } else { self.bz.sqrt() },
409        )
410    }
411
412    #[inline]
413    pub fn cbrt(&self) -> Jzazbz {
414        Jzazbz::new(self.jz.cbrt(), self.az.cbrt(), self.bz.cbrt())
415    }
416}
417
418impl Pow<f32> for Jzazbz {
419    type Output = Jzazbz;
420
421    #[inline]
422    fn pow(self, rhs: f32) -> Self::Output {
423        Jzazbz::new(self.jz.powf(rhs), self.az.powf(rhs), self.bz.powf(rhs))
424    }
425}
426
427impl Pow<Jzazbz> for Jzazbz {
428    type Output = Jzazbz;
429
430    #[inline]
431    fn pow(self, rhs: Jzazbz) -> Self::Output {
432        Jzazbz::new(
433            self.jz.powf(rhs.jz),
434            self.az.powf(self.az),
435            self.bz.powf(self.bz),
436        )
437    }
438}