Coverage Report

Created: 2025-10-10 07:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.7.6/src/dt_ucs.rs
Line
Count
Source
1
/*
2
 * // Copyright (c) Radzivon Bartoshyk 6/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::mlaf::mlaf;
31
use pxfm::{f_atan2f, f_powf, f_sincosf};
32
33
/// Darktable UCS JCH ( Darktable Uniform Color Space )
34
#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
35
pub struct DtUchJch {
36
    pub j: f32,
37
    pub c: f32,
38
    pub h: f32,
39
}
40
41
/// Darktable UCS HSB ( Darktable Uniform Color Space )
42
#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
43
pub struct DtUchHsb {
44
    pub h: f32,
45
    pub s: f32,
46
    pub b: f32,
47
}
48
49
/// Darktable HCB ( Darktable Uniform Color Space )
50
#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
51
pub struct DtUchHcb {
52
    pub h: f32,
53
    pub c: f32,
54
    pub b: f32,
55
}
56
57
const DT_UCS_L_STAR_RANGE: f32 = 2.098883786377;
58
59
#[inline]
60
0
fn y_to_dt_ucs_l_star(y: f32) -> f32 {
61
0
    let y_hat = f_powf(y, 0.631651345306265);
62
0
    DT_UCS_L_STAR_RANGE * y_hat / (y_hat + 1.12426773749357)
63
0
}
64
65
#[inline]
66
0
fn dt_ucs_l_star_to_y(x: f32) -> f32 {
67
0
    f_powf(
68
0
        1.12426773749357 * x / (DT_UCS_L_STAR_RANGE - x),
69
        1.5831518565279648,
70
    )
71
0
}
72
73
const L_WHITE: f32 = 0.98805060;
74
75
#[inline]
76
0
fn dt_ucs_luv_to_ucs_jch(
77
0
    l_star: f32,
78
0
    l_white: f32,
79
0
    u_star_prime: f32,
80
0
    v_star_prime: f32,
81
0
) -> DtUchJch {
82
0
    let m2: f32 = mlaf(u_star_prime * u_star_prime, v_star_prime, v_star_prime); // square of colorfulness M
83
84
    // should be JCH[0] = powf(L_star / L_white), cz) but we treat only the case where cz = 1
85
0
    let j = l_star / l_white;
86
0
    let c =
87
0
        15.932993652962535 * f_powf(l_star, 0.6523997524738018) * f_powf(m2, 0.6007557017508491)
88
0
            / l_white;
89
0
    let h = f_atan2f(v_star_prime, u_star_prime);
90
0
    DtUchJch::new(j, c, h)
91
0
}
92
93
#[inline]
94
0
fn dt_ucs_xy_to_uv(x: f32, y: f32) -> (f32, f32) {
95
    const X_C: [f32; 3] = [-0.783941002840055, 0.745273540913283, 0.318707282433486];
96
    const Y_C: [f32; 3] = [0.277512987809202, -0.205375866083878, 2.16743692732158];
97
    const BIAS: [f32; 3] = [0.153836578598858, -0.165478376301988, 0.291320554395942];
98
99
0
    let mut u_c = mlaf(mlaf(BIAS[0], Y_C[0], y), X_C[0], x);
100
0
    let mut v_c = mlaf(mlaf(BIAS[1], Y_C[1], y), X_C[1], x);
101
0
    let d_c = mlaf(mlaf(BIAS[2], Y_C[2], y), X_C[2], x);
102
103
0
    let div = if d_c >= 0.0 {
104
0
        d_c.max(f32::MIN)
105
    } else {
106
0
        d_c.min(-f32::MIN)
107
    };
108
0
    u_c /= div;
109
0
    v_c /= div;
110
111
    const STAR_C: [f32; 2] = [1.39656225667, 1.4513954287];
112
    const STAR_HF_C: [f32; 2] = [1.49217352929, 1.52488637914];
113
114
0
    let u_star = STAR_C[0] * u_c / (u_c.abs() + STAR_HF_C[0]);
115
0
    let v_star = STAR_C[1] * v_c / (v_c.abs() + STAR_HF_C[1]);
116
117
    // The following is equivalent to a 2D matrix product
118
0
    let u_star_prime = mlaf(-1.124983854323892 * u_star, -0.980483721769325, v_star);
119
0
    let v_star_prime = mlaf(1.86323315098672 * u_star, 1.971853092390862, v_star);
120
0
    (u_star_prime, v_star_prime)
121
0
}
122
123
impl DtUchJch {
124
    #[inline]
125
0
    pub fn new(j: f32, c: f32, h: f32) -> DtUchJch {
126
0
        DtUchJch { j, c, h }
127
0
    }
128
129
    #[inline]
130
0
    pub fn from_xyz(xyz: Xyz) -> DtUchJch {
131
0
        DtUchJch::from_xyy(xyz.to_xyy())
132
0
    }
133
134
    #[inline]
135
0
    pub fn to_xyz(&self) -> Xyz {
136
0
        let xyy = self.to_xyy();
137
0
        Xyz::from_xyy(xyy)
138
0
    }
139
140
    #[inline]
141
0
    pub fn from_xyy(xyy: [f32; 3]) -> DtUchJch {
142
0
        let l_star = y_to_dt_ucs_l_star(xyy[2]);
143
        // let l_white = y_to_dt_ucs_l_star(1.);
144
145
0
        let (u_star_prime, v_star_prime) = dt_ucs_xy_to_uv(xyy[0], xyy[1]);
146
0
        dt_ucs_luv_to_ucs_jch(l_star, L_WHITE, u_star_prime, v_star_prime)
147
0
    }
148
149
    #[inline]
150
0
    pub fn to_xyy(&self) -> [f32; 3] {
151
        // let l_white: f32 = y_to_dt_ucs_l_star(1.0);
152
0
        let l_star = (self.j * L_WHITE).max(0.0).min(2.09885);
153
0
        let m = if l_star != 0. {
154
0
            f_powf(
155
0
                self.c * L_WHITE / (15.932993652962535 * f_powf(l_star, 0.6523997524738018)),
156
                0.8322850678616855,
157
            )
158
        } else {
159
0
            0.
160
        };
161
162
0
        let sin_cos_h = f_sincosf(self.h);
163
0
        let u_star_prime = m * sin_cos_h.1;
164
0
        let v_star_prime = m * sin_cos_h.0;
165
166
        // The following is equivalent to a 2D matrix product
167
0
        let u_star = mlaf(
168
0
            -5.037522385190711 * u_star_prime,
169
            -2.504856328185843,
170
0
            v_star_prime,
171
        );
172
0
        let v_star = mlaf(
173
0
            4.760029407436461 * u_star_prime,
174
            2.874012963239247,
175
0
            v_star_prime,
176
        );
177
178
        const F: [f32; 2] = [1.39656225667, 1.4513954287];
179
        const HF: [f32; 2] = [1.49217352929, 1.52488637914];
180
181
0
        let u_c = -HF[0] * u_star / (u_star.abs() - F[0]);
182
0
        let v_c = -HF[1] * v_star / (v_star.abs() - F[1]);
183
184
        const U_C: [f32; 3] = [0.167171472114775, -0.150959086409163, 0.940254742367256];
185
        const V_C: [f32; 3] = [0.141299802443708, -0.155185060382272, 1.000000000000000];
186
        const BIAS: [f32; 3] = [
187
            -0.00801531300850582,
188
            -0.00843312433578007,
189
            -0.0256325967652889,
190
        ];
191
192
0
        let mut x = mlaf(mlaf(BIAS[0], V_C[0], v_c), U_C[0], u_c);
193
0
        let mut y = mlaf(mlaf(BIAS[1], V_C[1], v_c), U_C[1], u_c);
194
0
        let d = mlaf(mlaf(BIAS[2], V_C[2], v_c), U_C[2], u_c);
195
196
0
        let div = if d >= 0.0 {
197
0
            d.max(f32::MIN)
198
        } else {
199
0
            d.min(-f32::MIN)
200
        };
201
0
        x /= div;
202
0
        y /= div;
203
0
        let yb = dt_ucs_l_star_to_y(l_star);
204
0
        [x, y, yb]
205
0
    }
206
}
207
208
impl DtUchHsb {
209
    #[inline]
210
0
    pub fn new(h: f32, s: f32, b: f32) -> DtUchHsb {
211
0
        DtUchHsb { h, s, b }
212
0
    }
213
214
    #[inline]
215
0
    pub fn from_jch(jch: DtUchJch) -> DtUchHsb {
216
0
        let b = jch.j * (f_powf(jch.c, 1.33654221029386) + 1.);
217
0
        let s = if b > 0. { jch.c / b } else { 0. };
218
0
        let h = jch.h;
219
0
        DtUchHsb::new(h, s, b)
220
0
    }
221
222
    #[inline]
223
0
    pub fn to_jch(&self) -> DtUchJch {
224
0
        let h = self.h;
225
0
        let c = self.s * self.b;
226
0
        let j = self.b / (f_powf(c, 1.33654221029386) + 1.);
227
0
        DtUchJch::new(j, c, h)
228
0
    }
229
}
230
231
impl DtUchHcb {
232
    #[inline]
233
0
    pub fn new(h: f32, c: f32, b: f32) -> DtUchHcb {
234
0
        DtUchHcb { h, c, b }
235
0
    }
236
237
    #[inline]
238
0
    pub fn from_jch(jch: DtUchJch) -> DtUchHcb {
239
0
        let b = jch.j * (f_powf(jch.c, 1.33654221029386) + 1.);
240
0
        let c = jch.c;
241
0
        let h = jch.h;
242
0
        DtUchHcb::new(h, c, b)
243
0
    }
244
245
    #[inline]
246
0
    pub fn to_jch(&self) -> DtUchJch {
247
0
        let h = self.h;
248
0
        let c = self.c;
249
0
        let j = self.b / (f_powf(self.c, 1.33654221029386) + 1.);
250
0
        DtUchJch::new(j, c, h)
251
0
    }
252
}
253
254
#[cfg(test)]
255
mod tests {
256
    use super::*;
257
258
    #[test]
259
    fn test_darktable_ucs_jch() {
260
        let xyy = [0.4, 0.2, 0.5];
261
        let ucs = DtUchJch::from_xyy(xyy);
262
        let xyy_rev = ucs.to_xyy();
263
        assert!(
264
            (xyy[0] - xyy_rev[0]).abs() < 1e-5,
265
            "Expected {}, got {}",
266
            xyy[0],
267
            xyy_rev[0]
268
        );
269
        assert!(
270
            (xyy[1] - xyy_rev[1]).abs() < 1e-5,
271
            "Expected {}, got {}",
272
            xyy[1],
273
            xyy_rev[1]
274
        );
275
        assert!(
276
            (xyy[2] - xyy_rev[2]).abs() < 1e-5,
277
            "Expected {}, got {}",
278
            xyy[2],
279
            xyy_rev[2]
280
        );
281
    }
282
283
    #[test]
284
    fn test_darktable_hsb() {
285
        let jch = DtUchJch::new(0.3, 0.6, 0.4);
286
        let hsb = DtUchHsb::from_jch(jch);
287
        let r_jch = hsb.to_jch();
288
289
        assert!(
290
            (r_jch.j - jch.j).abs() < 1e-5,
291
            "Expected {}, got {}",
292
            jch.j,
293
            r_jch.j
294
        );
295
        assert!(
296
            (r_jch.c - jch.c).abs() < 1e-5,
297
            "Expected {}, got {}",
298
            jch.c,
299
            r_jch.c
300
        );
301
        assert!(
302
            (r_jch.h - jch.h).abs() < 1e-5,
303
            "Expected {}, got {}",
304
            jch.h,
305
            r_jch.h
306
        );
307
    }
308
309
    #[test]
310
    fn test_darktable_hcb() {
311
        let jch = DtUchJch::new(0.3, 0.6, 0.4);
312
        let hcb = DtUchHcb::from_jch(jch);
313
        let r_jch = hcb.to_jch();
314
315
        assert!(
316
            (r_jch.j - jch.j).abs() < 1e-5,
317
            "Expected {}, got {}",
318
            jch.j,
319
            r_jch.j
320
        );
321
        assert!(
322
            (r_jch.c - jch.c).abs() < 1e-5,
323
            "Expected {}, got {}",
324
            jch.c,
325
            r_jch.c
326
        );
327
        assert!(
328
            (r_jch.h - jch.h).abs() < 1e-5,
329
            "Expected {}, got {}",
330
            jch.h,
331
            r_jch.h
332
        );
333
    }
334
335
    #[test]
336
    fn test_darktable_ucs_jch_from_xyz() {
337
        let xyz = Xyz::new(0.4, 0.2, 0.5);
338
        let ucs = DtUchJch::from_xyz(xyz);
339
        let xyy_rev = ucs.to_xyz();
340
        assert!(
341
            (xyz.x - xyz.x).abs() < 1e-5,
342
            "Expected {}, got {}",
343
            xyz.x,
344
            xyy_rev.x
345
        );
346
        assert!(
347
            (xyz.y - xyz.y).abs() < 1e-5,
348
            "Expected {}, got {}",
349
            xyz.y,
350
            xyy_rev.y
351
        );
352
        assert!(
353
            (xyz.z - xyz.z).abs() < 1e-5,
354
            "Expected {}, got {}",
355
            xyz.z,
356
            xyy_rev.z
357
        );
358
    }
359
}