1#[cfg(all(not(feature = "std"), feature = "libm"))]
2#[allow(unused_imports)]
3use crate::utils::no_std::FloatExt;
4use crate::{utils::math::matrix_multiply, Error};
5#[cfg(not(feature = "std"))]
6use alloc::{
7 format,
8 string::{String, ToString},
9};
10use core::{fmt, str::FromStr};
11#[cfg(feature = "serde")]
12use serde::Serialize;
13#[cfg(feature = "std")]
14use std::{
15 format,
16 string::{String, ToString},
17};
18
19pub const SRGB_TO_XYZ: [[f64; 3]; 3] = [
20 [0.41233895, 0.35762064, 0.18051042],
21 [0.2126, 0.7152, 0.0722],
22 [0.01932141, 0.11916382, 0.95034478],
23];
24pub const XYZ_TO_SRGB: [[f64; 3]; 3] = [
25 [
26 3.2413774792388685,
27 -1.5376652402851851,
28 -0.49885366846268053,
29 ],
30 [-0.9691452513005321, 1.8758853451067872, 0.04156585616912061],
31 [
32 0.05562093689691305,
33 -0.20395524564742123,
34 1.0571799111220335,
35 ],
36];
37pub const WHITE_POINT_D65: [f64; 3] = [95.047, 100.0, 108.883];
38
39#[derive(Debug, Default, Clone, Copy)]
40#[cfg_attr(feature = "serde", derive(Serialize))]
41pub struct Rgb {
42 pub red: u8,
43 pub green: u8,
44 pub blue: u8,
45}
46
47#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
64#[cfg_attr(feature = "serde", derive(Serialize))]
65pub struct Argb {
66 pub alpha: u8,
67 pub red: u8,
68 pub green: u8,
69 pub blue: u8,
70}
71
72#[derive(Debug, Default, Clone, Copy)]
73#[cfg_attr(feature = "serde", derive(Serialize))]
74pub struct LinearRgb {
75 pub red: f64,
76 pub green: f64,
77 pub blue: f64,
78}
79
80#[derive(Debug, Default, Clone, Copy)]
81#[cfg_attr(feature = "serde", derive(Serialize))]
82pub struct Xyz {
83 pub x: f64,
84 pub y: f64,
85 pub z: f64,
86}
87
88#[derive(Debug, Default, Clone, Copy)]
89#[cfg_attr(feature = "serde", derive(Serialize))]
90pub struct Lab {
91 pub l: f64,
92 pub a: f64,
93 pub b: f64,
94}
95
96impl From<Rgb> for Argb {
98 fn from(Rgb { red, green, blue }: Rgb) -> Self {
99 Self {
100 alpha: 255,
101 red,
102 green,
103 blue,
104 }
105 }
106}
107
108impl From<LinearRgb> for Argb {
110 fn from(linear: LinearRgb) -> Self {
111 let r = delinearized(linear.red);
112 let g = delinearized(linear.green);
113 let b = delinearized(linear.blue);
114
115 Rgb::new(r, g, b).into()
116 }
117}
118
119impl From<Xyz> for Argb {
121 fn from(Xyz { x, y, z }: Xyz) -> Self {
122 let matrix = XYZ_TO_SRGB;
123
124 let (linear_r, linear_g, linear_b) = (
125 matrix[0][2].mul_add(z, matrix[0][0].mul_add(x, matrix[0][1] * y)),
126 matrix[1][2].mul_add(z, matrix[1][0].mul_add(x, matrix[1][1] * y)),
127 matrix[2][2].mul_add(z, matrix[2][0].mul_add(x, matrix[2][1] * y)),
128 );
129
130 let r = delinearized(linear_r);
131 let g = delinearized(linear_g);
132 let b = delinearized(linear_b);
133
134 Rgb::new(r, g, b).into()
135 }
136}
137
138impl From<Argb> for Xyz {
140 fn from(
141 Argb {
142 alpha: _,
143 red,
144 green,
145 blue,
146 }: Argb,
147 ) -> Self {
148 let r = linearized(red);
149 let g = linearized(green);
150 let b = linearized(blue);
151
152 let [x, y, z] = matrix_multiply([r, g, b], SRGB_TO_XYZ);
153
154 Self { x, y, z }
155 }
156}
157
158impl From<Lab> for Argb {
160 fn from(Lab { l, a, b }: Lab) -> Self {
161 let white_point = WHITE_POINT_D65;
162
163 let fy = (l + 16.0) / 116.0;
164 let fx = a / 500.0 + fy;
165 let fz = fy - b / 200.0;
166
167 let x_normalized = lab_invf(fx);
168 let y_normalized = lab_invf(fy);
169 let z_normalized = lab_invf(fz);
170
171 let x = x_normalized * white_point[0];
172 let y = y_normalized * white_point[1];
173 let z = z_normalized * white_point[2];
174
175 Xyz::new(x, y, z).into()
176 }
177}
178
179impl From<Argb> for Lab {
180 fn from(
181 Argb {
182 alpha: _,
183 red,
184 green,
185 blue,
186 }: Argb,
187 ) -> Self {
188 let linear_r = linearized(red);
189 let linear_g = linearized(green);
190 let linear_b = linearized(blue);
191
192 let matrix = SRGB_TO_XYZ;
193
194 let (x, y, z) = (
195 matrix[0][2].mul_add(
196 linear_b,
197 matrix[0][0].mul_add(linear_r, matrix[0][1] * linear_g),
198 ),
199 matrix[1][2].mul_add(
200 linear_b,
201 matrix[1][0].mul_add(linear_r, matrix[1][1] * linear_g),
202 ),
203 matrix[2][2].mul_add(
204 linear_b,
205 matrix[2][0].mul_add(linear_r, matrix[2][1] * linear_g),
206 ),
207 );
208
209 let white_point = WHITE_POINT_D65;
210
211 let x_normalized = x / white_point[0];
212 let y_normalized = y / white_point[1];
213 let z_normalized = z / white_point[2];
214
215 let fx = lab_f(x_normalized);
216 let fy = lab_f(y_normalized);
217 let fz = lab_f(z_normalized);
218
219 let l = 116.0f64.mul_add(fy, -16.0);
220 let a = 500.0 * (fx - fy);
221 let b = 200.0 * (fy - fz);
222
223 Self { l, a, b }
224 }
225}
226
227const HASH: char = '#';
228
229impl FromStr for Argb {
230 type Err = Error;
231
232 fn from_str(hex: &str) -> Result<Self, Self::Err> {
233 let hex = hex.strip_prefix(HASH).unwrap_or(hex);
234
235 if ![3, 6, 8].contains(&hex.len()) {
236 return Err(Error::ParseRGB);
237 }
238
239 let hex_str = if hex.len() == 3 {
240 format!(
241 "FF{a}{a}{b}{b}{c}{c}",
242 a = hex.get(..1).unwrap(),
243 b = hex.get(1..2).unwrap(),
244 c = hex.get(2..3).unwrap()
245 )
246 } else if hex.len() == 6 {
247 format!("FF{hex}")
248 } else {
249 hex.to_string()
250 };
251
252 let hex_digit = u32::from_str_radix(&hex_str, 16).map_err(|_| Error::ParseRGB)?;
253
254 Ok(Self::from_u32(hex_digit))
255 }
256}
257
258impl Xyz {
259 pub const fn new(x: f64, y: f64, z: f64) -> Self {
260 Self { x, y, z }
261 }
262}
263
264impl Lab {
265 pub const fn new(l: f64, a: f64, b: f64) -> Self {
266 Self { l, a, b }
267 }
268}
269
270impl Rgb {
271 pub const fn new(red: u8, green: u8, blue: u8) -> Self {
272 Self { red, green, blue }
273 }
274}
275
276impl Argb {
277 pub const fn new(alpha: u8, red: u8, green: u8, blue: u8) -> Self {
278 Self {
279 alpha,
280 red,
281 green,
282 blue,
283 }
284 }
285
286 pub const fn from_u32(value: u32) -> Self {
287 Self {
288 alpha: ((value >> 24) & 0xFF) as u8,
289 red: ((value >> 16) & 0xFF) as u8,
290 green: ((value >> 8) & 0xFF) as u8,
291 blue: ((value) & 0xFF) as u8,
292 }
293 }
294
295 pub fn from_lstar(lstar: f64) -> Self {
301 let y = y_from_lstar(lstar);
302 let component = delinearized(y);
303
304 Rgb::new(component, component, component).into()
305 }
306
307 pub fn as_lstar(&self) -> f64 {
313 116.0f64.mul_add(lab_f(Xyz::from(*self).y / 100.0), -16.0)
314 }
315
316 fn hex(number: u8) -> String {
317 let string = format!("{number:x}");
318
319 if string.len() == 1 {
320 String::from("0") + &string
321 } else {
322 string
323 }
324 }
325
326 pub fn to_hex(&self) -> String {
327 format!(
328 "{}{}{}",
329 Self::hex(self.red),
330 Self::hex(self.green),
331 Self::hex(self.blue)
332 )
333 }
334
335 pub fn to_hex_with_pound(&self) -> String {
336 format!(
337 "#{}{}{}",
338 Self::hex(self.red),
339 Self::hex(self.green),
340 Self::hex(self.blue)
341 )
342 }
343}
344
345impl fmt::Display for Argb {
346 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347 write!(f, "{}", self.to_hex_with_pound())
348 }
349}
350
351pub fn y_from_lstar(lstar: f64) -> f64 {
362 100.0 * lab_invf((lstar + 16.0) / 116.0)
363}
364
365pub fn lstar_from_y(y: f64) -> f64 {
376 lab_f(y / 100.0).mul_add(116.0, -16.0)
377}
378
379pub fn linearized(rgb_component: u8) -> f64 {
385 let normalized = f64::from(rgb_component) / 255.0;
386
387 if normalized <= 0.040449936 {
388 normalized / 12.92 * 100.0
389 } else {
390 ((normalized + 0.055) / 1.055).powf(2.4) * 100.0
391 }
392}
393
394pub fn delinearized(rgb_component: f64) -> u8 {
400 let normalized = rgb_component / 100.0;
401 let delinearized = if normalized <= 0.0031308 {
402 normalized * 12.92
403 } else {
404 1.055f64.mul_add(normalized.powf(1.0 / 2.4), -0.055)
405 };
406
407 ((delinearized * 255.0).round() as u8).clamp(0, 255)
408}
409
410fn lab_f(t: f64) -> f64 {
411 let e = 216.0 / 24389.0;
412 let kappa: f64 = 24389.0 / 27.0;
413
414 if t > e {
415 t.cbrt()
416 } else {
417 kappa.mul_add(t, 16.0) / 116.0
418 }
419}
420
421fn lab_invf(ft: f64) -> f64 {
422 let e = 216.0 / 24389.0;
423 let kappa = 24389.0 / 27.0;
424 let ft3 = ft * ft * ft;
425
426 if ft3 > e {
427 ft3
428 } else {
429 116.0f64.mul_add(ft, -16.0) / kappa
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use super::Lab;
436 use crate::color::{delinearized, linearized, lstar_from_y, y_from_lstar, Argb, Xyz};
437 #[cfg(not(feature = "std"))]
438 use alloc::vec::Vec;
439 use float_cmp::assert_approx_eq;
440 #[cfg(feature = "std")]
441 use std::vec::Vec;
442
443 fn _range(start: f64, stop: f64, case_count: i64) -> Vec<f64> {
444 let step_size = (stop - start) / (case_count as f64 - 1.0);
445
446 (0..case_count)
447 .map(|index| step_size.mul_add(index as f64, start))
448 .collect()
449 }
450
451 fn rgb_range() -> Vec<u8> {
452 _range(0.0, 255.0, 8)
453 .into_iter()
454 .map(|element| element.round() as u8)
455 .collect()
456 }
457
458 fn full_rgb_range() -> Vec<u8> {
459 (0..=255).collect()
460 }
461
462 #[test]
463 fn test_range_integrity() {
464 let range = _range(3.0, 9999.0, 1234);
465
466 for (i, value) in range.into_iter().enumerate().take(1234) {
467 assert_approx_eq!(
468 f64,
469 value,
470 8.1070559611f64.mul_add(i as f64, 3.0),
471 epsilon = 1e-5
472 );
473 }
474 }
475
476 #[test]
477 fn test_yto_lstar_to_y() {
478 for y in _range(0.0, 100.0, 1001) {
479 let result = y_from_lstar(lstar_from_y(y));
480
481 assert_approx_eq!(f64, result, y, epsilon = 1e-5);
482 }
483 }
484
485 #[test]
486 fn test_lstar_to_yto_lstar() {
487 for lstar in _range(0.0, 100.0, 1001) {
488 let result = lstar_from_y(y_from_lstar(lstar));
489
490 assert_approx_eq!(f64, result, lstar, epsilon = 1e-5);
491 }
492 }
493
494 #[test]
495 fn test_yfrom_lstar() {
496 assert_approx_eq!(f64, y_from_lstar(0.0), 0.0, epsilon = 1e-5);
497 assert_approx_eq!(f64, y_from_lstar(0.1), 0.0110705, epsilon = 1e-5);
498 assert_approx_eq!(f64, y_from_lstar(0.2), 0.0221411, epsilon = 1e-5);
499 assert_approx_eq!(f64, y_from_lstar(0.3), 0.0332116, epsilon = 1e-5);
500 assert_approx_eq!(f64, y_from_lstar(0.4), 0.0442822, epsilon = 1e-5);
501 assert_approx_eq!(f64, y_from_lstar(0.5), 0.0553528, epsilon = 1e-5);
502 assert_approx_eq!(f64, y_from_lstar(1.0), 0.1107056, epsilon = 1e-5);
503 assert_approx_eq!(f64, y_from_lstar(2.0), 0.2214112, epsilon = 1e-5);
504 assert_approx_eq!(f64, y_from_lstar(3.0), 0.3321169, epsilon = 1e-5);
505 assert_approx_eq!(f64, y_from_lstar(4.0), 0.4428225, epsilon = 1e-5);
506 assert_approx_eq!(f64, y_from_lstar(5.0), 0.5535282, epsilon = 1e-5);
507 assert_approx_eq!(f64, y_from_lstar(8.0), 0.8856451, epsilon = 1e-5);
508 assert_approx_eq!(f64, y_from_lstar(10.0), 1.1260199, epsilon = 1e-5);
509 assert_approx_eq!(f64, y_from_lstar(15.0), 1.9085832, epsilon = 1e-5);
510 assert_approx_eq!(f64, y_from_lstar(20.0), 2.9890524, epsilon = 1e-5);
511 assert_approx_eq!(f64, y_from_lstar(25.0), 4.4154767, epsilon = 1e-5);
512 assert_approx_eq!(f64, y_from_lstar(30.0), 6.2359055, epsilon = 1e-5);
513 assert_approx_eq!(f64, y_from_lstar(40.0), 11.2509737, epsilon = 1e-5);
514 assert_approx_eq!(f64, y_from_lstar(50.0), 18.4186518, epsilon = 1e-5);
515 assert_approx_eq!(f64, y_from_lstar(60.0), 28.1233342, epsilon = 1e-5);
516 assert_approx_eq!(f64, y_from_lstar(70.0), 40.7494157, epsilon = 1e-5);
517 assert_approx_eq!(f64, y_from_lstar(80.0), 56.6812907, epsilon = 1e-5);
518 assert_approx_eq!(f64, y_from_lstar(90.0), 76.3033539, epsilon = 1e-5);
519 assert_approx_eq!(f64, y_from_lstar(95.0), 87.6183294, epsilon = 1e-5);
520 assert_approx_eq!(f64, y_from_lstar(99.0), 97.4360239, epsilon = 1e-5);
521 assert_approx_eq!(f64, y_from_lstar(100.0), 100.0, epsilon = 1e-5);
522 }
523
524 #[test]
525 fn test_lstar_from_y() {
526 assert_approx_eq!(f64, lstar_from_y(0.0), 0.0, epsilon = 1e-5);
527 assert_approx_eq!(f64, lstar_from_y(0.1), 0.9032962, epsilon = 1e-5);
528 assert_approx_eq!(f64, lstar_from_y(0.2), 1.8065925, epsilon = 1e-5);
529 assert_approx_eq!(f64, lstar_from_y(0.3), 2.7098888, epsilon = 1e-5);
530 assert_approx_eq!(f64, lstar_from_y(0.4), 3.6131851, epsilon = 1e-5);
531 assert_approx_eq!(f64, lstar_from_y(0.5), 4.5164814, epsilon = 1e-5);
532 assert_approx_eq!(f64, lstar_from_y(0.8856451), 8.0, epsilon = 1e-5);
533 assert_approx_eq!(f64, lstar_from_y(1.0), 8.9914424, epsilon = 1e-5);
534 assert_approx_eq!(f64, lstar_from_y(2.0), 15.4872443, epsilon = 1e-5);
535 assert_approx_eq!(f64, lstar_from_y(3.0), 20.0438970, epsilon = 1e-5);
536 assert_approx_eq!(f64, lstar_from_y(4.0), 23.6714419, epsilon = 1e-5);
537 assert_approx_eq!(f64, lstar_from_y(5.0), 26.7347653, epsilon = 1e-5);
538 assert_approx_eq!(f64, lstar_from_y(10.0), 37.8424304, epsilon = 1e-5);
539 assert_approx_eq!(f64, lstar_from_y(15.0), 45.6341970, epsilon = 1e-5);
540 assert_approx_eq!(f64, lstar_from_y(20.0), 51.8372115, epsilon = 1e-5);
541 assert_approx_eq!(f64, lstar_from_y(25.0), 57.0754208, epsilon = 1e-5);
542 assert_approx_eq!(f64, lstar_from_y(30.0), 61.6542222, epsilon = 1e-5);
543 assert_approx_eq!(f64, lstar_from_y(40.0), 69.4695307, epsilon = 1e-5);
544 assert_approx_eq!(f64, lstar_from_y(50.0), 76.0692610, epsilon = 1e-5);
545 assert_approx_eq!(f64, lstar_from_y(60.0), 81.8381891, epsilon = 1e-5);
546 assert_approx_eq!(f64, lstar_from_y(70.0), 86.9968642, epsilon = 1e-5);
547 assert_approx_eq!(f64, lstar_from_y(80.0), 91.6848609, epsilon = 1e-5);
548 assert_approx_eq!(f64, lstar_from_y(90.0), 95.9967686, epsilon = 1e-5);
549 assert_approx_eq!(f64, lstar_from_y(95.0), 98.0335184, epsilon = 1e-5);
550 assert_approx_eq!(f64, lstar_from_y(99.0), 99.6120372, epsilon = 1e-5);
551 assert_approx_eq!(f64, lstar_from_y(100.0), 100.0, epsilon = 1e-5);
552 }
553
554 #[test]
555 fn test_ycontinuity() {
556 let epsilon = 1e-6;
557 let delta = 1e-8;
558 let left = 8.0 - delta;
559 let mid = 8.0;
560 let right = 8.0 + delta;
561
562 assert_approx_eq!(
563 f64,
564 y_from_lstar(left),
565 y_from_lstar(mid),
566 epsilon = epsilon
567 );
568 assert_approx_eq!(
569 f64,
570 y_from_lstar(right),
571 y_from_lstar(mid),
572 epsilon = epsilon
573 );
574 }
575
576 #[test]
577 fn test_rgb_to_xyz_to_rgb() {
578 for r in rgb_range() {
579 for g in rgb_range() {
580 for b in rgb_range() {
581 let argb = Argb::new(255, r, g, b);
582 let xyz = Xyz::from(argb);
583 let converted = Argb::from(xyz);
584
585 assert_approx_eq!(f64, f64::from(converted.red), f64::from(r), epsilon = 1.5);
586 assert_approx_eq!(f64, f64::from(converted.green), f64::from(g), epsilon = 1.5);
587 assert_approx_eq!(f64, f64::from(converted.blue), f64::from(b), epsilon = 1.5);
588 }
589 }
590 }
591 }
592
593 #[test]
594 fn test_rgb_to_lab_to_rgb() {
595 for r in rgb_range() {
596 for g in rgb_range() {
597 for b in rgb_range() {
598 let argb = Argb::new(255, r, g, b);
599 let lab = Lab::from(argb);
600 let converted = Argb::from(lab);
601
602 assert_approx_eq!(f64, f64::from(converted.red), f64::from(r), epsilon = 1.5);
603 assert_approx_eq!(f64, f64::from(converted.green), f64::from(g), epsilon = 1.5);
604 assert_approx_eq!(f64, f64::from(converted.blue), f64::from(b), epsilon = 1.5);
605 }
606 }
607 }
608 }
609
610 #[test]
611 fn test_rgb_to_lstar_to_rgb() {
612 let full_rgb_range = full_rgb_range();
613
614 for component in full_rgb_range {
615 let argb = Argb::new(255, component, component, component);
616 let lstar = argb.as_lstar();
617 let converted = Argb::from_lstar(lstar);
618
619 assert_eq!(converted, argb);
620 }
621 }
622
623 #[test]
624 fn test_rgb_to_lstar_to_ycommutes() {
625 for r in rgb_range() {
626 for g in rgb_range() {
627 for b in rgb_range() {
628 let argb = Argb::new(255, r, g, b);
629 let lstar = argb.as_lstar();
630 let y = y_from_lstar(lstar);
631 let y2 = Xyz::from(argb).y;
632
633 assert_approx_eq!(f64, y, y2, epsilon = 1e-5);
634 }
635 }
636 }
637 }
638
639 #[test]
640 fn test_lstar_to_rgb_to_ycommutes() {
641 for lstar in _range(0.0, 100.0, 1001) {
642 let argb = Argb::from_lstar(lstar);
643 let y = Xyz::from(argb).y;
644 let y2 = y_from_lstar(lstar);
645
646 assert_approx_eq!(f64, y, y2, epsilon = 1.0);
647 }
648 }
649
650 #[test]
651 fn test_linearize_delinearize() {
652 let full_rgb_range = full_rgb_range();
653
654 for rgb_component in full_rgb_range {
655 let converted = delinearized(linearized(rgb_component));
656
657 assert_eq!(converted, rgb_component);
658 }
659 }
660}