/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 | | } |