palette/
rgb.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
//! Types for the RGB color space, including spaces and standards.
//!
//! # Linear And Non-linear RGB
//!
//! Colors in images are often "gamma corrected", or converted using some
//! non-linear transfer function into a format like sRGB before being stored or
//! displayed. This is done as a compression method and to prevent banding; it's
//! also a bit of a legacy from the ages of the CRT monitors, where the output
//! from the electron gun was non-linear. The problem is that these formats are
//! *non-linear color spaces*, which means that many operations that you may
//! want to perform on colors (addition, subtraction, multiplication, linear
//! interpolation, etc.) will work unexpectedly when performed in such a
//! non-linear color space. Thus, the compression has to be reverted to restore
//! linearity and ensure that many operations on the colors behave as expected.
//!
//! But, even when colors *are* 'linear', there is yet more to explore.
//!
//! The most common way that colors are defined, especially for computer
//! storage, is in terms of so-called *tristimulus values*, meaning that all
//! colors can be represented as a vector of three values.
//! The reason colors can generally be stored as only a three-dimensional
//! vector, and not an *N*-dimensional one, where *N* is some number of possible
//! wavelengths of light, is because our eyes contain only three types of cones.
//! Each of these cones has its own sensitivity curve in response to the
//! wavelengths of visible light, giving us three "dimensions" of sensitivity to color.
//! These cones are often called the L, M, and S (for long, medium, and short)
//! cones, and their sensitivity curves *roughly* position them as most
//! sensitive to "red", "green", and "blue" parts of the spectrum. As such, we
//! can choose only three values to represent any possible color that a human is
//! able to see. An interesting consequence of this is that humans can see two
//! different objects which are emitting *completely different actual light
//! spectra* as the *exact same perceptual color* so long as those wavelengths,
//! when transformed by the sensitivity curves of our cones, end up resulting in
//! the same L, M, and S values sent to our brains.
//!
//! A **color space** (which simply refers to a set of standards by which we map
//! a set of arbitrary values to real-world colors) which uses tristimulus
//! values is often defined in terms of
//!
//!  1. Its **primaries**
//!  2. Its **reference white** or **white point**
//!
//! The **primaries** together represent the total *gamut* (i.e. displayable
//! range of colors) of that color space. The **white point** defines a
//! concrete tristimulus value that corresponds to a real, physical white
//! reflecting object being lit by a known light source and observed by the
//! 'standard observer' (i.e. a standardized model of human color perception).
//!
//! The informal "RGB" color space is such a tristimulus color space, since it
//! is defined by three values, but it is underspecified since we don't know
//! which primaries are being used (i.e. how exactly are the canonical "red",
//! "green", and "blue" defined?), nor its white point. In most cases, when
//! people talk about "RGB" or "Linear RGB" colors, what they are *actually*
//! talking about is the "Linear sRGB" color space, which uses the primaries and
//! white point defined in the sRGB standard, but which *does not* have the
//! (non-linear) sRGB *transfer function* applied.
//!
//! Palette takes these details into account and encodes them as type
//! parameters, with sRGB as the default. The goal is to make it easy to use
//! colors correctly and still allow advanced users a high degree of
//! flexibility.

use crate::{
    encoding::{self, FromLinear, Gamma, IntoLinear, Linear},
    stimulus::{FromStimulus, Stimulus},
    white_point::Any,
    Mat3, Yxy,
};

pub use self::rgb::{FromHexError, Iter, Rgb, Rgba};

pub mod channels;
#[allow(clippy::module_inception)]
mod rgb;

/// Non-linear sRGB, the most common RGB input/output format.
///
/// If you are looking for "just RGB", this is probably it. This type alias
/// helps by locking the more generic [`Rgb`] type to the sRGB format.
///
/// See [`Rgb`] for more details on how to create a value and use it.
pub type Srgb<T = f32> = Rgb<encoding::Srgb, T>;

/// Non-linear sRGB with an alpha component.
///
/// This is a transparent version of [`Srgb`], which is commonly used as the
/// input or output format. If you are looking for "just RGBA", this is probably
/// it.
///
/// See [`Rgb`], [`Rgba`] and [`Alpha`](crate::Alpha) for more details on how to
/// create a value and use it.
pub type Srgba<T = f32> = Rgba<encoding::Srgb, T>;

/// Linear sRGB.
///
/// You probably want [`Srgb`] if you are looking for an input or output format
/// (or "just RGB"). This is the linear version of sRGB, which is what you would
/// usually convert to before working with the color.
///
/// See [`Rgb`] for more details on how to create a value and use it.
#[doc(alias = "linear")]
pub type LinSrgb<T = f32> = Rgb<Linear<encoding::Srgb>, T>;

/// Linear sRGB with an alpha component.
///
/// You probably want [`Srgba`] if you are looking for an input or output format
/// (or "just RGB"). This is the linear version of sRGBA, which is what you
/// would usually convert to before working with the color.
///
/// See [`Rgb`], [`Rgba`] and [`Alpha`](crate::Alpha) for more details on how to
/// create a value and use it.
#[doc(alias = "linear")]
pub type LinSrgba<T = f32> = Rgba<Linear<encoding::Srgb>, T>;

