Struct scarlet::color::XYZColor

source ·
pub struct XYZColor {
    pub x: f64,
    pub y: f64,
    pub z: f64,
    pub illuminant: Illuminant,
}
Expand description

A point in the CIE 1931 XYZ color space. Although any point in XYZ coordinate space is technically valid, in this library XYZ colors are treated as normalized so that Y=1 is the white point of whatever illuminant is being worked with.

Fields§

§x: f64

The X axis of the CIE 1931 XYZ space, roughly representing the long-wavelength receptors in the human eye: the red receptors. Usually between 0 and 1, but can range more than that.

§y: f64

The Y axis of the CIE 1931 XYZ space, roughly representing the middle-wavelength receptors in the human eye. In CIE 1931, this is fudged a little to correspond exactly with perceived luminance, so while this doesn’t exactly map to middle-wavelength receptors it has a far more useful analogue.

§z: f64

The Z axis of the CIE 1931 XYZ space, roughly representing the short-wavelength receptors in the human eye. Usually between 0 and 1, but can range more than that.

§illuminant: Illuminant

The illuminant that is assumed to be the lighting environment for this color. Although XYZ itself describes the human response to a color and so is independent of lighting, it is useful to consider the question “how would an object in one light look different in another?” and so, to contain all the information needed to track this, the illuminant is set. See the color_adapt() method to examine how this is used in the wild.

Implementations§

source§

impl XYZColor

source

pub fn color_adapt(&self, other_illuminant: Illuminant) -> XYZColor

Converts from one illuminant to a different one, such that a human receiving both sets of sensory stimuli in the corresponding lighting conditions would perceive an object with that color as not having changed. This process, called chromatic adaptation, happens subconsciously all the time: when someone walks into the shade, we don’t interpret that shift as their face turning blue. This process is not at all simple to compute, however, and many different algorithms for doing so exist: it is most likely that each person has their own idiosyncrasies with chromatic adaptation and so there is no perfect solution. Scarlet implements the Bradford transform, which is generally acknowledged to be one of the leading chromatic adaptation transforms. Nonetheless, for exact color science work other models are more appropriate, such as CIECAM02 if you can measure viewing conditions exactly. This transform may not give very good results when used with custom illuminants that wildly differ, but with the standard illuminants it does a very good job.

Example: The Fabled Dress

The most accessible way of describing color transformation is to take a look at this image, otherwise known as “the dress”. This showcases in a very apparent fashion the problems with very ambiguous lighting in chromatic adaptation: the photo is cropped to the point that some of the population perceives it to be in deep shade and for the dress to therefore be white and gold, while others perceive instead harsh sunlight and therefore perceive it as black and blue. (For reference, it is actually black and blue.) Scarlet can help us answer the question “how would this look to an observer with either judgment about the lighting conditions?” without needing the human eye! First, we use a photo editor to pick out two colors that represent both colors of the dress. Then, we’ll change the illuminant directly (without using chromatic adaptation, because we want to actually change the color), and then we’ll adapt back to D65 to represent on a screen the different colors.

let dress_bg = RGBColor::from_hex_code("#7d6e47").unwrap().to_xyz(Illuminant::D65);
let dress_fg = RGBColor::from_hex_code("#9aabd6").unwrap().to_xyz(Illuminant::D65);
// proposed sunlight illuminant: daylight in North America
// We could exaggerate the effect by creating an illuminant with greater Y value at the white
// point, but this will do
let sunlight = Illuminant::D50;
// proposed "shade" illuminant: created by picking the brightest point on the dress without
// glare subjectively, and then treating that as white
let shade_white = RGBColor::from_hex_code("#b0c5e4").unwrap().to_xyz(Illuminant::D65);
let shade = Illuminant::Custom([shade_white.x, shade_white.y, shade_white.z]);
// make copies of the colors and set illuminants
let mut black = dress_bg;
let mut blue = dress_fg;
let mut gold = dress_bg;
let mut white = dress_fg;
black.illuminant = sunlight;
blue.illuminant = sunlight;
gold.illuminant = shade;
white.illuminant = shade;
// we can just print them out now: the chromatic adaptation is done automatically to get back
// to the color space of the viewing monitor. This isn't exact, mostly because the shade
// illuminant is entirely fudged, but it's surprisingly good
let black_rgb: RGBColor = black.convert();
let blue_rgb: RGBColor = blue.convert();
let gold_rgb: RGBColor = gold.convert();
let white_rgb: RGBColor = white.convert();
println!("Black: {} Blue: {}", black_rgb.to_string(), blue_rgb.to_string());
println!("Gold: {}, White: {}", gold_rgb.to_string(), white_rgb.to_string());
source

