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