1use 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)]
15pub struct Oklab {
17 pub l: f32,
19 pub a: f32,
21 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 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 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}