Coverage Report

Created: 2026-06-07 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.8.1/src/oklab.rs
Line
Count
Source
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
 */
7
use crate::Rgb;
8
use crate::mlaf::mlaf;
9
use num_traits::Pow;
10
use pxfm::{f_cbrtf, f_powf};
11
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
12
13
#[repr(C)]
14
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
15
/// Struct that represent *Oklab* colorspace
16
pub struct Oklab {
17
    /// All values in Oklab intended to be normalized \[0; 1\]
18
    pub l: f32,
19
    /// A value range \[-0.5; 0.5\]
20
    pub a: f32,
21
    /// B value range \[-0.5; 0.5\]
22
    pub b: f32,
23
}
24
25
impl Oklab {
26
    #[inline]
27
0
    pub const fn new(l: f32, a: f32, b: f32) -> Oklab {
28
0
        Oklab { l, a, b }
29
0
    }
30
31
    #[inline]
32
    /// Convert Linear Rgb to [Oklab]
33
0
    pub fn from_linear_rgb(rgb: Rgb<f32>) -> Oklab {
34
0
        Self::linear_rgb_to_oklab(rgb)
35
0
    }
36
37
    #[inline]
38
0
    fn linear_rgb_to_oklab(rgb: Rgb<f32>) -> Oklab {
39
0
        let l = mlaf(
40
0
            mlaf(0.4122214708f32 * rgb.r, 0.5363325363f32, rgb.g),
41
            0.0514459929f32,
42
0
            rgb.b,
43
        );
44
0
        let m = mlaf(
45
0
            mlaf(0.2119034982f32 * rgb.r, 0.6806995451f32, rgb.g),
46
            0.1073969566f32,
47
0
            rgb.b,
48
        );
49
0
        let s = mlaf(
50
0
            mlaf(0.0883024619f32 * rgb.r, 0.2817188376f32, rgb.g),
51
            0.6299787005f32,
52
0
            rgb.b,
53
        );
54
55
0
        let l_cone = f_cbrtf(l);
56
0
        let m_cone = f_cbrtf(m);
57
0
        let s_cone = f_cbrtf(s);
58
59
0
        Oklab {
60
0
            l: mlaf(
61
0
                mlaf(0.2104542553f32 * l_cone, 0.7936177850f32, m_cone),
62
0
                -0.0040720468f32,
63
0
                s_cone,
64
0
            ),
65
0
            a: mlaf(
66
0
                mlaf(1.9779984951f32 * l_cone, -2.4285922050f32, m_cone),
67
0
                0.4505937099f32,
68
0
                s_cone,
69
0
            ),
70
0
            b: mlaf(
71
0
                mlaf(0.0259040371f32 * l_cone, 0.7827717662f32, m_cone),
72
0
                -0.8086757660f32,
73
0
                s_cone,
74
0
            ),
75
0
        }
76
0
    }
77
78
    #[inline]
79
    /// Converts to linear RGB
80
0
    pub fn to_linear_rgb(&self) -> Rgb<f32> {
81
0
        let l_ = mlaf(
82
0
            mlaf(self.l, 0.3963377774f32, self.a),
83
            0.2158037573f32,
84
0
            self.b,
85
        );
86
0
        let m_ = mlaf(
87
0
            mlaf(self.l, -0.1055613458f32, self.a),
88
            -0.0638541728f32,
89
0
            self.b,
90
        );
91
0
        let s_ = mlaf(
92
0
            mlaf(self.l, -0.0894841775f32, self.a),
93
            -1.2914855480f32,
94
0
            self.b,
95
        );
96
97
0
        let l = l_ * l_ * l_;
98
0
        let m = m_ * m_ * m_;
99
0
        let s = s_ * s_ * s_;
100
101
0
        Rgb::new(
102
0
            mlaf(
103
0
                mlaf(4.0767416621f32 * l, -3.3077115913f32, m),
104
                0.2309699292f32,
105
0
                s,
106
            ),
107
0
            mlaf(
108
0
                mlaf(-1.2684380046f32 * l, 2.6097574011f32, m),
109
                -0.3413193965f32,
110
0
                s,
111
            ),
112
0
            mlaf(
113
0
                mlaf(-0.0041960863f32 * l, -0.7034186147f32, m),
114
                1.7076147010f32,
115
0
                s,
116
            ),
117
        )
118
0
    }
119
120
    #[inline]
121
0
    pub fn hybrid_distance(&self, other: Self) -> f32 {
122
0
        let lax = self.l - other.l;
123
0
        let dax = self.a - other.a;
124
0
        let bax = self.b - other.b;
125
0
        (dax * dax + bax * bax).sqrt() + lax.abs()
126
0
    }
127
}
128
129
impl Oklab {
130
0
    pub fn euclidean_distance(&self, other: Self) -> f32 {
131
0
        let lax = self.l - other.l;
132
0
        let dax = self.a - other.a;
133
0
        let bax = self.b - other.b;
134
0
        (lax * lax + dax * dax + bax * bax).sqrt()
135
0
    }
136
}
137
138
impl Oklab {
139
0
    pub fn taxicab_distance(&self, other: Self) -> f32 {
140
0
        let lax = self.l - other.l;
141
0
        let dax = self.a - other.a;
142
0
        let bax = self.b - other.b;
143
0
        lax.abs() + dax.abs() + bax.abs()
144
0
    }
145
}
146
147
impl Add<Oklab> for Oklab {
148
    type Output = Oklab;
149
150
    #[inline]
151
0
    fn add(self, rhs: Self) -> Oklab {
152
0
        Oklab::new(self.l + rhs.l, self.a + rhs.a, self.b + rhs.b)
153
0
    }
154
}
155
156
impl Add<f32> for Oklab {
157
    type Output = Oklab;
158
159
    #[inline]
160
0
    fn add(self, rhs: f32) -> Oklab {
161
0
        Oklab::new(self.l + rhs, self.a + rhs, self.b + rhs)
162
0
    }
163
}
164
165
impl AddAssign<Oklab> for Oklab {
166
    #[inline]
167
0
    fn add_assign(&mut self, rhs: Oklab) {
168
0
        self.l += rhs.l;
169
0
        self.a += rhs.a;
170
0
        self.b += rhs.b;
171
0
    }
172
}
173
174
impl AddAssign<f32> for Oklab {
175
    #[inline]
176
0
    fn add_assign(&mut self, rhs: f32) {
177
0
        self.l += rhs;
178
0
        self.a += rhs;
179
0
        self.b += rhs;
180
0
    }
181
}
182
183
impl Mul<f32> for Oklab {
184
    type Output = Oklab;
185
186
    #[inline]
187
0
    fn mul(self, rhs: f32) -> Self::Output {
188
0
        Oklab::new(self.l * rhs, self.a * rhs, self.b * rhs)
189
0
    }
190
}
191
192
impl Mul<Oklab> for Oklab {
193
    type Output = Oklab;
194
195
    #[inline]
196
0
    fn mul(self, rhs: Oklab) -> Self::Output {
197
0
        Oklab::new(self.l * rhs.l, self.a * rhs.a, self.b * rhs.b)
198
0
    }
199
}
200
201
impl MulAssign<f32> for Oklab {
202
    #[inline]
203
0
    fn mul_assign(&mut self, rhs: f32) {
204
0
        self.l *= rhs;
205
0
        self.a *= rhs;
206
0
        self.b *= rhs;
207
0
    }
208
}
209
210
impl MulAssign<Oklab> for Oklab {
211
    #[inline]
212
0
    fn mul_assign(&mut self, rhs: Oklab) {
213
0
        self.l *= rhs.l;
214
0
        self.a *= rhs.a;
215
0
        self.b *= rhs.b;
216
0
    }
217
}
218
219
impl Sub<f32> for Oklab {
220
    type Output = Oklab;
221
222
    #[inline]
223
0
    fn sub(self, rhs: f32) -> Self::Output {
224
0
        Oklab::new(self.l - rhs, self.a - rhs, self.b - rhs)
225
0
    }
226
}
227
228
impl Sub<Oklab> for Oklab {
229
    type Output = Oklab;
230
231
    #[inline]
232
0
    fn sub(self, rhs: Oklab) -> Self::Output {
233
0
        Oklab::new(self.l - rhs.l, self.a - rhs.a, self.b - rhs.b)
234
0
    }
235
}
236
237
impl SubAssign<f32> for Oklab {
238
    #[inline]
239
0
    fn sub_assign(&mut self, rhs: f32) {
240
0
        self.l -= rhs;
241
0
        self.a -= rhs;
242
0
        self.b -= rhs;
243
0
    }
244
}
245
246
impl SubAssign<Oklab> for Oklab {
247
    #[inline]
248
0
    fn sub_assign(&mut self, rhs: Oklab) {
249
0
        self.l -= rhs.l;
250
0
        self.a -= rhs.a;
251
0
        self.b -= rhs.b;
252
0
    }
253
}
254
255
impl Div<f32> for Oklab {
256
    type Output = Oklab;
257
258
    #[inline]
259
0
    fn div(self, rhs: f32) -> Self::Output {
260
0
        Oklab::new(self.l / rhs, self.a / rhs, self.b / rhs)
261
0
    }
262
}
263
264
impl Div<Oklab> for Oklab {
265
    type Output = Oklab;
266
267
    #[inline]
268
0
    fn div(self, rhs: Oklab) -> Self::Output {
269
0
        Oklab::new(self.l / rhs.l, self.a / rhs.a, self.b / rhs.b)
270
0
    }
271
}
272
273
impl DivAssign<f32> for Oklab {
274
    #[inline]
275
0
    fn div_assign(&mut self, rhs: f32) {
276
0
        self.l /= rhs;
277
0
        self.a /= rhs;
278
0
        self.b /= rhs;
279
0
    }
280
}
281
282
impl DivAssign<Oklab> for Oklab {
283
    #[inline]
284
0
    fn div_assign(&mut self, rhs: Oklab) {
285
0
        self.l /= rhs.l;
286
0
        self.a /= rhs.a;
287
0
        self.b /= rhs.b;
288
0
    }
289
}
290
291
impl Neg for Oklab {
292
    type Output = Oklab;
293
294
    #[inline]
295
0
    fn neg(self) -> Self::Output {
296
0
        Oklab::new(-self.l, -self.a, -self.b)
297
0
    }
298
}
299
300
impl Pow<f32> for Oklab {
301
    type Output = Oklab;
302
303
    #[inline]
304
0
    fn pow(self, rhs: f32) -> Self::Output {
305
0
        Oklab::new(
306
0
            f_powf(self.l, rhs),
307
0
            f_powf(self.a, rhs),
308
0
            f_powf(self.b, rhs),
309
        )
310
0
    }
311
}
312
313
impl Pow<Oklab> for Oklab {
314
    type Output = Oklab;
315
316
    #[inline]
317
0
    fn pow(self, rhs: Oklab) -> Self::Output {
318
0
        Oklab::new(
319
0
            f_powf(self.l, rhs.l),
320
0
            f_powf(self.a, rhs.a),
321
0
            f_powf(self.b, rhs.b),
322
        )
323
0
    }
324
}
325
326
impl Oklab {
327
    #[inline]
328
0
    pub fn sqrt(&self) -> Oklab {
329
0
        Oklab::new(self.l.sqrt(), self.a.sqrt(), self.b.sqrt())
330
0
    }
331
332
    #[inline]
333
0
    pub fn cbrt(&self) -> Oklab {
334
0
        Oklab::new(f_cbrtf(self.l), f_cbrtf(self.a), f_cbrtf(self.b))
335
0
    }
336
}
337
338
#[cfg(test)]
339
mod tests {
340
    use super::*;
341
342
    #[test]
343
    fn round_trip() {
344
        let xyz = Rgb::new(0.1, 0.2, 0.3);
345
        let lab = Oklab::from_linear_rgb(xyz);
346
        let rolled_back = lab.to_linear_rgb();
347
        let dx = (xyz.r - rolled_back.r).abs();
348
        let dy = (xyz.g - rolled_back.g).abs();
349
        let dz = (xyz.b - rolled_back.b).abs();
350
        assert!(dx < 1e-5);
351
        assert!(dy < 1e-5);
352
        assert!(dz < 1e-5);
353
    }
354
}