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