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 /// 
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}