1#![allow(clippy::excessive_precision)]
8use crate::utils::mlaf;
9use crate::{EuclideanDistance, Rgb, TaxicabDistance, TransferFunction};
10use num_traits::Pow;
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 fn new(l: f32, a: f32, b: f32) -> Oklab {
28 Oklab { l, a, b }
29 }
30
31 #[inline]
32 pub fn from_srgb(rgb: Rgb<u8>) -> Oklab {
34 let rgb_float = rgb.to_rgb_f32();
35 let linearized = rgb_float.linearize(TransferFunction::Srgb);
36 Self::linear_rgb_to_oklab(linearized)
37 }
38
39 #[inline]
40 pub fn from_rgb(rgb: Rgb<u8>, transfer_function: TransferFunction) -> Oklab {
42 let rgb_float = rgb.to_rgb_f32();
43 let linearized = rgb_float.linearize(transfer_function);
44 Self::linear_rgb_to_oklab(linearized)
45 }
46
47 #[inline]
48 pub fn from_linear_rgb(rgb: Rgb<f32>) -> Oklab {
50 Self::linear_rgb_to_oklab(rgb)
51 }
52
53 #[inline]
54 pub fn to_srgb(&self) -> Rgb<u8> {
56 let linear_rgb = self.to_linear_rgb();
57 let transferred = linear_rgb.gamma(TransferFunction::Srgb);
58 transferred.to_u8()
59 }
60
61 #[inline]
62 pub fn to_rgb(&self, transfer_function: TransferFunction) -> Rgb<u8> {
64 let linear_rgb = self.to_linear_rgb();
65 let transferred = linear_rgb.gamma(transfer_function);
66 transferred.to_u8()
67 }
68
69 #[inline]
70 pub fn to_srgb_f32(&self) -> Rgb<f32> {
72 let linear_rgb = self.to_linear_rgb();
73 linear_rgb.gamma(TransferFunction::Srgb)
74 }
75
76 #[inline]
77 pub fn to_rgb_f32(&self, transfer_function: TransferFunction) -> Rgb<f32> {
79 let linear_rgb = self.to_linear_rgb();
80 linear_rgb.gamma(transfer_function)
81 }
82
83 #[inline]
84 fn linear_rgb_to_oklab(rgb: Rgb<f32>) -> Oklab {
85 let l = mlaf(
86 mlaf(0.4122214708f32 * rgb.r, 0.5363325363f32, rgb.g),
87 0.0514459929f32,
88 rgb.b,
89 );
90 let m = mlaf(
91 mlaf(0.2119034982f32 * rgb.r, 0.6806995451f32, rgb.g),
92 0.1073969566f32,
93 rgb.b,
94 );
95 let s = mlaf(
96 mlaf(0.0883024619f32 * rgb.r, 0.2817188376f32, rgb.g),
97 0.6299787005f32,
98 rgb.b,
99 );
100
101 let l_ = l.cbrt();
102 let m_ = m.cbrt();
103 let s_ = s.cbrt();
104
105 Oklab {
106 l: mlaf(
107 mlaf(0.2104542553f32 * l_, 0.7936177850f32, m_),
108 -0.0040720468f32,
109 s_,
110 ),
111 a: mlaf(
112 mlaf(1.9779984951f32 * l_, -2.4285922050f32, m_),
113 0.4505937099f32,
114 s_,
115 ),
116 b: mlaf(
117 mlaf(0.0259040371f32 * l_, 0.7827717662f32, m_),
118 -0.8086757660f32,
119 s_,
120 ),
121 }
122 }
123
124 #[inline]
125 pub fn to_linear_rgb(&self) -> Rgb<f32> {
127 let l_ = mlaf(
128 mlaf(self.l, 0.3963377774f32, self.a),
129 0.2158037573f32,
130 self.b,
131 );
132 let m_ = mlaf(
133 mlaf(self.l, -0.1055613458f32, self.a),
134 -0.0638541728f32,
135 self.b,
136 );
137 let s_ = mlaf(
138 mlaf(self.l, -0.0894841775f32, self.a),
139 -1.2914855480f32,
140 self.b,
141 );
142
143 let l = l_ * l_ * l_;
144 let m = m_ * m_ * m_;
145 let s = s_ * s_ * s_;
146
147 Rgb::new(
148 mlaf(
149 mlaf(4.0767416621f32 * l, -3.3077115913f32, m),
150 0.2309699292f32,
151 s,
152 ),
153 mlaf(
154 mlaf(-1.2684380046f32 * l, 2.6097574011f32, m),
155 -0.3413193965f32,
156 s,
157 ),
158 mlaf(
159 mlaf(-0.0041960863f32 * l, -0.7034186147f32, m),
160 1.7076147010f32,
161 s,
162 ),
163 )
164 }
165
166 #[inline]
167 pub fn hybrid_distance(&self, other: Self) -> f32 {
168 let lax = self.l - other.l;
169 let dax = self.a - other.a;
170 let bax = self.b - other.b;
171 (dax * dax + bax * bax).sqrt() + lax.abs()
172 }
173
174 pub const fn l_range() -> (f32, f32) {
175 (0., 1.)
176 }
177
178 pub const fn a_range() -> (f32, f32) {
179 (-0.5, 0.5)
180 }
181
182 pub const fn b_range() -> (f32, f32) {
183 (-0.5, 0.5)
184 }
185}
186
187impl EuclideanDistance for Oklab {
188 fn euclidean_distance(&self, other: Self) -> f32 {
189 let lax = self.l - other.l;
190 let dax = self.a - other.a;
191 let bax = self.b - other.b;
192 (lax * lax + dax * dax + bax * bax).sqrt()
193 }
194}
195
196impl TaxicabDistance for Oklab {
197 fn taxicab_distance(&self, other: Self) -> f32 {
198 let lax = self.l - other.l;
199 let dax = self.a - other.a;
200 let bax = self.b - other.b;
201 lax.abs() + dax.abs() + bax.abs()
202 }
203}
204
205impl Add<Oklab> for Oklab {
206 type Output = Oklab;
207
208 #[inline]
209 fn add(self, rhs: Self) -> Oklab {
210 Oklab::new(self.l + rhs.l, self.a + rhs.a, self.b + rhs.b)
211 }
212}
213
214impl Add<f32> for Oklab {
215 type Output = Oklab;
216
217 #[inline]
218 fn add(self, rhs: f32) -> Oklab {
219 Oklab::new(self.l + rhs, self.a + rhs, self.b + rhs)
220 }
221}
222
223impl AddAssign<Oklab> for Oklab {
224 #[inline]
225 fn add_assign(&mut self, rhs: Oklab) {
226 self.l += rhs.l;
227 self.a += rhs.a;
228 self.b += rhs.b;
229 }
230}
231
232impl AddAssign<f32> for Oklab {
233 #[inline]
234 fn add_assign(&mut self, rhs: f32) {
235 self.l += rhs;
236 self.a += rhs;
237 self.b += rhs;
238 }
239}
240
241impl Mul<f32> for Oklab {
242 type Output = Oklab;
243
244 #[inline]
245 fn mul(self, rhs: f32) -> Self::Output {
246 Oklab::new(self.l * rhs, self.a * rhs, self.b * rhs)
247 }
248}
249
250impl Mul<Oklab> for Oklab {
251 type Output = Oklab;
252
253 #[inline]
254 fn mul(self, rhs: Oklab) -> Self::Output {
255 Oklab::new(self.l * rhs.l, self.a * rhs.a, self.b * rhs.b)
256 }
257}
258
259impl MulAssign<f32> for Oklab {
260 #[inline]
261 fn mul_assign(&mut self, rhs: f32) {
262 self.l *= rhs;
263 self.a *= rhs;
264 self.b *= rhs;
265 }
266}
267
268impl MulAssign<Oklab> for Oklab {
269 #[inline]
270 fn mul_assign(&mut self, rhs: Oklab) {
271 self.l *= rhs.l;
272 self.a *= rhs.a;
273 self.b *= rhs.b;
274 }
275}
276
277impl Sub<f32> for Oklab {
278 type Output = Oklab;
279
280 #[inline]
281 fn sub(self, rhs: f32) -> Self::Output {
282 Oklab::new(self.l - rhs, self.a - rhs, self.b - rhs)
283 }
284}
285
286impl Sub<Oklab> for Oklab {
287 type Output = Oklab;
288
289 #[inline]
290 fn sub(self, rhs: Oklab) -> Self::Output {
291 Oklab::new(self.l - rhs.l, self.a - rhs.a, self.b - rhs.b)
292 }
293}
294
295impl SubAssign<f32> for Oklab {
296 #[inline]
297 fn sub_assign(&mut self, rhs: f32) {
298 self.l -= rhs;
299 self.a -= rhs;
300 self.b -= rhs;
301 }
302}
303
304impl SubAssign<Oklab> for Oklab {
305 #[inline]
306 fn sub_assign(&mut self, rhs: Oklab) {
307 self.l -= rhs.l;
308 self.a -= rhs.a;
309 self.b -= rhs.b;
310 }
311}
312
313impl Div<f32> for Oklab {
314 type Output = Oklab;
315
316 #[inline]
317 fn div(self, rhs: f32) -> Self::Output {
318 Oklab::new(self.l / rhs, self.a / rhs, self.b / rhs)
319 }
320}
321
322impl Div<Oklab> for Oklab {
323 type Output = Oklab;
324
325 #[inline]
326 fn div(self, rhs: Oklab) -> Self::Output {
327 Oklab::new(self.l / rhs.l, self.a / rhs.a, self.b / rhs.b)
328 }
329}
330
331impl DivAssign<f32> for Oklab {
332 #[inline]
333 fn div_assign(&mut self, rhs: f32) {
334 self.l /= rhs;
335 self.a /= rhs;
336 self.b /= rhs;
337 }
338}
339
340impl DivAssign<Oklab> for Oklab {
341 #[inline]
342 fn div_assign(&mut self, rhs: Oklab) {
343 self.l /= rhs.l;
344 self.a /= rhs.a;
345 self.b /= rhs.b;
346 }
347}
348
349impl Neg for Oklab {
350 type Output = Oklab;
351
352 #[inline]
353 fn neg(self) -> Self::Output {
354 Oklab::new(-self.l, -self.a, -self.b)
355 }
356}
357
358impl Pow<f32> for Oklab {
359 type Output = Oklab;
360
361 #[inline]
362 fn pow(self, rhs: f32) -> Self::Output {
363 Oklab::new(self.l.powf(rhs), self.a.powf(rhs), self.b.powf(rhs))
364 }
365}
366
367impl Pow<Oklab> for Oklab {
368 type Output = Oklab;
369
370 #[inline]
371 fn pow(self, rhs: Oklab) -> Self::Output {
372 Oklab::new(self.l.powf(rhs.l), self.a.powf(rhs.a), self.b.powf(rhs.b))
373 }
374}
375
376impl Oklab {
377 #[inline]
378 pub fn sqrt(&self) -> Oklab {
379 Oklab::new(
380 if self.l < 0. { 0. } else { self.l.sqrt() },
381 if self.a < 0. { 0. } else { self.a.sqrt() },
382 if self.b < 0. { 0. } else { self.b.sqrt() },
383 )
384 }
385
386 #[inline]
387 pub fn cbrt(&self) -> Oklab {
388 Oklab::new(self.l.cbrt(), self.a.cbrt(), self.b.cbrt())
389 }
390}