1mod ansi16;
2mod ansi256;
3mod cmyk;
4mod error;
5mod gamut;
6mod hsl;
7mod hsv;
8mod hue;
9mod lab;
10mod lchab;
11mod lchuv;
12mod luv;
13mod oklab;
14mod oklch;
15mod rgb;
16mod white_point;
17mod xyz;
18
19use std::{
20 fmt,
21 fmt::{Display, Formatter},
22 marker::PhantomData,
23 str::FromStr,
24};
25
26pub use ansi16::Ansi16;
27pub use ansi256::Ansi256;
28pub use cmyk::CMYK;
29pub use gamut::Gamut;
30pub use hsl::HSL;
31pub use hsv::HSV;
32pub use hue::Hue;
33pub(crate) use lab::xyz_to_lab;
34pub use lab::Lab;
35pub use lchab::LCHab;
36pub use lchuv::LCHuv;
37pub use luv::Luv;
38use num_traits::clamp;
39pub use oklab::Oklab;
40pub use oklch::Oklch;
41pub use rgb::RGB;
42pub use white_point::*;
43pub(crate) use xyz::rgb_to_xyz;
44pub use xyz::XYZ;
45
46use crate::{color::error::ColorError, math::FloatNumber};
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct Color<T, W = D65>
77where
78 T: FloatNumber,
79{
80 pub(super) l: T,
81 pub(super) a: T,
82 pub(super) b: T,
83 _marker: PhantomData<W>,
84}
85
86impl<T, W> Color<T, W>
87where
88 T: FloatNumber,
89 W: WhitePoint,
90{
91 #[must_use]
101 pub(crate) fn new(l: T, a: T, b: T) -> Self {
102 Self {
103 l,
104 a,
105 b,
106 _marker: PhantomData,
107 }
108 }
109
110 #[must_use]
115 pub fn is_light(&self) -> bool {
116 self.l > T::from_f32(50.0)
117 }
118
119 #[must_use]
124 pub fn is_dark(&self) -> bool {
125 !self.is_light()
126 }
127
128 #[must_use]
133 pub fn lightness(&self) -> T {
134 self.l
135 }
136
137 #[must_use]
142 pub fn chroma(&self) -> T {
143 (self.a.powi(2) + self.b.powi(2)).sqrt()
144 }
145
146 #[inline]
158 #[must_use]
159 pub fn delta_e(&self, other: &Self) -> T {
160 let delta_l = self.l - other.l;
161 let delta_a = self.a - other.a;
162 let delta_b = self.b - other.b;
163 (delta_l.powi(2) + delta_a.powi(2) + delta_b.powi(2)).sqrt()
164 }
165
166 #[must_use]
171 pub fn hue(&self) -> Hue<T> {
172 let degrees = self.b.atan2(self.a).to_degrees();
173 Hue::from_degrees(degrees)
174 }
175
176 #[must_use]
181 pub fn to_hex_string(&self) -> String {
182 let RGB { r, g, b } = self.to_rgb();
183 format!("#{r:02X}{g:02X}{b:02X}")
184 }
185
186 #[must_use]
191 pub fn to_rgb(&self) -> RGB {
192 RGB::from(&self.to_xyz())
193 }
194
195 #[must_use]
200 pub fn to_cmyk(&self) -> CMYK<T> {
201 CMYK::from(&self.to_rgb())
202 }
203
204 #[must_use]
209 pub fn to_hsl(&self) -> HSL<T> {
210 HSL::from(&self.to_rgb())
211 }
212
213 #[must_use]
218 pub fn to_hsv(&self) -> HSV<T> {
219 HSV::from(&self.to_rgb())
220 }
221
222 #[must_use]
227 pub fn to_xyz(&self) -> XYZ<T> {
228 XYZ::from(&self.to_lab())
229 }
230
231 #[must_use]
236 pub fn to_luv(&self) -> Luv<T, W> {
237 Luv::<T, W>::from(&self.to_xyz())
238 }
239
240 #[must_use]
245 pub fn to_lchuv(&self) -> LCHuv<T, W> {
246 LCHuv::<T, W>::from(&self.to_luv())
247 }
248
249 #[must_use]
254 pub fn to_lab(&self) -> Lab<T, W> {
255 Lab::<T, W>::new(self.l, self.a, self.b)
256 }
257
258 #[must_use]
263 pub fn to_lchab(&self) -> LCHab<T, W> {
264 LCHab::<T, W>::from(&self.to_lab())
265 }
266
267 #[must_use]
272 pub fn to_oklab(&self) -> Oklab<T> {
273 Oklab::from(&self.to_xyz())
274 }
275
276 #[must_use]
281 pub fn to_oklch(&self) -> Oklch<T> {
282 Oklch::from(&self.to_oklab())
283 }
284
285 #[must_use]
290 pub fn to_ansi16(&self) -> Ansi16 {
291 Ansi16::from(&self.to_rgb())
292 }
293
294 #[must_use]
299 pub fn to_ansi256(&self) -> Ansi256 {
300 Ansi256::from(&self.to_rgb())
301 }
302
303 #[must_use]
308 pub fn to_rgb_int(&self) -> u32 {
309 let rgb = self.to_rgb();
310 let r = rgb.r as u32;
311 let g = rgb.g as u32;
312 let b = rgb.b as u32;
313 (r << 16) | (g << 8) | b
314 }
315
316 #[must_use]
324 pub fn to_rgba_int(&self, alpha: u8) -> u32 {
325 let rgb = self.to_rgb();
326 let r = rgb.r as u32;
327 let g = rgb.g as u32;
328 let b = rgb.b as u32;
329 (r << 24) | (g << 16) | (b << 8) | alpha as u32
330 }
331
332 #[must_use]
341 pub fn mix(&self, other: &Self, fraction: T) -> Self {
342 let fraction = clamp(fraction, T::zero(), T::one());
343 Self {
344 l: self.l + fraction * (other.l - self.l),
345 a: self.a + fraction * (other.a - self.a),
346 b: self.b + fraction * (other.b - self.b),
347 _marker: PhantomData,
348 }
349 }
350}
351
352impl<T> Default for Color<T>
353where
354 T: FloatNumber,
355{
356 fn default() -> Self {
357 Self {
358 l: T::zero(),
359 a: T::zero(),
360 b: T::zero(),
361 _marker: PhantomData,
362 }
363 }
364}
365
366impl<T> Display for Color<T>
367where
368 T: FloatNumber,
369{
370 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
371 write!(
372 f,
373 "Color(l: {:.2}, a: {:.2}, b: {:.2})",
374 self.l, self.a, self.b
375 )
376 }
377}
378
379impl<T> From<u32> for Color<T>
380where
381 T: FloatNumber,
382{
383 fn from(value: u32) -> Self {
384 let r = (value >> 16) & 0xFF;
385 let g = (value >> 8) & 0xFF;
386 let b = value & 0xFF;
387 let (x, y, z) = rgb_to_xyz::<T>(r as u8, g as u8, b as u8);
388 let (l, a, b) = xyz_to_lab::<T, D65>(x, y, z);
389 Self::new(l, a, b)
390 }
391}
392
393impl<T> FromStr for Color<T>
394where
395 T: FloatNumber,
396{
397 type Err = ColorError;
398
399 fn from_str(s: &str) -> Result<Self, Self::Err> {
400 if !s.starts_with("#") {
401 return Err(ColorError::InvalidHexValue(s.to_string()));
402 }
403
404 let (r, g, b) = match s.len() {
405 4 => {
407 let r = u8::from_str_radix(&s[1..2].repeat(2), 16)
408 .map_err(|_| ColorError::InvalidHexValue(s.to_string()))?;
409 let g = u8::from_str_radix(&s[2..3].repeat(2), 16)
410 .map_err(|_| ColorError::InvalidHexValue(s.to_string()))?;
411 let b = u8::from_str_radix(&s[3..4].repeat(2), 16)
412 .map_err(|_| ColorError::InvalidHexValue(s.to_string()))?;
413 (r, g, b)
414 }
415 5 => {
417 let r = u8::from_str_radix(&s[1..2].repeat(2), 16)
418 .map_err(|_| ColorError::InvalidHexValue(s.to_string()))?;
419 let g = u8::from_str_radix(&s[2..3].repeat(2), 16)
420 .map_err(|_| ColorError::InvalidHexValue(s.to_string()))?;
421 let b = u8::from_str_radix(&s[3..4].repeat(2), 16)
422 .map_err(|_| ColorError::InvalidHexValue(s.to_string()))?;
423 let _ = u8::from_str_radix(&s[4..5].repeat(2), 16)
425 .map_err(|_| ColorError::InvalidHexValue(s.to_string()))?;
426 (r, g, b)
427 }
428 7 => {
430 let r = u8::from_str_radix(&s[1..3], 16)
431 .map_err(|_| ColorError::InvalidHexValue(s.to_string()))?;
432 let g = u8::from_str_radix(&s[3..5], 16)
433 .map_err(|_| ColorError::InvalidHexValue(s.to_string()))?;
434 let b = u8::from_str_radix(&s[5..7], 16)
435 .map_err(|_| ColorError::InvalidHexValue(s.to_string()))?;
436 (r, g, b)
437 }
438 9 => {
440 let r = u8::from_str_radix(&s[1..3], 16)
441 .map_err(|_| ColorError::InvalidHexValue(s.to_string()))?;
442 let g = u8::from_str_radix(&s[3..5], 16)
443 .map_err(|_| ColorError::InvalidHexValue(s.to_string()))?;
444 let b = u8::from_str_radix(&s[5..7], 16)
445 .map_err(|_| ColorError::InvalidHexValue(s.to_string()))?;
446 let _ = u8::from_str_radix(&s[7..9], 16)
448 .map_err(|_| ColorError::InvalidHexValue(s.to_string()))?;
449 (r, g, b)
450 }
451 _ => return Err(ColorError::InvalidHexValue(s.to_string())),
452 };
453
454 let (x, y, z) = rgb_to_xyz::<T>(r, g, b);
455 let (l, a, b) = xyz_to_lab::<T, D65>(x, y, z);
456 Ok(Self::new(l, a, b))
457 }
458}
459
460#[cfg(test)]
461mod tests {
462 use rstest::rstest;
463
464 use super::*;
465 use crate::assert_approx_eq;
466
467 #[test]
468 fn test_new() {
469 let actual: Color<f32> = Color::new(80.0, 0.0, 0.0);
471
472 assert_eq!(actual.l, 80.0);
474 assert_eq!(actual.a, 0.0);
475 assert_eq!(actual.b, 0.0);
476 }
477
478 #[rstest]
479 #[case((0.0, 0.0, 0.0), false)]
480 #[case((50.0, 0.0, 0.0), false)]
481 #[case((50.1, 0.0, 0.0), true)]
482 #[case((80.0, 0.0, 0.0), true)]
483 fn test_color_is_light(#[case] input: (f32, f32, f32), #[case] expected: bool) {
484 let color: Color<f32> = Color::new(input.0, input.1, input.2);
486 let actual = color.is_light();
487
488 assert_eq!(actual, expected);
490 }
491
492 #[rstest]
493 #[case((0.0, 0.0, 0.0), true)]
494 #[case((50.0, 0.0, 0.0), true)]
495 #[case((50.1, 0.0, 0.0), false)]
496 #[case((80.0, 0.0, 0.0), false)]
497 fn test_color_is_dark(#[case] input: (f32, f32, f32), #[case] expected: bool) {
498 let color: Color<f32> = Color::new(input.0, input.1, input.2);
500 let actual = color.is_dark();
501
502 assert_eq!(actual, expected);
504 }
505
506 #[test]
507 fn test_lightness() {
508 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
510 let actual = color.lightness();
511
512 assert_approx_eq!(actual, 91.1120);
514 }
515
516 #[test]
517 fn test_chroma() {
518 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
520 let actual = color.chroma();
521
522 assert_approx_eq!(actual, 50.120_117);
524 }
525
526 #[test]
527 fn test_delta_e() {
528 let color1: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
530 let color2: Color<f32> = Color::new(53.237_144, 80.088_320, 67.199_460);
531
532 let actual = color1.delta_e(&color2);
534
535 assert_approx_eq!(actual, 156.460388);
537 }
538
539 #[rstest]
540 #[case::black((0.0, 0.0, 0.0), 0.0)]
541 #[case::white((100.0, - 0.002_443, 0.011_384), 102.111_946)]
542 #[case::red((53.237_144, 80.088_320, 67.199_460), 39.998_900)]
543 #[case::green((87.735_535, - 86.183_550, 83.179_924), 136.016_020)]
544 #[case::blue((32.300_800, 79.194_260, - 107.868_910), 306.28503)]
545 #[case::cyan((91.114_750, - 48.080_950, - 14.142_858), 196.391_080,)]
546 #[case::magenta((60.322_700, 98.235_580, - 60.842_370), 328.227_940,)]
547 #[case::yellow((97.138_580, - 21.562_368, 94.476_760), 102.856_380)]
548 fn test_hue(#[case] input: (f32, f32, f32), #[case] expected: f32) {
549 let color: Color<f32> = Color::new(input.0, input.1, input.2);
551 let actual = color.hue();
552
553 assert_approx_eq!(actual.to_degrees(), expected, 1e-3);
555 }
556
557 #[test]
558 fn test_to_hex_string() {
559 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
561 let actual = color.to_hex_string();
562
563 assert_eq!(actual, "#00FFFF");
565 }
566
567 #[test]
568 fn test_to_rgb() {
569 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
571 let actual = color.to_rgb();
572
573 assert_eq!(actual, RGB::new(0, 255, 255));
575 }
576
577 #[test]
578 fn test_to_cmyk() {
579 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
581 let actual = color.to_cmyk();
582
583 assert_eq!(actual, CMYK::new(1.0, 0.0, 0.0, 0.0));
585 }
586
587 #[test]
588 fn test_to_hsl() {
589 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
591 let actual = color.to_hsl();
592
593 assert_approx_eq!(actual.h.to_degrees(), 180.0, 1e-3);
595 assert_approx_eq!(actual.s, 1.0);
596 assert_approx_eq!(actual.l, 0.5);
597 }
598
599 #[test]
600 fn test_to_hsv() {
601 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
603 let actual = color.to_hsv();
604
605 assert_approx_eq!(actual.h.to_degrees(), 180.0, 1e-3);
607 assert_approx_eq!(actual.s, 1.0);
608 assert_approx_eq!(actual.v, 1.0);
609 }
610
611 #[test]
612 fn test_to_xyz() {
613 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
615 let actual: XYZ<f32> = color.to_xyz();
616
617 assert_approx_eq!(actual.x, 0.5380, 1e-3);
619 assert_approx_eq!(actual.y, 0.7873, 1e-3);
620 assert_approx_eq!(actual.z, 1.0690, 1e-3);
621 }
622
623 #[test]
624 fn test_to_luv() {
625 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
627 let actual = color.to_luv();
628
629 assert_approx_eq!(actual.l, 91.1120);
631 assert_approx_eq!(actual.u, -70.480, 1e-3);
632 assert_approx_eq!(actual.v, -15.240, 1e-3);
633 }
634
635 #[test]
636 fn test_to_lchuv() {
637 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
639 let actual = color.to_lchuv();
640
641 assert_approx_eq!(actual.l, 91.1120);
643 assert_approx_eq!(actual.c, 72.109, 1e-3);
644 assert_approx_eq!(actual.h.to_degrees(), 192.202, 1e-3);
645 }
646
647 #[test]
648 fn test_to_lab() {
649 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
651 let actual = color.to_lab();
652
653 assert_approx_eq!(actual.l, 91.1120);
655 assert_approx_eq!(actual.a, -48.0806);
656 assert_approx_eq!(actual.b, -14.1521);
657 }
658
659 #[test]
660 fn test_to_oklab() {
661 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
663 let actual = color.to_oklab();
664
665 assert_approx_eq!(actual.l, 0.905, 1e-3);
667 assert_approx_eq!(actual.a, -0.149, 1e-3);
668 assert_approx_eq!(actual.b, -0.040, 1e-3);
669 }
670
671 #[test]
672 fn test_to_oklch() {
673 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
675 let actual = color.to_oklch();
676
677 assert_approx_eq!(actual.l, 0.905, 1e-3);
679 assert_approx_eq!(actual.c, 0.155, 1e-3);
680 assert_approx_eq!(actual.h.to_degrees(), 194.82, 1e-3);
681 }
682
683 #[test]
684 fn test_to_lchab() {
685 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
687 let actual = color.to_lchab();
688
689 assert_approx_eq!(actual.l, 91.1120, 1e-3);
691 assert_approx_eq!(actual.c, 50.120, 1e-3);
692 assert_approx_eq!(actual.h.to_degrees(), 196.401, 1e-3);
693 }
694
695 #[test]
696 fn test_to_anis16() {
697 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
699 let actual = color.to_ansi16();
700
701 assert_eq!(actual, Ansi16::bright_cyan());
703 }
704
705 #[test]
706 fn test_to_ansi256() {
707 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
709 let actual = color.to_ansi256();
710
711 assert_eq!(actual, Ansi256::new(51));
713 }
714
715 #[test]
716 fn test_to_rgb_int() {
717 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
719 let actual = color.to_rgb_int();
720
721 assert_eq!(actual, 0x00ffff);
723 }
724
725 #[test]
726 fn test_to_rgba_int() {
727 let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
729 let actual = color.to_rgba_int(128);
730
731 assert_eq!(actual, 0x00ffff80);
733 }
734
735 #[rstest]
736 #[case::mix_zero(0.0, (91.1120, -48.0806, -14.1521))]
737 #[case::mix_half(0.5, (73.0004, 18.2257, -5.0432))]
738 #[case::mix_full(1.0, (54.8888, 84.5321, 4.0656))]
739 #[case::mix_over(1.5, (54.8888, 84.5321, 4.0656))]
740 #[case::mix_negative(-0.5, (91.1120, -48.0806, -14.1521))]
741 fn test_mix(#[case] fraction: f32, #[case] (l, a, b): (f32, f32, f32)) {
742 let color1: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
744 let color2: Color<f32> = Color::new(54.8888, 84.5321, 4.0656);
745
746 let actual = color1.mix(&color2, fraction);
748
749 assert_approx_eq!(actual.l, l, 1e-3);
751 assert_approx_eq!(actual.a, a, 1e-3);
752 assert_approx_eq!(actual.b, b, 1e-3);
753 }
754
755 #[test]
756 fn test_default() {
757 let actual: Color<f64> = Color::default();
759
760 assert_eq!(
762 actual,
763 Color {
764 l: 0.0,
765 a: 0.0,
766 b: 0.0,
767 _marker: PhantomData
768 }
769 );
770 }
771
772 #[test]
773 fn test_fmt() {
774 let color: Color<f32> = Color::new(91.114_750, -48.080_950, -14.142_8581);
776 assert_eq!(
777 format!("{}", color),
778 "Color(l: 91.11, a: -48.08, b: -14.14)"
779 );
780 }
781
782 #[test]
783 fn test_from_u32() {
784 let actual: Color<f32> = Color::from(0xff0080);
786
787 assert_approx_eq!(actual.l, 54.8888, 1e-3);
789 assert_approx_eq!(actual.a, 84.5321, 1e-3);
790 assert_approx_eq!(actual.b, 4.0656, 1e-3);
791 }
792
793 #[rstest]
794 #[case::black_rgb("#000", 0.0, 0.0, 0.0)]
795 #[case::white_rgb("#fff", 100.0, - 0.002_443, 0.011_384)]
796 #[case::red_rgb("#f00", 53.237_144, 80.088_320, 67.199_460)]
797 #[case::green_rgb("#0f0", 87.735_535, - 86.183_550, 83.179_924)]
798 #[case::blue_rgb("#00f", 32.300_800, 79.194_260, - 107.868_910)]
799 #[case::cyan_rgb("#0ff", 91.114_750, - 48.080_950, - 14.142_858)]
800 #[case::magenta_rgb("#f0f", 60.322_700, 98.235_580, - 60.842_370)]
801 #[case::yellow_rgb("#ff0", 97.138_580, - 21.562_368, 94.476_760)]
802 #[case::black_rgba("#0000", 0.0, 0.0, 0.0)]
803 #[case::white_rgba("#ffff", 100.0, - 0.002_443, 0.011_384)]
804 #[case::red_rgba("#f00f", 53.237_144, 80.088_320, 67.199_460)]
805 #[case::green_rgba("#0f0f", 87.735_535, - 86.183_550, 83.179_924)]
806 #[case::blue_rgba("#00ff", 32.300_800, 79.194_260, - 107.868_910)]
807 #[case::cyan_rgba("#0fff", 91.114_750, - 48.080_950, - 14.142_858)]
808 #[case::magenta_rgba("#f0ff", 60.322_700, 98.235_580, - 60.842_370)]
809 #[case::yellow_rgba("#ff0f", 97.138_580, - 21.562_368, 94.476_760)]
810 #[case::black_rrggbb("#000000", 0.0, 0.0, 0.0)]
811 #[case::white_rrggbb("#ffffff", 100.0, - 0.002_443, 0.011_384)]
812 #[case::red_rrggbb("#ff0000", 53.237_144, 80.088_320, 67.199_460)]
813 #[case::green_rrggbb("#00ff00", 87.735_535, - 86.183_550, 83.179_924)]
814 #[case::blue_rrggbb("#0000ff", 32.300_800, 79.194_260, - 107.868_910)]
815 #[case::cyan_rrggbb("#00ffff", 91.114_750, - 48.080_950, - 14.142_858)]
816 #[case::magenta_rrggbb("#ff00ff", 60.322_700, 98.235_580, - 60.842_370)]
817 #[case::yellow_rrggbb("#ffff00", 97.138_580, - 21.562_368, 94.476_760)]
818 #[case::black_rrggbbaa("#000000ff", 0.0, 0.0, 0.0)]
819 #[case::white_rrggbbaa("#ffffffff", 100.0, - 0.002_443, 0.011_384)]
820 #[case::red_rrggbbaa("#ff0000ff", 53.237_144, 80.088_320, 67.199_460)]
821 #[case::green_rrggbbaa("#00ff00ff", 87.735_535, - 86.183_550, 83.179_924)]
822 #[case::blue_rrggbbaa("#0000ffff", 32.300_800, 79.194_260, - 107.868_910)]
823 #[case::cyan_rrggbbaa("#00ffffff", 91.114_750, - 48.080_950, - 14.142_858)]
824 #[case::magenta_rrggbbaa("#ff00ffff", 60.322_700, 98.235_580, - 60.842_370)]
825 #[case::yellow_rrggbbaa("#ffff00ff", 97.138_580, - 21.562_368, 94.476_760)]
826 fn test_from_str(#[case] input: &str, #[case] l: f32, #[case] a: f32, #[case] b: f32) {
827 let actual: Color<f32> = Color::from_str(input).unwrap();
829
830 assert_approx_eq!(actual.l, l, 1e-3);
832 assert_approx_eq!(actual.a, a, 1e-3);
833 assert_approx_eq!(actual.b, b, 1e-3);
834 }
835
836 #[rstest]
837 #[case::empty("")]
838 #[case::invalid("123456")]
839 #[case::invalid_length("#12345")]
840 #[case::invalid_prefix("123456")]
841 #[case::invalid_rgb_r("#g00")]
842 #[case::invalid_rgb_g("#0g0")]
843 #[case::invalid_rgb_b("#00g")]
844 #[case::invalid_rrggbb_r("#0g0000")]
845 #[case::invalid_rrggbb_g("#000g00")]
846 #[case::invalid_rrggbb_b("#00000g")]
847 #[case::invalid_rrggbbaa_r("#0g000000")]
848 #[case::invalid_rrggbbaa_g("#000g0000")]
849 #[case::invalid_rrggbbaa_b("#00000g00")]
850 #[case::invalid_rrggbbaa_a("#0000000g")]
851 fn test_from_str_error(#[case] input: &str) {
852 let actual = Color::<f32>::from_str(input);
854
855 assert!(actual.is_err());
857 assert_eq!(
858 actual.unwrap_err(),
859 ColorError::InvalidHexValue(input.to_string())
860 );
861 }
862}