1 pub mod ansi;
2 pub mod colorspace;
3 pub mod delta_e;
4 pub mod distinct;
5 mod helper;
6 pub mod named;
7 pub mod parser;
8 pub mod random;
9 mod types;
10 
11 use std::fmt;
12 
13 use colorspace::ColorSpace;
14 pub use helper::Fraction;
15 use helper::{clamp, interpolate, interpolate_angle, mod_positive};
16 use types::{Hue, Scalar};
17 
18 /// The representation of a color.
19 ///
20 /// Note:
21 /// - Colors outside the sRGB gamut (which cannot be displayed on a typical
22 ///   computer screen) can not be represented by `Color`.
23 /// - The `PartialEq` instance compares two `Color`s by comparing their (integer)
24 ///   RGB values. This is different from comparing the HSL values. For example,
25 ///   HSL has many different representations of black (arbitrary hue and
26 ///   saturation values).
27 #[derive(Clone)]
28 pub struct Color {
29     hue: Hue,
30     saturation: Scalar,
31     lightness: Scalar,
32     alpha: Scalar,
33 }
34 
35 // Illuminant D65 constants used for Lab color space conversions.
36 const D65_XN: Scalar = 0.950_470;
37 const D65_YN: Scalar = 1.0;
38 const D65_ZN: Scalar = 1.088_830;
39 
40 impl Color {
from_hsla(hue: Scalar, saturation: Scalar, lightness: Scalar, alpha: Scalar) -> Color41     pub fn from_hsla(hue: Scalar, saturation: Scalar, lightness: Scalar, alpha: Scalar) -> Color {
42         Self::from(&HSLA {
43             h: hue,
44             s: saturation,
45             l: lightness,
46             alpha,
47         })
48     }
49 
50     ///
from_hsl(hue: Scalar, saturation: Scalar, lightness: Scalar) -> Color51     pub fn from_hsl(hue: Scalar, saturation: Scalar, lightness: Scalar) -> Color {
52         Self::from(&HSLA {
53             h: hue,
54             s: saturation,
55             l: lightness,
56             alpha: 1.0,
57         })
58     }
59 
60     /// Create a `Color` from integer RGB values between 0 and 255 and a floating
61     /// point alpha value between 0.0 and 1.0.
from_rgba(r: u8, g: u8, b: u8, alpha: Scalar) -> Color62     pub fn from_rgba(r: u8, g: u8, b: u8, alpha: Scalar) -> Color {
63         // RGB to HSL conversion algorithm adapted from
64         // https://en.wikipedia.org/wiki/HSL_and_HSV
65         Self::from(&RGBA::<u8> { r, g, b, alpha })
66     }
67 
68     /// Create a `Color` from integer RGB values between 0 and 255.
from_rgb(r: u8, g: u8, b: u8) -> Color69     pub fn from_rgb(r: u8, g: u8, b: u8) -> Color {
70         Self::from(&RGBA::<u8> {
71             r,
72             g,
73             b,
74             alpha: 1.0,
75         })
76     }
77 
78     /// Create a `Color` from RGB and alpha values between 0.0 and 1.0. Values outside this range
79     /// will be clamped.
from_rgba_float(r: Scalar, g: Scalar, b: Scalar, alpha: Scalar) -> Color80     pub fn from_rgba_float(r: Scalar, g: Scalar, b: Scalar, alpha: Scalar) -> Color {
81         Self::from(&RGBA::<f64> { r, g, b, alpha })
82     }
83 
84     /// Create a `Color` from RGB values between 0.0 and 1.0. Values outside this range will be
85     /// clamped.
from_rgb_float(r: Scalar, g: Scalar, b: Scalar) -> Color86     pub fn from_rgb_float(r: Scalar, g: Scalar, b: Scalar) -> Color {
87         Self::from(&RGBA::<f64> {
88             r,
89             g,
90             b,
91             alpha: 1.0,
92         })
93     }
94 
95     /// Create a `Color` from XYZ coordinates in the CIE 1931 color space. Note that a `Color`
96     /// always represents a color in the sRGB gamut (colors that can be represented on a typical
97     /// computer screen) while the XYZ color space is bigger. This function will tend to create
98     /// fully saturated colors at the edge of the sRGB gamut if the coordinates lie outside the
99     /// sRGB range.
100     ///
101     /// See:
102     /// - https://en.wikipedia.org/wiki/CIE_1931_color_space
103     /// - https://en.wikipedia.org/wiki/SRGB
from_xyz(x: Scalar, y: Scalar, z: Scalar, alpha: Scalar) -> Color104     pub fn from_xyz(x: Scalar, y: Scalar, z: Scalar, alpha: Scalar) -> Color {
105         Self::from(&XYZ { x, y, z, alpha })
106     }
107 
108     /// Create a `Color` from LMS coordinates. This is the matrix inverse of the matrix that
109     /// appears in `to_lms`.
from_lms(l: Scalar, m: Scalar, s: Scalar, alpha: Scalar) -> Color110     pub fn from_lms(l: Scalar, m: Scalar, s: Scalar, alpha: Scalar) -> Color {
111         Self::from(&LMS { l, m, s, alpha })
112     }
113 
114     /// Create a `Color` from L, a and b coordinates coordinates in the Lab color
115     /// space. Note: See documentation for `from_xyz`. The same restrictions apply here.
116     ///
117     /// See: https://en.wikipedia.org/wiki/Lab_color_space
from_lab(l: Scalar, a: Scalar, b: Scalar, alpha: Scalar) -> Color118     pub fn from_lab(l: Scalar, a: Scalar, b: Scalar, alpha: Scalar) -> Color {
119         Self::from(&Lab { l, a, b, alpha })
120     }
121 
122     /// Create a `Color` from lightness, chroma and hue coordinates in the CIE LCh color space.
123     /// This is a cylindrical transform of the Lab color space. Note: See documentation for
124     /// `from_xyz`. The same restrictions apply here.
125     ///
126     /// See: https://en.wikipedia.org/wiki/Lab_color_space
from_lch(l: Scalar, c: Scalar, h: Scalar, alpha: Scalar) -> Color127     pub fn from_lch(l: Scalar, c: Scalar, h: Scalar, alpha: Scalar) -> Color {
128         Self::from(&LCh { l, c, h, alpha })
129     }
130 
131     /// Create a `Color` from  the four colours of the CMYK model: Cyan, Magenta, Yellow and Black.
132     /// The CMYK colours are subtractive. This means the colours get darker as you blend them together
from_cmyk(c: Scalar, m: Scalar, y: Scalar, k: Scalar) -> Color133     pub fn from_cmyk(c: Scalar, m: Scalar, y: Scalar, k: Scalar) -> Color {
134         Self::from(&CMYK { c, m, y, k })
135     }
136 
137     /// Convert a `Color` to its hue, saturation, lightness and alpha values. The hue is given
138     /// in degrees, as a number between 0.0 and 360.0. Saturation, lightness and alpha are numbers
139     /// between 0.0 and 1.0.
to_hsla(&self) -> HSLA140     pub fn to_hsla(&self) -> HSLA {
141         HSLA::from(self)
142     }
143 
144     /// Format the color as a HSL-representation string (`hsl(123, 50.3%, 80.1%)`).
to_hsl_string(&self, format: Format) -> String145     pub fn to_hsl_string(&self, format: Format) -> String {
146         format!(
147             "hsl({:.0},{space}{:.1}%,{space}{:.1}%)",
148             self.hue.value(),
149             100.0 * self.saturation,
150             100.0 * self.lightness,
151             space = if format == Format::Spaces { " " } else { "" }
152         )
153     }
154 
155     /// Convert a `Color` to its red, green, blue and alpha values. The RGB values are integers in
156     /// the range from 0 to 255. The alpha channel is a number between 0.0 and 1.0.
to_rgba(&self) -> RGBA<u8>157     pub fn to_rgba(&self) -> RGBA<u8> {
158         RGBA::<u8>::from(self)
159     }
160 
161     /// Format the color as a RGB-representation string (`rgb(255, 127,  0)`).
to_rgb_string(&self, format: Format) -> String162     pub fn to_rgb_string(&self, format: Format) -> String {
163         let rgba = RGBA::<u8>::from(self);
164         format!(
165             "rgb({r},{space}{g},{space}{b})",
166             r = rgba.r,
167             g = rgba.g,
168             b = rgba.b,
169             space = if format == Format::Spaces { " " } else { "" }
170         )
171     }
172 
173     /// Convert a `Color` to its cyan, magenta, yellow, and black values. The CMYK
174     /// values are floats smaller than or equal to 1.0.
to_cmyk(&self) -> CMYK175     pub fn to_cmyk(&self) -> CMYK {
176         CMYK::from(self)
177     }
178 
179     /// Format the color as a CMYK-representation string (`cmyk(0, 50, 100, 100)`).
to_cmyk_string(&self, format: Format) -> String180     pub fn to_cmyk_string(&self, format: Format) -> String {
181         let cmyk = CMYK::from(self);
182         format!(
183             "cmyk({c},{space}{m},{space}{y},{space}{k})",
184             c = (cmyk.c * 100.0).round(),
185             m = (cmyk.m * 100.0).round(),
186             y = (cmyk.y * 100.0).round(),
187             k = (cmyk.k * 100.0).round(),
188             space = if format == Format::Spaces { " " } else { "" }
189         )
190     }
191 
192     /// Format the color as a floating point RGB-representation string (`rgb(1.0, 0.5,  0)`).
to_rgb_float_string(&self, format: Format) -> String193     pub fn to_rgb_float_string(&self, format: Format) -> String {
194         let rgba = RGBA::<f64>::from(self);
195         format!(
196             "rgb({r:.3},{space}{g:.3},{space}{b:.3})",
197             r = rgba.r,
198             g = rgba.g,
199             b = rgba.b,
200             space = if format == Format::Spaces { " " } else { "" }
201         )
202     }
203 
204     /// Format the color as a RGB-representation string (`#fc0070`).
to_rgb_hex_string(&self, leading_hash: bool) -> String205     pub fn to_rgb_hex_string(&self, leading_hash: bool) -> String {
206         let rgba = self.to_rgba();
207         format!(
208             "{}{:02x}{:02x}{:02x}",
209             if leading_hash { "#" } else { "" },
210             rgba.r,
211             rgba.g,
212             rgba.b
213         )
214     }
215 
216     /// Convert a `Color` to its red, green, blue and alpha values. All numbers are from the range
217     /// between 0.0 and 1.0.
to_rgba_float(&self) -> RGBA<Scalar>218     pub fn to_rgba_float(&self) -> RGBA<Scalar> {
219         RGBA::<f64>::from(self)
220     }
221 
222     /// Return the color as an integer in RGB representation (`0xRRGGBB`)
to_u32(&self) -> u32223     pub fn to_u32(&self) -> u32 {
224         let rgba = self.to_rgba();
225         u32::from(rgba.r).wrapping_shl(16) + u32::from(rgba.g).wrapping_shl(8) + u32::from(rgba.b)
226     }
227 
228     /// Get XYZ coordinates according to the CIE 1931 color space.
229     ///
230     /// See:
231     /// - https://en.wikipedia.org/wiki/CIE_1931_color_space
232     /// - https://en.wikipedia.org/wiki/SRGB
to_xyz(&self) -> XYZ233     pub fn to_xyz(&self) -> XYZ {
234         XYZ::from(self)
235     }
236 
237     /// Get coordinates according to the LSM color space
238     ///
239     /// See https://en.wikipedia.org/wiki/LMS_color_space for info on the color space as well as an
240     /// algorithm for converting from CIE XYZ
to_lms(&self) -> LMS241     pub fn to_lms(&self) -> LMS {
242         LMS::from(self)
243     }
244 
245     /// Get L, a and b coordinates according to the Lab color space.
246     ///
247     /// See: https://en.wikipedia.org/wiki/Lab_color_space
to_lab(&self) -> Lab248     pub fn to_lab(&self) -> Lab {
249         Lab::from(self)
250     }
251 
252     /// Format the color as a Lab-representation string (`Lab(41, 83, -93)`).
to_lab_string(&self, format: Format) -> String253     pub fn to_lab_string(&self, format: Format) -> String {
254         let lab = Lab::from(self);
255         format!(
256             "Lab({l:.0},{space}{a:.0},{space}{b:.0})",
257             l = lab.l,
258             a = lab.a,
259             b = lab.b,
260             space = if format == Format::Spaces { " " } else { "" }
261         )
262     }
263 
264     /// Get L, C and h coordinates according to the CIE LCh color space.
265     ///
266     /// See: https://en.wikipedia.org/wiki/Lab_color_space
to_lch(&self) -> LCh267     pub fn to_lch(&self) -> LCh {
268         LCh::from(self)
269     }
270 
271     /// Format the color as a LCh-representation string (`LCh(0.3, 0.2, 0.1)`).
to_lch_string(&self, format: Format) -> String272     pub fn to_lch_string(&self, format: Format) -> String {
273         let lch = LCh::from(self);
274         format!(
275             "LCh({l:.0},{space}{c:.0},{space}{h:.0})",
276             l = lch.l,
277             c = lch.c,
278             h = lch.h,
279             space = if format == Format::Spaces { " " } else { "" }
280         )
281     }
282 
283     /// Pure black.
black() -> Color284     pub fn black() -> Color {
285         Color::from_hsl(0.0, 0.0, 0.0)
286     }
287 
288     /// Pure white.
white() -> Color289     pub fn white() -> Color {
290         Color::from_hsl(0.0, 0.0, 1.0)
291     }
292 
293     /// Red (`#ff0000`)
red() -> Color294     pub fn red() -> Color {
295         Color::from_rgb(255, 0, 0)
296     }
297 
298     /// Green (`#008000`)
green() -> Color299     pub fn green() -> Color {
300         Color::from_rgb(0, 128, 0)
301     }
302 
303     /// Blue (`#0000ff`)
blue() -> Color304     pub fn blue() -> Color {
305         Color::from_rgb(0, 0, 255)
306     }
307 
308     /// Yellow (`#ffff00`)
yellow() -> Color309     pub fn yellow() -> Color {
310         Color::from_rgb(255, 255, 0)
311     }
312 
313     /// Fuchsia (`#ff00ff`)
fuchsia() -> Color314     pub fn fuchsia() -> Color {
315         Color::from_rgb(255, 0, 255)
316     }
317 
318     /// Aqua (`#00ffff`)
aqua() -> Color319     pub fn aqua() -> Color {
320         Color::from_rgb(0, 255, 255)
321     }
322 
323     /// Lime (`#00ff00`)
lime() -> Color324     pub fn lime() -> Color {
325         Color::from_rgb(0, 255, 0)
326     }
327 
328     /// Maroon (`#800000`)
maroon() -> Color329     pub fn maroon() -> Color {
330         Color::from_rgb(128, 0, 0)
331     }
332 
333     /// Olive (`#808000`)
olive() -> Color334     pub fn olive() -> Color {
335         Color::from_rgb(128, 128, 0)
336     }
337 
338     /// Navy (`#000080`)
navy() -> Color339     pub fn navy() -> Color {
340         Color::from_rgb(0, 0, 128)
341     }
342 
343     /// Purple (`#800080`)
purple() -> Color344     pub fn purple() -> Color {
345         Color::from_rgb(128, 0, 128)
346     }
347 
348     /// Teal (`#008080`)
teal() -> Color349     pub fn teal() -> Color {
350         Color::from_rgb(0, 128, 128)
351     }
352 
353     /// Silver (`#c0c0c0`)
silver() -> Color354     pub fn silver() -> Color {
355         Color::from_rgb(192, 192, 192)
356     }
357 
358     /// Gray (`#808080`)
gray() -> Color359     pub fn gray() -> Color {
360         Color::from_rgb(128, 128, 128)
361     }
362 
363     /// Create a gray tone from a lightness value (0.0 is black, 1.0 is white).
graytone(lightness: Scalar) -> Color364     pub fn graytone(lightness: Scalar) -> Color {
365         Color::from_hsl(0.0, 0.0, lightness)
366     }
367 
368     /// Rotate along the "hue" axis.
rotate_hue(&self, delta: Scalar) -> Color369     pub fn rotate_hue(&self, delta: Scalar) -> Color {
370         Self::from_hsla(
371             self.hue.value() + delta,
372             self.saturation,
373             self.lightness,
374             self.alpha,
375         )
376     }
377 
378     /// Get the complementary color (hue rotated by 180°).
complementary(&self) -> Color379     pub fn complementary(&self) -> Color {
380         self.rotate_hue(180.0)
381     }
382 
383     /// Lighten a color by adding a certain amount (number between -1.0 and 1.0) to the lightness
384     /// channel. If the number is negative, the color is darkened.
lighten(&self, f: Scalar) -> Color385     pub fn lighten(&self, f: Scalar) -> Color {
386         Self::from_hsla(
387             self.hue.value(),
388             self.saturation,
389             self.lightness + f,
390             self.alpha,
391         )
392     }
393 
394     /// Darken a color by subtracting a certain amount (number between -1.0 and 1.0) from the
395     /// lightness channel. If the number is negative, the color is lightened.
darken(&self, f: Scalar) -> Color396     pub fn darken(&self, f: Scalar) -> Color {
397         self.lighten(-f)
398     }
399 
400     /// Increase the saturation of a color by adding a certain amount (number between -1.0 and 1.0)
401     /// to the saturation channel. If the number is negative, the color is desaturated.
saturate(&self, f: Scalar) -> Color402     pub fn saturate(&self, f: Scalar) -> Color {
403         Self::from_hsla(
404             self.hue.value(),
405             self.saturation + f,
406             self.lightness,
407             self.alpha,
408         )
409     }
410 
411     /// Decrease the saturation of a color by subtracting a certain amount (number between -1.0 and
412     /// 1.0) from the saturation channel. If the number is negative, the color is saturated.
desaturate(&self, f: Scalar) -> Color413     pub fn desaturate(&self, f: Scalar) -> Color {
414         self.saturate(-f)
415     }
416 
417     /// Adjust the long-, medium-, and short-wavelength cone perception of a color to simulate what
418     /// a colorblind person sees. Since there are multiple kinds of colorblindness, the desired
419     /// kind must be specified in `cb_ty`.
simulate_colorblindness(&self, cb_ty: ColorblindnessType) -> Color420     pub fn simulate_colorblindness(&self, cb_ty: ColorblindnessType) -> Color {
421         // Coefficients here are taken from
422         // https://ixora.io/projects/colorblindness/color-blindness-simulation-research/
423         let (l, m, s, alpha) = match cb_ty {
424             ColorblindnessType::Protanopia => {
425                 let LMS { m, s, alpha, .. } = self.to_lms();
426                 let l = 1.051_182_94 * m - 0.051_160_99 * s;
427                 (l, m, s, alpha)
428             }
429             ColorblindnessType::Deuteranopia => {
430                 let LMS { l, s, alpha, .. } = self.to_lms();
431                 let m = 0.951_309_2 * l + 0.048_669_92 * s;
432                 (l, m, s, alpha)
433             }
434             ColorblindnessType::Tritanopia => {
435                 let LMS { l, m, alpha, .. } = self.to_lms();
436                 let s = -0.867_447_36 * l + 1.867_270_89 * m;
437                 (l, m, s, alpha)
438             }
439         };
440 
441         Color::from_lms(l, m, s, alpha)
442     }
443 
444     /// Convert a color to a gray tone with the same perceived luminance (see `luminance`).
to_gray(&self) -> Color445     pub fn to_gray(&self) -> Color {
446         let hue = self.hue;
447         let c = self.to_lch();
448 
449         // the desaturation step is only needed to correct minor rounding errors.
450         let mut gray = Color::from_lch(c.l, 0.0, 0.0, 1.0).desaturate(1.0);
451 
452         // Restore the hue value (does not alter the color, but makes it able to add saturation
453         // again)
454         gray.hue = hue;
455 
456         gray
457     }
458 
459     /// The percieved brightness of the color (A number between 0.0 and 1.0).
460     ///
461     /// See: https://www.w3.org/TR/AERT#color-contrast
brightness(&self) -> Scalar462     pub fn brightness(&self) -> Scalar {
463         let c = self.to_rgba_float();
464         (299.0 * c.r + 587.0 * c.g + 114.0 * c.b) / 1000.0
465     }
466 
467     /// Determine whether a color is perceived as a light color (perceived brightness is larger
468     /// than 0.5).
is_light(&self) -> bool469     pub fn is_light(&self) -> bool {
470         self.brightness() > 0.5
471     }
472 
473     /// The relative brightness of a color (normalized to 0.0 for darkest black
474     /// and 1.0 for lightest white), according to the WCAG definition.
475     ///
476     /// See: https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
luminance(&self) -> Scalar477     pub fn luminance(&self) -> Scalar {
478         fn f(s: Scalar) -> Scalar {
479             if s <= 0.03928 {
480                 s / 12.92
481             } else {
482                 Scalar::powf((s + 0.055) / 1.055, 2.4)
483             }
484         }
485 
486         let c = self.to_rgba_float();
487         let r = f(c.r);
488         let g = f(c.g);
489         let b = f(c.b);
490 
491         0.2126 * r + 0.7152 * g + 0.0722 * b
492     }
493 
494     /// Contrast ratio between two colors as defined by the WCAG. The ratio can range from 1.0
495     /// to 21.0. Two colors with a contrast ratio of 4.5 or higher can be used as text color and
496     /// background color and should be well readable.
497     ///
498     /// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
contrast_ratio(&self, other: &Color) -> Scalar499     pub fn contrast_ratio(&self, other: &Color) -> Scalar {
500         let l_self = self.luminance();
501         let l_other = other.luminance();
502 
503         if l_self > l_other {
504             (l_self + 0.05) / (l_other + 0.05)
505         } else {
506             (l_other + 0.05) / (l_self + 0.05)
507         }
508     }
509 
510     /// Return a readable foreground text color (either `black` or `white`) for a
511     /// given background color.
text_color(&self) -> Color512     pub fn text_color(&self) -> Color {
513         // This threshold can be easily computed by solving
514         //
515         //   contrast(L_threshold, L_black) == contrast(L_threshold, L_white)
516         //
517         // where contrast(.., ..) is the color contrast as defined by the WCAG (see above)
518         const THRESHOLD: Scalar = 0.179;
519 
520         if self.luminance() > THRESHOLD {
521             Color::black()
522         } else {
523             Color::white()
524         }
525     }
526 
527     /// Compute the perceived 'distance' between two colors according to the CIE76 delta-E
528     /// standard. A distance below ~2.3 is not noticable.
529     ///
530     /// See: https://en.wikipedia.org/wiki/Color_difference
distance_delta_e_cie76(&self, other: &Color) -> Scalar531     pub fn distance_delta_e_cie76(&self, other: &Color) -> Scalar {
532         delta_e::cie76(&self.to_lab(), &other.to_lab())
533     }
534 
535     /// Compute the perceived 'distance' between two colors according to the CIEDE2000 delta-E
536     /// standard.
537     ///
538     /// See: https://en.wikipedia.org/wiki/Color_difference
distance_delta_e_ciede2000(&self, other: &Color) -> Scalar539     pub fn distance_delta_e_ciede2000(&self, other: &Color) -> Scalar {
540         delta_e::ciede2000(&self.to_lab(), &other.to_lab())
541     }
542 
543     /// Mix two colors by linearly interpolating between them in the specified color space.
544     /// For the angle-like components (hue), the shortest path along the unit circle is chosen.
mix<C: ColorSpace>(self: &Color, other: &Color, fraction: Fraction) -> Color545     pub fn mix<C: ColorSpace>(self: &Color, other: &Color, fraction: Fraction) -> Color {
546         C::from_color(self)
547             .mix(&C::from_color(other), fraction)
548             .into_color()
549     }
550 }
551 
552 // by default Colors will be printed into HSLA fromat
553 impl fmt::Display for Color {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result554     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
555         write!(f, "{}", HSLA::from(self).to_string())
556     }
557 }
558 
559 impl fmt::Debug for Color {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result560     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
561         write!(f, "Color::from_{}", self.to_rgb_string(Format::NoSpaces))
562     }
563 }
564 
565 impl PartialEq for Color {
eq(&self, other: &Color) -> bool566     fn eq(&self, other: &Color) -> bool {
567         self.to_rgba() == other.to_rgba()
568     }
569 }
570 
571 impl From<&HSLA> for Color {
from(color: &HSLA) -> Self572     fn from(color: &HSLA) -> Self {
573         Color {
574             hue: Hue::from(color.h),
575             saturation: clamp(0.0, 1.0, color.s),
576             lightness: clamp(0.0, 1.0, color.l),
577             alpha: clamp(0.0, 1.0, color.alpha),
578         }
579     }
580 }
581 
582 impl From<&RGBA<u8>> for Color {
from(color: &RGBA<u8>) -> Self583     fn from(color: &RGBA<u8>) -> Self {
584         let max_chroma = u8::max(u8::max(color.r, color.g), color.b);
585         let min_chroma = u8::min(u8::min(color.r, color.g), color.b);
586 
587         let chroma = max_chroma - min_chroma;
588         let chroma_s = Scalar::from(chroma) / 255.0;
589 
590         let r_s = Scalar::from(color.r) / 255.0;
591         let g_s = Scalar::from(color.g) / 255.0;
592         let b_s = Scalar::from(color.b) / 255.0;
593 
594         let hue = 60.0
595             * (if chroma == 0 {
596                 0.0
597             } else if color.r == max_chroma {
598                 mod_positive((g_s - b_s) / chroma_s, 6.0)
599             } else if color.g == max_chroma {
600                 (b_s - r_s) / chroma_s + 2.0
601             } else {
602                 (r_s - g_s) / chroma_s + 4.0
603             });
604 
605         let lightness = (Scalar::from(max_chroma) + Scalar::from(min_chroma)) / (255.0 * 2.0);
606         let saturation = if chroma == 0 {
607             0.0
608         } else {
609             chroma_s / (1.0 - Scalar::abs(2.0 * lightness - 1.0))
610         };
611         Self::from(&HSLA {
612             h: hue,
613             s: saturation,
614             l: lightness,
615             alpha: color.alpha,
616         })
617     }
618 }
619 
620 impl From<&RGBA<f64>> for Color {
from(color: &RGBA<f64>) -> Self621     fn from(color: &RGBA<f64>) -> Self {
622         let r = Scalar::round(clamp(0.0, 255.0, 255.0 * color.r)) as u8;
623         let g = Scalar::round(clamp(0.0, 255.0, 255.0 * color.g)) as u8;
624         let b = Scalar::round(clamp(0.0, 255.0, 255.0 * color.b)) as u8;
625         Self::from(&RGBA::<u8> {
626             r,
627             g,
628             b,
629             alpha: color.alpha,
630         })
631     }
632 }
633 
634 impl From<&XYZ> for Color {
from(color: &XYZ) -> Self635     fn from(color: &XYZ) -> Self {
636         #![allow(clippy::many_single_char_names)]
637         let f = |c| {
638             if c <= 0.003_130_8 {
639                 12.92 * c
640             } else {
641                 1.055 * Scalar::powf(c, 1.0 / 2.4) - 0.055
642             }
643         };
644 
645         let r = f(3.2406 * color.x - 1.5372 * color.y - 0.4986 * color.z);
646         let g = f(-0.9689 * color.x + 1.8758 * color.y + 0.0415 * color.z);
647         let b = f(0.0557 * color.x - 0.2040 * color.y + 1.0570 * color.z);
648 
649         Self::from(&RGBA::<f64> {
650             r,
651             g,
652             b,
653             alpha: color.alpha,
654         })
655     }
656 }
657 
658 impl From<&LMS> for Color {
from(color: &LMS) -> Self659     fn from(color: &LMS) -> Self {
660         #![allow(clippy::many_single_char_names)]
661         let x = 1.91020 * color.l - 1.112_120 * color.m + 0.201_908 * color.s;
662         let y = 0.37095 * color.l + 0.629_054 * color.m + 0.000_000 * color.s;
663         let z = 0.00000 * color.l + 0.000_000 * color.m + 1.000_000 * color.s;
664         Self::from(&XYZ {
665             x,
666             y,
667             z,
668             alpha: color.alpha,
669         })
670     }
671 }
672 
673 impl From<&Lab> for Color {
from(color: &Lab) -> Self674     fn from(color: &Lab) -> Self {
675         #![allow(clippy::many_single_char_names)]
676         const DELTA: Scalar = 6.0 / 29.0;
677 
678         let finv = |t| {
679             if t > DELTA {
680                 Scalar::powf(t, 3.0)
681             } else {
682                 3.0 * DELTA * DELTA * (t - 4.0 / 29.0)
683             }
684         };
685 
686         let l_ = (color.l + 16.0) / 116.0;
687         let x = D65_XN * finv(l_ + color.a / 500.0);
688         let y = D65_YN * finv(l_);
689         let z = D65_ZN * finv(l_ - color.b / 200.0);
690 
691         Self::from(&XYZ {
692             x,
693             y,
694             z,
695             alpha: color.alpha,
696         })
697     }
698 }
699 
700 impl From<&LCh> for Color {
from(color: &LCh) -> Self701     fn from(color: &LCh) -> Self {
702         #![allow(clippy::many_single_char_names)]
703         const DEG2RAD: Scalar = std::f64::consts::PI / 180.0;
704 
705         let a = color.c * Scalar::cos(color.h * DEG2RAD);
706         let b = color.c * Scalar::sin(color.h * DEG2RAD);
707 
708         Self::from(&Lab {
709             l: color.l,
710             a,
711             b,
712             alpha: color.alpha,
713         })
714     }
715 }
716 
717 // from CMYK to Color so you can do -> let new_color = Color::from(&some_cmyk);
718 impl From<&CMYK> for Color {
from(color: &CMYK) -> Self719     fn from(color: &CMYK) -> Self {
720         #![allow(clippy::many_single_char_names)]
721         let r = 255.0 * ((1.0 - color.c) / 100.0) * ((1.0 - color.k) / 100.0);
722         let g = 255.0 * ((1.0 - color.m) / 100.0) * ((1.0 - color.k) / 100.0);
723         let b = 255.0 * ((1.0 - color.y) / 100.0) * ((1.0 - color.k) / 100.0);
724 
725         Color::from(&RGBA::<f64> {
726             r,
727             g,
728             b,
729             alpha: 1.0,
730         })
731     }
732 }
733 
734 #[derive(Debug, Clone, PartialEq)]
735 pub struct RGBA<T> {
736     pub r: T,
737     pub g: T,
738     pub b: T,
739     pub alpha: Scalar,
740 }
741 
742 impl ColorSpace for RGBA<f64> {
from_color(c: &Color) -> Self743     fn from_color(c: &Color) -> Self {
744         c.to_rgba_float()
745     }
746 
into_color(&self) -> Color747     fn into_color(&self) -> Color {
748         Color::from_rgba_float(self.r, self.g, self.b, self.alpha)
749     }
750 
mix(&self, other: &Self, fraction: Fraction) -> Self751     fn mix(&self, other: &Self, fraction: Fraction) -> Self {
752         Self {
753             r: interpolate(self.r, other.r, fraction),
754             g: interpolate(self.g, other.g, fraction),
755             b: interpolate(self.b, other.b, fraction),
756             alpha: interpolate(self.alpha, other.alpha, fraction),
757         }
758     }
759 }
760 
761 impl From<&Color> for RGBA<f64> {
from(color: &Color) -> Self762     fn from(color: &Color) -> Self {
763         let h_s = color.hue.value() / 60.0;
764         let chr = (1.0 - Scalar::abs(2.0 * color.lightness - 1.0)) * color.saturation;
765         let m = color.lightness - chr / 2.0;
766         let x = chr * (1.0 - Scalar::abs(h_s % 2.0 - 1.0));
767 
768         struct RGB(Scalar, Scalar, Scalar);
769 
770         let col = if h_s < 1.0 {
771             RGB(chr, x, 0.0)
772         } else if 1.0 <= h_s && h_s < 2.0 {
773             RGB(x, chr, 0.0)
774         } else if 2.0 <= h_s && h_s < 3.0 {
775             RGB(0.0, chr, x)
776         } else if 3.0 <= h_s && h_s < 4.0 {
777             RGB(0.0, x, chr)
778         } else if 4.0 <= h_s && h_s < 5.0 {
779             RGB(x, 0.0, chr)
780         } else {
781             RGB(chr, 0.0, x)
782         };
783 
784         RGBA {
785             r: col.0 + m,
786             g: col.1 + m,
787             b: col.2 + m,
788             alpha: color.alpha,
789         }
790     }
791 }
792 
793 impl From<&Color> for RGBA<u8> {
from(color: &Color) -> Self794     fn from(color: &Color) -> Self {
795         let c = RGBA::<f64>::from(color);
796         let r = Scalar::round(255.0 * c.r) as u8;
797         let g = Scalar::round(255.0 * c.g) as u8;
798         let b = Scalar::round(255.0 * c.b) as u8;
799 
800         RGBA {
801             r,
802             g,
803             b,
804             alpha: color.alpha,
805         }
806     }
807 }
808 
809 impl fmt::Display for RGBA<f64> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result810     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
811         write!(f, "rgb({r}, {g}, {b})", r = self.r, g = self.g, b = self.b,)
812     }
813 }
814 
815 impl fmt::Display for RGBA<u8> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result816     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
817         write!(f, "rgb({r}, {g}, {b})", r = self.r, g = self.g, b = self.b,)
818     }
819 }
820 
821 #[derive(Debug, Clone, PartialEq)]
822 pub struct HSLA {
823     pub h: Scalar,
824     pub s: Scalar,
825     pub l: Scalar,
826     pub alpha: Scalar,
827 }
828 
829 impl ColorSpace for HSLA {
from_color(c: &Color) -> Self830     fn from_color(c: &Color) -> Self {
831         c.to_hsla()
832     }
833 
into_color(&self) -> Color834     fn into_color(&self) -> Color {
835         Color::from_hsla(self.h, self.s, self.l, self.alpha)
836     }
837 
mix(&self, other: &Self, fraction: Fraction) -> Self838     fn mix(&self, other: &Self, fraction: Fraction) -> Self {
839         // make sure that the hue is preserved when mixing with gray colors
840         let self_hue = if self.s < 0.0001 { other.h } else { self.h };
841         let other_hue = if other.s < 0.0001 { self.h } else { other.h };
842 
843         Self {
844             h: interpolate_angle(self_hue, other_hue, fraction),
845             s: interpolate(self.s, other.s, fraction),
846             l: interpolate(self.l, other.l, fraction),
847             alpha: interpolate(self.alpha, other.alpha, fraction),
848         }
849     }
850 }
851 
852 impl From<&Color> for HSLA {
from(color: &Color) -> Self853     fn from(color: &Color) -> Self {
854         HSLA {
855             h: color.hue.value(),
856             s: color.saturation,
857             l: color.lightness,
858             alpha: color.alpha,
859         }
860     }
861 }
862 
863 impl fmt::Display for HSLA {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result864     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
865         write!(f, "hsl({h}, {s}, {l})", h = self.h, s = self.s, l = self.l,)
866     }
867 }
868 
869 #[derive(Debug, Clone, PartialEq)]
870 pub struct XYZ {
871     pub x: Scalar,
872     pub y: Scalar,
873     pub z: Scalar,
874     pub alpha: Scalar,
875 }
876 
877 impl From<&Color> for XYZ {
from(color: &Color) -> Self878     fn from(color: &Color) -> Self {
879         #![allow(clippy::many_single_char_names)]
880         let finv = |c_: f64| {
881             if c_ <= 0.04045 {
882                 c_ / 12.92
883             } else {
884                 Scalar::powf((c_ + 0.055) / 1.055, 2.4)
885             }
886         };
887 
888         let rec = RGBA::from(color);
889         let r = finv(rec.r);
890         let g = finv(rec.g);
891         let b = finv(rec.b);
892 
893         let x = 0.4124 * r + 0.3576 * g + 0.1805 * b;
894         let y = 0.2126 * r + 0.7152 * g + 0.0722 * b;
895         let z = 0.0193 * r + 0.1192 * g + 0.9505 * b;
896 
897         XYZ {
898             x,
899             y,
900             z,
901             alpha: color.alpha,
902         }
903     }
904 }
905 
906 impl fmt::Display for XYZ {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result907     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
908         write!(f, "XYZ({x}, {y}, {z})", x = self.x, y = self.y, z = self.z,)
909     }
910 }
911 
912 /// A color space whose axes correspond to the responsivity spectra of the long-, medium-, and
913 /// short-wavelength cone cells in the human eye. More info
914 /// [here](https://en.wikipedia.org/wiki/LMS_color_space).
915 #[derive(Debug, Clone, PartialEq)]
916 pub struct LMS {
917     pub l: Scalar,
918     pub m: Scalar,
919     pub s: Scalar,
920     pub alpha: Scalar,
921 }
922 
923 impl From<&Color> for LMS {
from(color: &Color) -> Self924     fn from(color: &Color) -> Self {
925         let XYZ { x, y, z, alpha } = XYZ::from(color);
926         let l = 0.38971 * x + 0.68898 * y - 0.07868 * z;
927         let m = -0.22981 * x + 1.18340 * y + 0.04641 * z;
928         let s = 0.00000 * x + 0.00000 * y + 1.00000 * z;
929 
930         LMS { l, m, s, alpha }
931     }
932 }
933 
934 impl fmt::Display for LMS {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result935     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
936         write!(f, "LMS({l}, {m}, {s})", l = self.l, m = self.m, s = self.s,)
937     }
938 }
939 
940 #[derive(Debug, Clone, PartialEq)]
941 pub struct Lab {
942     pub l: Scalar,
943     pub a: Scalar,
944     pub b: Scalar,
945     pub alpha: Scalar,
946 }
947 
948 impl ColorSpace for Lab {
from_color(c: &Color) -> Self949     fn from_color(c: &Color) -> Self {
950         c.to_lab()
951     }
952 
into_color(&self) -> Color953     fn into_color(&self) -> Color {
954         Color::from_lab(self.l, self.a, self.b, self.alpha)
955     }
956 
mix(&self, other: &Self, fraction: Fraction) -> Self957     fn mix(&self, other: &Self, fraction: Fraction) -> Self {
958         Self {
959             l: interpolate(self.l, other.l, fraction),
960             a: interpolate(self.a, other.a, fraction),
961             b: interpolate(self.b, other.b, fraction),
962             alpha: interpolate(self.alpha, other.alpha, fraction),
963         }
964     }
965 }
966 
967 impl From<&Color> for Lab {
from(color: &Color) -> Self968     fn from(color: &Color) -> Self {
969         let rec = XYZ::from(color);
970 
971         let cut = Scalar::powf(6.0 / 29.0, 3.0);
972         let f = |t| {
973             if t > cut {
974                 Scalar::powf(t, 1.0 / 3.0)
975             } else {
976                 (1.0 / 3.0) * Scalar::powf(29.0 / 6.0, 2.0) * t + 4.0 / 29.0
977             }
978         };
979 
980         let fy = f(rec.y / D65_YN);
981 
982         let l = 116.0 * fy - 16.0;
983         let a = 500.0 * (f(rec.x / D65_XN) - fy);
984         let b = 200.0 * (fy - f(rec.z / D65_ZN));
985 
986         Lab {
987             l,
988             a,
989             b,
990             alpha: color.alpha,
991         }
992     }
993 }
994 
995 impl fmt::Display for Lab {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result996     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
997         write!(f, "Lab({l}, {a}, {b})", l = self.l, a = self.a, b = self.b,)
998     }
999 }
1000 
1001 #[derive(Debug, Clone, PartialEq)]
1002 pub struct LCh {
1003     pub l: Scalar,
1004     pub c: Scalar,
1005     pub h: Scalar,
1006     pub alpha: Scalar,
1007 }
1008 
1009 impl ColorSpace for LCh {
from_color(c: &Color) -> Self1010     fn from_color(c: &Color) -> Self {
1011         c.to_lch()
1012     }
1013 
into_color(&self) -> Color1014     fn into_color(&self) -> Color {
1015         Color::from_lch(self.l, self.c, self.h, self.alpha)
1016     }
1017 
mix(&self, other: &Self, fraction: Fraction) -> Self1018     fn mix(&self, other: &Self, fraction: Fraction) -> Self {
1019         // make sure that the hue is preserved when mixing with gray colors
1020         let self_hue = if self.c < 0.1 { other.h } else { self.h };
1021         let other_hue = if other.c < 0.1 { self.h } else { other.h };
1022 
1023         Self {
1024             l: interpolate(self.l, other.l, fraction),
1025             c: interpolate(self.c, other.c, fraction),
1026             h: interpolate_angle(self_hue, other_hue, fraction),
1027             alpha: interpolate(self.alpha, other.alpha, fraction),
1028         }
1029     }
1030 }
1031 
1032 impl From<&Color> for LCh {
from(color: &Color) -> Self1033     fn from(color: &Color) -> Self {
1034         let Lab { l, a, b, alpha } = Lab::from(color);
1035 
1036         const RAD2DEG: Scalar = 180.0 / std::f64::consts::PI;
1037 
1038         let c = Scalar::sqrt(a * a + b * b);
1039         let h = mod_positive(Scalar::atan2(b, a) * RAD2DEG, 360.0);
1040 
1041         LCh { l, c, h, alpha }
1042     }
1043 }
1044 
1045 impl fmt::Display for LCh {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result1046     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1047         write!(f, "LCh({l}, {c}, {h})", l = self.l, c = self.c, h = self.h,)
1048     }
1049 }
1050 
1051 #[derive(Debug, Clone, PartialEq)]
1052 pub struct CMYK {
1053     pub c: Scalar,
1054     pub m: Scalar,
1055     pub y: Scalar,
1056     pub k: Scalar,
1057 }
1058 
1059 impl From<&Color> for CMYK {
from(color: &Color) -> Self1060     fn from(color: &Color) -> Self {
1061         let rgba = RGBA::<u8>::from(color);
1062         let r = (rgba.r as f64) / 255.0;
1063         let g = (rgba.g as f64) / 255.0;
1064         let b = (rgba.b as f64) / 255.0;
1065         let biggest = if r >= g && r >= b {
1066             r
1067         } else if g >= r && g >= b {
1068             g
1069         } else {
1070             b
1071         };
1072         let out_k = 1.0 - biggest;
1073         let out_c = (1.0 - r - out_k) / biggest;
1074         let out_m = (1.0 - g - out_k) / biggest;
1075         let out_y = (1.0 - b - out_k) / biggest;
1076 
1077         CMYK {
1078             c: if out_c.is_nan() { 0.0 } else { out_c },
1079             m: if out_m.is_nan() { 0.0 } else { out_m },
1080             y: if out_y.is_nan() { 0.0 } else { out_y },
1081             k: out_k,
1082         }
1083     }
1084 }
1085 
1086 impl fmt::Display for CMYK {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result1087     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1088         write!(
1089             f,
1090             "cmyk({c}, {m}, {y}, {k})",
1091             c = self.c,
1092             m = self.m,
1093             y = self.y,
1094             k = self.k,
1095         )
1096     }
1097 }
1098 
1099 /// A representation of the different kinds of colorblindness. More info
1100 /// [here](https://en.wikipedia.org/wiki/Color_blindness).
1101 pub enum ColorblindnessType {
1102     /// Protanopic people lack red cones
1103     Protanopia,
1104     /// Deuteranopic people lack green cones
1105     Deuteranopia,
1106     /// Tritanopic people lack blue cones
1107     Tritanopia,
1108 }
1109 
1110 #[derive(Debug, Clone, Copy, PartialEq)]
1111 pub enum Format {
1112     Spaces,
1113     NoSpaces,
1114 }
1115 
1116 /// The representation of a color stop for a `ColorScale`.
1117 /// The position defines where the color is placed from left (0.0) to right (1.0).
1118 #[derive(Debug, Clone)]
1119 struct ColorStop {
1120     color: Color,
1121     position: Fraction,
1122 }
1123 
1124 /// The representation of a color scale.
1125 /// The first `ColorStop` (position 0.0) defines the left end color.
1126 /// The last `ColorStop` (position 1.0) defines the right end color.
1127 #[derive(Debug, Clone)]
1128 pub struct ColorScale {
1129     color_stops: Vec<ColorStop>,
1130 }
1131 
1132 impl ColorScale {
1133     /// Create an empty `ColorScale`.
empty() -> Self1134     pub fn empty() -> Self {
1135         Self {
1136             color_stops: Vec::new(),
1137         }
1138     }
1139 
1140     /// Add a `Color` at the given position.
add_stop(&mut self, color: Color, position: Fraction) -> &mut Self1141     pub fn add_stop(&mut self, color: Color, position: Fraction) -> &mut Self {
1142         #![allow(clippy::float_cmp)]
1143         let same_position = self
1144             .color_stops
1145             .iter_mut()
1146             .find(|c| position.value() == c.position.value());
1147 
1148         match same_position {
1149             Some(color_stop) => color_stop.color = color,
1150             None => {
1151                 let next_index = self
1152                     .color_stops
1153                     .iter()
1154                     .position(|c| position.value() < c.position.value());
1155 
1156                 let index = next_index.unwrap_or_else(|| self.color_stops.len());
1157 
1158                 let color_stop = ColorStop { color, position };
1159 
1160                 self.color_stops.insert(index, color_stop);
1161             }
1162         };
1163 
1164         self
1165     }
1166 
1167     /// Get the color at the given position using the mixing function.
1168     ///
1169     /// Note:
1170     /// - No color is returned if position isn't between two color stops or the `ColorScale` is empty.
sample( &self, position: Fraction, mix: &dyn Fn(&Color, &Color, Fraction) -> Color, ) -> Option<Color>1171     pub fn sample(
1172         &self,
1173         position: Fraction,
1174         mix: &dyn Fn(&Color, &Color, Fraction) -> Color,
1175     ) -> Option<Color> {
1176         if self.color_stops.len() < 2 {
1177             return None;
1178         }
1179 
1180         let left_stop = self
1181             .color_stops
1182             .iter()
1183             .rev()
1184             .find(|c| position.value() >= c.position.value());
1185 
1186         let right_stop = self
1187             .color_stops
1188             .iter()
1189             .find(|c| position.value() <= c.position.value());
1190 
1191         match (left_stop, right_stop) {
1192             (Some(left_stop), Some(right_stop)) => {
1193                 let diff_color_stops = right_stop.position.value() - left_stop.position.value();
1194                 let diff_position = position.value() - left_stop.position.value();
1195                 let local_position = Fraction::from(diff_position / diff_color_stops);
1196 
1197                 let color = mix(&left_stop.color, &right_stop.color, local_position);
1198 
1199                 Some(color)
1200             }
1201             _ => None,
1202         }
1203     }
1204 }
1205 
1206 #[cfg(test)]
1207 mod tests {
1208     use super::*;
1209     use approx::assert_relative_eq;
1210 
assert_almost_equal(c1: &Color, c2: &Color)1211     fn assert_almost_equal(c1: &Color, c2: &Color) {
1212         let c1 = c1.to_rgba();
1213         let c2 = c2.to_rgba();
1214 
1215         assert!((c1.r as i32 - c2.r as i32).abs() <= 1);
1216         assert!((c1.g as i32 - c2.g as i32).abs() <= 1);
1217         assert!((c1.b as i32 - c2.b as i32).abs() <= 1);
1218     }
1219 
1220     #[test]
color_partial_eq()1221     fn color_partial_eq() {
1222         assert_eq!(
1223             Color::from_hsl(120.0, 0.3, 0.5),
1224             Color::from_hsl(360.0 + 120.0, 0.3, 0.5),
1225         );
1226         assert_eq!(
1227             Color::from_rgba(1, 2, 3, 0.3),
1228             Color::from_rgba(1, 2, 3, 0.3),
1229         );
1230         assert_eq!(Color::black(), Color::from_hsl(123.0, 0.3, 0.0));
1231         assert_eq!(Color::white(), Color::from_hsl(123.0, 0.3, 1.0));
1232 
1233         assert_ne!(
1234             Color::from_hsl(120.0, 0.3, 0.5),
1235             Color::from_hsl(122.0, 0.3, 0.5),
1236         );
1237         assert_ne!(
1238             Color::from_hsl(120.0, 0.3, 0.5),
1239             Color::from_hsl(120.0, 0.32, 0.5),
1240         );
1241         assert_ne!(
1242             Color::from_hsl(120.0, 0.3, 0.5),
1243             Color::from_hsl(120.0, 0.3, 0.52),
1244         );
1245         assert_ne!(
1246             Color::from_hsla(120.0, 0.3, 0.5, 0.9),
1247             Color::from_hsla(120.0, 0.3, 0.5, 0.901),
1248         );
1249         assert_ne!(
1250             Color::from_rgba(1, 2, 3, 0.3),
1251             Color::from_rgba(2, 2, 3, 0.3),
1252         );
1253         assert_ne!(
1254             Color::from_rgba(1, 2, 3, 0.3),
1255             Color::from_rgba(1, 3, 3, 0.3),
1256         );
1257         assert_ne!(
1258             Color::from_rgba(1, 2, 3, 0.3),
1259             Color::from_rgba(1, 2, 4, 0.3),
1260         );
1261     }
1262 
1263     #[test]
rgb_to_hsl_conversion()1264     fn rgb_to_hsl_conversion() {
1265         assert_eq!(Color::white(), Color::from_rgb_float(1.0, 1.0, 1.0));
1266         assert_eq!(Color::gray(), Color::from_rgb_float(0.5, 0.5, 0.5));
1267         assert_eq!(Color::black(), Color::from_rgb_float(0.0, 0.0, 0.0));
1268         assert_eq!(Color::red(), Color::from_rgb_float(1.0, 0.0, 0.0));
1269         assert_eq!(
1270             Color::from_hsl(60.0, 1.0, 0.375),
1271             Color::from_rgb_float(0.75, 0.75, 0.0)
1272         ); //yellow-green
1273         assert_eq!(Color::green(), Color::from_rgb_float(0.0, 0.5, 0.0));
1274         assert_eq!(
1275             Color::from_hsl(240.0, 1.0, 0.75),
1276             Color::from_rgb_float(0.5, 0.5, 1.0)
1277         ); // blue-ish
1278         assert_eq!(
1279             Color::from_hsl(49.5, 0.893, 0.497),
1280             Color::from_rgb_float(0.941, 0.785, 0.053)
1281         ); // yellow
1282         assert_eq!(
1283             Color::from_hsl(162.4, 0.779, 0.447),
1284             Color::from_rgb_float(0.099, 0.795, 0.591)
1285         ); // cyan 2
1286     }
1287 
1288     #[test]
rgb_roundtrip_conversion()1289     fn rgb_roundtrip_conversion() {
1290         let roundtrip = |h, s, l| {
1291             let color1 = Color::from_hsl(h, s, l);
1292             let rgb = color1.to_rgba();
1293             let color2 = Color::from_rgb(rgb.r, rgb.g, rgb.b);
1294             assert_eq!(color1, color2);
1295         };
1296 
1297         roundtrip(0.0, 0.0, 1.0);
1298         roundtrip(0.0, 0.0, 0.5);
1299         roundtrip(0.0, 0.0, 0.0);
1300         roundtrip(60.0, 1.0, 0.375);
1301         roundtrip(120.0, 1.0, 0.25);
1302         roundtrip(240.0, 1.0, 0.75);
1303         roundtrip(49.5, 0.893, 0.497);
1304         roundtrip(162.4, 0.779, 0.447);
1305 
1306         for degree in 0..360 {
1307             roundtrip(Scalar::from(degree), 0.5, 0.8);
1308         }
1309     }
1310 
1311     #[test]
to_u32()1312     fn to_u32() {
1313         assert_eq!(0, Color::black().to_u32());
1314         assert_eq!(0xff0000, Color::red().to_u32());
1315         assert_eq!(0xffffff, Color::white().to_u32());
1316         assert_eq!(0xf4230f, Color::from_rgb(0xf4, 0x23, 0x0f).to_u32());
1317     }
1318 
1319     #[test]
xyz_conversion()1320     fn xyz_conversion() {
1321         assert_eq!(Color::white(), Color::from_xyz(0.9505, 1.0, 1.0890, 1.0));
1322         assert_eq!(Color::red(), Color::from_xyz(0.4123, 0.2126, 0.01933, 1.0));
1323         assert_eq!(
1324             Color::from_hsl(109.999, 0.08654, 0.407843),
1325             Color::from_xyz(0.13123, 0.15372, 0.13174, 1.0)
1326         );
1327 
1328         let roundtrip = |h, s, l| {
1329             let color1 = Color::from_hsl(h, s, l);
1330             let xyz1 = color1.to_xyz();
1331             let color2 = Color::from_xyz(xyz1.x, xyz1.y, xyz1.z, 1.0);
1332             assert_almost_equal(&color1, &color2);
1333         };
1334 
1335         for hue in 0..360 {
1336             roundtrip(Scalar::from(hue), 0.2, 0.8);
1337         }
1338     }
1339 
1340     #[test]
lms_conversion()1341     fn lms_conversion() {
1342         let roundtrip = |h, s, l| {
1343             let color1 = Color::from_hsl(h, s, l);
1344             let lms1 = color1.to_lms();
1345             let color2 = Color::from_lms(lms1.l, lms1.m, lms1.s, 1.0);
1346             assert_almost_equal(&color1, &color2);
1347         };
1348 
1349         for hue in 0..360 {
1350             roundtrip(Scalar::from(hue), 0.2, 0.8);
1351         }
1352     }
1353 
1354     #[test]
lab_conversion()1355     fn lab_conversion() {
1356         assert_eq!(Color::red(), Color::from_lab(53.233, 80.109, 67.22, 1.0));
1357 
1358         let roundtrip = |h, s, l| {
1359             let color1 = Color::from_hsl(h, s, l);
1360             let lab1 = color1.to_lab();
1361             let color2 = Color::from_lab(lab1.l, lab1.a, lab1.b, 1.0);
1362             assert_almost_equal(&color1, &color2);
1363         };
1364 
1365         for hue in 0..360 {
1366             roundtrip(Scalar::from(hue), 0.2, 0.8);
1367         }
1368     }
1369 
1370     #[test]
lch_conversion()1371     fn lch_conversion() {
1372         assert_eq!(
1373             Color::from_hsl(0.0, 1.0, 0.245),
1374             Color::from_lch(24.829, 60.093, 38.18, 1.0)
1375         );
1376 
1377         let roundtrip = |h, s, l| {
1378             let color1 = Color::from_hsl(h, s, l);
1379             let lch1 = color1.to_lch();
1380             let color2 = Color::from_lch(lch1.l, lch1.c, lch1.h, 1.0);
1381             assert_almost_equal(&color1, &color2);
1382         };
1383 
1384         for hue in 0..360 {
1385             roundtrip(Scalar::from(hue), 0.2, 0.8);
1386         }
1387     }
1388 
1389     #[test]
rotate_hue()1390     fn rotate_hue() {
1391         assert_eq!(Color::lime(), Color::red().rotate_hue(120.0));
1392     }
1393 
1394     #[test]
complementary()1395     fn complementary() {
1396         assert_eq!(Color::fuchsia(), Color::lime().complementary());
1397         assert_eq!(Color::lime(), Color::fuchsia().complementary());
1398     }
1399 
1400     #[test]
lighten()1401     fn lighten() {
1402         assert_eq!(
1403             Color::from_hsl(90.0, 0.5, 0.7),
1404             Color::from_hsl(90.0, 0.5, 0.3).lighten(0.4)
1405         );
1406         assert_eq!(
1407             Color::from_hsl(90.0, 0.5, 1.0),
1408             Color::from_hsl(90.0, 0.5, 0.3).lighten(0.8)
1409         );
1410     }
1411 
1412     #[test]
to_gray()1413     fn to_gray() {
1414         let salmon = Color::from_rgb(250, 128, 114);
1415         assert_eq!(0.0, salmon.to_gray().to_hsla().s);
1416         assert_relative_eq!(
1417             salmon.luminance(),
1418             salmon.to_gray().luminance(),
1419             max_relative = 0.01
1420         );
1421 
1422         assert_eq!(Color::graytone(0.3), Color::graytone(0.3).to_gray());
1423     }
1424 
1425     #[test]
brightness()1426     fn brightness() {
1427         assert_eq!(0.0, Color::black().brightness());
1428         assert_eq!(1.0, Color::white().brightness());
1429         assert_eq!(0.5, Color::graytone(0.5).brightness());
1430     }
1431 
1432     #[test]
luminance()1433     fn luminance() {
1434         assert_eq!(1.0, Color::white().luminance());
1435         let hotpink = Color::from_rgb(255, 105, 180);
1436         assert_relative_eq!(0.347, hotpink.luminance(), max_relative = 0.01);
1437         assert_eq!(0.0, Color::black().luminance());
1438     }
1439 
1440     #[test]
contrast_ratio()1441     fn contrast_ratio() {
1442         assert_relative_eq!(21.0, Color::black().contrast_ratio(&Color::white()));
1443         assert_relative_eq!(21.0, Color::white().contrast_ratio(&Color::black()));
1444 
1445         assert_relative_eq!(1.0, Color::white().contrast_ratio(&Color::white()));
1446         assert_relative_eq!(1.0, Color::red().contrast_ratio(&Color::red()));
1447 
1448         assert_relative_eq!(
1449             4.26,
1450             Color::from_rgb(255, 119, 153).contrast_ratio(&Color::from_rgb(0, 68, 85)),
1451             max_relative = 0.01
1452         );
1453     }
1454 
1455     #[test]
text_color()1456     fn text_color() {
1457         assert_eq!(Color::white(), Color::graytone(0.4).text_color());
1458         assert_eq!(Color::black(), Color::graytone(0.6).text_color());
1459     }
1460 
1461     #[test]
distance_delta_e_cie76()1462     fn distance_delta_e_cie76() {
1463         let c = Color::from_rgb(255, 127, 14);
1464         assert_eq!(0.0, c.distance_delta_e_cie76(&c));
1465 
1466         let c1 = Color::from_rgb(50, 100, 200);
1467         let c2 = Color::from_rgb(200, 10, 0);
1468         assert_eq!(123.0, c1.distance_delta_e_cie76(&c2).round());
1469     }
1470 
1471     #[test]
to_hsl_string()1472     fn to_hsl_string() {
1473         let c = Color::from_hsl(91.3, 0.541, 0.983);
1474         assert_eq!("hsl(91, 54.1%, 98.3%)", c.to_hsl_string(Format::Spaces));
1475     }
1476 
1477     #[test]
to_rgb_string()1478     fn to_rgb_string() {
1479         let c = Color::from_rgb(255, 127, 4);
1480         assert_eq!("rgb(255, 127, 4)", c.to_rgb_string(Format::Spaces));
1481     }
1482 
1483     #[test]
to_rgb_float_string()1484     fn to_rgb_float_string() {
1485         assert_eq!(
1486             "rgb(0.000, 0.000, 0.000)",
1487             Color::black().to_rgb_float_string(Format::Spaces)
1488         );
1489 
1490         assert_eq!(
1491             "rgb(1.000, 1.000, 1.000)",
1492             Color::white().to_rgb_float_string(Format::Spaces)
1493         );
1494 
1495         // some minor rounding errors here, but that is to be expected:
1496         let c = Color::from_rgb_float(0.12, 0.45, 0.78);
1497         assert_eq!(
1498             "rgb(0.122, 0.451, 0.780)",
1499             c.to_rgb_float_string(Format::Spaces)
1500         );
1501     }
1502 
1503     #[test]
to_rgb_hex_string()1504     fn to_rgb_hex_string() {
1505         let c = Color::from_rgb(255, 127, 4);
1506         assert_eq!("ff7f04", c.to_rgb_hex_string(false));
1507         assert_eq!("#ff7f04", c.to_rgb_hex_string(true));
1508     }
1509 
1510     #[test]
to_lab_string()1511     fn to_lab_string() {
1512         let c = Color::from_lab(41.0, 83.0, -93.0, 1.0);
1513         assert_eq!("Lab(41, 83, -93)", c.to_lab_string(Format::Spaces));
1514     }
1515 
1516     #[test]
to_lch_string()1517     fn to_lch_string() {
1518         let c = Color::from_lch(52.0, 44.0, 271.0, 1.0);
1519         assert_eq!("LCh(52, 44, 271)", c.to_lch_string(Format::Spaces));
1520     }
1521 
1522     #[test]
mix()1523     fn mix() {
1524         assert_eq!(
1525             Color::purple(),
1526             Color::red().mix::<RGBA<f64>>(&Color::blue(), Fraction::from(0.5))
1527         );
1528         assert_eq!(
1529             Color::fuchsia(),
1530             Color::red().mix::<HSLA>(&Color::blue(), Fraction::from(0.5))
1531         );
1532     }
1533 
1534     #[test]
mixing_with_gray_preserves_hue()1535     fn mixing_with_gray_preserves_hue() {
1536         let hue = 123.0;
1537 
1538         let input = Color::from_hsla(hue, 0.5, 0.5, 1.0);
1539 
1540         let hue_after_mixing = |other| input.mix::<HSLA>(&other, Fraction::from(0.5)).to_hsla().h;
1541 
1542         assert_eq!(hue, hue_after_mixing(Color::black()));
1543         assert_eq!(hue, hue_after_mixing(Color::graytone(0.2)));
1544         assert_eq!(hue, hue_after_mixing(Color::graytone(0.7)));
1545         assert_eq!(hue, hue_after_mixing(Color::white()));
1546     }
1547 
1548     #[test]
color_scale_add_preserves_ordering()1549     fn color_scale_add_preserves_ordering() {
1550         let mut color_scale = ColorScale::empty();
1551 
1552         color_scale
1553             .add_stop(Color::red(), Fraction::from(0.5))
1554             .add_stop(Color::gray(), Fraction::from(0.0))
1555             .add_stop(Color::blue(), Fraction::from(1.0));
1556 
1557         assert_eq!(color_scale.color_stops.get(0).unwrap().color, Color::gray());
1558         assert_eq!(color_scale.color_stops.get(1).unwrap().color, Color::red());
1559         assert_eq!(color_scale.color_stops.get(2).unwrap().color, Color::blue());
1560     }
1561 
1562     #[test]
color_scale_empty_sample_none()1563     fn color_scale_empty_sample_none() {
1564         let mix = Color::mix::<Lab>;
1565 
1566         let color_scale = ColorScale::empty();
1567 
1568         let color = color_scale.sample(Fraction::from(0.0), &mix);
1569 
1570         assert_eq!(color, None);
1571     }
1572 
1573     #[test]
color_scale_one_color_sample_none()1574     fn color_scale_one_color_sample_none() {
1575         let mix = Color::mix::<Lab>;
1576 
1577         let mut color_scale = ColorScale::empty();
1578 
1579         color_scale.add_stop(Color::red(), Fraction::from(0.0));
1580 
1581         let color = color_scale.sample(Fraction::from(0.0), &mix);
1582 
1583         assert_eq!(color, None);
1584     }
1585 
1586     #[test]
color_scale_sample_same_position()1587     fn color_scale_sample_same_position() {
1588         let mix = Color::mix::<Lab>;
1589 
1590         let mut color_scale = ColorScale::empty();
1591 
1592         color_scale
1593             .add_stop(Color::red(), Fraction::from(0.0))
1594             .add_stop(Color::green(), Fraction::from(1.0))
1595             .add_stop(Color::blue(), Fraction::from(0.0))
1596             .add_stop(Color::white(), Fraction::from(1.0));
1597 
1598         let sample_blue = color_scale.sample(Fraction::from(0.0), &mix).unwrap();
1599         let sample_white = color_scale.sample(Fraction::from(1.0), &mix).unwrap();
1600 
1601         assert_eq!(sample_blue, Color::blue());
1602         assert_eq!(sample_white, Color::white());
1603     }
1604 
1605     #[test]
color_scale_sample()1606     fn color_scale_sample() {
1607         let mix = Color::mix::<Lab>;
1608 
1609         let mut color_scale = ColorScale::empty();
1610 
1611         color_scale
1612             .add_stop(Color::green(), Fraction::from(1.0))
1613             .add_stop(Color::red(), Fraction::from(0.0));
1614 
1615         let sample_red_green = color_scale.sample(Fraction::from(0.5), &mix).unwrap();
1616 
1617         let mix_red_green = mix(&Color::red(), &Color::green(), Fraction::from(0.5));
1618 
1619         assert_eq!(sample_red_green, mix_red_green);
1620     }
1621 
1622     #[test]
color_scale_sample_position()1623     fn color_scale_sample_position() {
1624         let mix = Color::mix::<Lab>;
1625 
1626         let mut color_scale = ColorScale::empty();
1627 
1628         color_scale
1629             .add_stop(Color::green(), Fraction::from(0.5))
1630             .add_stop(Color::red(), Fraction::from(0.0))
1631             .add_stop(Color::blue(), Fraction::from(1.0));
1632 
1633         let sample_red = color_scale.sample(Fraction::from(0.0), &mix).unwrap();
1634         let sample_green = color_scale.sample(Fraction::from(0.5), &mix).unwrap();
1635         let sample_blue = color_scale.sample(Fraction::from(1.0), &mix).unwrap();
1636 
1637         let sample_red_green = color_scale.sample(Fraction::from(0.25), &mix).unwrap();
1638         let sample_green_blue = color_scale.sample(Fraction::from(0.75), &mix).unwrap();
1639 
1640         let mix_red_green = mix(&Color::red(), &Color::green(), Fraction::from(0.50));
1641         let mix_green_blue = mix(&Color::green(), &Color::blue(), Fraction::from(0.50));
1642 
1643         assert_eq!(sample_red, Color::red());
1644         assert_eq!(sample_green, Color::green());
1645         assert_eq!(sample_blue, Color::blue());
1646 
1647         assert_eq!(sample_red_green, mix_red_green);
1648         assert_eq!(sample_green_blue, mix_green_blue);
1649     }
1650 
1651     #[test]
to_cmyk_string()1652     fn to_cmyk_string() {
1653         let white = Color::from_rgb(255, 255, 255);
1654         assert_eq!("cmyk(0, 0, 0, 0)", white.to_cmyk_string(Format::Spaces));
1655 
1656         let black = Color::from_rgb(0, 0, 0);
1657         assert_eq!("cmyk(0, 0, 0, 100)", black.to_cmyk_string(Format::Spaces));
1658 
1659         let c = Color::from_rgb(19, 19, 1);
1660         assert_eq!("cmyk(0, 0, 95, 93)", c.to_cmyk_string(Format::Spaces));
1661 
1662         let c1 = Color::from_rgb(55, 55, 55);
1663         assert_eq!("cmyk(0, 0, 0, 78)", c1.to_cmyk_string(Format::Spaces));
1664 
1665         let c2 = Color::from_rgb(136, 117, 78);
1666         assert_eq!("cmyk(0, 14, 43, 47)", c2.to_cmyk_string(Format::Spaces));
1667 
1668         let c3 = Color::from_rgb(143, 111, 76);
1669         assert_eq!("cmyk(0, 22, 47, 44)", c3.to_cmyk_string(Format::Spaces));
1670     }
1671 }
1672