tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

mod.rs (23230B)


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