Coverage Report

Created: 2025-12-20 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.7.11/src/jzazbz.rs
Line
Count
Source
1
/*
2
 * // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
3
 * //
4
 * // Redistribution and use in source and binary forms, with or without modification,
5
 * // are permitted provided that the following conditions are met:
6
 * //
7
 * // 1.  Redistributions of source code must retain the above copyright notice, this
8
 * // list of conditions and the following disclaimer.
9
 * //
10
 * // 2.  Redistributions in binary form must reproduce the above copyright notice,
11
 * // this list of conditions and the following disclaimer in the documentation
12
 * // and/or other materials provided with the distribution.
13
 * //
14
 * // 3.  Neither the name of the copyright holder nor the names of its
15
 * // contributors may be used to endorse or promote products derived from
16
 * // this software without specific prior written permission.
17
 * //
18
 * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
 * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
 * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
 * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
 * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
 * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
 * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 */
29
use crate::Xyz;
30
use crate::jzczhz::Jzczhz;
31
use crate::mlaf::mlaf;
32
use num_traits::Pow;
33
use pxfm::{dirty_powf, f_cbrtf, f_powf};
34
use std::ops::{
35
    Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
36
};
37
38
#[inline]
39
0
fn perceptual_quantizer(x: f32) -> f32 {
40
0
    if x <= 0. {
41
0
        return 0.;
42
0
    }
43
0
    let xx = dirty_powf(x * 1e-4, 0.1593017578125);
44
0
    let rs = dirty_powf(
45
0
        mlaf(0.8359375, 18.8515625, xx) / mlaf(1., 18.6875, xx),
46
        134.034375,
47
    );
48
0
    if rs.is_nan() {
49
0
        return 0.;
50
0
    }
51
0
    rs
52
0
}
53
54
#[inline]
55
0
fn perceptual_quantizer_inverse(x: f32) -> f32 {
56
0
    if x <= 0. {
57
0
        return 0.;
58
0
    }
59
0
    let xx = dirty_powf(x, 7.460772656268214e-03);
60
0
    let rs = 1e4
61
0
        * dirty_powf(
62
0
            (0.8359375 - xx) / mlaf(-18.8515625, 18.6875, xx),
63
0
            6.277394636015326,
64
0
        );
65
0
    if rs.is_nan() {
66
0
        return 0.;
67
0
    }
68
0
    rs
69
0
}
70
71
#[repr(C)]
72
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Default)]
73
/// Represents Jzazbz
74
pub struct Jzazbz {
75
    /// Jz(lightness) generally expects to be between `0.0..1.0`.
76
    pub jz: f32,
77
    /// Az generally expects to be between `-0.5..0.5`.
78
    pub az: f32,
79
    /// Bz generally expects to be between `-0.5..0.5`.
80
    pub bz: f32,
81
}
82
83
impl Jzazbz {
84
    /// Constructs new instance
85
    #[inline]
86
0
    pub fn new(jz: f32, az: f32, bz: f32) -> Jzazbz {
87
0
        Jzazbz { jz, az, bz }
88
0
    }
89
90
    /// Creates new [Jzazbz] from CIE [Xyz].
91
    ///
92
    /// JzAzBz is defined in D65 white point, adapt XYZ if needed first.
93
    #[inline]
94
0
    pub fn from_xyz(xyz: Xyz) -> Jzazbz {
95
0
        Self::from_xyz_with_display_luminance(xyz, 200.)
96
0
    }
97
98
    /// Creates new [Jzazbz] from CIE [Xyz].
99
    ///
100
    /// JzAzBz is defined in D65 white point, adapt XYZ if needed first.
101
    #[inline]
102
0
    pub fn from_xyz_with_display_luminance(xyz: Xyz, display_luminance: f32) -> Jzazbz {
103
0
        let abs_xyz = xyz * display_luminance;
104
0
        let lp = perceptual_quantizer(mlaf(
105
0
            mlaf(0.674207838 * abs_xyz.x, 0.382799340, abs_xyz.y),
106
            -0.047570458,
107
0
            abs_xyz.z,
108
        ));
109
0
        let mp = perceptual_quantizer(mlaf(
110
0
            mlaf(0.149284160 * abs_xyz.x, 0.739628340, abs_xyz.y),
111
            0.083327300,
112
0
            abs_xyz.z,
113
        ));
114
0
        let sp = perceptual_quantizer(mlaf(
115
0
            mlaf(0.070941080 * abs_xyz.x, 0.174768000, abs_xyz.y),
116
            0.670970020,
117
0
            abs_xyz.z,
118
        ));
119
0
        let iz = 0.5 * (lp + mp);
120
0
        let az = mlaf(mlaf(3.524000 * lp, -4.066708, mp), 0.542708, sp);
121
0
        let bz = mlaf(mlaf(0.199076 * lp, 1.096799, mp), -1.295875, sp);
122
0
        let jz = (0.44 * iz) / mlaf(1., -0.56, iz) - 1.6295499532821566e-11;
123
0
        Jzazbz::new(jz, az, bz)
124
0
    }
125
126
    /// Converts [Jzazbz] to [Xyz] D65
127
    #[inline]
128
0
    pub fn to_xyz(&self, display_luminance: f32) -> Xyz {
129
0
        let jz = self.jz + 1.6295499532821566e-11;
130
131
0
        let iz = jz / mlaf(0.44f32, 0.56, jz);
132
0
        let l = perceptual_quantizer_inverse(mlaf(
133
0
            mlaf(iz, 1.386050432715393e-1, self.az),
134
            5.804731615611869e-2,
135
0
            self.bz,
136
        ));
137
0
        let m = perceptual_quantizer_inverse(mlaf(
138
0
            mlaf(iz, -1.386050432715393e-1, self.az),
139
            -5.804731615611891e-2,
140
0
            self.bz,
141
        ));
142
0
        let s = perceptual_quantizer_inverse(mlaf(
143
0
            mlaf(iz, -9.601924202631895e-2, self.az),
144
            -8.118918960560390e-1,
145
0
            self.bz,
146
        ));
147
0
        let x = mlaf(
148
0
            mlaf(1.661373055774069e+00 * l, -9.145230923250668e-01, m),
149
            2.313620767186147e-01,
150
0
            s,
151
        );
152
0
        let y = mlaf(
153
0
            mlaf(-3.250758740427037e-01 * l, 1.571847038366936e+00, m),
154
            -2.182538318672940e-01,
155
0
            s,
156
        );
157
0
        let z = mlaf(
158
0
            mlaf(-9.098281098284756e-02 * l, -3.127282905230740e-01, m),
159
            1.522766561305260e+00,
160
0
            s,
161
        );
162
0
        let rel_luminance = 1f32 / display_luminance;
163
0
        Xyz::new(x, y, z) * rel_luminance
164
0
    }
165
166
    /// Converts into *Jzczhz*
167
    #[inline]
168
0
    pub fn to_jzczhz(&self) -> Jzczhz {
169
0
        Jzczhz::from_jzazbz(*self)
170
0
    }
171
172
    #[inline]
173
0
    pub fn euclidean_distance(&self, other: Self) -> f32 {
174
0
        let djz = self.jz - other.jz;
175
0
        let daz = self.az - other.az;
176
0
        let dbz = self.bz - other.bz;
177
0
        (djz * djz + daz * daz + dbz * dbz).sqrt()
178
0
    }
179
180
    #[inline]
181
0
    pub fn taxicab_distance(&self, other: Self) -> f32 {
182
0
        let djz = self.jz - other.jz;
183
0
        let daz = self.az - other.az;
184
0
        let dbz = self.bz - other.bz;
185
0
        djz.abs() + daz.abs() + dbz.abs()
186
0
    }
187
}
188
189
impl Index<usize> for Jzazbz {
190
    type Output = f32;
191
192
    #[inline]
193
0
    fn index(&self, index: usize) -> &f32 {
194
0
        match index {
195
0
            0 => &self.jz,
196
0
            1 => &self.az,
197
0
            2 => &self.bz,
198
0
            _ => panic!("Index out of bounds for Jzazbz"),
199
        }
200
0
    }
201
}
202
203
impl IndexMut<usize> for Jzazbz {
204
    #[inline]
205
0
    fn index_mut(&mut self, index: usize) -> &mut f32 {
206
0
        match index {
207
0
            0 => &mut self.jz,
208
0
            1 => &mut self.az,
209
0
            2 => &mut self.bz,
210
0
            _ => panic!("Index out of bounds for Jzazbz"),
211
        }
212
0
    }
213
}
214
215
impl Add<f32> for Jzazbz {
216
    type Output = Jzazbz;
217
218
    #[inline]
219
0
    fn add(self, rhs: f32) -> Self::Output {
220
0
        Jzazbz::new(self.jz + rhs, self.az + rhs, self.bz + rhs)
221
0
    }
222
}
223
224
impl Sub<f32> for Jzazbz {
225
    type Output = Jzazbz;
226
227
    #[inline]
228
0
    fn sub(self, rhs: f32) -> Self::Output {
229
0
        Jzazbz::new(self.jz - rhs, self.az - rhs, self.bz - rhs)
230
0
    }
231
}
232
233
impl Mul<f32> for Jzazbz {
234
    type Output = Jzazbz;
235
236
    #[inline]
237
0
    fn mul(self, rhs: f32) -> Self::Output {
238
0
        Jzazbz::new(self.jz * rhs, self.az * rhs, self.bz * rhs)
239
0
    }
240
}
241
242
impl Div<f32> for Jzazbz {
243
    type Output = Jzazbz;
244
245
    #[inline]
246
0
    fn div(self, rhs: f32) -> Self::Output {
247
0
        Jzazbz::new(self.jz / rhs, self.az / rhs, self.bz / rhs)
248
0
    }
249
}
250
251
impl Add<Jzazbz> for Jzazbz {
252
    type Output = Jzazbz;
253
254
    #[inline]
255
0
    fn add(self, rhs: Jzazbz) -> Self::Output {
256
0
        Jzazbz::new(self.jz + rhs.jz, self.az + rhs.az, self.bz + rhs.bz)
257
0
    }
258
}
259
260
impl Sub<Jzazbz> for Jzazbz {
261
    type Output = Jzazbz;
262
263
    #[inline]
264
0
    fn sub(self, rhs: Jzazbz) -> Self::Output {
265
0
        Jzazbz::new(self.jz - rhs.jz, self.az - rhs.az, self.bz - rhs.bz)
266
0
    }
267
}
268
269
impl Mul<Jzazbz> for Jzazbz {
270
    type Output = Jzazbz;
271
272
    #[inline]
273
0
    fn mul(self, rhs: Jzazbz) -> Self::Output {
274
0
        Jzazbz::new(self.jz * rhs.jz, self.az * rhs.az, self.bz * rhs.bz)
275
0
    }
276
}
277
278
impl Div<Jzazbz> for Jzazbz {
279
    type Output = Jzazbz;
280
281
    #[inline]
282
0
    fn div(self, rhs: Jzazbz) -> Self::Output {
283
0
        Jzazbz::new(self.jz / rhs.jz, self.az / rhs.az, self.bz / rhs.bz)
284
0
    }
285
}
286
287
impl AddAssign<Jzazbz> for Jzazbz {
288
    #[inline]
289
0
    fn add_assign(&mut self, rhs: Jzazbz) {
290
0
        self.jz += rhs.jz;
291
0
        self.az += rhs.az;
292
0
        self.bz += rhs.bz;
293
0
    }
294
}
295
296
impl SubAssign<Jzazbz> for Jzazbz {
297
    #[inline]
298
0
    fn sub_assign(&mut self, rhs: Jzazbz) {
299
0
        self.jz -= rhs.jz;
300
0
        self.az -= rhs.az;
301
0
        self.bz -= rhs.bz;
302
0
    }
303
}
304
305
impl MulAssign<Jzazbz> for Jzazbz {
306
    #[inline]
307
0
    fn mul_assign(&mut self, rhs: Jzazbz) {
308
0
        self.jz *= rhs.jz;
309
0
        self.az *= rhs.az;
310
0
        self.bz *= rhs.bz;
311
0
    }
312
}
313
314
impl DivAssign<Jzazbz> for Jzazbz {
315
    #[inline]
316
0
    fn div_assign(&mut self, rhs: Jzazbz) {
317
0
        self.jz /= rhs.jz;
318
0
        self.az /= rhs.az;
319
0
        self.bz /= rhs.bz;
320
0
    }
321
}
322
323
impl AddAssign<f32> for Jzazbz {
324
    #[inline]
325
0
    fn add_assign(&mut self, rhs: f32) {
326
0
        self.jz += rhs;
327
0
        self.az += rhs;
328
0
        self.bz += rhs;
329
0
    }
330
}
331
332
impl SubAssign<f32> for Jzazbz {
333
    #[inline]
334
0
    fn sub_assign(&mut self, rhs: f32) {
335
0
        self.jz -= rhs;
336
0
        self.az -= rhs;
337
0
        self.bz -= rhs;
338
0
    }
339
}
340
341
impl MulAssign<f32> for Jzazbz {
342
    #[inline]
343
0
    fn mul_assign(&mut self, rhs: f32) {
344
0
        self.jz *= rhs;
345
0
        self.az *= rhs;
346
0
        self.bz *= rhs;
347
0
    }
348
}
349
350
impl DivAssign<f32> for Jzazbz {
351
    #[inline]
352
0
    fn div_assign(&mut self, rhs: f32) {
353
0
        self.jz /= rhs;
354
0
        self.az /= rhs;
355
0
        self.bz /= rhs;
356
0
    }
357
}
358
359
impl Neg for Jzazbz {
360
    type Output = Jzazbz;
361
362
    #[inline]
363
0
    fn neg(self) -> Self::Output {
364
0
        Jzazbz::new(-self.jz, -self.az, -self.bz)
365
0
    }
366
}
367
368
impl Jzazbz {
369
    #[inline]
370
0
    pub fn sqrt(&self) -> Jzazbz {
371
0
        Jzazbz::new(self.jz.sqrt(), self.az.sqrt(), self.bz.sqrt())
372
0
    }
373
374
    #[inline]
375
0
    pub fn cbrt(&self) -> Jzazbz {
376
0
        Jzazbz::new(f_cbrtf(self.jz), f_cbrtf(self.az), f_cbrtf(self.bz))
377
0
    }
378
}
379
380
impl Pow<f32> for Jzazbz {
381
    type Output = Jzazbz;
382
383
    #[inline]
384
0
    fn pow(self, rhs: f32) -> Self::Output {
385
0
        Jzazbz::new(
386
0
            f_powf(self.jz, rhs),
387
0
            f_powf(self.az, rhs),
388
0
            f_powf(self.bz, rhs),
389
        )
390
0
    }
391
}
392
393
impl Pow<Jzazbz> for Jzazbz {
394
    type Output = Jzazbz;
395
396
    #[inline]
397
0
    fn pow(self, rhs: Jzazbz) -> Self::Output {
398
0
        Jzazbz::new(
399
0
            f_powf(self.jz, rhs.jz),
400
0
            f_powf(self.az, self.az),
401
0
            f_powf(self.bz, self.bz),
402
        )
403
0
    }
404
}
405
406
#[cfg(test)]
407
mod tests {
408
    use super::*;
409
410
    #[test]
411
    fn jzazbz_round() {
412
        let xyz = Xyz::new(0.5, 0.4, 0.3);
413
        let jzazbz = Jzazbz::from_xyz_with_display_luminance(xyz, 253f32);
414
        let old_xyz = jzazbz.to_xyz(253f32);
415
        assert!(
416
            (xyz.x - old_xyz.x).abs() <= 1e-3,
417
            "{:?} != {:?}",
418
            xyz,
419
            old_xyz
420
        );
421
        assert!(
422
            (xyz.y - old_xyz.y).abs() <= 1e-3,
423
            "{:?} != {:?}",
424
            xyz,
425
            old_xyz
426
        );
427
        assert!(
428
            (xyz.z - old_xyz.z).abs() <= 1e-3,
429
            "{:?} != {:?}",
430
            xyz,
431
            old_xyz
432
        );
433
    }
434
}