color/
tag.rs

1// Copyright 2024 the Color Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! The color space tag enum.
5
6use crate::{
7    A98Rgb, Aces2065_1, AcesCg, Chromaticity, ColorSpace, ColorSpaceLayout, DisplayP3, Hsl, Hwb,
8    Lab, Lch, LinearSrgb, Missing, Oklab, Oklch, ProphotoRgb, Rec2020, Srgb, XyzD50, XyzD65,
9};
10
11/// The color space tag for [dynamic colors].
12///
13/// This represents a fixed set of known color spaces. The set contains all
14/// color spaces in the CSS Color 4 spec and includes some other color spaces
15/// useful for computer graphics.
16///
17/// The integer values of these variants can change in breaking releases.
18///
19/// [dynamic colors]: crate::DynamicColor
20//
21// Note: when adding an RGB-like color space, add to `same_analogous`.
22#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
23#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
24#[non_exhaustive]
25#[repr(u8)]
26pub enum ColorSpaceTag {
27    /// The [`Srgb`] color space.
28    Srgb = 0,
29    /// The [`LinearSrgb`] color space.
30    LinearSrgb = 1,
31    /// The [`Lab`] color space.
32    Lab = 2,
33    /// The [`Lch`] color space.
34    Lch = 3,
35    /// The [`Hsl`] color space.
36    Hsl = 4,
37    /// The [`Hwb`] color space.
38    Hwb = 5,
39    /// The [`Oklab`] color space.
40    Oklab = 6,
41    /// The [`Oklch`] color space.
42    Oklch = 7,
43    /// The [`DisplayP3`] color space.
44    DisplayP3 = 8,
45    /// The [`A98Rgb`] color space.
46    A98Rgb = 9,
47    /// The [`ProphotoRgb`] color space.
48    ProphotoRgb = 10,
49    /// The [`Rec2020`] color space.
50    Rec2020 = 11,
51    /// The [`Aces2065_1`] color space.
52    Aces2065_1 = 15,
53    /// The [`AcesCg`] color space.
54    AcesCg = 12,
55    /// The [`XyzD50`] color space.
56    XyzD50 = 13,
57    /// The [`XyzD65`] color space.
58    XyzD65 = 14,
59    // NOTICE: If a new value is added, be sure to modify `MAX_VALUE` in the bytemuck impl. Also
60    // note the variants' integer values are not necessarily in order, allowing newly added color
61    // space tags to be grouped with related color spaces.
62}
63
64impl ColorSpaceTag {
65    pub(crate) fn layout(self) -> ColorSpaceLayout {
66        match self {
67            Self::Lch | Self::Oklch => ColorSpaceLayout::HueThird,
68            Self::Hsl | Self::Hwb => ColorSpaceLayout::HueFirst,
69            _ => ColorSpaceLayout::Rectangular,
70        }
71    }
72
73    /// Whether all components of the two color spaces are analogous. See also
74    /// Section 12.2 of CSS Color 4, defining which components are analogous:
75    /// <https://www.w3.org/TR/2024/CRD-css-color-4-20240213/#interpolation-missing>.
76    ///
77    /// Note: if color spaces are the same, then they're also analogous, but
78    /// in that case we wouldn't do the conversion, so this function is not
79    /// guaranteed to return the correct answer in those cases.
80    pub(crate) fn same_analogous(self, other: Self) -> bool {
81        use ColorSpaceTag::*;
82        matches!(
83            (self, other),
84            (
85                Srgb | LinearSrgb
86                    | DisplayP3
87                    | A98Rgb
88                    | ProphotoRgb
89                    | Rec2020
90                    | Aces2065_1
91                    | AcesCg
92                    | XyzD50
93                    | XyzD65,
94                Srgb | LinearSrgb
95                    | DisplayP3
96                    | A98Rgb
97                    | ProphotoRgb
98                    | Rec2020
99                    | Aces2065_1
100                    | AcesCg
101                    | XyzD50
102                    | XyzD65
103            ) | (Lab | Oklab, Lab | Oklab)
104                | (Lch | Oklch, Lch | Oklch)
105        )
106    }
107
108    pub(crate) fn l_missing(self, missing: Missing) -> bool {
109        use ColorSpaceTag::*;
110        match self {
111            Lab | Lch | Oklab | Oklch => missing.contains(0),
112            Hsl => missing.contains(2),
113            _ => false,
114        }
115    }
116
117    pub(crate) fn set_l_missing(self, missing: &mut Missing, components: &mut [f32; 4]) {
118        use ColorSpaceTag::*;
119        match self {
120            Lab | Lch | Oklab | Oklch => {
121                missing.insert(0);
122                components[0] = 0.0;
123            }
124            Hsl => {
125                missing.insert(2);
126                components[2] = 0.0;
127            }
128            _ => (),
129        }
130    }
131
132    pub(crate) fn c_missing(self, missing: Missing) -> bool {
133        use ColorSpaceTag::*;
134        match self {
135            Lab | Lch | Oklab | Oklch | Hsl => missing.contains(1),
136            _ => false,
137        }
138    }
139
140    pub(crate) fn set_c_missing(self, missing: &mut Missing, components: &mut [f32; 4]) {
141        use ColorSpaceTag::*;
142        match self {
143            Lab | Lch | Oklab | Oklch | Hsl => {
144                missing.insert(1);
145                components[1] = 0.0;
146            }
147            _ => (),
148        }
149    }
150
151    pub(crate) fn h_missing(self, missing: Missing) -> bool {
152        self.layout()
153            .hue_channel()
154            .is_some_and(|ix| missing.contains(ix))
155    }
156
157    pub(crate) fn set_h_missing(self, missing: &mut Missing, components: &mut [f32; 4]) {
158        if let Some(ix) = self.layout().hue_channel() {
159            missing.insert(ix);
160            components[ix] = 0.0;
161        }
162    }
163
164    /// Convert an opaque color from linear sRGB.
165    ///
166    /// This is the tagged counterpart of [`ColorSpace::from_linear_srgb`].
167    pub fn from_linear_srgb(self, rgb: [f32; 3]) -> [f32; 3] {
168        match self {
169            Self::Srgb => Srgb::from_linear_srgb(rgb),
170            Self::LinearSrgb => rgb,
171            Self::Lab => Lab::from_linear_srgb(rgb),
172            Self::Lch => Lch::from_linear_srgb(rgb),
173            Self::Oklab => Oklab::from_linear_srgb(rgb),
174            Self::Oklch => Oklch::from_linear_srgb(rgb),
175            Self::DisplayP3 => DisplayP3::from_linear_srgb(rgb),
176            Self::A98Rgb => A98Rgb::from_linear_srgb(rgb),
177            Self::ProphotoRgb => ProphotoRgb::from_linear_srgb(rgb),
178            Self::Rec2020 => Rec2020::from_linear_srgb(rgb),
179            Self::Aces2065_1 => Aces2065_1::from_linear_srgb(rgb),
180            Self::AcesCg => AcesCg::from_linear_srgb(rgb),
181            Self::XyzD50 => XyzD50::from_linear_srgb(rgb),
182            Self::XyzD65 => XyzD65::from_linear_srgb(rgb),
183            Self::Hsl => Hsl::from_linear_srgb(rgb),
184            Self::Hwb => Hwb::from_linear_srgb(rgb),
185        }
186    }
187
188    /// Convert an opaque color to linear sRGB.
189    ///
190    /// This is the tagged counterpart of [`ColorSpace::to_linear_srgb`].
191    pub fn to_linear_srgb(self, src: [f32; 3]) -> [f32; 3] {
192        match self {
193            Self::Srgb => Srgb::to_linear_srgb(src),
194            Self::LinearSrgb => src,
195            Self::Lab => Lab::to_linear_srgb(src),
196            Self::Lch => Lch::to_linear_srgb(src),
197            Self::Oklab => Oklab::to_linear_srgb(src),
198            Self::Oklch => Oklch::to_linear_srgb(src),
199            Self::DisplayP3 => DisplayP3::to_linear_srgb(src),
200            Self::A98Rgb => A98Rgb::to_linear_srgb(src),
201            Self::ProphotoRgb => ProphotoRgb::to_linear_srgb(src),
202            Self::Rec2020 => Rec2020::to_linear_srgb(src),
203            Self::Aces2065_1 => Aces2065_1::to_linear_srgb(src),
204            Self::AcesCg => AcesCg::to_linear_srgb(src),
205            Self::XyzD50 => XyzD50::to_linear_srgb(src),
206            Self::XyzD65 => XyzD65::to_linear_srgb(src),
207            Self::Hsl => Hsl::to_linear_srgb(src),
208            Self::Hwb => Hwb::to_linear_srgb(src),
209        }
210    }
211
212    /// Convert the color components into the target color space.
213    ///
214    /// This is the tagged counterpart of [`ColorSpace::convert`].
215    pub fn convert(self, target: Self, src: [f32; 3]) -> [f32; 3] {
216        match (self, target) {
217            _ if self == target => src,
218            (Self::Oklab, Self::Oklch) | (Self::Lab, Self::Lch) => Oklab::convert::<Oklch>(src),
219            (Self::Oklch, Self::Oklab) | (Self::Lch, Self::Lab) => Oklch::convert::<Oklab>(src),
220            (Self::Srgb, Self::Hsl) => Srgb::convert::<Hsl>(src),
221            (Self::Hsl, Self::Srgb) => Hsl::convert::<Srgb>(src),
222            (Self::Srgb, Self::Hwb) => Srgb::convert::<Hwb>(src),
223            (Self::Hwb, Self::Srgb) => Hwb::convert::<Srgb>(src),
224            (Self::Hsl, Self::Hwb) => Hsl::convert::<Hwb>(src),
225            (Self::Hwb, Self::Hsl) => Hwb::convert::<Hsl>(src),
226            _ => target.from_linear_srgb(self.to_linear_srgb(src)),
227        }
228    }
229
230    /// Convert an opaque color from linear sRGB, without chromatic adaptation.
231    ///
232    /// For most use-cases you should consider using the chromatically-adapting
233    /// [`ColorSpaceTag::from_linear_srgb`] instead.
234    ///
235    /// This is the tagged counterpart of [`ColorSpace::from_linear_srgb_absolute`].
236    pub fn from_linear_srgb_absolute(self, rgb: [f32; 3]) -> [f32; 3] {
237        match self {
238            Self::Srgb => Srgb::from_linear_srgb_absolute(rgb),
239            Self::LinearSrgb => rgb,
240            Self::Lab => Lab::from_linear_srgb_absolute(rgb),
241            Self::Lch => Lch::from_linear_srgb_absolute(rgb),
242            Self::Oklab => Oklab::from_linear_srgb_absolute(rgb),
243            Self::Oklch => Oklch::from_linear_srgb_absolute(rgb),
244            Self::DisplayP3 => DisplayP3::from_linear_srgb_absolute(rgb),
245            Self::A98Rgb => A98Rgb::from_linear_srgb_absolute(rgb),
246            Self::ProphotoRgb => ProphotoRgb::from_linear_srgb_absolute(rgb),
247            Self::Rec2020 => Rec2020::from_linear_srgb_absolute(rgb),
248            Self::Aces2065_1 => Aces2065_1::from_linear_srgb_absolute(rgb),
249            Self::AcesCg => AcesCg::from_linear_srgb_absolute(rgb),
250            Self::XyzD50 => XyzD50::from_linear_srgb_absolute(rgb),
251            Self::XyzD65 => XyzD65::from_linear_srgb_absolute(rgb),
252            Self::Hsl => Hsl::from_linear_srgb_absolute(rgb),
253            Self::Hwb => Hwb::from_linear_srgb_absolute(rgb),
254        }
255    }
256
257    /// Convert an opaque color to linear sRGB, without chromatic adaptation.
258    ///
259    /// For most use-cases you should consider using the chromatically-adapting
260    /// [`ColorSpaceTag::to_linear_srgb`] instead.
261    ///
262    /// This is the tagged counterpart of [`ColorSpace::to_linear_srgb_absolute`].
263    pub fn to_linear_srgb_absolute(self, src: [f32; 3]) -> [f32; 3] {
264        match self {
265            Self::Srgb => Srgb::to_linear_srgb_absolute(src),
266            Self::LinearSrgb => src,
267            Self::Lab => Lab::to_linear_srgb_absolute(src),
268            Self::Lch => Lch::to_linear_srgb_absolute(src),
269            Self::Oklab => Oklab::to_linear_srgb_absolute(src),
270            Self::Oklch => Oklch::to_linear_srgb_absolute(src),
271            Self::DisplayP3 => DisplayP3::to_linear_srgb_absolute(src),
272            Self::A98Rgb => A98Rgb::to_linear_srgb_absolute(src),
273            Self::ProphotoRgb => ProphotoRgb::to_linear_srgb_absolute(src),
274            Self::Rec2020 => Rec2020::to_linear_srgb_absolute(src),
275            Self::Aces2065_1 => Aces2065_1::to_linear_srgb_absolute(src),
276            Self::AcesCg => AcesCg::to_linear_srgb_absolute(src),
277            Self::XyzD50 => XyzD50::to_linear_srgb_absolute(src),
278            Self::XyzD65 => XyzD65::to_linear_srgb_absolute(src),
279            Self::Hsl => Hsl::to_linear_srgb_absolute(src),
280            Self::Hwb => Hwb::to_linear_srgb_absolute(src),
281        }
282    }
283
284    /// Convert the color components into the target color space, without chromatic adaptation.
285    ///
286    /// For most use-cases you should consider using the chromatically-adapting
287    /// [`ColorSpaceTag::convert`] instead.
288    ///
289    /// This is the tagged counterpart of [`ColorSpace::convert_absolute`]. See the documentation
290    /// on [`ColorSpace::convert_absolute`] for more information.
291    pub fn convert_absolute(self, target: Self, src: [f32; 3]) -> [f32; 3] {
292        match (self, target) {
293            _ if self == target => src,
294            (Self::Oklab, Self::Oklch) | (Self::Lab, Self::Lch) => {
295                Oklab::convert_absolute::<Oklch>(src)
296            }
297            (Self::Oklch, Self::Oklab) | (Self::Lch, Self::Lab) => {
298                Oklch::convert_absolute::<Oklab>(src)
299            }
300            (Self::Srgb, Self::Hsl) => Srgb::convert_absolute::<Hsl>(src),
301            (Self::Hsl, Self::Srgb) => Hsl::convert_absolute::<Srgb>(src),
302            (Self::Srgb, Self::Hwb) => Srgb::convert_absolute::<Hwb>(src),
303            (Self::Hwb, Self::Srgb) => Hwb::convert_absolute::<Srgb>(src),
304            (Self::Hsl, Self::Hwb) => Hsl::convert_absolute::<Hwb>(src),
305            (Self::Hwb, Self::Hsl) => Hwb::convert_absolute::<Hsl>(src),
306            _ => target.from_linear_srgb_absolute(self.to_linear_srgb_absolute(src)),
307        }
308    }
309
310    /// Chromatically adapt the color between the given white point chromaticities.
311    ///
312    /// This is the tagged counterpart of [`ColorSpace::chromatically_adapt`].
313    ///
314    /// The color is assumed to be under a reference white point of `from` and is chromatically
315    /// adapted to the given white point `to`. The linear Bradford transform is used to perform the
316    /// chromatic adaptation.
317    pub fn chromatically_adapt(
318        self,
319        src: [f32; 3],
320        from: Chromaticity,
321        to: Chromaticity,
322    ) -> [f32; 3] {
323        match self {
324            Self::Srgb => Srgb::chromatically_adapt(src, from, to),
325            Self::LinearSrgb => LinearSrgb::chromatically_adapt(src, from, to),
326            Self::Lab => Lab::chromatically_adapt(src, from, to),
327            Self::Lch => Lch::chromatically_adapt(src, from, to),
328            Self::Oklab => Oklab::chromatically_adapt(src, from, to),
329            Self::Oklch => Oklch::chromatically_adapt(src, from, to),
330            Self::DisplayP3 => DisplayP3::chromatically_adapt(src, from, to),
331            Self::A98Rgb => A98Rgb::chromatically_adapt(src, from, to),
332            Self::ProphotoRgb => ProphotoRgb::chromatically_adapt(src, from, to),
333            Self::Rec2020 => Rec2020::chromatically_adapt(src, from, to),
334            Self::Aces2065_1 => Aces2065_1::chromatically_adapt(src, from, to),
335            Self::AcesCg => AcesCg::chromatically_adapt(src, from, to),
336            Self::XyzD50 => XyzD50::chromatically_adapt(src, from, to),
337            Self::XyzD65 => XyzD65::chromatically_adapt(src, from, to),
338            Self::Hsl => Hsl::chromatically_adapt(src, from, to),
339            Self::Hwb => Hwb::chromatically_adapt(src, from, to),
340        }
341    }
342
343    /// Scale the chroma by the given amount.
344    ///
345    /// This is the tagged counterpart of [`ColorSpace::scale_chroma`].
346    pub fn scale_chroma(self, src: [f32; 3], scale: f32) -> [f32; 3] {
347        match self {
348            Self::LinearSrgb => LinearSrgb::scale_chroma(src, scale),
349            Self::Oklab | Self::Lab => Oklab::scale_chroma(src, scale),
350            Self::Oklch | Self::Lch | Self::Hsl => Oklch::scale_chroma(src, scale),
351            _ => {
352                let rgb = self.to_linear_srgb(src);
353                let scaled = LinearSrgb::scale_chroma(rgb, scale);
354                self.from_linear_srgb(scaled)
355            }
356        }
357    }
358
359    /// Clip the color's components to fit within the natural gamut of the color space.
360    ///
361    /// See [`ColorSpace::clip`] for more details.
362    pub fn clip(self, src: [f32; 3]) -> [f32; 3] {
363        match self {
364            Self::Srgb => Srgb::clip(src),
365            Self::LinearSrgb => LinearSrgb::clip(src),
366            Self::Lab => Lab::clip(src),
367            Self::Lch => Lch::clip(src),
368            Self::Oklab => Oklab::clip(src),
369            Self::Oklch => Oklch::clip(src),
370            Self::DisplayP3 => DisplayP3::clip(src),
371            Self::A98Rgb => A98Rgb::clip(src),
372            Self::ProphotoRgb => ProphotoRgb::clip(src),
373            Self::Rec2020 => Rec2020::clip(src),
374            Self::Aces2065_1 => Aces2065_1::clip(src),
375            Self::AcesCg => AcesCg::clip(src),
376            Self::XyzD50 => XyzD50::clip(src),
377            Self::XyzD65 => XyzD65::clip(src),
378            Self::Hsl => Hsl::clip(src),
379            Self::Hwb => Hwb::clip(src),
380        }
381    }
382}