pub fn approx_equal(&self, other: &XYZColor) -> bool

Returns true if the given other XYZ color’s coordinates are all within acceptable error of each other, which helps account for necessary floating-point errors in conversions. To test whether two colors are indistinguishable to humans, use instead Color::visually_indistinguishable.

Example
let xyz1 = XYZColor{x: 0.3, y: 0., z: 0., illuminant: Illuminant::D65};
// note that the difference in illuminant won't be taken into account
let xyz2 = XYZColor{x: 0.1 + 0.1 + 0.1, y: 0., z: 0., illuminant: Illuminant::D55};
// note that because of rounding error these aren't exactly equal!
assert!(xyz1.x != xyz2.x);
// using approx_equal, we can avoid these sorts of errors
assert!(xyz1.approx_equal(&xyz2));
source

pub fn approx_visually_equal(&self, other: &XYZColor) -> bool

Returns true if the given other XYZ color would look identically in a different color space. Uses an approximate float equality that helps resolve errors due to floating-point representation, only testing if the two floats are within 0.001 of each other.

Example
assert!(XYZColor::white_point(Illuminant::D65).approx_visually_equal(&XYZColor::white_point(Illuminant::D50)));
source

pub fn white_point(illuminant: Illuminant) -> XYZColor

Gets the XYZColor corresponding to pure white in the given light environment.

Example
let white1 = XYZColor::white_point(Illuminant::D65);
let white2 = XYZColor::white_point(Illuminant::D50);
assert!(white1.approx_visually_equal(&white2));

Trait Implementations§

source§

impl Clone for XYZColor

source§

fn clone(&self) -> XYZColor

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Color for XYZColor

source§

fn from_xyz(xyz: XYZColor) -> XYZColor

Converts from a color in CIE 1931 XYZ to the given color type. Read more
source§

fn to_xyz(&self, illuminant: Illuminant) -> XYZColor

Converts from the given color type to a color in CIE 1931 XYZ space. Because most color types don’t include illuminant information, it is provided instead, as an enum. For most applications, D50 or D65 is a good choice. Read more
source§

fn convert<T: Color>(&self) -> T

Converts generic colors from one representation to another. This is done by going back and forth from the CIE 1931 XYZ space, using the illuminant D50 (although this should not affect the results). Just like collect() and other methods in the standard library, the use of type inference will usually allow for clean syntax, but occasionally the turbofish is necessary. Read more
source§

fn hue(&self) -> f64

Gets the generally most accurate version of hue for a given color: the hue coordinate in CIELCH. There are generally considered four “unique hues” that humans perceive as not decomposable into other hues (when mixing additively): these are red, yellow, green, and blue. These unique hues have values of 0, 90, 180, and 270 degrees respectively, with other colors interpolated between them. This returned value will never be outside the range 0 to 360. For more information, you can start at the Wikpedia page. Read more
source§

fn set_hue(&mut self, new_hue: f64)

Sets a perceptually-accurate version hue of a color, even if the space itself does not have a conception of hue. This uses the CIELCH version of hue. To use another one, simply convert and set it manually. If the given hue is not between 0 and 360, it is shifted in that range by adding multiples of 360. Read more
source§

fn lightness(&self) -> f64

Gets a perceptually-accurate version of lightness as a value from 0 to 100, where 0 is black and 100 is pure white. The exact value used is CIELAB’s definition of luminance, which is generally considered a very good standard. Note that this is nonlinear with respect to the physical amount of light emitted: a material with 18% reflectance has a lightness value of 50, not 18. Read more
source§

