1use std::f64::consts::PI;
2use std::cmp::Ordering;
3use std::str;
4
5const M: [[f64;3];3] = [
6 [3.240969941904521, -1.537383177570093, -0.498610760293],
7 [-0.96924363628087, 1.87596750150772, 0.041555057407175],
8 [0.055630079696993, -0.20397695888897, 1.056971514242878],
9];
10
11const M_INV: [[f64;3];3] = [
12
13 [0.41239079926595, 0.35758433938387, 0.18048078840183],
14 [0.21263900587151, 0.71516867876775, 0.072192315360733],
15 [0.019330818715591, 0.11919477979462, 0.95053215224966],
16];
17
18const REF_Y : f64 = 1.0;
19const REF_U : f64 = 0.19783000664283;
20const REF_V : f64 = 0.46831999493879;
21const KAPPA : f64 = 903.2962962;
23const EPSILON : f64 = 0.0088564516;
24
25pub fn hsluv_to_hex(hsl: (f64, f64, f64)) -> String {
27 rgb_to_hex(
28 hsluv_to_rgb(hsl)
29 )
30}
31
32pub fn hpluv_to_hex(hsl: (f64, f64, f64)) -> String {
34 rgb_to_hex(
35 hpluv_to_rgb(hsl)
36 )
37}
38
39pub fn hex_to_hsluv(hex: &str) -> (f64, f64, f64) {
41 rgb_to_hsluv(
42 hex_to_rgb(hex)
43 )
44}
45
46pub fn hex_to_hpluv(hex: &str) -> (f64, f64, f64) {
48 rgb_to_hpluv(
49 hex_to_rgb(hex)
50 )
51}
52
53pub fn hsluv_to_lch(hsl: (f64, f64, f64)) ->(f64, f64, f64) {
55 let (h, s, l) = hsl;
56 match l {
57 l if l > 99.9999999 => (100.0, 0.0, h),
58 l if l < 0.00000001 => (0.0, 0.0, h),
59 _ => {
60 let mx = max_chroma_for(l, h);
61 let c = mx/100.0 * s;
62 (l, c, h)
63 }
64 }
65}
66
67pub fn hpluv_to_lch(hpl: (f64, f64, f64) ) -> (f64, f64, f64) {
69 let (h, p, l) = hpl;
70 match l {
71 l if l > 99.9999999 => (100.0, 0.0, h),
72 l if l < 0.00000001 => (0.0, 0.0, h),
73 _ => {
74 let mx = max_safe_chroma_for(l);
75 let c = mx/100.0 * p;
76 (l, c, h)
77 }
78 }
79}
80
81pub fn lch_to_luv(lch: (f64, f64, f64)) -> (f64, f64, f64) {
83 let (l, c, h) = lch;
84 let hrad = degrees_to_radians(h);
85 let u = hrad.cos() * c;
86 let v = hrad.sin() * c;
87
88 (l, u, v)
89}
90
91pub fn lch_to_hsluv(lch: (f64, f64, f64)) -> (f64, f64, f64) {
93 let (l, c, h) = lch;
94 match l {
95 l if l > 99.9999999 => (h, 0.0, 100.0),
96 l if l < 0.00000001 => (h, 0.0, 0.0),
97 _ => {
98 let mx = max_chroma_for(l, h);
99 let s = c / mx * 100.0;
100 (h, s, l)
101 }
102 }
103}
104
105pub fn lch_to_hpluv(lch: (f64, f64, f64)) -> (f64, f64, f64) {
107 let (l, c, h) = lch;
108 match l {
109 l if l > 99.9999999 => (h, 0.0, 100.0),
110 l if l < 0.00000001 => (h, 0.0, 0.0),
111 _ => {
112 let mx = max_safe_chroma_for(l);
113 let s = c / mx * 100.0;
114 (h, s, l)
115 }
116 }
117}
118
119pub fn lch_to_rgb(lch: (f64, f64, f64)) -> (f64, f64, f64) {
121 xyz_to_rgb(
122 luv_to_xyz(
123 lch_to_luv(lch)
124 )
125 )
126}
127
128pub fn hsluv_to_rgb(hsl: (f64, f64, f64)) -> (f64, f64, f64) {
130 xyz_to_rgb(
131 luv_to_xyz(
132 lch_to_luv(
133 hsluv_to_lch(hsl)
134 )
135 )
136 )
137}
138
139pub fn hpluv_to_rgb(hsl: (f64, f64, f64)) -> (f64, f64, f64) {
141 lch_to_rgb(
142 hpluv_to_lch(hsl)
143 )
144}
145
146pub fn xyz_to_rgb(xyz: (f64, f64, f64)) -> (f64, f64, f64) {
148 let xyz_vec = vec![xyz.0, xyz.1, xyz.2];
149 let abc: Vec<f64> = M.iter().map(|i| from_linear(dot_product(&i.to_vec(), &xyz_vec))).collect();
150 (abc[0], abc[1], abc[2])
151}
152
153pub fn luv_to_xyz(luv: (f64, f64, f64)) -> (f64, f64, f64) {
155 let (l, u, v) = luv;
156
157 if l == 0.0 {
158 return (0.0, 0.0, 0.0);
159 }
160
161 let var_y = f_inv(l);
162 let var_u = u / (13.0 * l) + REF_U;
163 let var_v = v / (13.0 * l) + REF_V;
164
165 let y = var_y * REF_Y;
166 let x = 0.0 - (9.0 * y * var_u) / ((var_u - 4.0) * var_v - var_u * var_v);
167 let z = (9.0 * y - (15.0 * var_v * y) - (var_v * x)) / (3.0 * var_v);
168
169 (x, y, z)
170}
171
172pub fn xyz_to_luv(xyz: (f64, f64, f64)) -> (f64, f64, f64) {
174 let (x, y, z) = xyz;
175 let l = f(y);
176
177 if l == 0.0 || (xyz == (0.0, 0.0, 0.0)) {
178 return (0.0, 0.0, 0.0);
179 }
180
181 let var_u = (4.0 * x) / (x + (15.0 * y) + (3.0 *z));
182 let var_v = (9.0 * y) / (x + (15.0 * y) + (3.0 *z));
183 let u = 13.0 * l * (var_u - REF_U);
184 let v = 13.0 * l * (var_v - REF_V);
185
186 (l, u, v)
187}
188
189pub fn rgb_to_hsluv(rgb: (f64, f64, f64)) -> (f64, f64, f64) {
191 lch_to_hsluv(rgb_to_lch(rgb))
192}
193
194pub fn rgb_to_hpluv(rgb: (f64, f64, f64)) -> (f64, f64, f64) {
196 lch_to_hpluv(rgb_to_lch(rgb))
197}
198
199pub fn rgb_to_lch(rgb: (f64, f64, f64)) -> (f64, f64, f64) {
201 luv_to_lch(
202 xyz_to_luv(
203 rgb_to_xyz(rgb)
204 )
205 )
206}
207
208pub fn rgb_to_xyz(rgb: (f64, f64, f64)) -> (f64, f64, f64) {
210 let rgbl = vec![to_linear(rgb.0), to_linear(rgb.1), to_linear(rgb.2)];
211 let mapping : Vec<f64> = M_INV.iter().map(|i| dot_product(&i.to_vec(), &rgbl)).collect();
212 (mapping[0], mapping[1], mapping[2])
213}
214
215pub fn luv_to_lch(luv: (f64, f64, f64)) -> (f64, f64, f64) {
217 let (l, u, v) = luv;
218 let c = (u*u + v*v).sqrt();
219 if c < 0.00000001 {
220 (l, c, 0.0)
221 } else {
222 let hrad = f64::atan2(v, u);
223 let mut h = radians_to_degrees(hrad);
224 if h < 0.0 {
225 h += 360.0;
226 }
227 (l, c, h)
228 }
229}
230
231pub fn rgb_to_hex(rgb: (f64, f64, f64)) -> String {
233 let (r,g,b) = rgb_prepare(rgb);
234 format!("#{:02x}{:02x}{:02x}", r,g,b)
235}
236
237pub fn hex_to_rgb(raw_hex: &str) -> (f64, f64, f64) {
239 let hex = raw_hex.trim_start_matches('#');
240 if hex.len() != 6 {
241 println!("Not a hex string!");
242 return (0.0,0.0,0.0)
243 }
244 let mut chunks = hex.as_bytes().chunks(2);
245 let red = i64::from_str_radix(str::from_utf8(chunks.next().unwrap()).unwrap(), 16);
246 let green = i64::from_str_radix(str::from_utf8(chunks.next().unwrap()).unwrap(), 16);
247 let blue = i64::from_str_radix(str::from_utf8(chunks.next().unwrap()).unwrap(), 16);
248 ( (red.unwrap_or(0) as f64) / 255.0, (green.unwrap_or(0) as f64) / 255.0, (blue.unwrap_or(0) as f64) / 255.0 )
249}
250
251
252fn f_inv(t: f64) -> f64 {
253 if t > 8.0 {
254 REF_Y * ( (t + 16.0) / 116.0 ).powf(3.0)
255 } else {
256 REF_Y * t / KAPPA
257 }
258}
259
260fn to_linear(c: f64) -> f64 {
261 if c > 0.04045 {
262 ( (c + 0.055) / 1.055).powf(2.4)
263 } else {
264 c / 12.92
265 }
266}
267
268fn from_linear(c: f64) -> f64 {
269 if c <= 0.0031308 {
270 12.92 * c
271 } else {
272 1.055 * (c.powf(1.0/2.4)) - 0.055
273 }
274}
275
276fn f(t:f64) -> f64 {
277 if t > EPSILON {
278 116.0 * ( (t / REF_Y).powf(1.0/3.0) ) - 16.0
279 } else {
280 t / REF_Y * KAPPA
281 }
282}
283
284fn dot_product(a: &[f64], b: &[f64] ) -> f64 {
285 a.iter().zip(b.iter()).map(|(i, j)| i * j).sum()
286}
287
288fn rgb_prepare(rgb: (f64, f64, f64)) -> (u8, u8, u8) {
289 (clamp(rgb.0), clamp(rgb.1), clamp(rgb.2))
290}
291
292fn clamp(v: f64) -> u8 {
293 let mut rounded = (v * 1000.0).round() / 1000.0;
294 if rounded < 0.0 {
295 rounded = 0.0;
296 }
297 if rounded > 1.0 {
298 rounded = 1.0;
299 }
300 (rounded * 255.0).round() as u8
301}
302
303fn max_chroma_for(l: f64, h: f64) -> f64 {
304 let hrad = h / 360.0 * PI * 2.0;
305
306 let mut lengths : Vec<f64> = get_bounds(l)
307 .iter()
308 .map(|line| length_of_ray_until_intersect(hrad, line))
309 .filter(|length| length > &0.0 )
310 .collect::<Vec<f64>>();
311
312 lengths.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal) );
313 lengths[0]
314}
315
316fn max_safe_chroma_for(l: f64) -> f64 {
317 let mut lengths = Vec::new();
318 get_bounds(l).iter().for_each(|line| {
319 let x = intersect_line_line((line.0, line.1), (-1.0/line.0, 0.0) );
320 lengths.push(distance_from_pole((x, line.1 + x * line.0)));
321 });
322 lengths.sort_by(|a,b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
323 lengths[0]
324}
325
326fn intersect_line_line(line1: (f64, f64), line2: (f64, f64)) -> f64 {
327 (line1.1 - line2.1) / (line2.0 - line1.0)
328}
329
330fn distance_from_pole(point: (f64, f64)) -> f64 {
331 (point.0.powi(2) + point.1.powi(2)).sqrt()
332}
333
334fn get_bounds(l: f64) -> Vec<(f64, f64)> {
335 let sub1 = ((l + 16.0).powi(3))/ 1560896.0;
336 let sub2 = match sub1 {
337 s if s > EPSILON => s,
338 _ => l / KAPPA
339 };
340
341 let mut bounds = Vec::new();
342
343 for ms in &M {
344 let (m1, m2, m3) = (ms[0], ms[1], ms[2]);
345 for t in 0..2 {
346 let top1 = (284517.0 * m1 - 94839.0 * m3) * sub2;
347 let top2 = (838422.0 * m3 + 769860.0 * m2 + 731718.0 * m1) * l * sub2 - 769860.0 * f64::from(t) * l;
348 let bottom = (632260.0 * m3 - 126452.0 * m2) * sub2 + 126452.0 * f64::from(t);
349
350 bounds.push((top1/bottom, top2/bottom));
351 }
352 }
353 bounds
354}
355
356fn length_of_ray_until_intersect(theta: f64, line: &(f64, f64)) -> f64 {
357 let (m1, b1) = *line;
358 let length = b1 / (theta.sin() - m1 * theta.cos());
359 if length < 0.0 {
360 -0.0001
361 } else {
362 length
363 }
364}
365
366fn radians_to_degrees(rad: f64) -> f64 {
367 rad * 180.0 / PI
368}
369
370fn degrees_to_radians(deg: f64) -> f64 {
371 deg * PI / 180.0
372}