1use 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#[repr(C)]
14#[derive(Copy, Clone, PartialOrd, PartialEq)]
15pub struct Oklch {
16 pub l: f32,
18 pub c: f32,
20 pub h: f32,
22}
23
24impl Oklch {
25 #[inline]
27 pub const fn new(l: f32, c: f32, h: f32) -> Oklch {
28 Oklch { l, c, h }
29 }
30
31 #[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 #[inline]
43 pub fn to_linear_rgb(&self) -> Rgb<f32> {
44 let oklab = self.to_oklab();
45 oklab.to_linear_rgb()
46 }
47
48 #[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 #[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}