fn set_lightness(&mut self, new_lightness: f64)

Sets a perceptually-accurate version of lightness, which ranges between 0 and 100 for visible colors. Any values outside of this range will be clamped within it. Read more
source§

fn chroma(&self) -> f64

Gets a perceptually-accurate version of chroma, defined as colorfulness relative to a similarly illuminated white. This has no explicit upper bound, but is always positive and generally between 0 and 180 for visible colors. This is done using the CIELCH model. Read more
source§

fn set_chroma(&mut self, new_chroma: f64)

Sets a perceptually-accurate version of chroma, defined as colorfulness relative to a similarly illuminated white. Uses CIELCH’s defintion of chroma for implementation. Any value below 0 will be clamped up to 0, but because the upper bound depends on the hue and lightness no clamping will be done. This means that this method has a higher chance than normal of producing imaginary colors and any output from this method should be checked. Read more
source§

fn saturation(&self) -> f64

Gets a perceptually-accurate version of saturation, defined as chroma relative to lightness. Generally ranges from 0 to around 10, although exact bounds are tricky. from This means that e.g., a very dark purple could be very highly saturated even if it does not seem so relative to lighter colors. This is computed using the CIELCH model and computing chroma divided by lightness: if the lightness is 0, the saturation is also said to be 0. There is no official formula except ones that require more information than this model of colors has, but the CIELCH formula is fairly standard. Read more
source§

fn set_saturation(&mut self, new_sat: f64)

Sets a perceptually-accurate version of saturation, defined as chroma relative to lightness. Does this without modifying lightness or hue. Any negative value will be clamped to 0, but because the maximum saturation is not well-defined any positive value will be used as is: this means that this method is more likely than others to produce imaginary colors. Uses the CIELCH color space. Generally, saturation ranges from 0 to about 1, but it can go higher. Read more
source§

fn grayscale(&self) -> Selfwhere Self: Sized,

Returns a new Color of the same type as before, but with chromaticity removed: effectively, a color created solely using a mix of black and white that has the same lightness as before. This uses the CIELAB luminance definition, which is considered a good standard and is perceptually accurate for the most part. Read more
source§

fn distance<T: Color>(&self, other: &T) -> f64

Returns a metric of the distance between the given color and another that attempts to accurately reflect human perception. This is done by using the CIEDE2000 difference formula, the current international and industry standard. The result, being a distance, will never be negative: it has no defined upper bound, although anything larger than 100 would be very extreme. A distance of 1.0 is conservatively the smallest possible noticeable difference: anything that is below 1.0 is almost guaranteed to be indistinguishable to most people. Read more
source§

fn visually_indistinguishable<T: Color>(&self, other: &T) -> bool

Using the metric that two colors with a CIEDE2000 distance of less than 1 are indistinguishable, determines whether two colors are visually distinguishable from each other. For more, check out this guide. Read more
source§

impl Debug for XYZColor

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<XYZColor> for XYZColor

source§

fn eq(&self, other: &XYZColor) -> bool

This method tests for self and other values to be equal, and is used by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
source§

impl Copy for XYZColor

source§

impl StructuralPartialEq for XYZColor

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for Twhere U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
§

impl<SS, SP> SupersetOf<SS> for SPwhere SS: SubsetOf<SP>,

§

fn to_subset(&self) -> Option<SS>

The inverse inclusion map: attempts to construct self from the equivalent element of its superset. Read more
§

fn is_in_subset(&self) -> bool

Checks if self is actually part of its subset T (and can be converted to it).
§

fn to_subset_unchecked(&self) -> SS

Use with care! Same as self.to_subset but without any property checks. Always succeeds.
§

fn from_subset(element: &SS) -> SP

The inclusion map: converts self to the equivalent element of its superset.
source§

impl<T> ToOwned for Twhere T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<G1, G2> Within<G2> for G1where G2: Contains<G1>,

source§

fn is_within(&self, b: &G2) -> bool

source§

impl<T> Scalar for Twhere T: 'static + Clone + PartialEq<T> + Debug,