Skip to main content

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