/// Gamma 2.2 encoded sRGB.
///
/// This is similar to [`Srgb`], but uses the exponent function as an
/// approximation. It's a common trick to speed up conversion when accuracy can
/// be sacrificed. It's still faster to use `Srgb` when also converting to and
/// from `u8` at the same time.
///
/// See [`Rgb`] for more details on how to create a value and use it.
pub type GammaSrgb<T = f32> = Rgb<Gamma<encoding::Srgb>, T>;

/// Gamma 2.2 encoded sRGB with an alpha component.
///
/// This is similar to [`Srgba`], but uses the exponent function as an
/// approximation. It's a common trick to speed up conversion when accuracy can
/// be sacrificed. It's still faster to use `Srgba` when also converting to and
/// from `u8` at the same time.
///
/// See [`Rgb`], [`Rgba`] and [`Alpha`](crate::Alpha) for more details on how to
/// create a value and use it.
pub type GammaSrgba<T = f32> = Rgba<Gamma<encoding::Srgb>, T>;

/// An RGB space and a transfer function.
pub trait RgbStandard {
    /// The RGB color space.
    type Space: RgbSpace;

    /// The transfer function for the color components.
    type TransferFn;
}

impl<Sp, Tf> RgbStandard for (Sp, Tf)
where
    Sp: RgbSpace,
{
    type Space = Sp;
    type TransferFn = Tf;
}

impl<Pr, Wp, Tf> RgbStandard for (Pr, Wp, Tf)
where
    (Pr, Wp): RgbSpace,
{
    type Space = (Pr, Wp);
    type TransferFn = Tf;
}

/// A set of primaries and a white point.
pub trait RgbSpace {
    /// The primaries of the RGB color space.
    type Primaries;

    /// The white point of the RGB color space.
    type WhitePoint;

    /// Get a pre-defined matrix for converting an RGB value with this standard
    /// into an XYZ value.
    ///
    /// Returning `None` (as in the default implementation) means that the
    /// matrix will be computed dynamically, which is significantly slower.
    #[inline(always)]
    fn rgb_to_xyz_matrix() -> Option<Mat3<f64>> {
        None
    }

    /// Get a pre-defined matrix for converting an XYZ value into an RGB value
    /// with this standard.
    ///
    /// Returning `None` (as in the default implementation) means that the
    /// matrix will be computed dynamically, which is significantly slower.
    #[inline(always)]
    fn xyz_to_rgb_matrix() -> Option<Mat3<f64>> {
        None
    }
}

impl<P, W> RgbSpace for (P, W) {
    type Primaries = P;
    type WhitePoint = W;
}

/// Represents the red, green and blue primaries of an RGB space.
pub trait Primaries<T> {
    /// Primary red.
    fn red() -> Yxy<Any, T>;
    /// Primary green.
    fn green() -> Yxy<Any, T>;
    /// Primary blue.
    fn blue() -> Yxy<Any, T>;
}

impl<T, U> From<LinSrgb<T>> for Srgb<U>
where
    crate::encoding::Srgb: RgbStandard<Space = crate::encoding::Srgb> + FromLinear<T, U>,
{
    #[inline]
    fn from(lin_srgb: LinSrgb<T>) -> Self {
        lin_srgb.into_encoding()
    }
}

impl<T, U> From<Srgb<T>> for LinSrgb<U>
where
    crate::encoding::Srgb: RgbStandard<Space = crate::encoding::Srgb> + IntoLinear<U, T>,
{
    #[inline]
    fn from(srgb: Srgb<T>) -> Self {
        srgb.into_linear()
    }
}

impl<T, U> From<LinSrgb<T>> for Srgba<U>
where
    U: Stimulus,
    crate::encoding::Srgb: RgbStandard<Space = crate::encoding::Srgb> + FromLinear<T, U>,
{
    #[inline]
    fn from(lin_srgb: LinSrgb<T>) -> Self {
        let non_lin = Srgb::from_linear(lin_srgb);
        non_lin.into()
    }
}

impl<T, U> From<LinSrgba<T>> for Srgba<U>
where
    U: FromStimulus<T>,
    crate::encoding::Srgb: RgbStandard<Space = crate::encoding::Srgb> + FromLinear<T, U>,
{
    #[inline]
    fn from(lin_srgba: LinSrgba<T>) -> Self {
        Srgba::from_linear(lin_srgba)
    }
}

impl<T, U> From<Srgb<T>> for LinSrgba<U>
where
    U: Stimulus,
    crate::encoding::Srgb: RgbStandard<Space = crate::encoding::Srgb> + IntoLinear<U, T>,
{
    #[inline]
    fn from(srgb: Srgb<T>) -> Self {
        srgb.into_linear().into()
    }
}

impl<T, U> From<Srgba<T>> for LinSrgba<U>
where
    U: FromStimulus<T>,
    crate::encoding::Srgb: RgbStandard<Space = crate::encoding::Srgb> + IntoLinear<U, T>,
{
    #[inline]
    fn from(srgba: Srgba<T>) -> Self {
        srgba.into_linear()
    }
}

/// A packed representation of RGBA in RGBA order.
pub type PackedRgba<P = u32> = crate::cast::Packed<channels::Rgba, P>;

/// A packed representation of RGBA in ARGB order.
pub type PackedArgb<P = u32> = crate::cast::Packed<channels::Argb, P>;

/// A packed representation of RGBA in BGRA order.
pub type PackedBgra<P = u32> = crate::cast::Packed<channels::Bgra, P>;

/// A packed representation of RGBA in ABGR order.
pub type PackedAbgr<P = u32> = crate::cast::Packed<channels::Abgr, P>;