auto_palette/color/
mod.rs

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/// The color representation.
49///
50/// # Type Parameters
51/// * `T` - The floating point type.
52/// * `W` - The white point type.
53///
54/// # Examples
55/// ```
56/// use std::str::FromStr;
57///
58/// use auto_palette::color::Color;
59///
60/// let color: Color<f32> = Color::from_str("#2c7de7").unwrap();
61/// assert!(color.is_light());
62/// assert!(color.lightness() - 52.917 < 1e-3);
63/// assert!(color.chroma() - 61.981 < 1e-3);
64/// assert!(color.hue().to_degrees() - 282.662 < 1e-3);
65///
66/// let rgb = color.to_rgb();
67/// assert_eq!(format!("{}", rgb), "RGB(44, 125, 231)");
68///
69/// let hsl = color.to_hsl();
70/// assert_eq!(format!("{}", hsl), "HSL(214.01, 0.80, 0.54)");
71///
72/// let lab = color.to_lab();
73/// assert_eq!(format!("{}", lab), "Lab(52.92, 13.59, -60.47)");
74/// ```
75#[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    /// Creates a new `Color` instance.
92    ///
93    /// # Arguments
94    /// * `l` - The value of l.
95    /// * `a` - The value of a.
96    /// * `b` - The value of b.
97    ///
98    /// # Returns
99    /// A new `Color` instance.
100    #[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    /// Returns whether this color is light.
111    ///
112    /// # Returns
113    /// `true` if the color is light, otherwise `false`.
114    #[must_use]
115    pub fn is_light(&self) -> bool {
116        self.l > T::from_f32(50.0)
117    }
118
119    /// Returns whether this color is dark.
120    ///
121    /// # Returns
122    /// `true` if the color is dark, otherwise `false`.
123    #[must_use]
124    pub fn is_dark(&self) -> bool {
125        !self.is_light()
126    }
127
128    /// Returns the lightness of this color.
129    ///
130    /// # Returns
131    /// The lightness of this color.
132    #[must_use]
133    pub fn lightness(&self) -> T {
134        self.l
135    }
136
137    /// Returns the chroma of this color.
138    ///
139    /// # Returns
140    /// The chroma of this color.
141    #[must_use]
142    pub fn chroma(&self) -> T {
143        (self.a.powi(2) + self.b.powi(2)).sqrt()
144    }
145
146    /// Calculates the delta E76 value between this color and another color.
147    ///
148    /// # Arguments
149    /// * `other` - The other color to compare against.
150    ///
151    /// # Returns
152    /// The delta E76 value, which is a measure of the difference between two colors.
153    ///
154    /// # Note
155    /// This method uses the CIE76 formula, which is a simple Euclidean distance in the L*a*b* color space.
156    /// [Color difference CIE76 - Wikipedia](https://en.wikipedia.org/wiki/Color_difference#CIE76)
157    #[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    /// Returns the hue of this color. The hue is the angle of the vector in the a*b* plane.
167    ///
168    /// # Returns
169    /// The hue of this color.
170    #[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    /// Converts this color to a hexadecimal string.
177    ///
178    /// # Returns
179    /// The hexadecimal string representation of this color.
180    #[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    /// Converts this color to the RGB color space.
187    ///
188    /// # Returns
189    /// The converted `RGB` color.
190    #[must_use]
191    pub fn to_rgb(&self) -> RGB {
192        RGB::from(&self.to_xyz())
193    }
194
195    /// Converts this color to the CMYK color space.
196    ///
197    /// # Returns
198    /// The converted `CMYK` color.
199    #[must_use]
200    pub fn to_cmyk(&self) -> CMYK<T> {
201        CMYK::from(&self.to_rgb())
202    }
203
204    /// Converts this color to the HSL color space.
205    ///
206    /// # Returns
207    /// The converted `HSL` color.
208    #[must_use]
209    pub fn to_hsl(&self) -> HSL<T> {
210        HSL::from(&self.to_rgb())
211    }
212
213    /// Converts this color to the HSV color space.
214    ///
215    /// # Returns
216    /// The converted `HSV` color.
217    #[must_use]
218    pub fn to_hsv(&self) -> HSV<T> {
219        HSV::from(&self.to_rgb())
220    }
221
222    /// Converts this color to the CIE XYZ color space.
223    ///
224    /// # Returns
225    /// The converted `XYZ` color.
226    #[must_use]
227    pub fn to_xyz(&self) -> XYZ<T> {
228        XYZ::from(&self.to_lab())
229    }
230
231    /// Converts this color to the CIE L*u*v* color space.
232    ///
233    /// # Returns
234    /// The converted `Luv` color.
235    #[must_use]
236    pub fn to_luv(&self) -> Luv<T, W> {
237        Luv::<T, W>::from(&self.to_xyz())
238    }
239
240    /// Converts this color to the CIE LCH(uv) color space.
241    ///
242    /// # Returns
243    /// The converted `LCHuv` color.
244    #[must_use]
245    pub fn to_lchuv(&self) -> LCHuv<T, W> {
246        LCHuv::<T, W>::from(&self.to_luv())
247    }
248
249    /// Converts this color to the CIE L*a*b* color space.
250    ///
251    /// # Returns
252    /// The converted `Lab` color.
253    #[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    /// Converts this color to the CIE LCH(ab) color space.
259    ///
260    /// # Returns
261    /// The converted `LCHab` color.
262    #[must_use]
263    pub fn to_lchab(&self) -> LCHab<T, W> {
264        LCHab::<T, W>::from(&self.to_lab())
265    }
266
267    /// Converts this color to the CIE Oklab color space.
268    ///
269    /// # Returns
270    /// The converted `Oklab` color.
271    #[must_use]
272    pub fn to_oklab(&self) -> Oklab<T> {
273        Oklab::from(&self.to_xyz())
274    }
275
276    /// Converts this color to the CIE Oklch color space.
277    ///
278    /// # Returns
279    /// The converted `Oklch` color.
280    #[must_use]
281    pub fn to_oklch(&self) -> Oklch<T> {
282        Oklch::from(&self.to_oklab())
283    }
284
285    /// Converts this color to the 4-bit ANSI 16 color space.
286    ///
287    /// # Returns
288    /// The converted `Ansi16` color.
289    #[must_use]
290    pub fn to_ansi16(&self) -> Ansi16 {
291        Ansi16::from(&self.to_rgb())
292    }
293
294    /// Converts this color to the 8-bit ANSI 256 color space.
295    ///
296    /// # Returns
297    /// The converted `Ansi256` color.
298    #[must_use]
299    pub fn to_ansi256(&self) -> Ansi256 {
300        Ansi256::from(&self.to_rgb())
301    }
302
303    /// Converts this color to a 32-bit integer representation in RGB.
304    ///
305    /// # Returns
306    /// The converted RGB integer representation.
307    #[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    /// Converts this color to a 32-bit integer representation in RGBA.
317    ///
318    /// # Arguments
319    /// * `alpha` - The alpha value (0-255).
320    ///
321    /// # Returns
322    /// The converted RGBA integer representation.
323    #[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    /// Mixes this color with another color by a given fraction.
333    ///
334    /// # Arguments
335    /// * `other` - The other color to mix with.
336    /// * `fraction` - The fraction to mix the two colors (0.0 to 1.0).
337    ///
338    /// # Returns
339    /// A new `Color` instance that is the result of mixing the two colors.
340    #[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            // Handle 3-digit hex color #RGB
406            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            // Handle 4-digit hex color #RGBA
416            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                // Check whether the alpha value is valid
424                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            // Handle 6-digit hex color #RRGGBB
429            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            // Handle 8-digit hex color #RRGGBBAA
439            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                // Check whether the alpha value is valid
447                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        // Act
470        let actual: Color<f32> = Color::new(80.0, 0.0, 0.0);
471
472        // Assert
473        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        // Act
485        let color: Color<f32> = Color::new(input.0, input.1, input.2);
486        let actual = color.is_light();
487
488        // Assert
489        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        // Act
499        let color: Color<f32> = Color::new(input.0, input.1, input.2);
500        let actual = color.is_dark();
501
502        // Assert
503        assert_eq!(actual, expected);
504    }
505
506    #[test]
507    fn test_lightness() {
508        // Act
509        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
510        let actual = color.lightness();
511
512        // Assert
513        assert_approx_eq!(actual, 91.1120);
514    }
515
516    #[test]
517    fn test_chroma() {
518        // Act
519        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
520        let actual = color.chroma();
521
522        // Assert
523        assert_approx_eq!(actual, 50.120_117);
524    }
525
526    #[test]
527    fn test_delta_e() {
528        // Arrange
529        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        // Act
533        let actual = color1.delta_e(&color2);
534
535        // Assert
536        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        // Act
550        let color: Color<f32> = Color::new(input.0, input.1, input.2);
551        let actual = color.hue();
552
553        // Assert
554        assert_approx_eq!(actual.to_degrees(), expected, 1e-3);
555    }
556
557    #[test]
558    fn test_to_hex_string() {
559        // Act
560        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
561        let actual = color.to_hex_string();
562
563        // Assert
564        assert_eq!(actual, "#00FFFF");
565    }
566
567    #[test]
568    fn test_to_rgb() {
569        // Act
570        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
571        let actual = color.to_rgb();
572
573        // Assert
574        assert_eq!(actual, RGB::new(0, 255, 255));
575    }
576
577    #[test]
578    fn test_to_cmyk() {
579        // Act
580        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
581        let actual = color.to_cmyk();
582
583        // Assert
584        assert_eq!(actual, CMYK::new(1.0, 0.0, 0.0, 0.0));
585    }
586
587    #[test]
588    fn test_to_hsl() {
589        // Act
590        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
591        let actual = color.to_hsl();
592
593        // Assert
594        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        // Act
602        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
603        let actual = color.to_hsv();
604
605        // Assert
606        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        // Act
614        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
615        let actual: XYZ<f32> = color.to_xyz();
616
617        // Assert
618        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        // Act
626        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
627        let actual = color.to_luv();
628
629        // Assert
630        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        // Act
638        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
639        let actual = color.to_lchuv();
640
641        // Assert
642        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        // Act
650        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
651        let actual = color.to_lab();
652
653        // Assert
654        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        // Act
662        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
663        let actual = color.to_oklab();
664
665        // Assert
666        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        // Act
674        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
675        let actual = color.to_oklch();
676
677        // Assert
678        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        // Act
686        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
687        let actual = color.to_lchab();
688
689        // Assert
690        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        // Act
698        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
699        let actual = color.to_ansi16();
700
701        // Assert
702        assert_eq!(actual, Ansi16::bright_cyan());
703    }
704
705    #[test]
706    fn test_to_ansi256() {
707        // Act
708        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
709        let actual = color.to_ansi256();
710
711        // Assert
712        assert_eq!(actual, Ansi256::new(51));
713    }
714
715    #[test]
716    fn test_to_rgb_int() {
717        // Act
718        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
719        let actual = color.to_rgb_int();
720
721        // Assert
722        assert_eq!(actual, 0x00ffff);
723    }
724
725    #[test]
726    fn test_to_rgba_int() {
727        // Act
728        let color: Color<f32> = Color::new(91.1120, -48.0806, -14.1521);
729        let actual = color.to_rgba_int(128);
730
731        // Assert
732        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        // Arrange
743        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        // Act
747        let actual = color1.mix(&color2, fraction);
748
749        // Assert
750        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        // Act
758        let actual: Color<f64> = Color::default();
759
760        // Assert
761        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        // Act & Assert
775        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        // Act
785        let actual: Color<f32> = Color::from(0xff0080);
786
787        // Assert
788        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        // Act
828        let actual: Color<f32> = Color::from_str(input).unwrap();
829
830        // Assert
831        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        // Act
853        let actual = Color::<f32>::from_str(input);
854
855        // Assert
856        assert!(actual.is_err());
857        assert_eq!(
858            actual.unwrap_err(),
859            ColorError::InvalidHexValue(input.to_string())
860        );
861    }
862}