palette/
rgb.rs

1//! Types for the RGB color space, including spaces and standards.
2//!
3//! # Linear And Non-linear RGB
4//!
5//! Colors in images are often "gamma corrected", or converted using some
6//! non-linear transfer function into a format like sRGB before being stored or
7//! displayed. This is done as a compression method and to prevent banding; it's
8//! also a bit of a legacy from the ages of the CRT monitors, where the output
9//! from the electron gun was non-linear. The problem is that these formats are
10//! *non-linear color spaces*, which means that many operations that you may
11//! want to perform on colors (addition, subtraction, multiplication, linear
12//! interpolation, etc.) will work unexpectedly when performed in such a
13//! non-linear color space. Thus, the compression has to be reverted to restore
14//! linearity and ensure that many operations on the colors behave as expected.
15//!
16//! But, even when colors *are* 'linear', there is yet more to explore.
17//!
18//! The most common way that colors are defined, especially for computer
19//! storage, is in terms of so-called *tristimulus values*, meaning that all
20//! colors can be represented as a vector of three values.
21//! The reason colors can generally be stored as only a three-dimensional
22//! vector, and not an *N*-dimensional one, where *N* is some number of possible
23//! wavelengths of light, is because our eyes contain only three types of cones.
24//! Each of these cones has its own sensitivity curve in response to the
25//! wavelengths of visible light, giving us three "dimensions" of sensitivity to color.
26//! These cones are often called the L, M, and S (for long, medium, and short)
27//! cones, and their sensitivity curves *roughly* position them as most
28//! sensitive to "red", "green", and "blue" parts of the spectrum. As such, we
29//! can choose only three values to represent any possible color that a human is
30//! able to see. An interesting consequence of this is that humans can see two
31//! different objects which are emitting *completely different actual light
32//! spectra* as the *exact same perceptual color* so long as those wavelengths,
33//! when transformed by the sensitivity curves of our cones, end up resulting in
34//! the same L, M, and S values sent to our brains.
35//!
36//! A **color space** (which simply refers to a set of standards by which we map
37//! a set of arbitrary values to real-world colors) which uses tristimulus
38//! values is often defined in terms of
39//!
40//!  1. Its **primaries**
41//!  2. Its **reference white** or **white point**
42//!
43//! The **primaries** together represent the total *gamut* (i.e. displayable
44//! range of colors) of that color space. The **white point** defines a
45//! concrete tristimulus value that corresponds to a real, physical white
46//! reflecting object being lit by a known light source and observed by the
47//! 'standard observer' (i.e. a standardized model of human color perception).
48//!
49//! The informal "RGB" color space is such a tristimulus color space, since it
50//! is defined by three values, but it is underspecified since we don't know
51//! which primaries are being used (i.e. how exactly are the canonical "red",
52//! "green", and "blue" defined?), nor its white point. In most cases, when
53//! people talk about "RGB" or "Linear RGB" colors, what they are *actually*
54//! talking about is the "Linear sRGB" color space, which uses the primaries and
55//! white point defined in the sRGB standard, but which *does not* have the
56//! (non-linear) sRGB *transfer function* applied.
57//!
58//! Palette takes these details into account and encodes them as type
59//! parameters, with sRGB as the default. The goal is to make it easy to use
60//! colors correctly and still allow advanced users a high degree of
61//! flexibility.
62
63use crate::{
64    encoding::{self, FromLinear, Gamma, IntoLinear, Linear},
65    stimulus::{FromStimulus, Stimulus},
66    white_point::Any,
67    Mat3, Yxy,
68};
69
70pub use self::rgb::{FromHexError, Iter, Rgb, Rgba};
71
72pub mod channels;
73#[allow(clippy::module_inception)]
74mod rgb;
75
76/// Non-linear sRGB, the most common RGB input/output format.
77///
78/// If you are looking for "just RGB", this is probably it. This type alias
79/// helps by locking the more generic [`Rgb`] type to the sRGB format.
80///
81/// See [`Rgb`] for more details on how to create a value and use it.
82pub type Srgb<T = f32> = Rgb<encoding::Srgb, T>;
83
84/// Non-linear sRGB with an alpha component.
85///
86/// This is a transparent version of [`Srgb`], which is commonly used as the
87/// input or output format. If you are looking for "just RGBA", this is probably
88/// it.
89///
90/// See [`Rgb`], [`Rgba`] and [`Alpha`](crate::Alpha) for more details on how to
91/// create a value and use it.
92pub type Srgba<T = f32> = Rgba<encoding::Srgb, T>;
93
94/// Linear sRGB.
95///
96/// You probably want [`Srgb`] if you are looking for an input or output format
97/// (or "just RGB"). This is the linear version of sRGB, which is what you would
98/// usually convert to before working with the color.
99///
100/// See [`Rgb`] for more details on how to create a value and use it.
101#[doc(alias = "linear")]
102pub type LinSrgb<T = f32> = Rgb<Linear<encoding::Srgb>, T>;
103
104/// Linear sRGB with an alpha component.
105///
106/// You probably want [`Srgba`] if you are looking for an input or output format
107/// (or "just RGB"). This is the linear version of sRGBA, which is what you
108/// would usually convert to before working with the color.
109///
110/// See [`Rgb`], [`Rgba`] and [`Alpha`](crate::Alpha) for more details on how to
111/// create a value and use it.
112#[doc(alias = "linear")]
113pub type LinSrgba<T = f32> = Rgba<Linear<encoding::Srgb>, T>;
114
115/// Gamma 2.2 encoded sRGB.
116///
117/// This is similar to [`Srgb`], but uses the exponent function as an
118/// approximation. It's a common trick to speed up conversion when accuracy can
119/// be sacrificed. It's still faster to use `Srgb` when also converting to and
120/// from `u8` at the same time.
121///
122/// See [`Rgb`] for more details on how to create a value and use it.
123pub type GammaSrgb<T = f32> = Rgb<Gamma<encoding::Srgb>, T>;
124
125/// Gamma 2.2 encoded sRGB with an alpha component.
126///
127/// This is similar to [`Srgba`], but uses the exponent function as an
128/// approximation. It's a common trick to speed up conversion when accuracy can
129/// be sacrificed. It's still faster to use `Srgba` when also converting to and
130/// from `u8` at the same time.
131///
132/// See [`Rgb`], [`Rgba`] and [`Alpha`](crate::Alpha) for more details on how to
133/// create a value and use it.
134pub type GammaSrgba<T = f32> = Rgba<Gamma<encoding::Srgb>, T>;
135
136/// An RGB space and a transfer function.
137pub trait RgbStandard {
138    /// The RGB color space.
139    type Space: RgbSpace;
140
141    /// The transfer function for the color components.
142    type TransferFn;
143}
144
145impl<Sp, Tf> RgbStandard for (Sp, Tf)
146where
147    Sp: RgbSpace,
148{
149    type Space = Sp;
150    type TransferFn = Tf;
151}
152
153impl<Pr, Wp, Tf> RgbStandard for (Pr, Wp, Tf)
154where
155    (Pr, Wp): RgbSpace,
156{
157    type Space = (Pr, Wp);
158    type TransferFn = Tf;
159}
160
161/// A set of primaries and a white point.
162pub trait RgbSpace {
163    /// The primaries of the RGB color space.
164    type Primaries;
165
166    /// The white point of the RGB color space.
167    type WhitePoint;
168
169    /// Get a pre-defined matrix for converting an RGB value with this standard
170    /// into an XYZ value.
171    ///
172    /// Returning `None` (as in the default implementation) means that the
173    /// matrix will be computed dynamically, which is significantly slower.
174    #[inline(always)]
175    fn rgb_to_xyz_matrix() -> Option<Mat3<f64>> {
176        None
177    }
178
179    /// Get a pre-defined matrix for converting an XYZ value into an RGB value
180    /// with this standard.
181    ///
182    /// Returning `None` (as in the default implementation) means that the
183    /// matrix will be computed dynamically, which is significantly slower.
184    #[inline(always)]
185    fn xyz_to_rgb_matrix() -> Option<Mat3<f64>> {
186        None
187    }
188}
189
190impl<P, W> RgbSpace for (P, W) {
191    type Primaries = P;
192    type WhitePoint = W;
193}
194
195/// Represents the red, green and blue primaries of an RGB space.
196pub trait Primaries<T> {
197    /// Primary red.
198    fn red() -> Yxy<Any, T>;
199    /// Primary green.
200    fn green() -> Yxy<Any, T>;
201    /// Primary blue.
202    fn blue() -> Yxy<Any, T>;
203}
204
205impl<T, U> From<LinSrgb<T>> for Srgb<U>
206where
207    crate::encoding::Srgb: RgbStandard<Space = crate::encoding::Srgb> + FromLinear<T, U>,
208{
209    #[inline]
210    fn from(lin_srgb: LinSrgb<T>) -> Self {
211        lin_srgb.into_encoding()
212    }
213}
214
215impl<T, U> From<Srgb<T>> for LinSrgb<U>
216where
217    crate::encoding::Srgb: RgbStandard<Space = crate::encoding::Srgb> + IntoLinear<U, T>,
218{
219    #[inline]
220    fn from(srgb: Srgb<T>) -> Self {
221        srgb.into_linear()
222    }
223}
224
225impl<T, U> From<LinSrgb<T>> for Srgba<U>
226where
227    U: Stimulus,
228    crate::encoding::Srgb: RgbStandard<Space = crate::encoding::Srgb> + FromLinear<T, U>,
229{
230    #[inline]
231    fn from(lin_srgb: LinSrgb<T>) -> Self {
232        let non_lin = Srgb::from_linear(lin_srgb);
233        non_lin.into()
234    }
235}
236
237impl<T, U> From<LinSrgba<T>> for Srgba<U>
238where
239    U: FromStimulus<T>,
240    crate::encoding::Srgb: RgbStandard<Space = crate::encoding::Srgb> + FromLinear<T, U>,
241{
242    #[inline]
243    fn from(lin_srgba: LinSrgba<T>) -> Self {
244        Srgba::from_linear(lin_srgba)
245    }
246}
247
248impl<T, U> From<Srgb<T>> for LinSrgba<U>
249where
250    U: Stimulus,
251    crate::encoding::Srgb: RgbStandard<Space = crate::encoding::Srgb> + IntoLinear<U, T>,
252{
253    #[inline]
254    fn from(srgb: Srgb<T>) -> Self {
255        srgb.into_linear().into()
256    }
257}
258
259impl<T, U> From<Srgba<T>> for LinSrgba<U>
260where
261    U: FromStimulus<T>,
262    crate::encoding::Srgb: RgbStandard<Space = crate::encoding::Srgb> + IntoLinear<U, T>,
263{
264    #[inline]
265    fn from(srgba: Srgba<T>) -> Self {
266        srgba.into_linear()
267    }
268}
269
270/// A packed representation of RGBA in RGBA order.
271pub type PackedRgba<P = u32> = crate::cast::Packed<channels::Rgba, P>;
272
273/// A packed representation of RGBA in ARGB order.
274pub type PackedArgb<P = u32> = crate::cast::Packed<channels::Argb, P>;
275
276/// A packed representation of RGBA in BGRA order.
277pub type PackedBgra<P = u32> = crate::cast::Packed<channels::Bgra, P>;
278
279/// A packed representation of RGBA in ABGR order.
280pub type PackedAbgr<P = u32> = crate::cast::Packed<channels::Abgr, P>;