scarlet/
color.rs

1//! This file defines the [`Color`] trait, the foundational defining trait of
2//! the entire library. Despite the dizzying amount of things [`Color`] can do
3//! in Scarlet, especially with its extending traits, the definition is quite
4//! simple: anything that can be converted to and from the [CIE 1931 XYZ
5//! space](https://en.wikipedia.org/wiki/CIE_1931_color_space). This color space
6//! is common to use as a master space, and Scarlet is no different. What makes
7//! XYZ unique is that it can be computed directly from the spectral data of a
8//! color. Although Scarlet does not implement this due to its scope, this
9//! property makes it possible to derive XYZ colors from real-world data,
10//! something that no other color space can do the same way.
11//!
12//! The thing that makes [`XYZColor`], the base implementation of the CIE 1931
13//! XYZ space, special is that it is the only color object in Scarlet that keeps
14//! track of its own illuminant data. Every other color space assumes a viewing
15//! environment, but because XYZ color maps directly to neural perception it
16//! keeps track of what environment the color is being viewed in. This allows
17//! Scarlet to translate between color spaces that have different assumptions
18//! seamlessly. (If you notice that Scarlet's values for conversions differ from
19//! other sources, this may be why: some sources don't do this properly or
20//! implement it differently. Scarlet generally follows best practices and
21//! industry standards, but file an issue if you feel this is not true.) The
22//! essential workflow of [`Color`], and therefore Scarlet, is generally like
23//! this: convert between different color spaces using the generic [`convert<T:
24//! Color>()`](trait.Color.html#method.convert) method, which allows any
25//! [`Color`] to be interconverted to any other representation. Leverage the
26//! specific attributes of each color space if need be (for example, using the
27//! hue or luminance attributes), and then convert back to a suitable display
28//! space. The many other methods of [`Color`] make some of the more common such
29//! patterns simple to do.
30//!
31
32use std::collections::HashMap;
33use std::convert::From;
34use std::error::Error;
35use std::fmt;
36use std::marker::Sized;
37use std::num::ParseIntError;
38use std::result::Result::Err;
39use std::str::FromStr;
40use std::string::ToString;
41
42use super::coord::Coord;
43use colors::cielabcolor::CIELABColor;
44use colors::cielchcolor::CIELCHColor;
45use consts;
46use consts::BRADFORD_TRANSFORM as BRADFORD;
47use consts::BRADFORD_TRANSFORM_LU as BRADFORD_LU;
48use consts::STANDARD_RGB_TRANSFORM as SRGB;
49use consts::STANDARD_RGB_TRANSFORM_LU as SRGB_LU;
50use csscolor::{parse_rgb_str, CSSParseError};
51use illuminants::Illuminant;
52
53use nalgebra::base::Vector;
54use nalgebra::vector;
55
56#[cfg(feature = "terminal")]
57use termion::color::{Bg, Fg, Reset, Rgb};
58
59/// A point in the CIE 1931 XYZ color space. Although any point in XYZ coordinate space is technically
60/// valid, in this library XYZ colors are treated as normalized so that Y=1 is the white point of
61/// whatever illuminant is being worked with.
62#[derive(Debug, Copy, Clone, PartialEq)]
63pub struct XYZColor {
64    /// The X axis of the CIE 1931 XYZ space, roughly representing the long-wavelength receptors in
65    /// the human eye: the red receptors. Usually between 0 and 1, but can range more than that.
66    pub x: f64,
67    /// The Y axis of the CIE 1931 XYZ space, roughly representing the middle-wavelength receptors in
68    /// the human eye. In CIE 1931, this is fudged a little to correspond exactly with perceived
69    /// luminance, so while this doesn't exactly map to middle-wavelength receptors it has a far more
70    /// useful analogue.
71    pub y: f64,
72    /// The Z axis of the CIE 1931 XYZ space, roughly representing the short-wavelength receptors in
73    /// the human eye. Usually between 0 and 1, but can range more than that.
74    pub z: f64,
75    /// The illuminant that is assumed to be the lighting environment for this color. Although XYZ
76    /// itself describes the human response to a color and so is independent of lighting, it is
77    /// useful to consider the question "how would an object in one light look different in
78    /// another?" and so, to contain all the information needed to track this, the illuminant is
79    /// set. See the [`color_adapt()`] method to examine how this is used in the wild.
80    ///
81    /// [`color_adapt()`]: #method.color_adapt
82    pub illuminant: Illuminant,
83}
84
85impl XYZColor {
86    /// Converts from one illuminant to a different one, such that a human receiving both sets of
87    /// sensory stimuli in the corresponding lighting conditions would perceive an object with that
88    /// color as not having changed. This process, called [*chromatic
89    /// adaptation*](https://en.wikipedia.org/wiki/Chromatic_adaptation), happens subconsciously all
90    /// the time: when someone walks into the shade, we don't interpret that shift as their face
91    /// turning blue. This process is not at all simple to compute, however, and many different
92    /// algorithms for doing so exist: it is most likely that each person has their own idiosyncrasies
93    /// with chromatic adaptation and so there is no perfect solution. Scarlet implements the
94    /// *Bradford transform*, which is generally acknowledged to be one of the leading chromatic
95    /// adaptation transforms. Nonetheless, for exact color science work other models are more
96    /// appropriate, such as CIECAM02 if you can measure viewing conditions exactly. This transform
97    /// may not give very good results when used with custom illuminants that wildly differ, but with
98    /// the standard illuminants it does a very good job.
99    /// # Example: The Fabled Dress
100    /// The most accessible way of describing color transformation is to take a look at [this
101    /// image](https://upload.wikimedia.org/wikipedia/en/a/a8/The_Dress_%28viral_phenomenon%29.png),
102    /// otherwise known as "the dress". This showcases in a very apparent fashion the problems with
103    /// very ambiguous lighting in chromatic adaptation: the photo is cropped to the point that some
104    /// of the population perceives it to be in deep shade and for the dress to therefore be white and
105    /// gold, while others perceive instead harsh sunlight and therefore perceive it as black and
106    /// blue. (For reference, it is actually black and blue.) Scarlet can help us answer the question
107    /// "how would this look to an observer with either judgment about the lighting conditions?"
108    /// without needing the human eye!
109    /// First, we use a photo editor to pick out two colors that represent both colors of the
110    /// dress. Then, we'll change the illuminant directly (without using chromatic adaptation, because
111    /// we want to actually change the color), and then we'll adapt back to D65 to represent on a
112    /// screen the different colors.
113    ///
114    /// ```rust
115    /// # use scarlet::prelude::*;
116    /// let dress_bg = RGBColor::from_hex_code("#7d6e47").unwrap().to_xyz(Illuminant::D65);
117    /// let dress_fg = RGBColor::from_hex_code("#9aabd6").unwrap().to_xyz(Illuminant::D65);
118    /// // proposed sunlight illuminant: daylight in North America
119    /// // We could exaggerate the effect by creating an illuminant with greater Y value at the white
120    /// // point, but this will do
121    /// let sunlight = Illuminant::D50;
122    /// // proposed "shade" illuminant: created by picking the brightest point on the dress without
123    /// // glare subjectively, and then treating that as white
124    /// let shade_white = RGBColor::from_hex_code("#b0c5e4").unwrap().to_xyz(Illuminant::D65);
125    /// let shade = Illuminant::Custom([shade_white.x, shade_white.y, shade_white.z]);
126    /// // make copies of the colors and set illuminants
127    /// let mut black = dress_bg;
128    /// let mut blue = dress_fg;
129    /// let mut gold = dress_bg;
130    /// let mut white = dress_fg;
131    /// black.illuminant = sunlight;
132    /// blue.illuminant = sunlight;
133    /// gold.illuminant = shade;
134    /// white.illuminant = shade;
135    /// // we can just print them out now: the chromatic adaptation is done automatically to get back
136    /// // to the color space of the viewing monitor. This isn't exact, mostly because the shade
137    /// // illuminant is entirely fudged, but it's surprisingly good
138    /// let black_rgb: RGBColor = black.convert();
139    /// let blue_rgb: RGBColor = blue.convert();
140    /// let gold_rgb: RGBColor = gold.convert();
141    /// let white_rgb: RGBColor = white.convert();
142    /// println!("Black: {} Blue: {}", black_rgb.to_string(), blue_rgb.to_string());
143    /// println!("Gold: {}, White: {}", gold_rgb.to_string(), white_rgb.to_string());
144    /// ```
145    pub fn color_adapt(&self, other_illuminant: Illuminant) -> XYZColor {
146        // no need to transform if same illuminant
147        if other_illuminant == self.illuminant {
148            *self
149        } else {
150            // convert to Bradford RGB space
151            // &* needed because lazy_static uses a different type which implements Deref
152            let rgb = *BRADFORD * vector![self.x, self.y, self.z];
153
154            // get the RGB values for the white point of the illuminant we are currently using and
155            // the one we want: wr here stands for "white reference", i.e., the one we're converting
156            // to
157            let rgb_w = *BRADFORD * Vector::from(self.illuminant.white_point().to_vec());
158            let rgb_wr = *BRADFORD * Vector::from(other_illuminant.white_point().to_vec());
159
160            // perform the transform
161            // this usually includes a parameter indicating how much you want to adapt, but it's
162            // assumed that we want total adaptation: D = 1. Maybe this could change someday?
163
164            // because each white point has already been normalized to Y = 1, we don't need ap
165            // factor for it, which simplifies calculation even more than setting D = 1 and makes it
166            // just a linear transform
167            // scale by the ratio of luminance: it should always be 1, but with rounding error it
168            // isn't
169            let r_c = rgb[0] * rgb_wr[0] / rgb_w[0];
170            let g_c = rgb[1] * rgb_wr[1] / rgb_w[1];
171            // there's a slight nonlinearity here that I will omit
172            let b_c = rgb[2] * rgb_wr[2] / rgb_w[2];
173            // convert back to XYZ using inverse of previous matrix
174
175            // using LU decomposition for accuracy
176            let xyz_c = BRADFORD_LU
177                .solve(&vector![r_c, g_c, b_c])
178                .expect("Matrix is invertible.");
179            XYZColor {
180                x: xyz_c[0],
181                y: xyz_c[1],
182                z: xyz_c[2],
183                illuminant: other_illuminant,
184            }
185        }
186    }
187    /// Returns `true` if the given other XYZ color's coordinates are all within acceptable error of
188    /// each other, which helps account for necessary floating-point errors in conversions. To test
189    /// whether two colors are indistinguishable to humans, use instead
190    /// [`Color::visually_indistinguishable`].
191    /// # Example
192    ///
193    /// ```
194    /// # use scarlet::color::XYZColor;
195    /// # use scarlet::illuminants::Illuminant;
196    /// let xyz1 = XYZColor{x: 0.3, y: 0., z: 0., illuminant: Illuminant::D65};
197    /// // note that the difference in illuminant won't be taken into account
198    /// let xyz2 = XYZColor{x: 0.1 + 0.1 + 0.1, y: 0., z: 0., illuminant: Illuminant::D55};
199    /// // note that because of rounding error these aren't exactly equal!
200    /// assert!(xyz1.x != xyz2.x);
201    /// // using approx_equal, we can avoid these sorts of errors
202    /// assert!(xyz1.approx_equal(&xyz2));
203    /// ```
204    ///
205    /// [`Color::visually_indistinguishable`]: ../color/trait.Color.html#method.visually_indistinguishable
206    pub fn approx_equal(&self, other: &XYZColor) -> bool {
207        (self.x - other.x).abs() <= 1e-15
208            && (self.y - other.y).abs() <= 1e-15
209            && (self.z - other.z).abs() <= 1e-15
210    }
211
212    /// Returns `true` if the given other XYZ color would look identically in a different color
213    /// space. Uses an approximate float equality that helps resolve errors due to floating-point
214    /// representation, only testing if the two floats are within 0.001 of each other.
215    /// # Example
216    ///
217    /// ```
218    /// # use scarlet::color::XYZColor;
219    /// # use scarlet::illuminants::Illuminant;
220    /// assert!(XYZColor::white_point(Illuminant::D65).approx_visually_equal(&XYZColor::white_point(Illuminant::D50)));
221    /// ```
222    pub fn approx_visually_equal(&self, other: &XYZColor) -> bool {
223        let other_c = other.color_adapt(self.illuminant);
224        self.approx_equal(&other_c)
225    }
226    /// Gets the XYZColor corresponding to pure white in the given light environment.
227    /// # Example
228    ///
229    /// ```
230    /// # use scarlet::color::XYZColor;
231    /// # use scarlet::illuminants::Illuminant;
232    /// let white1 = XYZColor::white_point(Illuminant::D65);
233    /// let white2 = XYZColor::white_point(Illuminant::D50);
234    /// assert!(white1.approx_visually_equal(&white2));
235    /// ```
236    pub fn white_point(illuminant: Illuminant) -> XYZColor {
237        let wp = illuminant.white_point();
238        XYZColor {
239            x: wp[0],
240            y: wp[1],
241            z: wp[2],
242            illuminant,
243        }
244    }
245}
246
247/// A trait that represents any color representation that can be converted to and from the CIE 1931 XYZ
248/// color space. See module-level documentation for more information and examples.
249pub trait Color: Sized {
250    /// Converts from a color in CIE 1931 XYZ to the given color type.
251    ///
252    /// # Example
253    ///
254    /// ```
255    /// # use scarlet::color::XYZColor;
256    /// # use scarlet::prelude::*;
257    /// # use std::error::Error;
258    /// # fn try_main() -> Result<(), Box<Error>> {
259    /// let rgb1 = RGBColor::from_hex_code("#ffffff")?;
260    /// // any illuminant would work: Scarlet takes care of that automatically
261    /// let rgb2 = RGBColor::from_xyz(XYZColor::white_point(Illuminant::D65));
262    /// assert_eq!(rgb1.to_string(), rgb2.to_string());
263    /// # Ok(())
264    /// # }
265    /// # fn main () {
266    /// #     try_main().unwrap();
267    /// # }
268    /// ```
269    fn from_xyz(xyz: XYZColor) -> Self;
270    /// Converts from the given color type to a color in CIE 1931 XYZ space. Because most color types
271    /// don't include illuminant information, it is provided instead, as an enum. For most
272    /// applications, D50 or D65 is a good choice.
273    ///
274    /// # Example
275    ///
276    /// ```
277    /// # use scarlet::prelude::*;
278    /// # use scarlet::colors::{CIELABColor, CIELCHColor};
279    /// // CIELAB is implicitly D50
280    /// let lab = CIELABColor{l: 100., a: 0., b: 0.};
281    /// // sRGB is implicitly D65
282    /// let rgb = RGBColor{r: 1., g: 1., b: 1.};
283    /// // conversion to a different illuminant keeps their difference
284    /// let lab_xyz = lab.to_xyz(Illuminant::D75);
285    /// let rgb_xyz = rgb.to_xyz(Illuminant::D75);
286    /// assert!(!lab_xyz.approx_equal(&rgb_xyz));
287    /// // on the other hand, CIELCH is in D50, so its white will be the same as CIELAB
288    /// let lch_xyz = CIELCHColor{l: 100., c: 0., h: 0.}.to_xyz(Illuminant::D75);
289    /// assert!(lab_xyz.approx_equal(&lch_xyz));
290    /// ```
291    fn to_xyz(&self, illuminant: Illuminant) -> XYZColor;
292    /// Converts generic colors from one representation to another. This is done by going back and
293    /// forth from the CIE 1931 XYZ space, using the illuminant D50 (although this should not affect
294    /// the results). Just like [`collect()`] and other methods in the standard library, the use of
295    /// type inference will usually allow for clean syntax, but occasionally the turbofish is
296    /// necessary.
297    ///
298    /// # Example
299    ///
300    /// ```
301    /// # use scarlet::prelude::*;
302    /// # use scarlet::color::XYZColor;
303    /// let xyz = XYZColor{x: 0.2, y: 0.6, z: 0.3, illuminant: Illuminant::D65};
304    /// // how would this look like as the closest hex code?
305    ///
306    /// // the following two lines are equivalent. The first is preferred for simple variable
307    /// // allocation, but in more complex scenarios sometimes it's unnecessarily cumbersome
308    /// let rgb1: RGBColor = xyz.convert();
309    /// let rgb2 = xyz.convert::<RGBColor>();
310    /// assert_eq!(rgb1.to_string(), rgb2.to_string());
311    /// println!("{}", rgb1.to_string());
312    /// ```
313    ///
314    /// [`collect()`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect
315    fn convert<T: Color>(&self) -> T {
316        // theoretically, the illuminant shouldn't matter as long as the color conversions are
317        // correct. D50 is a common gamut for use in internal conversions, so for spaces like CIELAB
318        // it will produce the least error
319        T::from_xyz(self.to_xyz(Illuminant::D50))
320    }
321    /// "Colors" a given piece of text with terminal escape codes to allow it to be printed out in the
322    /// given foreground color. Will cause problems with terminals that do not support truecolor.
323    /// Requires the `terminal` feature.
324    ///
325    /// # Example
326    /// This demo prints out a square of colors that have the same luminance in CIELAB and HSL to
327    /// compare the validity of their lightness correlates. It can also simply be used to test whether
328    /// a terminal supports printing color. Note that, in some terminal emulators, this can be very
329    /// slow: it's unclear why.
330    ///
331    /// ```
332    /// # use scarlet::prelude::*;
333    /// # use scarlet::colors::{CIELABColor, HSLColor};
334    /// let mut line;
335    /// println!("");
336    /// for i in 0..20 {
337    ///     line = String::from("");
338    ///     for j in 0..20 {
339    ///         let lab = CIELABColor{l: 50., a: 5. * i as f64, b: 5. * j as f64};
340    ///         line.push_str(lab.write_colored_str("#").as_str());
341    ///     }
342    ///     println!("{}", line);
343    /// }
344    /// println!("");
345    /// for i in 0..20 {
346    ///     line = String::from("");
347    ///     for j in 0..20 {
348    ///         let hsl = HSLColor{h: i as f64 * 18., s: j as f64 * 0.05, l: 0.50};
349    ///         line.push_str(hsl.write_colored_str("#").as_str());
350    ///     }
351    ///     println!("{}", line);
352    /// }
353    /// ```
354    #[cfg(feature = "terminal")]
355    fn write_colored_str(&self, text: &str) -> String {
356        let rgb: RGBColor = self.convert();
357        rgb.base_write_colored_str(text)
358    }
359    /// Returns a string which, when printed in a truecolor-supporting terminal, will have both the
360    /// foreground and background of the desired color, appearing as a complete square. Requires the
361    /// `terminal` feature
362    ///
363    /// # Example
364    /// This is the same one as above, but with a complete block of color instead of the # mark.
365    ///
366    /// ```
367    /// # use scarlet::prelude::*;
368    /// # use scarlet::colors::{CIELABColor, HSLColor};
369    /// let mut line;
370    /// println!("");
371    /// for i in 0..20 {
372    ///     line = String::from("");
373    ///     for j in 0..20 {
374    ///         let lab = CIELABColor{l: 50., a: 5. * i as f64, b: 5. * j as f64};
375    ///         line.push_str(lab.write_color().as_str());
376    ///     }
377    ///     println!("{}", line);
378    /// }
379    /// println!("");
380    /// for i in 0..20 {
381    ///     line = String::from("");
382    ///     for j in 0..20 {
383    ///         let hsl = HSLColor{h: i as f64 * 18., s: j as f64 * 0.05, l: 0.50};
384    ///         line.push_str(hsl.write_color().as_str());
385    ///     }
386    ///     println!("{}", line);
387    /// }
388    /// ```
389    #[cfg(feature = "terminal")]
390    fn write_color(&self) -> String {
391        let rgb: RGBColor = self.convert();
392        rgb.base_write_color()
393    }
394
395    /// Gets the generally most accurate version of hue for a given color: the hue coordinate in
396    /// CIELCH. There are generally considered four "unique hues" that humans perceive as not
397    /// decomposable into other hues (when mixing additively): these are red, yellow, green, and
398    /// blue. These unique hues have values of 0, 90, 180, and 270 degrees respectively, with other
399    /// colors interpolated between them. This returned value will never be outside the range 0 to
400    /// 360. For more information, you can start at [the Wikpedia page](https://en.wikipedia.org/wiki/Hue).
401    ///
402    /// This generally shouldn't differ all that much from HSL or HSV, but it is slightly more
403    /// accurate to human perception and so is generally superior. This should be preferred over
404    /// manually converting to HSL or HSV.
405    ///
406    /// # Example
407    /// One problem with using RGB to work with lightness and hue is that it fails to account for
408    /// hue shifts as lightness changes, such as the difference between yellow and brown. When this
409    /// causes a shift from red towards blue, it's called the [*Purkinje
410    /// effect*](https://en.wikipedia.org/wiki/Purkinje_effect). This example demonstrates how this
411    /// can trip up color manipulation if you don't use more perceptually accurate color spaces.
412    ///
413    /// ```
414    /// # use scarlet::prelude::*;
415    /// let bright_red = RGBColor{r: 0.9, g: 0., b: 0.};
416    /// // One would think that adding or subtracting red here would keep the hue constant
417    /// let darker_red = RGBColor{r: 0.3, g: 0., b: 0.};
418    /// // However, note that the hue has shifted towards the blue end of the spectrum: in this case,
419    /// // closer to 0 by a substantial amount
420    /// println!("{} {}", bright_red.hue(), darker_red.hue());
421    /// assert!(bright_red.hue() - darker_red.hue() >= 8.);
422    /// ```
423    fn hue(&self) -> f64 {
424        let lch: CIELCHColor = self.convert();
425        lch.h
426    }
427
428    /// Sets a perceptually-accurate version hue of a color, even if the space itself does not have a
429    /// conception of hue. This uses the CIELCH version of hue. To use another one, simply convert and
430    /// set it manually. If the given hue is not between 0 and 360, it is shifted in that range by
431    /// adding multiples of 360.
432    /// # Example
433    /// This example shows that RGB primaries are not exact standins for the hue they're named for,
434    /// and using Scarlet can improve color accuracy.
435    ///
436    /// ```
437    /// # use scarlet::prelude::*;
438    /// let blue = RGBColor{r: 0., g: 0., b: 1.};
439    /// // this is a setter, so we make a copy first so we have two colors
440    /// let mut red = blue;
441    /// red.set_hue(0.); // "ideal" red
442    /// // not the same red as RGB's red!
443    /// println!("{}", red.to_string());
444    /// assert!(!red.visually_indistinguishable(&RGBColor{r: 1., g: 0., b: 0.}));
445    /// ```
446    fn set_hue(&mut self, new_hue: f64) {
447        let mut lch: CIELCHColor = self.convert();
448        lch.h = if (0.0..=360.0).contains(&new_hue) {
449            new_hue
450        } else if new_hue < 0.0 {
451            new_hue - 360.0 * (new_hue / 360.0).floor()
452        } else {
453            new_hue - 360.0 * (new_hue / 360.0).ceil()
454        };
455        *self = lch.convert();
456    }
457
458    /// Gets a perceptually-accurate version of lightness as a value from 0 to 100, where 0 is black
459    /// and 100 is pure white. The exact value used is CIELAB's definition of luminance, which is
460    /// generally considered a very good standard. Note that this is nonlinear with respect to the
461    /// physical amount of light emitted: a material with 18% reflectance has a lightness value of 50,
462    /// not 18.
463    /// # Examples
464    /// HSL and HSV are often used to get luminance. We'll see why this can be horrifically
465    /// inaccurate.
466    ///
467    /// HSL uses the average of the largest and smallest RGB components. This doesn't account for the
468    /// fact that some colors have inherently more or less brightness (for instance, yellow looks much
469    /// brighter than purple). This is sometimes called *chroma*: we would say that purple has high
470    /// chroma. (In Scarlet, chroma usually means something else: check the [`chroma`](#method.chroma) method for more
471    /// info.)
472    ///
473    /// ```
474    /// # use scarlet::prelude::*;
475    /// # use scarlet::colors::HSLColor;
476    /// let purple = HSLColor{h: 300., s: 0.8, l: 0.5};
477    /// let yellow = HSLColor{h: 60., s: 0.8, l: 0.5};
478    /// // these have completely different perceptual luminance values
479    /// println!("{} {}", purple.lightness(), yellow.lightness());
480    /// assert!(yellow.lightness() - purple.lightness() >= 30.);
481    /// ```
482    /// HSV has to take the cake: it simply uses the maximum RGB component. This means that for
483    /// highly-saturated colors with high chroma, it gives results that aren't even remotely close to
484    /// the true perception of lightness.
485    ///
486    /// ```
487    /// # use scarlet::prelude::*;
488    /// # use scarlet::colors::HSVColor;
489    /// let purple = HSVColor{h: 300., s: 1., v: 1.};
490    /// let white = HSVColor{h: 300., s: 0., v: 1.};
491    /// println!("{} {}", purple.lightness(), white.lightness());
492    /// assert!(white.lightness() - purple.lightness() >= 39.);
493    /// ```
494    /// Hue has only small differences across different color systems, but as you can see lightness is
495    /// a completely different story. HSL/HSV and CIELAB can disagree by up to a third of the entire
496    /// range of lightness! This means that any use of HSL or HSV for luminance is liable to be
497    /// extraordinarily inaccurate if used for widely different chromas. Thus, use of this method is
498    /// always preferred unless you explicitly need HSL or HSV.
499    fn lightness(&self) -> f64 {
500        let lab: CIELABColor = self.convert();
501        lab.l
502    }
503
504    /// Sets a perceptually-accurate version of lightness, which ranges between 0 and 100 for visible
505    /// colors. Any values outside of this range will be clamped within it.
506    /// # Example
507    /// As we saw in the [`lightness`](#method.lightness) method, purple and yellow tend to trip up HSV and HSL: the
508    /// color system doesn't account for how much brighter the color yellow is compared to the color
509    /// purple. What would equiluminant purple and yellow look like? We can find out.
510    ///
511    /// ```
512    /// # use scarlet::prelude::*;
513    /// # use scarlet::colors::HSLColor;
514    /// let purple = HSLColor{h: 300., s: 0.8, l: 0.8};
515    /// let mut yellow = HSLColor{h: 60., s: 0.8, l: 0.8};
516    /// // increasing purple's brightness to yellow results in colors outside the HSL gamut, so we'll
517    /// // do it the other way
518    /// yellow.set_lightness(purple.lightness());
519    /// // note that the hue has to shift a little, at least according to HSL, but they barely disagree
520    /// println!("{}", yellow.h); // prints 60.611 or thereabouts
521    /// // the L component has to shift a lot to achieve perceptual equiluminance, as well as a ton of
522    /// // desaturation, because a saturated dark yellow is really more like brown and is a different
523    /// // hue or out of gamut
524    /// assert!(purple.l - yellow.l > 0.15);
525    /// // essentially, the different hue and saturation is worth .15 luminance
526    /// assert!(yellow.s < 0.4);  // saturation has decreased a lot
527    /// ```
528    fn set_lightness(&mut self, new_lightness: f64) {
529        let mut lab: CIELABColor = self.convert();
530        lab.l = if (0.0..=100.0).contains(&new_lightness) {
531            new_lightness
532        } else if new_lightness < 0.0 {
533            0.0
534        } else {
535            100.0
536        };
537        *self = lab.convert()
538    }
539
540    /// Gets a perceptually-accurate version of *chroma*, defined as colorfulness relative to a
541    /// similarly illuminated white. This has no explicit upper bound, but is always positive and
542    /// generally between 0 and 180 for visible colors. This is done using the CIELCH model.
543    /// # Example
544    /// Chroma differs from saturation in that it doesn't account for lightness as much as saturation:
545    /// there are just fewer colors at really low light levels, and so most colors appear less
546    /// colorful. This can either be the desired measure of this effect, or it can be more suitable to
547    /// use saturation. A comparison:
548    ///
549    /// ```
550    /// # use scarlet::prelude::*;
551    /// let dark_purple = RGBColor{r: 0.4, g: 0., b: 0.4};
552    /// let bright_purple = RGBColor{r: 0.8, g: 0., b: 0.8};
553    /// println!("{} {}", dark_purple.chroma(), bright_purple.chroma());
554    /// // chromas differ widely: about 57 for the first and 94 for the second
555    /// assert!(bright_purple.chroma() - dark_purple.chroma() >= 35.);
556    /// ```
557    fn chroma(&self) -> f64 {
558        let lch: CIELCHColor = self.convert();
559        lch.c
560    }
561
562    /// Sets a perceptually-accurate version of *chroma*, defined as colorfulness relative to a
563    /// similarly illuminated white. Uses CIELCH's defintion of chroma for implementation. Any value
564    /// below 0 will be clamped up to 0, but because the upper bound depends on the hue and
565    /// lightness no clamping will be done. This means that this method has a higher chance than
566    /// normal of producing imaginary colors and any output from this method should be checked.
567    /// # Example
568    /// We can use the purple example from above, and see what an equivalent chroma to the dark purple
569    /// would look like at a high lightness.
570    ///
571    /// ```
572    /// # use scarlet::prelude::*;
573    /// let dark_purple = RGBColor{r: 0.4, g: 0., b: 0.4};
574    /// let bright_purple = RGBColor{r: 0.8, g: 0., b: 0.8};
575    /// let mut changed_purple = bright_purple;
576    /// changed_purple.set_chroma(dark_purple.chroma());
577    /// println!("{} {}", bright_purple.to_string(), changed_purple.to_string());
578    /// // prints #CC00CC #AC4FA8
579    /// ```
580    fn set_chroma(&mut self, new_chroma: f64) {
581        let mut lch: CIELCHColor = self.convert();
582        lch.c = if new_chroma < 0.0 { 0.0 } else { new_chroma };
583        *self = lch.convert();
584    }
585
586    /// Gets a perceptually-accurate version of *saturation*, defined as chroma relative to
587    /// lightness. Generally ranges from 0 to around 10, although exact bounds are tricky. from This
588    /// means that e.g., a very dark purple could be very highly saturated even if it does not seem
589    /// so relative to lighter colors. This is computed using the CIELCH model and computing chroma
590    /// divided by lightness: if the lightness is 0, the saturation is also said to be 0. There is
591    /// no official formula except ones that require more information than this model of colors has,
592    /// but the CIELCH formula is fairly standard.
593    /// # Example
594    ///
595    /// ```
596    /// # use scarlet::prelude::*;
597    /// let red = RGBColor{r: 1., g: 0.2, b: 0.2};
598    /// let dark_red = RGBColor{r: 0.7, g: 0., b: 0.};
599    /// assert!(dark_red.saturation() > red.saturation());
600    /// assert!(dark_red.chroma() < red.chroma());
601    /// ```
602    fn saturation(&self) -> f64 {
603        let lch: CIELCHColor = self.convert();
604        if lch.l == 0.0 {
605            0.0
606        } else {
607            lch.c / lch.l
608        }
609    }
610
611    /// Sets a perceptually-accurate version of *saturation*, defined as chroma relative to
612    /// lightness. Does this without modifying lightness or hue. Any negative value will be clamped
613    /// to 0, but because the maximum saturation is not well-defined any positive value will be used
614    /// as is: this means that this method is more likely than others to produce imaginary
615    /// colors. Uses the CIELCH color space. Generally, saturation ranges from 0 to about 1, but it
616    /// can go higher.
617    /// # Example
618    ///
619    /// ```
620    /// # use scarlet::prelude::*;
621    /// let red = RGBColor{r: 0.5, g: 0.2, b: 0.2};
622    /// let mut changed_red = red;
623    /// changed_red.set_saturation(1.5);
624    /// println!("{} {}", red.to_string(), changed_red.to_string());
625    /// // prints #803333 #8B262C
626    /// ```
627    fn set_saturation(&mut self, new_sat: f64) {
628        let mut lch: CIELCHColor = self.convert();
629        lch.c = if new_sat < 0.0 { 0.0 } else { new_sat * lch.l };
630        *self = lch.convert();
631    }
632    /// Returns a new [`Color`] of the same type as before, but with chromaticity removed: effectively,
633    /// a color created solely using a mix of black and white that has the same lightness as
634    /// before. This uses the CIELAB luminance definition, which is considered a good standard and is
635    /// perceptually accurate for the most part.
636    /// # Example
637    ///
638    /// ```
639    /// # use scarlet::prelude::*;
640    /// # use scarlet::colors::HSVColor;
641    /// let rgb = RGBColor{r: 0.7, g: 0.5, b: 0.9};
642    /// let hsv = HSVColor{h: 290., s: 0.5, v: 0.8};
643    /// // type annotation is superfluous: just note how grayscale works within the type of a color.
644    /// let rgb_grey: RGBColor = rgb.grayscale();
645    /// let hsv_grey: HSVColor = hsv.grayscale();
646    /// // saturation may not be truly zero because of different illuminants and definitions of grey,
647    /// // but it's pretty close
648    /// println!("{:?} {:?}", hsv_grey, rgb_grey);
649    /// assert!(hsv_grey.s < 0.001);
650    /// // ditto for RGB
651    /// assert!((rgb_grey.r - rgb_grey.g).abs() <= 0.01);
652    /// assert!((rgb_grey.r - rgb_grey.b).abs() <= 0.01);
653    /// assert!((rgb_grey.g - rgb_grey.b).abs() <= 0.01);
654    /// ```
655    fn grayscale(&self) -> Self
656    where
657        Self: Sized,
658    {
659        let mut lch: CIELCHColor = self.convert();
660        lch.c = 0.0;
661        lch.convert()
662    }
663
664    /// Returns a metric of the distance between the given color and another that attempts to
665    /// accurately reflect human perception. This is done by using the CIEDE2000 difference formula,
666    /// the current international and industry standard. The result, being a distance, will never be
667    /// negative: it has no defined upper bound, although anything larger than 100 would be very
668    /// extreme. A distance of 1.0 is conservatively the smallest possible noticeable difference:
669    /// anything that is below 1.0 is almost guaranteed to be indistinguishable to most people.
670    ///
671    /// It's important to note that, just like chromatic adaptation, there's no One True Function for
672    /// determining color difference. This is a best effort by the scientific community, but
673    /// individual variance, difficulty of testing, and the idiosyncrasies of human vision make this
674    /// difficult. For the vast majority of applications, however, this should work correctly. It
675    /// works best with small differences, so keep that in mind: it's relatively hard to quantify
676    /// whether bright pink and brown are more or less similar than bright blue and dark red.
677    ///
678    /// For more, check out the [associated
679    /// guide](https://github.com/nicholas-miklaucic/scarlet/blob/master/color_distance.md).
680    ///
681    /// # Examples
682    ///
683    /// Using the distance between points in RGB space, or really any color space, as a way of
684    /// measuring difference runs into some problems, which we can examine using a more accurate
685    /// function. The main problem, as the below image shows
686    /// [(source)](https://commons.wikimedia.org/wiki/File:CIExy1931_MacAdam.png), is that our
687    /// sensitivity to color variance shifts a lot depending on what hue the colors being compared
688    /// are. (In the image, the ellipses are drawn ten times as large as the smallest perceptible
689    /// difference: the larger the ellipse, the less sensitive the human eye is to changes in that
690    /// region.) Perceptual uniformity is the goal for color spaces like CIELAB, but this is a
691    /// failure point.
692    ///
693    /// ![MacAdam ellipses showing areas of indistinguishability scaled by a factor of 10. The green
694    /// ellipses are much wider than the
695    /// blue.](https://upload.wikimedia.org/wikipedia/commons/thumb/f/f4/CIExy1931_MacAdam.png/800px-CIExy1931_MacAdam.png)
696    ///
697    /// The other problem is that our sensitivity to lightness also shifts a lot depending on the
698    /// conditions: we're not as at distinguishing dark grey from black, but better at
699    /// distinguishing very light grey from white. We can examine these phenomena using Scarlet.
700    ///
701    /// ```
702    /// # use scarlet::prelude::*;
703    /// let dark_grey = RGBColor{r: 0.05, g: 0.05, b: 0.05};
704    /// let black = RGBColor{r: 0.0, g: 0.0, b: 0.0};
705    /// let light_grey = RGBColor{r: 0.95, g: 0.95, b: 0.95};
706    /// let white = RGBColor{r: 1., g: 1., b: 1.,};
707    /// // RGB already includes a factor to attempt to compensate for the color difference due to
708    /// // lighting. As we'll see, however, it's not enough to compensate for this.
709    /// println!("{} {} {} {}", dark_grey.to_string(), black.to_string(), light_grey.to_string(),
710    /// white.to_string());
711    /// // prints #0D0D0D #000000 #F2F2F2 #FFFFFF
712    /// //
713    /// // noticeable error: not very large at this scale, but the effect exaggerates for very similar colors
714    /// assert!(dark_grey.distance(&black) < 0.9 * light_grey.distance(&white));
715    /// ```
716    ///
717    /// ```
718    /// # use scarlet::prelude::*;
719    /// let mut green1 = RGBColor{r: 0.05, g: 0.9, b: 0.05};
720    /// let mut green2 = RGBColor{r: 0.05, g: 0.91, b: 0.05};
721    /// let blue1 = RGBColor{r: 0.05, g: 0.05, b: 0.9};
722    /// let blue2 = RGBColor{r: 0.05, g: 0.05, b: 0.91};
723    /// // to remove the effect of lightness on color perception, equalize them
724    /// green1.set_lightness(blue1.lightness());
725    /// green2.set_lightness(blue2.lightness());
726    /// // In RGB these have the same difference. This formula accounts for the perceptual distance, however.
727    /// println!("{} {} {} {}", green1.to_string(), green2.to_string(), blue1.to_string(),
728    /// blue2.to_string());
729    /// // prints #0DE60D #0DEB0D #0D0DE6 #0D0DEB
730    /// //
731    /// // very small error, but nonetheless roughly 1% off
732    /// assert!(green1.distance(&green2) / blue1.distance(&blue2) < 0.992);
733    /// ```
734    fn distance<T: Color>(&self, other: &T) -> f64 {
735        // implementation reference found here:
736        // https://pdfs.semanticscholar.org/969b/c38ea067dd22a47a44bcb59c23807037c8d8.pdf
737
738        // I'm going to match the notation in that text pretty much exactly: it's the only way to
739        // keep this both concise and readable
740
741        // first convert to LAB
742        let lab1: CIELABColor = self.convert();
743        let lab2: CIELABColor = other.convert();
744        // step 1: calculation of C and h
745        // the method hypot returns sqrt(a^2 + b^2)
746        let c_star_1: f64 = lab1.a.hypot(lab1.b);
747        let c_star_2: f64 = lab2.a.hypot(lab2.b);
748
749        let c_bar_ab: f64 = (c_star_1 + c_star_2) / 2.0;
750        let g = 0.5 * (1.0 - ((c_bar_ab.powi(7)) / (c_bar_ab.powi(7) + 25.0f64.powi(7))).sqrt());
751
752        let a_prime_1 = (1.0 + g) * lab1.a;
753        let a_prime_2 = (1.0 + g) * lab2.a;
754
755        let c_prime_1 = a_prime_1.hypot(lab1.b);
756        let c_prime_2 = a_prime_2.hypot(lab2.b);
757
758        // this closure simply does the atan2 like CIELCH, but safely accounts for a == b == 0
759        // we're gonna do this twice, so I just use a closure
760        let h_func = |a: f64, b: f64| {
761            if a == 0.0 && b == 0.0 {
762                0.0
763            } else {
764                let val = b.atan2(a).to_degrees();
765                if val < 0.0 {
766                    val + 360.0
767                } else {
768                    val
769                }
770            }
771        };
772
773        let h_prime_1 = h_func(a_prime_1, lab1.b);
774        let h_prime_2 = h_func(a_prime_2, lab2.b);
775
776        // step 2: computing delta L, delta C, and delta H
777        // take a deep breath, you got this!
778
779        let delta_l = lab2.l - lab1.l;
780        let delta_c = c_prime_2 - c_prime_1;
781        // essentially, compute the difference in hue but keep it in the right range
782        let delta_angle_h = if c_prime_1 * c_prime_2 == 0.0 {
783            0.0
784        } else if (h_prime_2 - h_prime_1).abs() <= 180.0 {
785            h_prime_2 - h_prime_1
786        } else if h_prime_2 - h_prime_1 > 180.0 {
787            h_prime_2 - h_prime_1 - 360.0
788        } else {
789            h_prime_2 - h_prime_1 + 360.0
790        };
791        // now get the Cartesian equivalent of the angle difference in hue
792        // this also corrects for chromaticity mattering less at low luminances
793        let delta_h =
794            2.0 * (c_prime_1 * c_prime_2).sqrt() * (delta_angle_h / 2.0).to_radians().sin();
795
796        // step 3: the color difference
797        // if you're reading this, it's not too late to back out
798        let l_bar_prime = (lab1.l + lab2.l) / 2.0;
799        let c_bar_prime = (c_prime_1 + c_prime_2) / 2.0;
800        let h_bar_prime = if c_prime_1 * c_prime_2 == 0.0 {
801            h_prime_1 + h_prime_2
802        } else if (h_prime_2 - h_prime_1).abs() <= 180.0 {
803            (h_prime_1 + h_prime_2) / 2.0
804        } else if h_prime_1 + h_prime_2 < 360.0 {
805            (h_prime_1 + h_prime_2 + 360.0) / 2.0
806        } else {
807            (h_prime_1 + h_prime_2 - 360.0) / 2.0
808        };
809
810        // we're gonna use this a lot
811        let deg_cos = |x: f64| x.to_radians().cos();
812
813        let t = 1.0 - 0.17 * deg_cos(h_bar_prime - 30.0)
814            + 0.24 * deg_cos(2.0 * h_bar_prime)
815            + 0.32 * deg_cos(3.0 * h_bar_prime + 6.0)
816            - 0.20 * deg_cos(4.0 * h_bar_prime - 63.0);
817
818        let delta_theta = 30.0 * (-((h_bar_prime - 275.0) / 25.0).powi(2)).exp();
819        let r_c = 2.0 * (c_bar_prime.powi(7) / (c_bar_prime.powi(7) + 25.0f64.powi(7))).sqrt();
820        let s_l = 1.0
821            + ((0.015 * (l_bar_prime - 50.0).powi(2))
822                / (20.0 + (l_bar_prime - 50.0).powi(2)).sqrt());
823        let s_c = 1.0 + 0.045 * c_bar_prime;
824        let s_h = 1.0 + 0.015 * c_bar_prime * t;
825        let r_t = -r_c * (2.0 * delta_theta).to_radians().sin();
826        // finally, the end result
827        // in the original there are three parametric weights, used for weighting differences in
828        // lightness, chroma, or hue. In pretty much any application, including this one, all of
829        // these are 1, so they're omitted
830        ((delta_l / s_l).powi(2)
831            + (delta_c / s_c).powi(2)
832            + (delta_h / s_h).powi(2)
833            + r_t * (delta_c / s_c) * (delta_h / s_h))
834            .sqrt()
835    }
836    /// Using the metric that two colors with a CIEDE2000 distance of less than 1 are
837    /// indistinguishable, determines whether two colors are visually distinguishable from each
838    /// other. For more, check out [this guide](../color_distance.html).
839    ///
840    /// # Examples
841    ///
842    /// ```
843    /// # use scarlet::color::{RGBColor, Color};
844    ///
845    /// let color1 = RGBColor::from_hex_code("#123456").unwrap();
846    /// let color2 = RGBColor::from_hex_code("#123556").unwrap();
847    /// let color3 = RGBColor::from_hex_code("#333333").unwrap();
848    ///
849    /// assert!(color1.visually_indistinguishable(&color2)); // yes, they are visually indistinguishable
850    /// assert!(color2.visually_indistinguishable(&color1)); // yes, the same two points
851    /// assert!(!color1.visually_indistinguishable(&color3)); // not visually distinguishable
852    /// ```
853    fn visually_indistinguishable<T: Color>(&self, other: &T) -> bool {
854        self.distance(other) <= 1.0
855    }
856}
857
858impl Color for XYZColor {
859    fn from_xyz(xyz: XYZColor) -> XYZColor {
860        xyz
861    }
862    #[allow(unused_variables)]
863    fn to_xyz(&self, illuminant: Illuminant) -> XYZColor {
864        *self
865    }
866}
867
868#[derive(Debug, Copy, Clone)]
869/// A color with red, green, and blue primaries of specified intensity, specifically in the sRGB
870/// gamut: most computer screens use this to display colors. The attributes `r`, `g`, and `b` are
871/// floating-point numbers from 0 to 1 for visible colors, allowing the avoidance of rounding errors
872/// or clamping errors when converting to and from RGB. Many conveniences are afforded so that
873/// working with RGB as if it were instead three integers from 0-255 is painless. Note that the
874/// integers generated from the underlying floating-point numbers round away from 0.
875///
876/// Examples of this abound: this is used ubiquitously in Scarlet. Check the
877/// [`Color`] documentation for plenty.
878///
879/// [`Color`]: ../color/trait.Color.html
880pub struct RGBColor {
881    /// The red component. Ranges from 0 to 1 for numbers displayable by sRGB machines.
882    pub r: f64,
883    /// The green component. Ranges from 0 to 1 for numbers displayable by sRGB machines.
884    pub g: f64,
885    /// The blue component. Ranges from 0 to 1 for numbers displayable by sRGB machines.
886    pub b: f64,
887}
888
889impl RGBColor {
890    /// Gets an 8-byte version of the red component, as a `u8`. Clamps values outside of the range 0-1
891    /// and discretizes, so this may not correspond to the exact values kept internally.
892    /// # Example
893    ///
894    /// ```
895    /// # use scarlet::prelude::*;
896    /// let super_red = RGBColor{r: 1.2, g: 0., b: 0.};
897    /// let non_integral_red = RGBColor{r: 0.999, g: 0., b: 0.};
898    /// // the first one will get clamped in range, the second one will be rounded
899    /// assert_eq!(super_red.int_r(), non_integral_red.int_r());
900    /// assert_eq!(super_red.int_r(), 255);
901    /// ```
902    pub fn int_r(&self) -> u8 {
903        // first clamp, then multiply by 255, round, and discretize
904        if self.r < 0.0 {
905            0_u8
906        } else if self.r > 1.0 {
907            255_u8
908        } else {
909            (self.r * 255.0).round() as u8
910        }
911    }
912    /// Gets an 8-byte version of the green component, as a `u8`. Clamps values outside of the range 0-1
913    /// and discretizes, so this may not correspond to the exact values kept internally.
914    /// # Example
915    ///
916    /// ```
917    /// # use scarlet::prelude::*;
918    /// let super_green = RGBColor{r: 0., g: 1.2, b: 0.};
919    /// let non_integral_green = RGBColor{r: 0., g: 0.999, b: 0.};
920    /// // the first one will get clamped in range, the second one will be rounded
921    /// assert_eq!(super_green.int_g(), non_integral_green.int_g());
922    /// assert_eq!(super_green.int_g(), 255);
923    /// ```
924    pub fn int_g(&self) -> u8 {
925        // first clamp, then multiply by 255, round, and discretize
926        if self.g < 0.0 {
927            0_u8
928        } else if self.g > 1.0 {
929            255_u8
930        } else {
931            (self.g * 255.0).round() as u8
932        }
933    }
934    /// Gets an 8-byte version of the blue component, as a `u8`. Clamps values outside of the range 0-1
935    /// and discretizes, so this may not correspond to the exact values kept internally.
936    /// # Example
937    ///
938    /// ```
939    /// # use scarlet::prelude::*;
940    /// let super_blue = RGBColor{r: 0., g: 0., b: 1.2};
941    /// let non_integral_blue = RGBColor{r: 0., g: 0., b: 0.999};
942    /// // the first one will get clamped in range, the second one will be rounded
943    /// assert_eq!(super_blue.int_b(), non_integral_blue.int_b());
944    /// assert_eq!(super_blue.int_b(), 255);
945    /// ```
946    pub fn int_b(&self) -> u8 {
947        // first clamp, then multiply by 255, round, and discretize
948        if self.b < 0.0 {
949            0_u8
950        } else if self.b > 1.0 {
951            255_u8
952        } else {
953            (self.b * 255.0).round() as u8
954        }
955    }
956    /// Purely for convenience: gives a tuple with the three integer versions of the components. Used
957    /// over standard conversion traits to avoid ambiguous operations.
958    /// # Example
959    ///
960    /// ```
961    /// # use scarlet::prelude::*;
962    /// let color = RGBColor{r: 0.3, g: 0.6, b: 0.7};
963    /// assert_eq!(color.int_rgb_tup(), (color.int_r(), color.int_g(), color.int_b()));
964    /// ```
965    pub fn int_rgb_tup(&self) -> (u8, u8, u8) {
966        (self.int_r(), self.int_g(), self.int_b())
967    }
968    /// Given a string, returns that string wrapped in codes that will color the foreground. Used
969    /// for the trait implementation of write_colored_str, which should be used instead. Requires
970    /// the `terminal` feature.
971    #[cfg(feature = "terminal")]
972    fn base_write_colored_str(&self, text: &str) -> String {
973        format!(
974            "{code}{text}{reset}",
975            code = Fg(Rgb(self.int_r(), self.int_g(), self.int_b())),
976            text = text,
977            reset = Fg(Reset)
978        )
979    }
980    /// Used for the Color `write_color()` method. Requires the `terminal` feature.
981    #[cfg(feature = "terminal")]
982    fn base_write_color(&self) -> String {
983        format!(
984            "{bg}{fg}{text}{reset_fg}{reset_bg}",
985            bg = Bg(Rgb(self.int_r(), self.int_g(), self.int_b())),
986            fg = Fg(Rgb(self.int_r(), self.int_g(), self.int_b())),
987            text = "â– ",
988            reset_fg = Fg(Reset),
989            reset_bg = Bg(Reset),
990        )
991    }
992}
993
994impl PartialEq for RGBColor {
995    fn eq(&self, other: &RGBColor) -> bool {
996        self.r == other.r && self.g == other.g && self.b == other.b
997    }
998}
999
1000impl From<(u8, u8, u8)> for RGBColor {
1001    fn from(rgb: (u8, u8, u8)) -> RGBColor {
1002        let (r, g, b) = rgb;
1003        RGBColor {
1004            r: f64::from(r) / 255.0,
1005            g: f64::from(g) / 255.0,
1006            b: f64::from(b) / 255.0,
1007        }
1008    }
1009}
1010
1011impl From<RGBColor> for (u8, u8, u8) {
1012    fn from(val: RGBColor) -> Self {
1013        (val.int_r(), val.int_g(), val.int_b())
1014    }
1015}
1016
1017impl From<Coord> for RGBColor {
1018    fn from(c: Coord) -> RGBColor {
1019        RGBColor {
1020            r: c.x,
1021            g: c.y,
1022            b: c.z,
1023        }
1024    }
1025}
1026
1027impl From<RGBColor> for Coord {
1028    fn from(val: RGBColor) -> Self {
1029        Coord {
1030            x: val.r,
1031            y: val.g,
1032            z: val.b,
1033        }
1034    }
1035}
1036
1037impl ToString for RGBColor {
1038    fn to_string(&self) -> String {
1039        format!(
1040            "#{:02X}{:02X}{:02X}",
1041            self.int_r(),
1042            self.int_g(),
1043            self.int_b()
1044        )
1045    }
1046}
1047
1048impl Color for RGBColor {
1049    fn from_xyz(xyz: XYZColor) -> RGBColor {
1050        // sRGB uses D65 as the assumed illuminant: convert the given value to that
1051        let xyz_d65 = xyz.color_adapt(Illuminant::D65);
1052        // first, get linear RGB values (i.e., without gamma correction)
1053        // https://en.wikipedia.org/wiki/SRGB#Specification_of_the_transformation
1054
1055        let lin_rgb_vec = *SRGB * vector![xyz_d65.x, xyz_d65.y, xyz_d65.z];
1056        // now we scale for gamma correction
1057        let gamma_correct = |x: &f64| {
1058            if x <= &0.0031308 {
1059                12.92 * x
1060            } else {
1061                1.055 * x.powf(1.0 / 2.4) - 0.055
1062            }
1063        };
1064        let float_vec: Vec<f64> = lin_rgb_vec.iter().map(gamma_correct).collect();
1065        RGBColor {
1066            r: float_vec[0],
1067            g: float_vec[1],
1068            b: float_vec[2],
1069        }
1070    }
1071    fn to_xyz(&self, illuminant: Illuminant) -> XYZColor {
1072        let uncorrect_gamma = |x: &f64| {
1073            if x <= &0.04045 {
1074                x / 12.92
1075            } else {
1076                ((x + 0.055) / 1.055).powf(2.4)
1077            }
1078        };
1079        let rgb_vec = vector![
1080            uncorrect_gamma(&self.r),
1081            uncorrect_gamma(&self.g),
1082            uncorrect_gamma(&self.b)
1083        ];
1084
1085        // invert the matrix multiplication used in from_xyz()
1086        // use LU decomposition for accuracy
1087        let xyz_vec = SRGB_LU.solve(&rgb_vec).expect("Matrix is invertible.");
1088
1089        // sRGB, which this is based on, uses D65 as white, but you can convert to whatever
1090        // illuminant is specified
1091        let converted = XYZColor {
1092            x: xyz_vec[0],
1093            y: xyz_vec[1],
1094            z: xyz_vec[2],
1095            illuminant: Illuminant::D65,
1096        };
1097        converted.color_adapt(illuminant)
1098    }
1099}
1100
1101/// An error type that results from an invalid attempt to convert a string into an RGB color.
1102#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
1103pub enum RGBParseError {
1104    /// This indicates that function syntax was acceptable, but the numbers were out of range, such as
1105    /// the invalid string `"rgb(554, 23, 553)"`.
1106    OutOfRange,
1107    /// This indicates that the hex string was malformed in some way.
1108    InvalidHexSyntax,
1109    /// This indicates a syntax error in the string that was supposed to be a valid rgb( function.
1110    InvalidFuncSyntax,
1111    /// This indicated an invalid color name was supplied to the `from_color_name()` function.
1112    InvalidX11Name,
1113}
1114
1115impl fmt::Display for RGBParseError {
1116    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1117        write!(f, "RGB parsing error")
1118    }
1119}
1120
1121impl From<ParseIntError> for RGBParseError {
1122    fn from(_err: ParseIntError) -> RGBParseError {
1123        RGBParseError::OutOfRange
1124    }
1125}
1126
1127impl From<CSSParseError> for RGBParseError {
1128    fn from(_err: CSSParseError) -> RGBParseError {
1129        RGBParseError::InvalidFuncSyntax
1130    }
1131}
1132
1133impl Error for RGBParseError {
1134    fn description(&self) -> &str {
1135        match *self {
1136            RGBParseError::OutOfRange => "RGB coordinates out of range",
1137            RGBParseError::InvalidHexSyntax => "Invalid hex code syntax",
1138            RGBParseError::InvalidFuncSyntax => "Invalid \"rgb(\" function call syntax",
1139            RGBParseError::InvalidX11Name => "Invalid X11 color name",
1140        }
1141    }
1142}
1143
1144impl RGBColor {
1145    /// Given a string that represents a hex code, returns the RGB color that the given hex code
1146    /// represents. Four formats are accepted: `"#rgb"` as a shorthand for `"#rrggbb"`, `#rrggbb` by
1147    /// itself, and either of those formats without `#`: `"rgb"` or `"rrggbb"` are acceptable. Returns
1148    /// a ColorParseError if the given string does not follow one of these formats.
1149    /// # Example
1150    ///
1151    /// ```
1152    /// # use scarlet::prelude::*;
1153    /// # fn try_main() -> Result<(), RGBParseError> {
1154    /// let fuchsia = RGBColor::from_hex_code("#ff00ff")?;
1155    /// // if 3 digits, interprets as doubled
1156    /// let fuchsia2 = RGBColor::from_hex_code("f0f")?;
1157    /// assert_eq!(fuchsia.int_rgb_tup(), fuchsia2.int_rgb_tup());
1158    /// assert_eq!(fuchsia.int_rgb_tup(), (255, 0, 255));
1159    /// let err = RGBColor::from_hex_code("#afafa");
1160    /// let err2 = RGBColor::from_hex_code("#gafd22");
1161    /// assert_eq!(err, err2);
1162    /// # Ok(())
1163    /// # }
1164    /// # try_main().unwrap();
1165    /// ```
1166    // otherwise you have really long lines with different reasons for throwing the same error
1167    #[allow(clippy::if_same_then_else)]
1168    pub fn from_hex_code(hex: &str) -> Result<RGBColor, RGBParseError> {
1169        let mut chars: Vec<char> = hex.chars().collect();
1170        // check if leading hex, remove if so
1171        if chars[0] == '#' {
1172            chars.remove(0);
1173        }
1174        // can only have 3 or 6 characters: error if not so
1175        if chars.len() != 3 && chars.len() != 6 {
1176            Err(RGBParseError::InvalidHexSyntax)
1177        // now split on invalid hex
1178        } else if !chars.iter().all(|&c| "0123456789ABCDEFabcdef".contains(c)) {
1179            Err(RGBParseError::InvalidHexSyntax)
1180        // split on whether it's #rgb or #rrggbb
1181        } else if chars.len() == 6 {
1182            let mut rgb: Vec<u8> = Vec::new();
1183            for _i in 0..3 {
1184                // this should never fail, logically, but if by some miracle it did it'd just
1185                // return an OutOfRangeError
1186                rgb.push(
1187                    u8::from_str_radix(chars.drain(..2).collect::<String>().as_str(), 16).unwrap(),
1188                );
1189            }
1190            Ok(RGBColor::from((rgb[0], rgb[1], rgb[2])))
1191        } else {
1192            // len must be 3 from earlier
1193            let mut rgb: Vec<u8> = Vec::new();
1194            for _i in 0..3 {
1195                // again, this shouldn't ever fail, but if it did it'd just return an
1196                // OutOfRangeError
1197                let c: Vec<char> = chars.drain(..1).collect();
1198                rgb.push(
1199                    u8::from_str_radix(c.iter().chain(c.iter()).collect::<String>().as_str(), 16)
1200                        .unwrap(),
1201                );
1202            }
1203            Ok(RGBColor::from((rgb[0], rgb[1], rgb[2])))
1204        }
1205    }
1206    /// Gets the RGB color corresponding to an X11 color name. Case is ignored.
1207    /// # Example
1208    ///
1209    /// ```
1210    /// # use scarlet::prelude::*;
1211    /// # fn try_main() -> Result<(), RGBParseError> {
1212    /// let fuchsia = RGBColor::from_color_name("fuchsia")?;
1213    /// let fuchsia2 = RGBColor::from_color_name("FuCHSiA")?;
1214    /// assert_eq!(fuchsia.int_rgb_tup(), fuchsia2.int_rgb_tup());
1215    /// assert_eq!(fuchsia.int_rgb_tup(), (255, 0, 255));
1216    /// let err = RGBColor::from_color_name("fuccshai");
1217    /// let err2 = RGBColor::from_color_name("foobar");
1218    /// assert_eq!(err, err2);
1219    /// # Ok(())
1220    /// # }
1221    /// # try_main().unwrap();
1222    /// ```
1223    pub fn from_color_name(name: &str) -> Result<RGBColor, RGBParseError> {
1224        // this is the full list of X11 color names
1225        // I used a Python script to process it from this site:
1226        // https://github.com/bahamas10/css-color-names/blob/master/css-color-names.json
1227        // I added the special "transparent" referring to #00000000
1228        let color_names: Vec<&str> = consts::X11_NAMES.to_vec();
1229        let color_codes: Vec<&str> = consts::X11_COLOR_CODES.to_vec();
1230        let mut names_to_codes = HashMap::new();
1231
1232        for (i, color_name) in color_names.iter().enumerate() {
1233            names_to_codes.insert(color_name, color_codes[i]);
1234        }
1235
1236        // now just return the converted value or raise one if not in hashmap
1237        match names_to_codes.get(&name.to_lowercase().as_str()) {
1238            None => Err(RGBParseError::InvalidX11Name),
1239            Some(x) => Self::from_hex_code(x),
1240        }
1241    }
1242}
1243
1244impl FromStr for RGBColor {
1245    type Err = RGBParseError;
1246
1247    fn from_str(s: &str) -> Result<RGBColor, RGBParseError> {
1248        match RGBColor::from_hex_code(s) {
1249            Err(_e) => match RGBColor::from_color_name(s) {
1250                Err(_e) => match parse_rgb_str(s) {
1251                    Err(_e) => Err(_e.into()),
1252                    Ok(nums) => Ok(RGBColor::from(nums)),
1253                },
1254                Ok(rgb) => Ok(rgb),
1255            },
1256            Ok(rgb) => Ok(rgb),
1257        }
1258    }
1259}
1260
1261#[cfg(test)]
1262mod tests {
1263    #[allow(unused_imports)]
1264    use super::*;
1265    use consts::TEST_PRECISION;
1266
1267    #[test]
1268    fn test_visual_distinguishability() {
1269        let color1 = RGBColor::from_hex_code("#123456").unwrap();
1270        let color2 = RGBColor::from_hex_code("#123556").unwrap();
1271        let color3 = RGBColor::from_hex_code("#333333").unwrap();
1272        assert!(color1.visually_indistinguishable(&color2));
1273        assert!(color2.visually_indistinguishable(&color1));
1274        assert!(!color1.visually_indistinguishable(&color3));
1275    }
1276
1277    #[cfg(feature = "terminal")]
1278    #[test]
1279    #[ignore]
1280    fn can_display_colors() {
1281        let range = 120;
1282        let mut col;
1283        let mut line;
1284        let mut c;
1285        let mut h;
1286        println!();
1287        for i in 0..range {
1288            h = (i as f64) / (range as f64) * 360.;
1289            line = String::new();
1290            for j in 0..range {
1291                c = j as f64;
1292                col = CIELCHColor {
1293                    l: 70.,
1294                    c: c / 2.,
1295                    h,
1296                };
1297                line += col.write_color().as_str();
1298            }
1299            println!("{}", line);
1300        }
1301        println!();
1302    }
1303
1304    #[test]
1305    fn xyz_to_rgb() {
1306        let xyz = XYZColor {
1307            x: 0.41874,
1308            y: 0.21967,
1309            z: 0.05649,
1310            illuminant: Illuminant::D65,
1311        };
1312        let rgb: RGBColor = xyz.convert();
1313        assert_eq!(rgb.int_r(), 254);
1314        assert_eq!(rgb.int_g(), 23);
1315        assert_eq!(rgb.int_b(), 55);
1316    }
1317
1318    #[test]
1319    fn rgb_to_xyz() {
1320        let rgb = RGBColor::from((45, 28, 156));
1321        let xyz: XYZColor = rgb.to_xyz(Illuminant::D65);
1322        // these won't match exactly cuz floats, so I just check within a margin
1323        assert!((xyz.x - 0.0750).abs() <= 0.01);
1324        assert!((xyz.y - 0.0379).abs() <= 0.01);
1325        assert!((xyz.z - 0.3178).abs() <= 0.01);
1326        assert!(rgb.distance(&xyz) <= TEST_PRECISION);
1327    }
1328    #[test]
1329    fn test_rgb_to_string() {
1330        let c1 = RGBColor::from((0, 0, 0));
1331        let c2 = RGBColor::from((244, 182, 33));
1332        let c3 = RGBColor::from((0, 255, 0));
1333        assert_eq!(c1.to_string(), "#000000");
1334        assert_eq!(c2.to_string(), "#F4B621");
1335        assert_eq!(c3.to_string(), "#00FF00");
1336    }
1337    #[test]
1338    fn test_xyz_color_adaptation() {
1339        // I can literally not find a single API or something that does this so I can check the
1340        // values, so I'll just hope that it's good enough to check that converting between several
1341        // illuminants and back again gets something good
1342        let c1 = XYZColor {
1343            x: 0.5,
1344            y: 0.75,
1345            z: 0.6,
1346            illuminant: Illuminant::D65,
1347        };
1348        let c2 = c1.color_adapt(Illuminant::D50).color_adapt(Illuminant::D55);
1349        let c3 = c1.color_adapt(Illuminant::D75).color_adapt(Illuminant::D55);
1350        assert!((c3.x - c2.x).abs() <= 0.01);
1351        assert!((c3.y - c2.y).abs() <= 0.01);
1352        assert!((c3.z - c2.z).abs() <= 0.01);
1353        assert!(c2.distance(&c3) <= TEST_PRECISION);
1354    }
1355    #[test]
1356    fn test_error_buildup_color_adaptation() {
1357        // this is essentially just seeing how consistent the inverse function is for the Bradford
1358        // transform
1359        let xyz = XYZColor {
1360            x: 0.5,
1361            y: 0.4,
1362            z: 0.6,
1363            illuminant: Illuminant::D65,
1364        };
1365        let mut xyz2;
1366        const MAX_ITERS_UNTIL_UNACCEPTABLE_ERROR: usize = 100;
1367        for i in 0..MAX_ITERS_UNTIL_UNACCEPTABLE_ERROR {
1368            let lum = [
1369                Illuminant::D50,
1370                Illuminant::D55,
1371                Illuminant::D65,
1372                Illuminant::D75,
1373            ][i % 4];
1374            xyz2 = xyz.color_adapt(lum);
1375            assert!(xyz2.approx_visually_equal(&xyz));
1376        }
1377    }
1378    #[test]
1379    fn test_chromatic_adapation_to_same_light() {
1380        let xyz = XYZColor {
1381            x: 0.4,
1382            y: 0.6,
1383            z: 0.2,
1384            illuminant: Illuminant::D65,
1385        };
1386        let xyz2 = xyz.color_adapt(Illuminant::D65);
1387        assert_eq!(xyz, xyz2);
1388    }
1389    #[cfg(feature = "terminal")]
1390    #[test]
1391    #[ignore]
1392    fn fun_dress_color_adaptation_demo() {
1393        // the famous dress colors, taken completely out of the lighting conditions using GIMP
1394        let dress_bg = RGBColor::from_hex_code("#7d6e47")
1395            .unwrap()
1396            .to_xyz(Illuminant::D65);
1397        let dress_fg = RGBColor::from_hex_code("#9aabd6")
1398            .unwrap()
1399            .to_xyz(Illuminant::D65);
1400
1401        // helper closure to print block of color
1402        let block_size = 50;
1403        let print_col = |c: XYZColor| {
1404            println!();
1405            for _i in 0..block_size {
1406                println!("{}", c.write_color().repeat(block_size));
1407            }
1408        };
1409
1410        // make two "proposed" illuminants: different observers disagree on which one from the image!
1411        // bright sunlight, clearly the incorrect one (actually, correct, just the one I don't see)
1412        let sunlight = Illuminant::D50; // essentially daylight in East US, approximately
1413                                        // dark shade, clearly the correct one (joking, it's the one I see)
1414                                        // just taking a point in the image that looks like white in shade
1415        let dress_wp = RGBColor::from_hex_code("#69718b").unwrap();
1416        let shade_wp = dress_wp.to_xyz(Illuminant::D65);
1417        let shade = Illuminant::Custom([shade_wp.x, shade_wp.y, shade_wp.z]);
1418        // print alternate blocks of color: first the dress interpreted in sunlight (black and blue),
1419        // then the dress interpreted in shade (white and gold)
1420        let mut black = dress_bg;
1421        let mut blue = dress_fg;
1422        black.illuminant = sunlight;
1423        blue.illuminant = sunlight;
1424
1425        let mut gold = dress_bg;
1426        let mut white = dress_fg;
1427        gold.illuminant = shade;
1428        white.illuminant = shade;
1429
1430        let black_rgb: RGBColor = black.convert();
1431        let blue_rgb: RGBColor = blue.convert();
1432        let gold_rgb: RGBColor = gold.convert();
1433        let white_rgb: RGBColor = white.convert();
1434        println!(
1435            "Black: {} Blue: {}",
1436            black_rgb.to_string(),
1437            blue_rgb.to_string()
1438        );
1439        println!(
1440            "Gold: {}, White: {}",
1441            gold_rgb.to_string(),
1442            white_rgb.to_string()
1443        );
1444        print_col(black);
1445        print_col(blue);
1446        print_col(gold);
1447        print_col(white);
1448    }
1449
1450    #[cfg(feature = "terminal")]
1451    #[test]
1452    #[ignore]
1453    fn fun_color_adaptation_demo() {
1454        println!();
1455        let w: usize = 120;
1456        let h: usize = 60;
1457        let d50_wp = Illuminant::D50.white_point();
1458        let d75_wp = Illuminant::D75.white_point();
1459        let d50 = XYZColor {
1460            x: d50_wp[0],
1461            y: d50_wp[1],
1462            z: d50_wp[2],
1463            illuminant: Illuminant::D65,
1464        };
1465        let d75 = XYZColor {
1466            x: d75_wp[0],
1467            y: d75_wp[1],
1468            z: d75_wp[2],
1469            illuminant: Illuminant::D65,
1470        };
1471        for _ in 0..h + 1 {
1472            println!(
1473                "{}{}",
1474                d50.write_color().repeat(w / 2),
1475                d75.write_color().repeat(w / 2)
1476            );
1477        }
1478
1479        println!();
1480        println!();
1481        let y = 0.5;
1482        println!();
1483        for i in 0..(h + 1) {
1484            let mut line = String::from("");
1485            let x = i as f64 * 0.9 / h as f64;
1486            for j in 0..(w / 2) {
1487                let z = j as f64 * 0.9 / w as f64;
1488                line.push_str(
1489                    XYZColor {
1490                        x,
1491                        y,
1492                        z,
1493                        illuminant: Illuminant::D50,
1494                    }
1495                    .write_color()
1496                    .as_str(),
1497                );
1498            }
1499            for j in (w / 2)..(w + 1) {
1500                let z = j as f64 * 0.9 / w as f64;
1501                line.push_str(
1502                    XYZColor {
1503                        x,
1504                        y,
1505                        z,
1506                        illuminant: Illuminant::D75,
1507                    }
1508                    .write_color()
1509                    .as_str(),
1510                );
1511            }
1512            println!("{}", line);
1513        }
1514        println!();
1515        println!();
1516        for i in 0..(h + 1) {
1517            let mut line = String::from("");
1518            let x = i as f64 * 0.9 / h as f64;
1519            for j in 0..w {
1520                let z = j as f64 * 0.9 / w as f64;
1521                line.push_str(
1522                    XYZColor {
1523                        x,
1524                        y,
1525                        z,
1526                        illuminant: Illuminant::D65,
1527                    }
1528                    .write_color()
1529                    .as_str(),
1530                );
1531            }
1532            println!("{}", line);
1533        }
1534    }
1535    #[test]
1536    fn test_rgb_from_hex() {
1537        // test rgb format
1538        let rgb = RGBColor::from_hex_code("#172844").unwrap();
1539        assert_eq!(rgb.int_r(), 23);
1540        assert_eq!(rgb.int_g(), 40);
1541        assert_eq!(rgb.int_b(), 68);
1542        // test with letters and no hex
1543        let rgb = RGBColor::from_hex_code("a1F1dB").unwrap();
1544        assert_eq!(rgb.int_r(), 161);
1545        assert_eq!(rgb.int_g(), 241);
1546        assert_eq!(rgb.int_b(), 219);
1547        // test for error if 7 chars
1548        let rgb = RGBColor::from_hex_code("#1244444");
1549        assert!(matches!(rgb, Err(x) if x == RGBParseError::InvalidHexSyntax));
1550        // test for error if invalid hex chars
1551        let rgb = RGBColor::from_hex_code("#ffggbb");
1552        assert!(matches!(rgb, Err(x) if x == RGBParseError::InvalidHexSyntax));
1553    }
1554    #[test]
1555    fn test_rgb_from_name() {
1556        let rgb = RGBColor::from_color_name("yeLlowgreEn").unwrap();
1557        assert_eq!(rgb.int_r(), 154);
1558        assert_eq!(rgb.int_g(), 205);
1559        assert_eq!(rgb.int_b(), 50);
1560        // test error
1561        let rgb = RGBColor::from_color_name("thisisnotavalidnamelol");
1562        assert!(match rgb {
1563            Err(x) if x == RGBParseError::InvalidX11Name => true,
1564            _ => false,
1565        });
1566    }
1567    #[test]
1568    fn test_rgb_from_func() {
1569        let rgb: RGBColor = "rgb(67%, 205, .937)".parse().unwrap();
1570        assert_eq!(*"#ABCDEF", rgb.to_string());
1571        assert_eq!(
1572            Err(RGBParseError::InvalidFuncSyntax),
1573            "rgb(53%%, 23, 44)".parse::<RGBColor>()
1574        );
1575    }
1576    #[test]
1577    fn test_string_parsing_all() {
1578        assert_eq!(
1579            *"#123456",
1580            "rgb(18, 52, 86)".parse::<RGBColor>().unwrap().to_string()
1581        );
1582        assert_eq!(
1583            *"#123456",
1584            "#123456".parse::<RGBColor>().unwrap().to_string()
1585        );
1586        assert_eq!(*"#000000", "black".parse::<RGBColor>().unwrap().to_string());
1587    }
1588    #[test]
1589    fn test_to_string() {
1590        for hex in ["#000000", "#ABCDEF", "#1A2B3C", "#D00A12", "#40AA50"].iter() {
1591            assert_eq!(*hex, RGBColor::from_hex_code(hex).unwrap().to_string());
1592        }
1593    }
1594    #[cfg(feature = "terminal")]
1595    #[test]
1596    #[ignore]
1597    fn lightness_demo() {
1598        use colors::{CIELABColor, HSLColor};
1599        let mut line;
1600        println!();
1601        for i in 0..20 {
1602            line = String::from("");
1603            for j in 0..20 {
1604                let lab = CIELABColor {
1605                    l: 50.,
1606                    a: 5. * i as f64,
1607                    b: 5. * j as f64,
1608                };
1609                line.push_str(lab.write_colored_str("#").as_str());
1610            }
1611            println!("{}", line);
1612        }
1613        println!();
1614        for i in 0..20 {
1615            line = String::from("");
1616            for j in 0..20 {
1617                let hsl = HSLColor {
1618                    h: i as f64 * 18.,
1619                    s: j as f64 * 0.05,
1620                    l: 0.50,
1621                };
1622                line.push_str(hsl.write_colored_str("#").as_str());
1623            }
1624            println!("{}", line);
1625        }
1626    }
1627    #[test]
1628    fn test_ciede2000() {
1629        // this implements the fancy test cases found here:
1630        // https://pdfs.semanticscholar.org/969b/c38ea067dd22a47a44bcb59c23807037c8d8.pdf
1631        let l_1 = vec![
1632            50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0,
1633            50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 60.2574, 63.0109, 61.2901,
1634            35.0831, 22.7233, 36.4612, 90.8027, 90.9257, 6.7747, 2.0776,
1635        ];
1636        let l_2 = vec![
1637            50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0,
1638            50.0, 50.0, 73.0, 61.0, 56.0, 58.0, 50.0, 50.0, 50.0, 50.0, 60.4626, 62.8187, 61.4292,
1639            35.0232, 23.0331, 36.2715, 91.1528, 88.6381, 5.8714, 0.9033,
1640        ];
1641        let a_1 = vec![
1642            2.6772, 3.1571, 2.8361, -1.3802, -1.1848, -0.9009, 0.0, -1.0, 2.49, 2.49, 2.49, 2.49,
1643            -0.001, -0.001, -0.001, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, -34.0099,
1644            -31.0961, 3.7196, -44.1164, 20.0904, 47.858, -2.0831, -0.5406, -0.2908, 0.0795,
1645        ];
1646        let a_2 = vec![
1647            0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, -2.49, -2.49, -2.49, -2.49, 0.0009, 0.001,
1648            0.0011, 0.0, 25.0, -5.0, -27.0, 24.0, 3.1736, 3.2972, 1.8634, 3.2592, -34.1751,
1649            -29.7946, 2.248, -40.0716, 14.973, 50.5065, -1.6435, -0.8985, -0.0985, -0.0636,
1650        ];
1651        let b_1 = vec![
1652            -79.7751, -77.2803, -74.02, -84.2814, -84.8006, -85.5211, 0.0, 2.0, -0.001, -0.001,
1653            -0.001, -0.001, 2.49, 2.49, 2.49, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 36.2677,
1654            -5.8663, -5.3901, 3.7933, -46.6940, 18.3852, 1.441, -0.9208, -2.4247, -1.135,
1655        ];
1656        let b_2 = vec![
1657            -82.7485, -82.7485, -82.7485, -82.7485, -82.7485, -82.7485, 2.0, 0.0, 0.0009, 0.001,
1658            0.0011, 0.0012, -2.49, -2.49, -2.49, -2.5, -18.0, 29.0, -3.0, 15.0, 0.5854, 0.0,
1659            0.5757, 0.3350, 39.4387, -4.0864, -4.962, 1.5901, -42.5619, 21.2231, 0.0447, -0.7239,
1660            -2.2286, -0.5514,
1661        ];
1662        let d_e = vec![
1663            2.0425, 2.8615, 3.4412, 1.0, 1.0, 1.0, 2.3669, 2.3669, 7.1792, 7.1792, 7.2195, 7.2195,
1664            4.8045, 4.8045, 4.7461, 4.3065, 27.1492, 22.8977, 31.9030, 19.4535, 1.0, 1.0, 1.0, 1.0,
1665            1.2644, 1.263, 1.8731, 1.8645, 2.0373, 1.4146, 1.4441, 1.5381, 0.6377, 0.9082,
1666        ];
1667        assert_eq!(l_1.len(), 34);
1668        assert_eq!(l_2.len(), 34);
1669        assert_eq!(a_1.len(), 34);
1670        assert_eq!(a_2.len(), 34);
1671        assert_eq!(b_1.len(), 34);
1672        assert_eq!(b_2.len(), 34);
1673        assert_eq!(d_e.len(), 34);
1674        for i in 0..34 {
1675            let lab1 = CIELABColor {
1676                l: l_1[i],
1677                a: a_1[i],
1678                b: b_1[i],
1679            };
1680            let lab2 = CIELABColor {
1681                l: l_2[i],
1682                a: a_2[i],
1683                b: b_2[i],
1684            };
1685            // only good to 4 decimal points
1686            assert!((lab1.distance(&lab2) - d_e[i]).abs() <= 1e-4);
1687            assert!((lab2.distance(&lab1) - d_e[i]).abs() <= 1e-4);
1688        }
1689    }
1690    #[test]
1691    fn test_hue_chroma_lightness_saturation() {
1692        let mut rgb;
1693        let mut rgb2;
1694        for code in [
1695            "#12000D", "#FAFA22", "#FF0000", "#0000FF", "#FF0FDF", "#2266AA", "#001200", "#FFAAFF",
1696            "#003462", "#466223", "#AAFFBC",
1697        ]
1698        .iter()
1699        {
1700            // hue
1701            rgb = RGBColor::from_hex_code(code).unwrap();
1702            let h = rgb.hue();
1703            rgb.set_hue(345.0);
1704            assert!((rgb.hue() - 345.0).abs() <= 1e-4);
1705            rgb2 = rgb;
1706            rgb2.set_hue(h);
1707            assert_eq!(rgb2.to_string(), String::from(*code));
1708
1709            // chroma
1710            rgb = RGBColor::from_hex_code(code).unwrap();
1711            let c = rgb.chroma();
1712            rgb.set_chroma(45.0);
1713            assert!((rgb.chroma() - 45.0).abs() <= 1e-4);
1714            rgb2 = rgb;
1715            rgb2.set_chroma(c);
1716            assert_eq!(rgb2.to_string(), String::from(*code));
1717
1718            // lightness
1719            rgb = RGBColor::from_hex_code(code).unwrap();
1720            let l = rgb.lightness();
1721            rgb.set_lightness(23.0);
1722            assert!((rgb.lightness() - 23.0).abs() <= 1e-4);
1723            rgb2 = rgb;
1724            rgb2.set_lightness(l);
1725            assert_eq!(rgb2.to_string(), String::from(*code));
1726
1727            // saturation
1728            rgb = RGBColor::from_hex_code(code).unwrap();
1729            let s = rgb.saturation();
1730            rgb.set_saturation(0.4);
1731            assert!((rgb.saturation() - 0.4).abs() <= 1e-4);
1732            rgb2 = rgb;
1733            rgb2.set_saturation(s);
1734            assert_eq!(rgb2.to_string(), String::from(*code));
1735        }
1736    }
1737    #[test]
1738    #[ignore]
1739    fn color_scheme() {
1740        let mut colors: Vec<RGBColor> = vec![];
1741        for i in 0..8 {
1742            colors.push(
1743                CIELCHColor {
1744                    l: i as f64 / 7. * 100.,
1745                    c: 0.,
1746                    h: 0.,
1747                }
1748                .convert(),
1749            );
1750        }
1751        for j in 0..8 {
1752            colors.push(
1753                CIELCHColor {
1754                    l: 50.,
1755                    c: 70.,
1756                    h: j as f64 / 8. * 360. + 10.,
1757                }
1758                .convert(),
1759            );
1760        }
1761        println!();
1762        for color in colors {
1763            println!("{}", color.to_string());
1764        }
1765    }
1766}