1#![allow(non_snake_case)]
2#![allow(non_camel_case_types)]
3#![allow(non_upper_case_globals)]
4
5use std::f32::consts::PI;
11use std::marker::PhantomData;
12pub mod consts;
13pub mod utils;
14use consts::VC;
15pub use consts::{LCD, SCD, UCS};
16use utils::*;
17#[cfg(all(feature = "sse", any(target_arch = "x86", target_arch = "x86_64")))]
18pub mod sse;
19
20#[cfg(feature = "approximate_math")]
21#[allow(unused_imports)]
22use micromath::F32Ext;
23
24#[derive(Default, Debug, Copy, Clone)]
26pub struct sRGB {
27 pub r: u8,
28 pub g: u8,
29 pub b: u8,
30}
31
32impl From<[u8; 3]> for sRGB {
33 fn from(rgb: [u8; 3]) -> sRGB {
34 sRGB {
35 r: rgb[0],
36 g: rgb[1],
37 b: rgb[2],
38 }
39 }
40}
41
42impl From<(u8, u8, u8)> for sRGB {
43 fn from(rgb: (u8, u8, u8)) -> sRGB {
44 sRGB {
45 r: rgb.0,
46 g: rgb.1,
47 b: rgb.2,
48 }
49 }
50}
51
52#[derive(Default, Debug, Copy, Clone)]
54pub struct LinearRGB {
55 pub r: f32,
56 pub g: f32,
57 pub b: f32,
58}
59
60impl From<&sRGB> for LinearRGB {
61 fn from(srgb: &sRGB) -> LinearRGB {
62 unsafe {
64 LinearRGB {
65 r: *consts::sRGB_LOOKUP.get_unchecked(srgb.r as usize),
66 g: *consts::sRGB_LOOKUP.get_unchecked(srgb.g as usize),
67 b: *consts::sRGB_LOOKUP.get_unchecked(srgb.b as usize),
68 }
69 }
70 }
71}
72
73impl<T: Into<sRGB>> From<T> for LinearRGB {
74 fn from(rgb: T) -> LinearRGB {
75 LinearRGB::from(&rgb.into())
76 }
77}
78
79#[derive(Debug, Copy, Clone)]
81pub struct XYZ {
82 pub x: f32,
83 pub y: f32,
84 pub z: f32,
85}
86
87impl From<&LinearRGB> for XYZ {
88 fn from(rgb: &LinearRGB) -> XYZ {
89 #[cfg(all(feature = "sse",any(target_arch = "x86", target_arch = "x86_64")))]
90 {
91 unsafe {
92 if is_x86_feature_detected!("sse") {
93 let res = sse::sse_xyz(rgb);
94 return XYZ {
95 x: res[0],
96 y: res[1],
97 z: res[2],
98 };
99 }
100 }
101 }
102
103 XYZ {
104 x: ((rgb.r * 0.4124) + (rgb.g * 0.3576) + (rgb.b * 0.1805)) * 100.0,
105 y: ((rgb.r * 0.2126) + (rgb.g * 0.7152) + (rgb.b * 0.0722)) * 100.0,
106 z: ((rgb.r * 0.0193) + (rgb.g * 0.1192) + (rgb.b * 0.9505)) * 100.0,
107 }
108 }
109}
110
111impl<T: Into<sRGB>> From<T> for XYZ {
112 fn from(rgb: T) -> XYZ {
113 XYZ::from(&LinearRGB::from(&rgb.into()))
114 }
115}
116
117#[derive(Debug, Copy, Clone)]
119pub struct LMS {
120 pub l: f32,
121 pub m: f32,
122 pub s: f32,
123}
124
125impl From<&XYZ> for LMS {
126 fn from(xyz: &XYZ) -> LMS {
127 #[cfg(all(feature = "sse",any(target_arch = "x86", target_arch = "x86_64")))]
128 {
129 unsafe {
130 if is_x86_feature_detected!("sse") {
131 let res = sse::sse_lms(xyz);
132 return LMS {
133 l: res[0],
134 m: res[1],
135 s: res[2],
136 };
137 }
138 }
139 }
140
141 LMS {
142 l: (0.7328 * xyz.x) + (0.4296 * xyz.y) - (0.1624 * xyz.z),
143 m: (-0.7036 * xyz.x) + (1.6975 * xyz.y) + (0.0061 * xyz.z),
144 s: (0.0030 * xyz.x) + (0.0136 * xyz.y) + (0.9834 * xyz.z),
145 }
146 }
147}
148
149impl<T: Into<sRGB>> From<T> for LMS {
150 fn from(rgb: T) -> LMS {
151 LMS::from(&XYZ::from(&LinearRGB::from(&rgb.into())))
152 }
153}
154
155#[derive(Debug, Copy, Clone)]
157pub struct HPE {
158 pub lh: f32,
159 pub mh: f32,
160 pub sh: f32,
161}
162
163impl From<&LMS> for HPE {
164 fn from(lms: &LMS) -> HPE {
165 #[cfg(all(feature = "sse",any(target_arch = "x86", target_arch = "x86_64")))]
166 {
167 unsafe {
168 if is_x86_feature_detected!("sse") {
169 let res = sse::sse_hpe(lms);
170 return HPE {
171 lh: res[0],
172 mh: res[1],
173 sh: res[2],
174 };
175 }
176 }
177 }
178
179 HPE {
180 lh: (0.7409792 * lms.l) + (0.2180250 * lms.m) + (0.0410058 * lms.s),
181 mh: (0.2853532 * lms.l) + (0.6242014 * lms.m) + (0.0904454 * lms.s),
182 sh: (-0.0096280 * lms.l) - (0.0056980 * lms.m) + (1.0153260 * lms.s),
183 }
184 }
185}
186
187impl<T: Into<sRGB>> From<T> for HPE {
188 fn from(rgb: T) -> HPE {
189 HPE::from(&LMS::from(&XYZ::from(&LinearRGB::from(&rgb.into()))))
190 }
191}
192
193#[derive(Default, Debug, Copy, Clone)]
195pub struct JCh {
196 pub J: f32,
197 pub C: f32,
198 pub H: f32,
199 pub h: f32,
200 pub Q: f32,
201 pub M: f32,
202 pub s: f32,
203}
204
205impl From<&LMS> for JCh {
206 fn from(lms: &LMS) -> JCh {
207 let [lc, mc, sc, _] = transform_cones([lms.l, lms.m, lms.s, 0.0]);
208
209 let hpe_transforms = HPE::from(&LMS {
210 l: lc,
211 m: mc,
212 s: sc,
213 });
214
215 let [lpa, mpa, spa, _] = nonlinear_adaptation(
216 [hpe_transforms.lh, hpe_transforms.mh, hpe_transforms.sh, 0.0],
217 VC::fl,
218 );
219
220 let ca = lpa - ((12.0 * mpa) / 11.0) + (spa / 11.0);
221 let cb = (1.0 / 9.0) * (lpa + mpa - 2.0 * spa);
222
223 let mut result_color = JCh::default();
224
225 result_color.h = (180.0 / PI) * cb.atan2(ca);
226 if result_color.h < 0.0 {
227 result_color.h += 360.0;
228 }
229
230 let H = match result_color.h {
231 h if h < 20.14 => {
232 let temp = ((h + 122.47) / 1.2) + ((20.14 - h) / 0.8);
233 300.0 + (100.0 * ((h + 122.47) / 1.2)) / temp
234 }
235 h if h < 90.0 => {
236 let temp = ((h - 20.14) / 0.8) + ((90.0 - h) / 0.7);
237 (100.0 * ((h - 20.14) / 0.8)) / temp
238 }
239
240 h if h < 164.25 => {
241 let temp = ((h - 90.0) / 0.7) + ((164.25 - h) / 1.0);
242 100.0 + ((100.0 * ((h - 90.0) / 0.7)) / temp)
243 }
244 h if h < 237.53 => {
245 let temp = ((h - 164.25) / 1.0) + ((237.53 - h) / 1.2);
246 200.0 + ((100.0 * ((h - 164.25) / 1.0)) / temp)
247 }
248 h => {
249 let temp = ((h - 237.53) / 1.2) + ((360.0 - h + 20.14) / 0.8);
250 300.0 + ((100.0 * ((h - 237.53) / 1.2)) / temp)
251 }
252 };
253
254 result_color.H = H;
255
256 let a = (2.0 * lpa + mpa + 0.05 * spa - 0.305) * VC::nbb;
257 result_color.J = 100.0 * (a / VC::achromatic_response_to_white).powf(VC::c * VC::z);
258
259 let et = 0.25 * (((result_color.h * PI) / 180.0 + 2.0).cos() + 3.8);
260 let t = (50000.0 / 13.0) * VC::nc * VC::ncb * et * (ca.powi(2) + cb.powi(2)).sqrt()
261 / (lpa + mpa + (21.0 / 20.0) * spa);
262
263 result_color.C = t.powf(0.9f32)
264 * (result_color.J / 100.0).sqrt()
265 * (1.64 - 0.29f32.powf(VC::n)).powf(0.73f32);
266
267 result_color.Q = (4.0 / VC::c)
268 * (result_color.J / 100.0).sqrt()
269 * (VC::achromatic_response_to_white + 4.0f32)
270 * VC::fl.powf(0.25f32);
271
272 result_color.M = result_color.C * VC::fl.powf(0.25f32);
273
274 result_color.s = 100.0 * (result_color.M / result_color.Q).sqrt();
275
276 result_color
277 }
278}
279
280impl<T: Into<sRGB>> From<T> for JCh {
281 fn from(rgb: T) -> JCh {
282 JCh::from(&LMS::from(&XYZ::from(&LinearRGB::from(&rgb.into()))))
283 }
284}
285
286pub trait JabSpace {
288 const k_l: f32;
289 const c1: f32;
290 const c2: f32;
291}
292
293#[derive(Default, Debug, Copy, Clone)]
297pub struct Jab<S: JabSpace> {
298 pub J: f32,
299 pub a: f32,
300 pub b: f32,
301 space: PhantomData<S>,
302}
303
304impl<S: JabSpace> Jab<S> {
305 pub const fn new_const(J: f32, a: f32, b: f32) -> Jab<S> {
306 Jab {
307 J,
308 a,
309 b,
310 space: PhantomData
311 }
312 }
313}
314
315impl<S: JabSpace> From<&JCh> for Jab<S> {
316 fn from(cam02: &JCh) -> Jab<S> {
317 let j_prime = ((1.0 + 100.0 * S::c1) * cam02.J) / (1.0 + S::c1 * cam02.J) / S::k_l;
318
319 let m_prime = (1.0 / S::c2) * (1.0 + S::c2 * cam02.M).ln();
320
321 Jab {
322 J: j_prime,
323 a: m_prime * ((PI / 180.0) * cam02.h).cos(),
324 b: m_prime * ((PI / 180.0) * cam02.h).sin(),
325 space: PhantomData,
326 }
327 }
328}
329
330impl<T: Into<sRGB>, S: JabSpace> From<T> for Jab<S> {
331 fn from(rgb: T) -> Jab<S> {
332 Jab::<S>::from(&JCh::from(&LMS::from(&XYZ::from(&LinearRGB::from(
333 &rgb.into(),
334 )))))
335 }
336}
337
338impl<S: JabSpace> From<[f32; 3]> for Jab<S> {
339 fn from(jab: [f32; 3]) -> Jab<S> {
340 Jab {
341 J: jab[0],
342 a: jab[1],
343 b: jab[2],
344 space: PhantomData,
345 }
346 }
347}
348
349impl<S: JabSpace> From<(f32, f32, f32)> for Jab<S> {
350 fn from(jab: (f32, f32, f32)) -> Jab<S> {
351 Jab {
352 J: jab.0,
353 a: jab.1,
354 b: jab.2,
355 space: PhantomData,
356 }
357 }
358}
359
360impl<S: JabSpace> Jab<S> {
361 pub fn squared_difference(&self, other: &Jab<S>) -> f32 {
362 let diff_j = (self.J - other.J).abs();
363 let diff_a = (self.a - other.a).abs();
364 let diff_b = (self.b - other.b).abs();
365
366 (diff_j / S::k_l).powi(2) + diff_a.powi(2) + diff_b.powi(2)
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 use crate::{consts::UCS, JCh, Jab};
373
374 macro_rules! float_eq {
375 ($lhs:expr, $rhs:expr) => {
376 assert_eq!(format!("{:.2}", $lhs), $rhs)
377 };
378 }
379
380 #[test]
382 fn jch_channels() {
383 float_eq!(JCh::from([0, 0, 0]).J, "0.00");
384 float_eq!(JCh::from([50, 50, 50]).J, "14.92");
385 float_eq!(JCh::from([100, 100, 100]).J, "32.16");
386 float_eq!(JCh::from([150, 150, 150]).J, "52.09");
387 float_eq!(JCh::from([200, 200, 200]).J, "74.02");
388 float_eq!(JCh::from([250, 250, 250]).J, "97.57");
389 float_eq!(JCh::from([255, 255, 255]).J, "100.00");
390
391 let red = JCh::from([255, 0, 0]);
392 float_eq!(red.J, "46.93");
393 float_eq!(red.C, "111.30");
394 float_eq!(red.h, "32.15");
395 }
396
397 #[test]
398 fn jab_channels() {
399 float_eq!(Jab::<UCS>::from([0, 0, 0]).J, "0.00");
400 float_eq!(Jab::<UCS>::from([50, 50, 50]).J, "22.96");
401 float_eq!(Jab::<UCS>::from([150, 150, 150]).J, "64.89");
402 let white = Jab::<UCS>::from([255, 255, 255]);
403 float_eq!(white.J, "100.00");
404 float_eq!(white.a, "-1.91");
405 float_eq!(white.b, "-1.15");
406 let red = Jab::<UCS>::from([255, 0, 0]);
407 float_eq!(red.J, "60.05");
408 float_eq!(red.a, "38.69");
409 float_eq!(red.b, "24.32");
410 let blue = Jab::<UCS>::from([0, 0, 255]);
411 float_eq!(blue.J, "31.22");
412 float_eq!(blue.a, "-8.38");
413 float_eq!(blue.b, "-39.16");
414 }
415}