style/color/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Color support functions.
6
7/// cbindgen:ignore
8pub mod convert;
9
10mod color_function;
11pub mod component;
12pub mod mix;
13pub mod parsing;
14mod to_css;
15
16use self::parsing::ChannelKeyword;
17pub use color_function::*;
18use component::ColorComponent;
19use cssparser::color::PredefinedColorSpace;
20
21/// The 3 components that make up a color.  (Does not include the alpha component)
22#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
23#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
24#[repr(C)]
25pub struct ColorComponents(pub f32, pub f32, pub f32);
26
27impl ColorComponents {
28    /// Apply a function to each of the 3 components of the color.
29    #[must_use]
30    pub fn map(self, f: impl Fn(f32) -> f32) -> Self {
31        Self(f(self.0), f(self.1), f(self.2))
32    }
33}
34
35impl std::ops::Mul for ColorComponents {
36    type Output = Self;
37
38    fn mul(self, rhs: Self) -> Self::Output {
39        Self(self.0 * rhs.0, self.1 * rhs.1, self.2 * rhs.2)
40    }
41}
42
43impl std::ops::Div for ColorComponents {
44    type Output = Self;
45
46    fn div(self, rhs: Self) -> Self::Output {
47        Self(self.0 / rhs.0, self.1 / rhs.1, self.2 / rhs.2)
48    }
49}
50
51/// A color space representation in the CSS specification.
52///
53/// https://drafts.csswg.org/css-color-4/#typedef-color-space
54#[derive(
55    Clone,
56    Copy,
57    Debug,
58    Eq,
59    MallocSizeOf,
60    Parse,
61    PartialEq,
62    ToAnimatedValue,
63    ToComputedValue,
64    ToCss,
65    ToResolvedValue,
66    ToShmem,
67)]
68#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
69#[repr(u8)]
70pub enum ColorSpace {
71    /// A color specified in the sRGB color space with either the rgb/rgba(..)
72    /// functions or the newer color(srgb ..) function. If the color(..)
73    /// function is used, the AS_COLOR_FUNCTION flag will be set. Examples:
74    /// "color(srgb 0.691 0.139 0.259)", "rgb(176, 35, 66)"
75    Srgb = 0,
76    /// A color specified in the Hsl notation in the sRGB color space, e.g.
77    /// "hsl(289.18 93.136% 65.531%)"
78    /// https://drafts.csswg.org/css-color-4/#the-hsl-notation
79    Hsl,
80    /// A color specified in the Hwb notation in the sRGB color space, e.g.
81    /// "hwb(740deg 20% 30%)"
82    /// https://drafts.csswg.org/css-color-4/#the-hwb-notation
83    Hwb,
84    /// A color specified in the Lab color format, e.g.
85    /// "lab(29.2345% 39.3825 20.0664)".
86    /// https://w3c.github.io/csswg-drafts/css-color-4/#lab-colors
87    Lab,
88    /// A color specified in the Lch color format, e.g.
89    /// "lch(29.2345% 44.2 27)".
90    /// https://w3c.github.io/csswg-drafts/css-color-4/#lch-colors
91    Lch,
92    /// A color specified in the Oklab color format, e.g.
93    /// "oklab(40.101% 0.1147 0.0453)".
94    /// https://w3c.github.io/csswg-drafts/css-color-4/#lab-colors
95    Oklab,
96    /// A color specified in the Oklch color format, e.g.
97    /// "oklch(40.101% 0.12332 21.555)".
98    /// https://w3c.github.io/csswg-drafts/css-color-4/#lch-colors
99    Oklch,
100    /// A color specified with the color(..) function and the "srgb-linear"
101    /// color space, e.g. "color(srgb-linear 0.435 0.017 0.055)".
102    SrgbLinear,
103    /// A color specified with the color(..) function and the "display-p3"
104    /// color space, e.g. "color(display-p3 0.84 0.19 0.72)".
105    DisplayP3,
106    /// A color specified with the color(..) function and the "display-p3-linear"
107    /// color space.
108    DisplayP3Linear,
109    /// A color specified with the color(..) function and the "a98-rgb" color
110    /// space, e.g. "color(a98-rgb 0.44091 0.49971 0.37408)".
111    A98Rgb,
112    /// A color specified with the color(..) function and the "prophoto-rgb"
113    /// color space, e.g. "color(prophoto-rgb 0.36589 0.41717 0.31333)".
114    ProphotoRgb,
115    /// A color specified with the color(..) function and the "rec2020" color
116    /// space, e.g. "color(rec2020 0.42210 0.47580 0.35605)".
117    Rec2020,
118    /// A color specified with the color(..) function and the "xyz-d50" color
119    /// space, e.g. "color(xyz-d50 0.2005 0.14089 0.4472)".
120    XyzD50,
121    /// A color specified with the color(..) function and the "xyz-d65" or "xyz"
122    /// color space, e.g. "color(xyz-d65 0.21661 0.14602 0.59452)".
123    /// NOTE: https://drafts.csswg.org/css-color-4/#resolving-color-function-values
124    ///       specifies that `xyz` is an alias for the `xyz-d65` color space.
125    #[parse(aliases = "xyz")]
126    XyzD65,
127}
128
129impl ColorSpace {
130    /// Returns whether this is a `<rectangular-color-space>`.
131    #[inline]
132    pub fn is_rectangular(&self) -> bool {
133        !self.is_polar()
134    }
135
136    /// Returns whether this is a `<polar-color-space>`.
137    #[inline]
138    pub fn is_polar(&self) -> bool {
139        matches!(self, Self::Hsl | Self::Hwb | Self::Lch | Self::Oklch)
140    }
141
142    /// Returns true if the color has RGB or XYZ components.
143    #[inline]
144    pub fn is_rgb_or_xyz_like(&self) -> bool {
145        match self {
146            Self::Srgb
147            | Self::SrgbLinear
148            | Self::DisplayP3
149            | Self::DisplayP3Linear
150            | Self::A98Rgb
151            | Self::ProphotoRgb
152            | Self::Rec2020
153            | Self::XyzD50
154            | Self::XyzD65 => true,
155            _ => false,
156        }
157    }
158
159    /// Returns an index of the hue component in the color space, otherwise
160    /// `None`.
161    #[inline]
162    pub fn hue_index(&self) -> Option<usize> {
163        match self {
164            Self::Hsl | Self::Hwb => Some(0),
165            Self::Lch | Self::Oklch => Some(2),
166
167            _ => {
168                debug_assert!(!self.is_polar());
169                None
170            },
171        }
172    }
173}
174
175/// Flags used when serializing colors.
176#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
177#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
178#[repr(C)]
179pub struct ColorFlags(u8);
180bitflags! {
181    impl ColorFlags : u8 {
182        /// Whether the 1st color component is `none`.
183        const C0_IS_NONE = 1 << 0;
184        /// Whether the 2nd color component is `none`.
185        const C1_IS_NONE = 1 << 1;
186        /// Whether the 3rd color component is `none`.
187        const C2_IS_NONE = 1 << 2;
188        /// Whether the alpha component is `none`.
189        const ALPHA_IS_NONE = 1 << 3;
190        /// Marks that this color is in the legacy color format. This flag is
191        /// only valid for the `Srgb` color space.
192        const IS_LEGACY_SRGB = 1 << 4;
193    }
194}
195
196/// An absolutely specified color, using either rgb(), rgba(), lab(), lch(),
197/// oklab(), oklch() or color().
198#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, ToShmem, ToTyped)]
199#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
200#[repr(C)]
201pub struct AbsoluteColor {
202    /// The 3 components that make up colors in any color space.
203    pub components: ColorComponents,
204    /// The alpha component of the color.
205    pub alpha: f32,
206    /// The current color space that the components represent.
207    pub color_space: ColorSpace,
208    /// Extra flags used durring serialization of this color.
209    pub flags: ColorFlags,
210}
211
212/// Given an [`AbsoluteColor`], return the 4 float components as the type given,
213/// e.g.:
214///
215/// ```rust
216/// let srgb = AbsoluteColor::new(ColorSpace::Srgb, 1.0, 0.0, 0.0, 0.0);
217/// let floats = color_components_as!(&srgb, [f32; 4]); // [1.0, 0.0, 0.0, 0.0]
218/// ```
219macro_rules! color_components_as {
220    ($c:expr, $t:ty) => {{
221        // This macro is not an inline function, because we can't use the
222        // generic  type ($t) in a constant expression as per:
223        // https://github.com/rust-lang/rust/issues/76560
224        const_assert_eq!(std::mem::size_of::<$t>(), std::mem::size_of::<[f32; 4]>());
225        const_assert_eq!(std::mem::align_of::<$t>(), std::mem::align_of::<[f32; 4]>());
226        const_assert!(std::mem::size_of::<AbsoluteColor>() >= std::mem::size_of::<$t>());
227        const_assert_eq!(
228            std::mem::align_of::<AbsoluteColor>(),
229            std::mem::align_of::<$t>()
230        );
231
232        std::mem::transmute::<&ColorComponents, &$t>(&$c.components)
233    }};
234}
235
236/// Holds details about each component passed into creating a new [`AbsoluteColor`].
237pub struct ComponentDetails {
238    value: f32,
239    is_none: bool,
240}
241
242impl From<f32> for ComponentDetails {
243    fn from(value: f32) -> Self {
244        Self {
245            value,
246            is_none: false,
247        }
248    }
249}
250
251impl From<u8> for ComponentDetails {
252    fn from(value: u8) -> Self {
253        Self {
254            value: value as f32 / 255.0,
255            is_none: false,
256        }
257    }
258}
259
260impl From<Option<f32>> for ComponentDetails {
261    fn from(value: Option<f32>) -> Self {
262        if let Some(value) = value {
263            Self {
264                value,
265                is_none: false,
266            }
267        } else {
268            Self {
269                value: 0.0,
270                is_none: true,
271            }
272        }
273    }
274}
275
276impl From<ColorComponent<f32>> for ComponentDetails {
277    fn from(value: ColorComponent<f32>) -> Self {
278        if let ColorComponent::Value(value) = value {
279            Self {
280                value,
281                is_none: false,
282            }
283        } else {
284            Self {
285                value: 0.0,
286                is_none: true,
287            }
288        }
289    }
290}
291
292impl AbsoluteColor {
293    /// A fully transparent color in the legacy syntax.
294    pub const TRANSPARENT_BLACK: Self = Self {
295        components: ColorComponents(0.0, 0.0, 0.0),
296        alpha: 0.0,
297        color_space: ColorSpace::Srgb,
298        flags: ColorFlags::IS_LEGACY_SRGB,
299    };
300
301    /// An opaque black color in the legacy syntax.
302    pub const BLACK: Self = Self {
303        components: ColorComponents(0.0, 0.0, 0.0),
304        alpha: 1.0,
305        color_space: ColorSpace::Srgb,
306        flags: ColorFlags::IS_LEGACY_SRGB,
307    };
308
309    /// An opaque white color in the legacy syntax.
310    pub const WHITE: Self = Self {
311        components: ColorComponents(1.0, 1.0, 1.0),
312        alpha: 1.0,
313        color_space: ColorSpace::Srgb,
314        flags: ColorFlags::IS_LEGACY_SRGB,
315    };
316
317    /// Create a new [`AbsoluteColor`] with the given [`ColorSpace`] and
318    /// components.
319    pub fn new(
320        color_space: ColorSpace,
321        c1: impl Into<ComponentDetails>,
322        c2: impl Into<ComponentDetails>,
323        c3: impl Into<ComponentDetails>,
324        alpha: impl Into<ComponentDetails>,
325    ) -> Self {
326        let mut flags = ColorFlags::empty();
327
328        macro_rules! cd {
329            ($c:expr,$flag:expr) => {{
330                let component_details = $c.into();
331                if component_details.is_none {
332                    flags |= $flag;
333                }
334                component_details.value
335            }};
336        }
337
338        let mut components = ColorComponents(
339            cd!(c1, ColorFlags::C0_IS_NONE),
340            cd!(c2, ColorFlags::C1_IS_NONE),
341            cd!(c3, ColorFlags::C2_IS_NONE),
342        );
343
344        let alpha = cd!(alpha, ColorFlags::ALPHA_IS_NONE);
345
346        // Lightness for Lab and Lch is clamped to [0..100].
347        if matches!(color_space, ColorSpace::Lab | ColorSpace::Lch) {
348            components.0 = components.0.clamp(0.0, 100.0);
349        }
350
351        // Lightness for Oklab and Oklch is clamped to [0..1].
352        if matches!(color_space, ColorSpace::Oklab | ColorSpace::Oklch) {
353            components.0 = components.0.clamp(0.0, 1.0);
354        }
355
356        // Chroma must not be less than 0.
357        if matches!(color_space, ColorSpace::Lch | ColorSpace::Oklch) {
358            components.1 = components.1.max(0.0);
359        }
360
361        // Alpha is always clamped to [0..1].
362        let alpha = alpha.clamp(0.0, 1.0);
363
364        Self {
365            components,
366            alpha,
367            color_space,
368            flags,
369        }
370    }
371
372    /// Convert this color into the sRGB color space and set it to the legacy
373    /// syntax.
374    #[inline]
375    #[must_use]
376    pub fn into_srgb_legacy(self) -> Self {
377        let mut result = if !matches!(self.color_space, ColorSpace::Srgb) {
378            self.to_color_space(ColorSpace::Srgb)
379        } else {
380            self
381        };
382
383        // Explicitly set the flags to IS_LEGACY_SRGB only to clear out the
384        // *_IS_NONE flags, because the legacy syntax doesn't allow "none".
385        result.flags = ColorFlags::IS_LEGACY_SRGB;
386
387        result
388    }
389
390    /// Create a new [`AbsoluteColor`] from rgba legacy syntax values in the sRGB color space.
391    pub fn srgb_legacy(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
392        let mut result = Self::new(ColorSpace::Srgb, red, green, blue, alpha);
393        result.flags = ColorFlags::IS_LEGACY_SRGB;
394        result
395    }
396
397    /// Return all the components of the color in an array.  (Includes alpha)
398    #[inline]
399    pub fn raw_components(&self) -> &[f32; 4] {
400        unsafe { color_components_as!(self, [f32; 4]) }
401    }
402
403    /// Returns true if this color is in the legacy color syntax.
404    #[inline]
405    pub fn is_legacy_syntax(&self) -> bool {
406        // rgb(), rgba(), hsl(), hsla(), hwb(), hwba()
407        match self.color_space {
408            ColorSpace::Srgb => self.flags.contains(ColorFlags::IS_LEGACY_SRGB),
409            ColorSpace::Hsl | ColorSpace::Hwb => true,
410            _ => false,
411        }
412    }
413
414    /// Returns true if this color is fully transparent.
415    #[inline]
416    pub fn is_transparent(&self) -> bool {
417        self.flags.contains(ColorFlags::ALPHA_IS_NONE) || self.alpha == 0.0
418    }
419
420    /// Return an optional first component.
421    #[inline]
422    pub fn c0(&self) -> Option<f32> {
423        if self.flags.contains(ColorFlags::C0_IS_NONE) {
424            None
425        } else {
426            Some(self.components.0)
427        }
428    }
429
430    /// Return an optional second component.
431    #[inline]
432    pub fn c1(&self) -> Option<f32> {
433        if self.flags.contains(ColorFlags::C1_IS_NONE) {
434            None
435        } else {
436            Some(self.components.1)
437        }
438    }
439
440    /// Return an optional second component.
441    #[inline]
442    pub fn c2(&self) -> Option<f32> {
443        if self.flags.contains(ColorFlags::C2_IS_NONE) {
444            None
445        } else {
446            Some(self.components.2)
447        }
448    }
449
450    /// Return an optional alpha component.
451    #[inline]
452    pub fn alpha(&self) -> Option<f32> {
453        if self.flags.contains(ColorFlags::ALPHA_IS_NONE) {
454            None
455        } else {
456            Some(self.alpha)
457        }
458    }
459
460    /// Return the value of a component by its channel keyword.
461    pub fn get_component_by_channel_keyword(
462        &self,
463        channel_keyword: ChannelKeyword,
464    ) -> Result<Option<f32>, ()> {
465        if channel_keyword == ChannelKeyword::Alpha {
466            return Ok(self.alpha());
467        }
468
469        Ok(match self.color_space {
470            ColorSpace::Srgb => {
471                if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) {
472                    match channel_keyword {
473                        ChannelKeyword::R => self.c0().map(|v| v * 255.0),
474                        ChannelKeyword::G => self.c1().map(|v| v * 255.0),
475                        ChannelKeyword::B => self.c2().map(|v| v * 255.0),
476                        _ => return Err(()),
477                    }
478                } else {
479                    match channel_keyword {
480                        ChannelKeyword::R => self.c0(),
481                        ChannelKeyword::G => self.c1(),
482                        ChannelKeyword::B => self.c2(),
483                        _ => return Err(()),
484                    }
485                }
486            },
487            ColorSpace::Hsl => match channel_keyword {
488                ChannelKeyword::H => self.c0(),
489                ChannelKeyword::S => self.c1(),
490                ChannelKeyword::L => self.c2(),
491                _ => return Err(()),
492            },
493            ColorSpace::Hwb => match channel_keyword {
494                ChannelKeyword::H => self.c0(),
495                ChannelKeyword::W => self.c1(),
496                ChannelKeyword::B => self.c2(),
497                _ => return Err(()),
498            },
499            ColorSpace::Lab | ColorSpace::Oklab => match channel_keyword {
500                ChannelKeyword::L => self.c0(),
501                ChannelKeyword::A => self.c1(),
502                ChannelKeyword::B => self.c2(),
503                _ => return Err(()),
504            },
505            ColorSpace::Lch | ColorSpace::Oklch => match channel_keyword {
506                ChannelKeyword::L => self.c0(),
507                ChannelKeyword::C => self.c1(),
508                ChannelKeyword::H => self.c2(),
509                _ => return Err(()),
510            },
511            ColorSpace::SrgbLinear
512            | ColorSpace::DisplayP3
513            | ColorSpace::DisplayP3Linear
514            | ColorSpace::A98Rgb
515            | ColorSpace::ProphotoRgb
516            | ColorSpace::Rec2020 => match channel_keyword {
517                ChannelKeyword::R => self.c0(),
518                ChannelKeyword::G => self.c1(),
519                ChannelKeyword::B => self.c2(),
520                _ => return Err(()),
521            },
522            ColorSpace::XyzD50 | ColorSpace::XyzD65 => match channel_keyword {
523                ChannelKeyword::X => self.c0(),
524                ChannelKeyword::Y => self.c1(),
525                ChannelKeyword::Z => self.c2(),
526                _ => return Err(()),
527            },
528        })
529    }
530
531    /// Convert this color to the specified color space.
532    pub fn to_color_space(&self, color_space: ColorSpace) -> Self {
533        use ColorSpace::*;
534
535        if self.color_space == color_space {
536            return self.clone();
537        }
538
539        // Conversion functions doesn't handle NAN component values, so they are
540        // converted to 0.0. They do however need to know if a component is
541        // missing, so we use NAN as the marker for that.
542        macro_rules! missing_to_nan {
543            ($c:expr) => {{
544                if let Some(v) = $c {
545                    crate::values::normalize(v)
546                } else {
547                    f32::NAN
548                }
549            }};
550        }
551
552        let components = ColorComponents(
553            missing_to_nan!(self.c0()),
554            missing_to_nan!(self.c1()),
555            missing_to_nan!(self.c2()),
556        );
557
558        let result = match (self.color_space, color_space) {
559            // We have simplified conversions that do not need to convert to XYZ
560            // first. This improves performance, because it skips at least 2
561            // matrix multiplications and reduces float rounding errors.
562            (Srgb, Hsl) => convert::rgb_to_hsl(&components),
563            (Srgb, Hwb) => convert::rgb_to_hwb(&components),
564            (Hsl, Srgb) => convert::hsl_to_rgb(&components),
565            (Hwb, Srgb) => convert::hwb_to_rgb(&components),
566            (Lab, Lch) | (Oklab, Oklch) => convert::orthogonal_to_polar(
567                &components,
568                convert::epsilon_for_range(0.0, if color_space == Lch { 100.0 } else { 1.0 }),
569            ),
570            (Lch, Lab) | (Oklch, Oklab) => convert::polar_to_orthogonal(&components),
571
572            // All other conversions need to convert to XYZ first.
573            _ => {
574                let (xyz, white_point) = match self.color_space {
575                    Lab => convert::to_xyz::<convert::Lab>(&components),
576                    Lch => convert::to_xyz::<convert::Lch>(&components),
577                    Oklab => convert::to_xyz::<convert::Oklab>(&components),
578                    Oklch => convert::to_xyz::<convert::Oklch>(&components),
579                    Srgb => convert::to_xyz::<convert::Srgb>(&components),
580                    Hsl => convert::to_xyz::<convert::Hsl>(&components),
581                    Hwb => convert::to_xyz::<convert::Hwb>(&components),
582                    SrgbLinear => convert::to_xyz::<convert::SrgbLinear>(&components),
583                    DisplayP3 => convert::to_xyz::<convert::DisplayP3>(&components),
584                    DisplayP3Linear => convert::to_xyz::<convert::DisplayP3Linear>(&components),
585                    A98Rgb => convert::to_xyz::<convert::A98Rgb>(&components),
586                    ProphotoRgb => convert::to_xyz::<convert::ProphotoRgb>(&components),
587                    Rec2020 => convert::to_xyz::<convert::Rec2020>(&components),
588                    XyzD50 => convert::to_xyz::<convert::XyzD50>(&components),
589                    XyzD65 => convert::to_xyz::<convert::XyzD65>(&components),
590                };
591
592                match color_space {
593                    Lab => convert::from_xyz::<convert::Lab>(&xyz, white_point),
594                    Lch => convert::from_xyz::<convert::Lch>(&xyz, white_point),
595                    Oklab => convert::from_xyz::<convert::Oklab>(&xyz, white_point),
596                    Oklch => convert::from_xyz::<convert::Oklch>(&xyz, white_point),
597                    Srgb => convert::from_xyz::<convert::Srgb>(&xyz, white_point),
598                    Hsl => convert::from_xyz::<convert::Hsl>(&xyz, white_point),
599                    Hwb => convert::from_xyz::<convert::Hwb>(&xyz, white_point),
600                    SrgbLinear => convert::from_xyz::<convert::SrgbLinear>(&xyz, white_point),
601                    DisplayP3 => convert::from_xyz::<convert::DisplayP3>(&xyz, white_point),
602                    DisplayP3Linear => {
603                        convert::from_xyz::<convert::DisplayP3Linear>(&xyz, white_point)
604                    },
605                    A98Rgb => convert::from_xyz::<convert::A98Rgb>(&xyz, white_point),
606                    ProphotoRgb => convert::from_xyz::<convert::ProphotoRgb>(&xyz, white_point),
607                    Rec2020 => convert::from_xyz::<convert::Rec2020>(&xyz, white_point),
608                    XyzD50 => convert::from_xyz::<convert::XyzD50>(&xyz, white_point),
609                    XyzD65 => convert::from_xyz::<convert::XyzD65>(&xyz, white_point),
610                }
611            },
612        };
613
614        // A NAN value coming from a conversion function means the the component
615        // is missing, so we convert it to None.
616        macro_rules! nan_to_missing {
617            ($v:expr) => {{
618                if $v.is_nan() {
619                    None
620                } else {
621                    Some($v)
622                }
623            }};
624        }
625
626        Self::new(
627            color_space,
628            nan_to_missing!(result.0),
629            nan_to_missing!(result.1),
630            nan_to_missing!(result.2),
631            self.alpha(),
632        )
633    }
634}
635
636impl From<PredefinedColorSpace> for ColorSpace {
637    fn from(value: PredefinedColorSpace) -> Self {
638        match value {
639            PredefinedColorSpace::Srgb => ColorSpace::Srgb,
640            PredefinedColorSpace::SrgbLinear => ColorSpace::SrgbLinear,
641            PredefinedColorSpace::DisplayP3 => ColorSpace::DisplayP3,
642            PredefinedColorSpace::DisplayP3Linear => ColorSpace::DisplayP3Linear,
643            PredefinedColorSpace::A98Rgb => ColorSpace::A98Rgb,
644            PredefinedColorSpace::ProphotoRgb => ColorSpace::ProphotoRgb,
645            PredefinedColorSpace::Rec2020 => ColorSpace::Rec2020,
646            PredefinedColorSpace::XyzD50 => ColorSpace::XyzD50,
647            PredefinedColorSpace::XyzD65 => ColorSpace::XyzD65,
648        }
649    }
650}