moxcms/
oklch.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::{Oklab, Rgb};
8use num_traits::Pow;
9use pxfm::{f_atan2f, f_cbrtf, f_hypotf, f_powf, f_sincosf};
10use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
11
12/// Represents *Oklch* colorspace
13#[repr(C)]
14#[derive(Copy, Clone, PartialOrd, PartialEq)]
15pub struct Oklch {
16    /// Lightness
17    pub l: f32,
18    /// Chroma
19    pub c: f32,
20    /// Hue
21    pub h: f32,
22}
23
24impl Oklch {
25    /// Creates new instance
26    #[inline]
27    pub const fn new(l: f32, c: f32, h: f32) -> Oklch {
28        Oklch { l, c, h }
29    }
30
31    /// Converts Linear [Rgb] into [Oklch]
32    ///
33    /// # Arguments
34    /// `transfer_function` - Transfer function into linear colorspace and its inverse
35    #[inline]
36    pub fn from_linear_rgb(rgb: Rgb<f32>) -> Oklch {
37        let oklab = Oklab::from_linear_rgb(rgb);
38        Oklch::from_oklab(oklab)
39    }
40
41    /// Converts [Oklch] into linear [Rgb]
42    #[inline]
43    pub fn to_linear_rgb(&self) -> Rgb<f32> {
44        let oklab = self.to_oklab();
45        oklab.to_linear_rgb()
46    }
47
48    /// Converts *Oklab* to *Oklch*
49    #[inline]
50    pub fn from_oklab(oklab: Oklab) -> Oklch {
51        let chroma = f_hypotf(oklab.b, oklab.a);
52        let hue = f_atan2f(oklab.b, oklab.a);
53        Oklch::new(oklab.l, chroma, hue)
54    }
55
56    /// Converts *Oklch* to *Oklab*
57    #[inline]
58    pub fn to_oklab(&self) -> Oklab {
59        let l = self.l;
60        let sincos = f_sincosf(self.h);
61        let a = self.c * sincos.1;
62        let b = self.c * sincos.0;
63        Oklab::new(l, a, b)
64    }
65}
66
67impl Oklch {
68    #[inline]
69    pub fn euclidean_distance(&self, other: Self) -> f32 {
70        let dl = self.l - other.l;
71        let dc = self.c - other.c;
72        let dh = self.h - other.h;
73        (dl * dl + dc * dc + dh * dh).sqrt()
74    }
75}
76
77impl Oklch {
78    #[inline]
79    pub fn taxicab_distance(&self, other: Self) -> f32 {
80        let dl = self.l - other.l;
81        let dc = self.c - other.c;
82        let dh = self.h - other.h;
83        dl.abs() + dc.abs() + dh.abs()
84    }
85}
86
87impl Add<Oklch> for Oklch {
88    type Output = Oklch;
89
90    #[inline]
91    fn add(self, rhs: Self) -> Oklch {
92        Oklch::new(self.l + rhs.l, self.c + rhs.c, self.h + rhs.h)
93    }
94}
95
96impl Add<f32> for Oklch {
97    type Output = Oklch;
98
99    #[inline]
100    fn add(self, rhs: f32) -> Oklch {
101        Oklch::new(self.l + rhs, self.c + rhs, self.h + rhs)
102    }
103}
104
105impl AddAssign<Oklch> for Oklch {
106    #[inline]
107    fn add_assign(&mut self, rhs: Oklch) {
108        self.l += rhs.l;
109        self.c += rhs.c;
110        self.h += rhs.h;
111    }
112}
113
114impl AddAssign<f32> for Oklch {
115    #[inline]
116    fn add_assign(&mut self, rhs: f32) {
117        self.l += rhs;
118        self.c += rhs;
119        self.h += rhs;
120    }
121}
122
123impl Mul<f32> for Oklch {
124    type Output = Oklch;
125
126    #[inline]
127    fn mul(self, rhs: f32) -> Self::Output {
128        Oklch::new(self.l * rhs, self.c * rhs, self.h * rhs)
129    }
130}
131
132impl Mul<Oklch> for Oklch {
133    type Output = Oklch;
134
135    #[inline]
136    fn mul(self, rhs: Oklch) -> Self::Output {
137        Oklch::new(self.l * rhs.l, self.c * rhs.c, self.h * rhs.h)
138    }
139}
140
141impl MulAssign<f32> for Oklch {
142    #[inline]
143    fn mul_assign(&mut self, rhs: f32) {
144        self.l *= rhs;
145        self.c *= rhs;
146        self.h *= rhs;
147    }
148}
149
150impl MulAssign<Oklch> for Oklch {
151    #[inline]
152    fn mul_assign(&mut self, rhs: Oklch) {
153        self.l *= rhs.l;
154        self.c *= rhs.c;
155        self.h *= rhs.h;
156    }
157}
158
159impl Sub<f32> for Oklch {
160    type Output = Oklch;
161
162    #[inline]
163    fn sub(self, rhs: f32) -> Self::Output {
164        Oklch::new(self.l - rhs, self.c - rhs, self.h - rhs)
165    }
166}
167
168impl Sub<Oklch> for Oklch {
169    type Output = Oklch;
170
171    #[inline]
172    fn sub(self, rhs: Oklch) -> Self::Output {
173        Oklch::new(self.l - rhs.l, self.c - rhs.c, self.h - rhs.h)
174    }
175}
176
177impl SubAssign<f32> for Oklch {
178    #[inline]
179    fn sub_assign(&mut self, rhs: f32) {
180        self.l -= rhs;
181        self.c -= rhs;
182        self.h -= rhs;
183    }
184}
185
186impl SubAssign<Oklch> for Oklch {
187    #[inline]
188    fn sub_assign(&mut self, rhs: Oklch) {
189        self.l -= rhs.l;
190        self.c -= rhs.c;
191        self.h -= rhs.h;
192    }
193}
194
195impl Div<f32> for Oklch {
196    type Output = Oklch;
197
198    #[inline]
199    fn div(self, rhs: f32) -> Self::Output {
200        Oklch::new(self.l / rhs, self.c / rhs, self.h / rhs)
201    }
202}
203
204impl Div<Oklch> for Oklch {
205    type Output = Oklch;
206
207    #[inline]
208    fn div(self, rhs: Oklch) -> Self::Output {
209        Oklch::new(self.l / rhs.l, self.c / rhs.c, self.h / rhs.h)
210    }
211}
212
213impl DivAssign<f32> for Oklch {
214    #[inline]
215    fn div_assign(&mut self, rhs: f32) {
216        self.l /= rhs;
217        self.c /= rhs;
218        self.h /= rhs;
219    }
220}
221
222impl DivAssign<Oklch> for Oklch {
223    #[inline]
224    fn div_assign(&mut self, rhs: Oklch) {
225        self.l /= rhs.l;
226        self.c /= rhs.c;
227        self.h /= rhs.h;
228    }
229}
230
231impl Neg for Oklch {
232    type Output = Oklch;
233
234    #[inline]
235    fn neg(self) -> Self::Output {
236        Oklch::new(-self.l, -self.c, -self.h)
237    }
238}
239
240impl Pow<f32> for Oklch {
241    type Output = Oklch;
242
243    #[inline]
244    fn pow(self, rhs: f32) -> Self::Output {
245        Oklch::new(
246            f_powf(self.l, rhs),
247            f_powf(self.c, rhs),
248            f_powf(self.h, rhs),
249        )
250    }
251}
252
253impl Pow<Oklch> for Oklch {
254    type Output = Oklch;
255
256    #[inline]
257    fn pow(self, rhs: Oklch) -> Self::Output {
258        Oklch::new(
259            f_powf(self.l, rhs.l),
260            f_powf(self.c, rhs.c),
261            f_powf(self.h, rhs.h),
262        )
263    }
264}
265
266impl Oklch {
267    #[inline]
268    pub fn sqrt(&self) -> Oklch {
269        Oklch::new(self.l.sqrt(), self.c.sqrt(), self.h.sqrt())
270    }
271
272    #[inline]
273    pub fn cbrt(&self) -> Oklch {
274        Oklch::new(f_cbrtf(self.l), f_cbrtf(self.c), f_cbrtf(self.h))
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281
282    #[test]
283    fn round_trip() {
284        let xyz = Rgb::new(0.1, 0.2, 0.3);
285        let lab = Oklch::from_linear_rgb(xyz);
286        let rolled_back = lab.to_linear_rgb();
287        let dx = (xyz.r - rolled_back.r).abs();
288        let dy = (xyz.g - rolled_back.g).abs();
289        let dz = (xyz.b - rolled_back.b).abs();
290        assert!(dx < 1e-5);
291        assert!(dy < 1e-5);
292        assert!(dz < 1e-5);
293    }
294}