1use std::ops::{Add, Mul};
24
25use angle::{Angle, Rad};
26use num_traits::{Float, NumCast, cast, zero, clamp};
27
28use crate::{Channel, Rgb, ToRgb, color_space::{Srgb, TransferFunction}};
29
30
31#[derive(Clone, Copy, Debug)]
32pub struct OkLab<T> {
33 pub l: T,
34 pub a: T,
35 pub b: T,
36}
37
38impl<T> OkLab<T>{
39 pub fn new(l: T, a: T, b: T) -> OkLab<T>{
40 OkLab { l, a, b }
41 }
42}
43
44
45impl<T: Copy> OkLab<T>{
46 pub fn luma(&self) -> T {
47 self.l
48 }
49}
50
51impl<T: Float> OkLab<T>{
52 pub fn chromacity(&self) -> T {
53 (self.a * self.a + self.b * self.b).sqrt()
54 }
55
56 pub fn hue(&self) -> Rad<T> {
57 let h = self.b.atan2(self.a);
58 if h < zero() {
59 Rad(h + cast(std::f64::consts::TAU).unwrap())
60 }else{
61 Rad(h)
62 }
63 }
64
65 pub fn offset_chromacity(&self, chroma_offset: T) -> OkLab<T>{
66 let current_croma = self.chromacity();
67 let offset_a = self.a / current_croma * chroma_offset;
68 let offset_b = self.b / current_croma * chroma_offset;
69 OkLab::new(
70 self.l,
71 self.a + offset_a,
72 self.b + offset_b,
73 )
74 }
75
76 pub fn from_hcl(hue: Rad<T>, chroma: T, luma: T) -> OkLab<T> {
77 let a = chroma * hue.cos();
78 let b = chroma * hue.sin();
79 OkLab {
80 l: luma,
81 a,
82 b,
83 }
84 }
85}
86
87pub trait ToOkLab {
88 fn to_oklab<T: Channel>(&self) -> OkLab<T>;
89}
90
91impl<T: Channel + NumCast + Float> ToRgb for OkLab<T> {
92 type Standard = Srgb;
93
94 fn to_rgb<U:Channel>(&self) -> crate::Rgb<U, Self::Standard> {
95 let l_ = self.l + cast::<_,T>(0.3963377774).unwrap() * self.a + cast::<_,T>(0.2158037573).unwrap() * self.b;
96 let m_ = self.l - cast::<_,T>(0.1055613458).unwrap() * self.a - cast::<_,T>(0.0638541728).unwrap() * self.b;
97 let s_ = self.l - cast::<_,T>(0.0894841775).unwrap() * self.a - cast::<_,T>(1.2914855480).unwrap() * self.b;
98
99 let l = l_*l_*l_;
100 let m = m_*m_*m_;
101 let s = s_*s_*s_;
102
103 Rgb::new(
104 Srgb::from_linear(cast::<_,T>(4.0767416621).unwrap() * l - cast::<_,T>(3.3077115913).unwrap() * m + cast::<_,T>(0.2309699292).unwrap() * s).to_channel(),
105 Srgb::from_linear(cast::<_,T>(-1.2684380046).unwrap() * l + cast::<_,T>(2.6097574011).unwrap() * m - cast::<_,T>(0.3413193965).unwrap() * s).to_channel(),
106 Srgb::from_linear(cast::<_,T>(-0.0041960863).unwrap() * l - cast::<_,T>(0.7034186147).unwrap() * m + cast::<_,T>(1.7076147010).unwrap() * s).to_channel(),
107 )
108 }
109}
110
111impl<T: Channel + Float + NumCast> Add for OkLab<T>{
112 type Output = OkLab<T>;
113 fn add(self, other: OkLab<T>) -> OkLab<T> {
114 OkLab::new(self.l + other.l, self.a + other.a, self.b + other.b)
115 }
116}
117
118impl<T: Channel + Float + NumCast> Mul<T> for OkLab<T>{
119 type Output = OkLab<T>;
120 fn mul(self, other: T) -> OkLab<T> {
121 OkLab::new(self.l * other, self.a * other, self.b * other)
122 }
123}
124
125#[derive(Clone, Copy)]
126struct LC { l: f32, c: f32 }
127
128fn compute_max_saturation(a: f32, b: f32) -> f32 {
129 let (k0, k1, k2, k3, k4, wl, wm, ws);
133
134 if -1.88170328 * a - 0.80936493 * b > 1. {
135 k0 = 1.19086277; k1 = 1.76576728; k2 = 0.59662641; k3 = 0.75515197; k4 = 0.56771245;
137 wl = 4.0767416621; wm = -3.3077115913; ws = 0.2309699292;
138 }else if 1.81444104 * a - 1.19445276 * b > 1. {
139 k0 = 0.73956515; k1 = -0.45954404; k2 = 0.08285427; k3 = 0.12541070; k4 = 0.14503204;
141 wl = -1.2684380046; wm = 2.6097574011; ws = -0.3413193965;
142 }else{
143 k0 = 1.35733652; k1 = -0.00915799; k2 = -1.15130210; k3 = -0.50559606; k4 = 0.00692167;
145 wl = -0.0041960863; wm = -0.7034186147; ws = 1.7076147010;
146 }
147
148 let mut saturation = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;
150
151 let k_l = 0.3963377774 * a + 0.2158037573 * b;
156 let k_m = -0.1055613458 * a - 0.0638541728 * b;
157 let k_s = -0.0894841775 * a - 1.2914855480 * b;
158
159 {
160 let l_ = 1. + saturation * k_l;
161 let m_ = 1. + saturation * k_m;
162 let s_ = 1. + saturation * k_s;
163
164 let l = l_ * l_ * l_;
165 let m = m_ * m_ * m_;
166 let s = s_ * s_ * s_;
167
168 let l_ds = 3. * k_l * l_ * l_;
169 let m_ds = 3. * k_m * m_ * m_;
170 let s_ds = 3. * k_s * s_ * s_;
171
172 let l_ds2 = 6. * k_l * k_l * l_;
173 let m_ds2 = 6. * k_m * k_m * m_;
174 let s_ds2 = 6. * k_s * k_s * s_;
175
176 let f = wl * l + wm * m + ws * s;
177 let f1 = wl * l_ds + wm * m_ds + ws * s_ds;
178 let f2 = wl * l_ds2 + wm * m_ds2 + ws * s_ds2;
179
180 saturation = saturation - f * f1 / (f1*f1 - 0.5 * f * f2);
181 }
182
183 saturation
184}
185
186fn find_cusp(a: f32, b: f32) -> LC {
187 let s_cusp = compute_max_saturation(a, b);
189
190 let rgb_at_max = OkLab{ l: 1., a: s_cusp * a, b: s_cusp * b }.to_rgb::<f32>();
192 let l_cusp = (1. / rgb_at_max.r.max(rgb_at_max.g).max(rgb_at_max.b)).cbrt();
193 let c_cusp = l_cusp * s_cusp;
194
195 LC { l: l_cusp , c: c_cusp }
196}
197
198
199fn find_gamut_intersection(a: f32, b: f32, l1: f32, h1: f32, l0: f32) -> f32 {
200 let cusp = find_cusp(a, b);
202 find_gamut_intersection_cusp(a, b, l1, h1, l0, cusp)
203}
204
205fn find_gamut_intersection_cusp(a: f32, b: f32, l1: f32, h1: f32, l0: f32, cusp: LC) -> f32 {
206 let mut t;
208 if ((l1 - l0) * cusp.c - (cusp.l - l0) * h1) <= 0.
209 {
210 t = cusp.c * l0 / (h1 * cusp.l + cusp.c * (l0 - l1));
213 }
214 else
215 {
216 t = cusp.c * (l0 - 1.) / (h1 * (cusp.l - 1.) + cusp.c * (l0 - l1));
220
221 {
223 let dl = l1 - l0;
224 let dc = h1;
225
226 let k_l = 0.3963377774 * a + 0.2158037573 * b;
227 let k_m = -0.1055613458 * a - 0.0638541728 * b;
228 let k_s = -0.0894841775 * a - 1.2914855480 * b;
229
230 let l_dt = dl + dc * k_l;
231 let m_dt = dl + dc * k_m;
232 let s_dt = dl + dc * k_s;
233
234
235 {
237 let luma = l0 * (1. - t) + t * l1;
238 let chroma = t * h1;
239
240 let l_ = luma + chroma * k_l;
241 let m_ = luma + chroma * k_m;
242 let s_ = luma + chroma * k_s;
243
244 let l = l_ * l_ * l_;
245 let m = m_ * m_ * m_;
246 let s = s_ * s_ * s_;
247
248 let ldt = 3. * l_dt * l_ * l_;
249 let mdt = 3. * m_dt * m_ * m_;
250 let sdt = 3. * s_dt * s_ * s_;
251
252 let ldt2 = 6. * l_dt * l_dt * l_;
253 let mdt2 = 6. * m_dt * m_dt * m_;
254 let sdt2 = 6. * s_dt * s_dt * s_;
255
256 let r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s - 1.;
257 let r1 = 4.0767416621 * ldt - 3.3077115913 * mdt + 0.2309699292 * sdt;
258 let r2 = 4.0767416621 * ldt2 - 3.3077115913 * mdt2 + 0.2309699292 * sdt2;
259
260 let u_r = r1 / (r1 * r1 - 0.5 * r * r2);
261 let t_r = -r * u_r;
262
263 let g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s - 1.;
264 let g1 = -1.2684380046 * ldt + 2.6097574011 * mdt - 0.3413193965 * sdt;
265 let g2 = -1.2684380046 * ldt2 + 2.6097574011 * mdt2 - 0.3413193965 * sdt2;
266
267 let u_g = g1 / (g1 * g1 - 0.5 * g * g2);
268 let t_g = -g * u_g;
269
270 let b = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s - 1.;
271 let b1 = -0.0041960863 * ldt - 0.7034186147 * mdt + 1.7076147010 * sdt;
272 let b2 = -0.0041960863 * ldt2 - 0.7034186147 * mdt2 + 1.7076147010 * sdt2;
273
274 let u_b = b1 / (b1 * b1 - 0.5 * b * b2);
275 let t_b = -b * u_b;
276
277 let t_r = if u_r >= 0. { t_r } else { f32::MAX };
278 let t_g = if u_g >= 0. { t_g } else { f32::MAX };
279 let t_b = if u_b >= 0. { t_b } else { f32::MAX };
280
281 t += t_r.min(t_g.min(t_b));
282 }
283 }
284 }
285
286 t
287}
288
289impl Rgb<f32> {
290 pub fn gamut_clip_preserve_chroma(&self) -> Rgb<f32> {
291 if self.r < 1. && self.g < 1. && self.b < 1. && self.r > 0. && self.g > 0. && self.b > 0. {
292 return *self;
293 }
294
295 let lab: OkLab<f32> = self.to_oklab();
296
297 let l = lab.l;
298 let eps = 0.00001;
299 let c = eps.max((lab.a * lab.a + lab.b * lab.b).sqrt());
300 let a = lab.a / c;
301 let b = lab.b / c;
302
303 let l0 = clamp(l, 0., 1.);
304
305 let t = find_gamut_intersection(a, b, l, c, l0);
306 let l_clipped = l0 * (1. - t) + t * l;
307 let c_clipped = t * c;
308
309 OkLab{ l: l_clipped, a: c_clipped * a, b: c_clipped * b }.to_rgb()
310 }
311
312 pub fn gamut_clip_project_to_0_5(&self) -> Rgb<f32> {
313 if self.r < 1. && self.g < 1. && self.b < 1. && self.r > 0. && self.g > 0. && self.b > 0. {
314 return *self;
315 }
316
317 let lab = self.to_oklab::<f32>();
318
319 let l = lab.l;
320 let eps = 0.00001;
321 let c = eps.max(lab.chromacity());
322 let a_ = lab.a / c;
323 let b_ = lab.b / c;
324
325 let l0 = 0.5;
326
327 let t = find_gamut_intersection(a_, b_, l, c, l0);
328 let l_clipped = l0 * (1. - t) + t * l;
329 let c_clipped = t * c;
330
331 OkLab{ l: l_clipped, a: c_clipped * a_, b: c_clipped * b_ }.to_rgb()
332 }
333
334 pub fn gamut_clip_project_to_l_cusp(&self) -> Rgb<f32> {
335 if self.r < 1. && self.g < 1. && self.b < 1. && self.r > 0. && self.g > 0. && self.b > 0. {
336 return *self;
337 }
338
339 let lab = self.to_oklab::<f32>();
340
341 let l = lab.l;
342 let eps = 0.00001;
343 let c = eps.max(lab.chromacity());
344 let a = lab.a / c;
345 let b = lab.b / c;
346
347 let cusp = find_cusp(a, b);
349
350 let l0 = cusp.l;
351
352 let t = find_gamut_intersection(a, b, l, c, l0);
353
354 let l_clipped = l0 * (1. - t) + t * l;
355 let c_clipped = t * c;
356
357 OkLab{ l: l_clipped, a: c_clipped * a, b: c_clipped * b }.to_rgb()
358 }
359
360 pub fn gamut_clip_adaptive_l0_0_5(&self) -> Rgb<f32> {
361 self.gamut_clip_adaptive_l0_0_5_alpha(0.05)
362 }
363
364 pub fn gamut_clip_adaptive_l0_0_5_alpha(&self, alpha: f32) -> Rgb<f32> {
365 if self.r < 1. && self.g < 1. && self.b < 1. && self.r > 0. && self.g > 0. && self.b > 0. {
366 return *self;
367 }
368
369 let lab = self.to_oklab::<f32>();
370
371 let l = lab.l;
372 let eps = 0.00001;
373 let c = eps.max(lab.chromacity());
374 let a = lab.a / c;
375 let b = lab.b / c;
376
377 let ld = l - 0.5;
378 let e1 = 0.5 + ld.abs() + alpha * c;
379 let l0 = 0.5*(1. + ld.signum()*(e1 - (e1*e1 - 2. * ld.abs()).sqrt()));
380
381 let t = find_gamut_intersection(a, b, l, c, l0);
382 let l_clipped = l0 * (1. - t) + t * l;
383 let c_clipped = t * c;
384
385 OkLab{ l: l_clipped, a: c_clipped * a, b: c_clipped * b }.to_rgb()
386 }
387
388 pub fn gamut_clip_adaptive_l0_l_cusp(&self) -> Rgb<f32> {
389 self.gamut_clip_adaptive_l0_l_cusp_alpha(0.05)
390 }
391
392 pub fn gamut_clip_adaptive_l0_l_cusp_alpha(&self, alpha:f32) -> Rgb<f32> {
393 if self.r < 1. && self.g < 1. && self.b < 1. && self.r > 0. && self.g > 0. && self.b > 0. {
394 return *self;
395 }
396
397 let lab = self.to_oklab::<f32>();
398
399 let l = lab.l;
400 let eps = 0.00001;
401 let c = eps.max(lab.chromacity());
402 let a = lab.a / c;
403 let b = lab.b / c;
404
405 let cusp = find_cusp(a, b);
407
408 let ld = l - cusp.l;
409 let k = 2. * if ld > 0. { 1. - cusp.l } else { cusp.l };
410
411 let e1 = 0.5*k + ld.abs() + alpha * c/k;
412 let l0 = cusp.l + 0.5 * (ld.signum() * (e1 - (e1 * e1 - 2. * k * ld.abs()).sqrt()));
413
414 let t = find_gamut_intersection(a, b, l, c, l0);
415 let l_clipped = l0 * (1. - t) + t * l;
416 let c_clipped = t * c;
417
418 OkLab{ l: l_clipped, a: c_clipped * a, b: c_clipped * b }.to_rgb()
419 }
420}
421
422struct ST { s: f32, t: f32 }
423
424impl LC {
425 fn to_st(&self) -> ST {
426 let l = self.l;
427 let c = self.c;
428 ST { s: c / l, t: c / (1. - l) }
429 }
430}
431
432pub struct OkHsv {
433 pub h: angle::Deg<f32>,
434 pub s: f32,
435 pub v: f32,
436}
437
438fn toe_inv(x: f32) -> f32
439{
440 const K1: f32 = 0.206;
441 const K2: f32 = 0.03;
442 const K3: f32 = (1. + K1) / (1. + K2);
443 (x * x + K1 * x) / (K3 * (x + K2))
444}
445
446impl ToRgb for OkHsv {
447 type Standard = Srgb;
448
449 fn to_rgb<U:Channel>(&self) -> crate::Rgb<U, Self::Standard> {
450 let h = self.h;
451 let s = self.s;
452 let v = self.v;
453
454 let a = h.cos();
455 let b = h.sin();
456
457 let cusp = find_cusp(a, b);
458 let st_max = cusp.to_st();
459 let s_max = st_max.s;
460 let t_max = st_max.t;
461 let s_0 = 0.5;
462 let k = 1. - s_0 / s_max;
463
464 let l_v = 1. - s * s_0 / (s_0 + t_max - t_max * k * s);
468 let c_v = s * t_max * s_0 / (s_0 + t_max - t_max * k * s);
469
470 let l = v * l_v;
471 let c = v * c_v;
472
473 let l_vt = toe_inv(l_v);
475 let c_vt = c_v * l_vt / l_v;
476
477 let l_new = toe_inv(l);
478 let c = c * l_new / l;
479 let l = l_new;
480
481 let rgb_scale = OkLab{ l: l_vt, a: a * c_vt, b: b * c_vt }.to_rgb::<f32>();
482 let scale_l = (1. / rgb_scale.r.max(rgb_scale.g).max(rgb_scale.b.max(0.))).cbrt();
483
484 let l = l * scale_l;
485 let c = c * scale_l;
486
487 OkLab{ l, a: c * a, b: c * b }.to_rgb()
488 }
489}
490
491pub struct OkHsl {
492 pub h: angle::Deg<f32>,
493 pub s: f32,
494 pub l: f32,
495}
496
497fn get_st_mid(a_: f32, b_: f32) -> ST
501{
502 let s = 0.11516993 + 1. / (
503 7.44778970 + 4.15901240 * b_
504 + a_ * (-2.19557347 + 1.75198401 * b_
505 + a_ * (-2.13704948 - 10.02301043 * b_
506 + a_ * (-4.24894561 + 5.38770819 * b_ + 4.69891013 * a_
507 )))
508 );
509
510 let t = 0.11239642 + 1. / (
511 1.61320320 - 0.68124379 * b_
512 + a_ * (0.40370612 + 0.90148123 * b_
513 + a_ * (-0.27087943 + 0.61223990 * b_
514 + a_ * (0.00299215 - 0.45399568 * b_ - 0.14661872 * a_
515 )))
516 );
517
518 ST { s, t }
519}
520
521struct Cs { c_0: f32, c_mid: f32, c_max: f32 }
522impl OkLab<f32> {
523 fn get_cs(self) -> Cs {
524 let cusp = find_cusp(self.a, self.b);
525
526 let c_max = find_gamut_intersection_cusp(self.a, self.b, self.l, 1., self.l, cusp);
527 let st_max = cusp.to_st();
528
529 let k = c_max / ((self.l * st_max.s).min(1. - self.l) * st_max.t);
531
532 let c_mid = {
533 let st_mid = get_st_mid(self.a, self.b);
534
535 let c_a = self.l * st_mid.s;
537 let c_b = (1. - self.l) * st_mid.t;
538 0.9 * k * (1. / (1. / (c_a * c_a * c_a * c_a) + 1. / (c_b * c_b * c_b * c_b))).sqrt().sqrt()
539 };
540
541 let c_0 = {
542 let c_a = self.l * 0.4;
544 let c_b = (1. - self.l) * 0.8;
545
546 (1. / (1. / (c_a * c_a) + 1. / (c_b * c_b))).sqrt()
548 };
549
550 Cs { c_0, c_mid, c_max }
551 }
552}
553
554impl ToRgb for OkHsl {
555 type Standard = Srgb;
556
557 fn to_rgb<U:Channel>(&self) -> crate::Rgb<U, Self::Standard> {
558 let h = self.h;
559 let s = self.s;
560 let l = self.l;
561
562 if l == 1.0 {
563 OkLab { l: 1., a: 1., b: 1. };
564 }else if l == 0. {
565 OkLab { l: 0., a: 0., b: 0. };
566 }
567
568 let a = h.cos();
569 let b = h.sin();
570 let l = toe_inv(l);
571
572 let cs = OkLab{l, a, b}.get_cs();
573 let c_0 = cs.c_0;
574 let c_mid = cs.c_mid;
575 let c_max = cs.c_max;
576
577 let mid = 0.8;
583 let mid_inv = 1.25;
584
585 let chroma = if s < mid {
586 let t = mid_inv * s;
587
588 let k_1 = mid * c_0;
589 let k_2 = 1. - k_1 / c_mid;
590
591 t * k_1 / (1. - k_2 * t)
592 }else{
593 let t = (s - mid)/ (1. - mid);
594
595 let k_0 = c_mid;
596 let k_1 = (1. - mid) * c_mid * c_mid * mid_inv * mid_inv / c_0;
597 let k_2 = 1. - (k_1) / (c_max - c_mid);
598
599 k_0 + t * k_1 / (1. - k_2 * t)
600 };
601
602 OkLab{ l, a: chroma * a, b: chroma * b }.to_rgb()
603 }
604}
605
606
607
608#[test]
609fn test_range_norm() {
610 use ToRgb;
611
612 for r in 0u8..=255 {
613 for g in 0u8..=255 {
614 for b in 0u8..=255 {
615 let rgb: crate::Rgb<f32> = crate::rgb!(r, g, b).to_rgb();
616 let oklab: OkLab<f32> = rgb.to_oklab();
617 assert!(oklab.l >= -0.000001);
618 assert!(oklab.l <= 1.000001);
619 assert!(oklab.chromacity() >= -0.000001);
620 assert!(oklab.chromacity() <= 1.000001);
621 }
622 }
623 }
624}
625
626#[test]
627fn test_symmetric_u8() {
628 for r in 0u8..=255 {
629 for g in 0u8..=255 {
630 for b in 0u8..=255 {
631 let rgb: Rgb<f32> = rgb!(r, g, b).to_rgb();
632 let oklab: OkLab<f32> = rgb.to_oklab();
633 let rgb_back: Rgb<f32> = oklab.to_rgb();
634 assert!((rgb.r - rgb_back.r).abs() <= 0.00015, "rgb.r {} rgb_back.r {} diff {}", rgb.r, rgb_back.r, (rgb.r - rgb_back.r));
635 assert!((rgb.g - rgb_back.g).abs() <= 0.00015, "rgb.g {} rgb_back.g {} diff {}", rgb.g, rgb_back.g, (rgb.g - rgb_back.g));
636 assert!((rgb.b - rgb_back.b).abs() <= 0.00015, "rgb.b {} rgb_back.b {} diff {}", rgb.b, rgb_back.b, (rgb.b - rgb_back.b));
637 }
638 }
639 }
640}
641
642#[test]
643fn test_symmetric_hcl_u8() {
644 for r in 0u8..=255 {
645 for g in 0u8..=255 {
646 for b in 0u8..=255 {
647 let rgb: Rgb<f32> = rgb!(r, g, b).to_rgb();
648 let oklab: OkLab<f32> = rgb.to_oklab();
649 let hue = oklab.hue();
650 let luma = oklab.luma();
651 let chroma = oklab.chromacity();
652 let oklabback = OkLab::from_hcl(hue, chroma, luma);
653 let rgb_back: Rgb<f32> = oklabback.to_rgb();
654 assert!((rgb.r - rgb_back.r).abs() <= 0.00015, "rgb.r {} rgb_back.r {} diff {}", rgb.r, rgb_back.r, (rgb.r - rgb_back.r));
655 assert!((rgb.g - rgb_back.g).abs() <= 0.00015, "rgb.g {} rgb_back.g {} diff {}", rgb.g, rgb_back.g, (rgb.g - rgb_back.g));
656 assert!((rgb.b - rgb_back.b).abs() <= 0.00015, "rgb.b {} rgb_back.b {} diff {}", rgb.b, rgb_back.b, (rgb.b - rgb_back.b));
657 }
658 }
659 }
660}
661
662#[test]
663fn test_ranges() {
664 let rgb: Rgb<f32> = crate::rgb_linear!(0.293055, 0.979167, 0.577595).to_standard();
665 let lab = rgb.to_oklab();
666 let hue = lab.hue();
667 let luma = lab.luma();
668 let chroma = lab.chromacity();
669 let hue_offset = 76.279404;
670 let hue = (hue + angle::Deg(hue_offset).to_rad()).wrap();
671 let labback = OkLab::from_hcl(hue, chroma, luma);
672 let rgb_back: Rgb<f32> = labback.to_rgb();
673 assert!(rgb_back.r >= -0.000001, "rgb.r {}", rgb_back.r);
674 assert!(rgb_back.g <= 1.000001, "rgb.r {}", rgb_back.r);
675 assert!(rgb_back.g >= -0.000001, "rgb.g {}", rgb_back.g);
676 assert!(rgb_back.g <= 1.000001, "rgb.g {}", rgb_back.g);
677 assert!(rgb_back.b >= -0.000001, "rgb.b {}", rgb_back.b);
678 assert!(rgb_back.b <= 1.000001, "rgb.b {}", rgb_back.b);
679}