parcel_css/values/
color.rs

1//! CSS color values.
2
3use super::angle::Angle;
4use super::number::CSSNumber;
5use super::percentage::Percentage;
6use crate::compat::Feature;
7use crate::error::{ParserError, PrinterError};
8use crate::macros::enum_property;
9use crate::printer::Printer;
10use crate::rules::supports::SupportsCondition;
11use crate::targets::Browsers;
12use crate::traits::{FallbackValues, Parse, ToCss};
13use bitflags::bitflags;
14use cssparser::*;
15use std::any::TypeId;
16use std::f32::consts::PI;
17use std::fmt::Write;
18
19/// A CSS [`<color>`](https://www.w3.org/TR/css-color-4/#color-type) value.
20///
21/// CSS supports many different color spaces to represent colors. The most common values
22/// are stored as RGBA using a single byte per component. Less common values are stored
23/// using a `Box` to reduce the amount of memory used per color.
24///
25/// Each color space is represented as a struct that implements the `From` and `Into` traits
26/// for all other color spaces, so it is possible to convert between color spaces easily.
27/// In addition, colors support [interpolation](#method.interpolate) as in the `color-mix()` function.
28#[derive(Debug, Clone, PartialEq)]
29#[cfg_attr(
30  feature = "serde",
31  derive(serde::Serialize, serde::Deserialize),
32  serde(tag = "type", content = "value", rename_all = "lowercase")
33)]
34pub enum CssColor {
35  /// The [`currentColor`](https://www.w3.org/TR/css-color-4/#currentcolor-color) keyword.
36  CurrentColor,
37  /// An value in the RGB color space, including values parsed as hex colors, or the `rgb()`, `hsl()`, and `hwb()` functions.
38  RGBA(RGBA),
39  /// A value in a LAB color space, including the `lab()`, `lch()`, `oklab()`, and `oklch()` functions.
40  LAB(Box<LABColor>),
41  /// A value in a predefined color space, e.g. `display-p3`.
42  Predefined(Box<PredefinedColor>),
43  /// A floating point representation of an RGB, HSL, or HWB color when it contains `none` components.
44  Float(Box<FloatColor>),
45}
46
47/// A color in a LAB color space, including the `lab()`, `lch()`, `oklab()`, and `oklch()` functions.
48#[derive(Debug, Clone, Copy, PartialEq)]
49#[cfg_attr(
50  feature = "serde",
51  derive(serde::Serialize, serde::Deserialize),
52  serde(tag = "type", content = "value", rename_all = "lowercase")
53)]
54pub enum LABColor {
55  /// A `lab()` color.
56  LAB(LAB),
57  /// An `lch()` color.
58  LCH(LCH),
59  /// An `oklab()` color.
60  OKLAB(OKLAB),
61  /// An `oklch()` color.
62  OKLCH(OKLCH),
63}
64
65/// A color in a predefined color space, e.g. `display-p3`.
66#[derive(Debug, Clone, Copy, PartialEq)]
67#[cfg_attr(
68  feature = "serde",
69  derive(serde::Serialize, serde::Deserialize),
70  serde(tag = "type", content = "value")
71)]
72pub enum PredefinedColor {
73  /// A color in the `srgb` color space.
74  #[cfg_attr(feature = "serde", serde(rename = "srgb"))]
75  SRGB(SRGB),
76  /// A color in the `srgb-linear` color space.
77  #[cfg_attr(feature = "serde", serde(rename = "srgb-linear"))]
78  SRGBLinear(SRGBLinear),
79  /// A color in the `display-p3` color space.
80  #[cfg_attr(feature = "serde", serde(rename = "display-p3"))]
81  DisplayP3(P3),
82  /// A color in the `a98-rgb` color space.
83  #[cfg_attr(feature = "serde", serde(rename = "a98-rgb"))]
84  A98(A98),
85  /// A color in the `prophoto-rgb` color space.
86  #[cfg_attr(feature = "serde", serde(rename = "prophoto-rgb"))]
87  ProPhoto(ProPhoto),
88  /// A color in the `rec2020` color space.
89  #[cfg_attr(feature = "serde", serde(rename = "rec2020"))]
90  Rec2020(Rec2020),
91  /// A color in the `xyz-d50` color space.
92  #[cfg_attr(feature = "serde", serde(rename = "xyz-d50"))]
93  XYZd50(XYZd50),
94  /// A color in the `xyz-d65` color space.
95  #[cfg_attr(feature = "serde", serde(rename = "xyz-d65"))]
96  XYZd65(XYZd65),
97}
98
99/// A floating point representation of color types that
100/// are usually stored as RGBA. These are used when there
101/// are any `none` components, which are represented as NaN.
102#[derive(Debug, Clone, Copy, PartialEq)]
103#[cfg_attr(
104  feature = "serde",
105  derive(serde::Serialize, serde::Deserialize),
106  serde(tag = "type", content = "value", rename_all = "lowercase")
107)]
108pub enum FloatColor {
109  /// An RGB color.
110  RGB(SRGB),
111  /// An HSL color.
112  HSL(HSL),
113  /// An HWB color.
114  HWB(HWB),
115}
116
117bitflags! {
118  /// A color type that is used as a fallback when compiling colors for older browsers.
119  pub struct ColorFallbackKind: u8 {
120    /// An RGB color fallback.
121    const RGB    = 0b01;
122    /// A P3 color fallback.
123    const P3     = 0b10;
124    /// A LAB color fallback.
125    const LAB    = 0b100;
126    /// An OKLAB color fallback.
127    const OKLAB  = 0b1000;
128  }
129}
130
131enum_property! {
132  /// A [color space](https://www.w3.org/TR/css-color-4/#interpolation-space) keyword
133  /// used in interpolation functions such as `color-mix()`.
134  enum ColorSpace {
135    "srgb": SRGB,
136    "srgb-linear": SRGBLinear,
137    "lab": LAB,
138    "oklab": OKLAB,
139    "xyz": XYZ,
140    "xyz-d50": XYZd50,
141    "xyz-d65": XYZd65,
142    "hsl": Hsl,
143    "hwb": Hwb,
144    "lch": LCH,
145    "oklch": OKLCH,
146  }
147}
148
149enum_property! {
150  /// A hue [interpolation method](https://www.w3.org/TR/css-color-4/#typedef-hue-interpolation-method)
151  /// used in interpolation functions such as `color-mix()`.
152  pub enum HueInterpolationMethod {
153    /// Angles are adjusted so that θ₂ - θ₁ ∈ [-180, 180].
154    Shorter,
155    /// Angles are adjusted so that θ₂ - θ₁ ∈ {0, [180, 360)}.
156    Longer,
157    /// Angles are adjusted so that θ₂ - θ₁ ∈ [0, 360).
158    Increasing,
159    /// Angles are adjusted so that θ₂ - θ₁ ∈ (-360, 0].
160    Decreasing,
161    /// No fixup is performed. Angles are interpolated in the same way as every other component.
162    Specified,
163  }
164}
165
166impl ColorFallbackKind {
167  pub(crate) fn lowest(&self) -> ColorFallbackKind {
168    // This finds the lowest set bit.
169    *self & ColorFallbackKind::from_bits_truncate(self.bits().wrapping_neg())
170  }
171
172  pub(crate) fn highest(&self) -> ColorFallbackKind {
173    // This finds the highest set bit.
174    if self.is_empty() {
175      return ColorFallbackKind::empty();
176    }
177
178    let zeros = 7 - self.bits().leading_zeros();
179    ColorFallbackKind::from_bits_truncate(1 << zeros)
180  }
181
182  pub(crate) fn and_below(&self) -> ColorFallbackKind {
183    if self.is_empty() {
184      return ColorFallbackKind::empty();
185    }
186
187    *self | ColorFallbackKind::from_bits_truncate(self.bits() - 1)
188  }
189
190  pub(crate) fn supports_condition<'i>(&self) -> SupportsCondition<'i> {
191    let s = match *self {
192      ColorFallbackKind::P3 => "color: color(display-p3 0 0 0)",
193      ColorFallbackKind::LAB => "color: lab(0% 0 0)",
194      _ => unreachable!(),
195    };
196
197    SupportsCondition::Declaration(s.into())
198  }
199}
200
201impl CssColor {
202  /// Returns the `currentColor` keyword.
203  pub fn current_color() -> CssColor {
204    CssColor::CurrentColor
205  }
206
207  /// Returns the `transparent` keyword.
208  pub fn transparent() -> CssColor {
209    CssColor::RGBA(RGBA::transparent())
210  }
211
212  /// Converts the color to RGBA.
213  pub fn to_rgb(&self) -> CssColor {
214    RGBA::from(self).into()
215  }
216
217  /// Converts the color to the LAB color space.
218  pub fn to_lab(&self) -> CssColor {
219    LAB::from(self).into()
220  }
221
222  /// Converts the color to the P3 color space.
223  pub fn to_p3(&self) -> CssColor {
224    P3::from(self).into()
225  }
226
227  pub(crate) fn get_possible_fallbacks(&self, targets: Browsers) -> ColorFallbackKind {
228    // Fallbacks occur in levels: Oklab -> Lab -> P3 -> RGB. We start with all levels
229    // below and including the authored color space, and remove the ones that aren't
230    // compatible with our browser targets.
231    let mut fallbacks = match self {
232      CssColor::CurrentColor | CssColor::RGBA(_) | CssColor::Float(..) => return ColorFallbackKind::empty(),
233      CssColor::LAB(lab) => match &**lab {
234        LABColor::LAB(..) | LABColor::LCH(..) => ColorFallbackKind::LAB.and_below(),
235        LABColor::OKLAB(..) | LABColor::OKLCH(..) => ColorFallbackKind::OKLAB.and_below(),
236      },
237      CssColor::Predefined(predefined) => match &**predefined {
238        PredefinedColor::DisplayP3(..) => ColorFallbackKind::P3.and_below(),
239        _ => {
240          if Feature::ColorFunction.is_compatible(targets) {
241            return ColorFallbackKind::empty();
242          }
243
244          ColorFallbackKind::LAB.and_below()
245        }
246      },
247    };
248
249    if fallbacks.contains(ColorFallbackKind::OKLAB) {
250      if Feature::OklabColors.is_compatible(targets) {
251        fallbacks.remove(ColorFallbackKind::LAB.and_below());
252      }
253    }
254
255    if fallbacks.contains(ColorFallbackKind::LAB) {
256      if Feature::LabColors.is_compatible(targets) {
257        fallbacks.remove(ColorFallbackKind::P3.and_below());
258      } else if Feature::LabColors.is_partially_compatible(targets) {
259        // We don't need P3 if Lab is supported by some of our targets.
260        // No browser implements Lab but not P3.
261        fallbacks.remove(ColorFallbackKind::P3);
262      }
263    }
264
265    if fallbacks.contains(ColorFallbackKind::P3) {
266      if Feature::P3Colors.is_compatible(targets) {
267        fallbacks.remove(ColorFallbackKind::RGB);
268      } else if fallbacks.highest() != ColorFallbackKind::P3 && !Feature::P3Colors.is_partially_compatible(targets)
269      {
270        // Remove P3 if it isn't supported by any targets, and wasn't the
271        // original authored color.
272        fallbacks.remove(ColorFallbackKind::P3);
273      }
274    }
275
276    fallbacks
277  }
278
279  /// Returns the color fallback types needed for the given browser targets.
280  pub fn get_necessary_fallbacks(&self, targets: Browsers) -> ColorFallbackKind {
281    // Get the full set of possible fallbacks, and remove the highest one, which
282    // will replace the original declaration. The remaining fallbacks need to be added.
283    let fallbacks = self.get_possible_fallbacks(targets);
284    fallbacks - fallbacks.highest()
285  }
286
287  /// Returns a fallback color for the given fallback type.
288  pub fn get_fallback(&self, kind: ColorFallbackKind) -> CssColor {
289    if matches!(self, CssColor::RGBA(_)) {
290      return self.clone();
291    }
292
293    match kind {
294      ColorFallbackKind::RGB => self.to_rgb(),
295      ColorFallbackKind::P3 => self.to_p3(),
296      ColorFallbackKind::LAB => self.to_lab(),
297      _ => unreachable!(),
298    }
299  }
300}
301
302impl FallbackValues for CssColor {
303  fn get_fallbacks(&mut self, targets: Browsers) -> Vec<CssColor> {
304    let fallbacks = self.get_necessary_fallbacks(targets);
305
306    let mut res = Vec::new();
307    if fallbacks.contains(ColorFallbackKind::RGB) {
308      res.push(self.to_rgb());
309    }
310
311    if fallbacks.contains(ColorFallbackKind::P3) {
312      res.push(self.to_p3());
313    }
314
315    if fallbacks.contains(ColorFallbackKind::LAB) {
316      *self = self.to_lab();
317    }
318
319    res
320  }
321}
322
323impl Default for CssColor {
324  fn default() -> CssColor {
325    CssColor::transparent()
326  }
327}
328
329impl From<Color> for CssColor {
330  fn from(color: Color) -> Self {
331    match color {
332      Color::CurrentColor => CssColor::CurrentColor,
333      Color::RGBA(rgba) => CssColor::RGBA(rgba),
334    }
335  }
336}
337
338impl<'i> Parse<'i> for CssColor {
339  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
340    let parser = ComponentParser { allow_none: false };
341    if let Ok(color) = input.try_parse(|input| Color::parse_with(&parser, input)) {
342      return Ok(color.into());
343    }
344
345    parse_color_function(input)
346  }
347}
348
349impl ToCss for CssColor {
350  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
351  where
352    W: std::fmt::Write,
353  {
354    match self {
355      CssColor::CurrentColor => dest.write_str("currentColor"),
356      CssColor::RGBA(color) => {
357        if color.alpha == 255 {
358          let hex: u32 = ((color.red as u32) << 16) | ((color.green as u32) << 8) | (color.blue as u32);
359          if let Some(name) = short_color_name(hex) {
360            return dest.write_str(name);
361          }
362
363          let compact = compact_hex(hex);
364          if hex == expand_hex(compact) {
365            write!(dest, "#{:03x}", compact)?;
366          } else {
367            write!(dest, "#{:06x}", hex)?;
368          }
369        } else {
370          // If the #rrggbbaa syntax is not supported by the browser targets, output rgba()
371          if let Some(targets) = dest.targets {
372            if !Feature::CssRrggbbaa.is_compatible(targets) {
373              dest.write_str("rgba(")?;
374              write!(dest, "{}", color.red)?;
375              dest.delim(',', false)?;
376              write!(dest, "{}", color.green)?;
377              dest.delim(',', false)?;
378              write!(dest, "{}", color.blue)?;
379              dest.delim(',', false)?;
380
381              // Try first with two decimal places, then with three.
382              let mut rounded_alpha = (color.alpha_f32() * 100.0).round() / 100.0;
383              let clamped = (rounded_alpha * 255.0).round().max(0.).min(255.0) as u8;
384              if clamped != color.alpha {
385                rounded_alpha = (color.alpha_f32() * 1000.).round() / 1000.;
386              }
387
388              rounded_alpha.to_css(dest)?;
389              dest.write_char(')')?;
390              return Ok(());
391            }
392          }
393
394          let hex: u32 = ((color.red as u32) << 24)
395            | ((color.green as u32) << 16)
396            | ((color.blue as u32) << 8)
397            | (color.alpha as u32);
398          let compact = compact_hex(hex);
399          if hex == expand_hex(compact) {
400            write!(dest, "#{:04x}", compact)?;
401          } else {
402            write!(dest, "#{:08x}", hex)?;
403          }
404        }
405        Ok(())
406      }
407      CssColor::LAB(lab) => match &**lab {
408        LABColor::LAB(lab) => write_components("lab", lab.l, lab.a, lab.b, lab.alpha, dest),
409        LABColor::LCH(lch) => write_components("lch", lch.l, lch.c, lch.h, lch.alpha, dest),
410        LABColor::OKLAB(lab) => write_components("oklab", lab.l, lab.a, lab.b, lab.alpha, dest),
411        LABColor::OKLCH(lch) => write_components("oklch", lch.l, lch.c, lch.h, lch.alpha, dest),
412      },
413      CssColor::Predefined(predefined) => write_predefined(predefined, dest),
414      CssColor::Float(float) => {
415        // Serialize as hex.
416        let srgb = SRGB::from(**float);
417        CssColor::from(srgb).to_css(dest)
418      }
419    }
420  }
421}
422
423// From esbuild: https://github.com/evanw/esbuild/blob/18e13bdfdca5cd3c7a2fae1a8bd739f8f891572c/internal/css_parser/css_decls_color.go#L218
424// 0xAABBCCDD => 0xABCD
425fn compact_hex(v: u32) -> u32 {
426  return ((v & 0x0FF00000) >> 12) | ((v & 0x00000FF0) >> 4);
427}
428
429// 0xABCD => 0xAABBCCDD
430fn expand_hex(v: u32) -> u32 {
431  return ((v & 0xF000) << 16) | ((v & 0xFF00) << 12) | ((v & 0x0FF0) << 8) | ((v & 0x00FF) << 4) | (v & 0x000F);
432}
433
434fn short_color_name(v: u32) -> Option<&'static str> {
435  // These names are shorter than their hex codes
436  let s = match v {
437    0x000080 => "navy",
438    0x008000 => "green",
439    0x008080 => "teal",
440    0x4b0082 => "indigo",
441    0x800000 => "maroon",
442    0x800080 => "purple",
443    0x808000 => "olive",
444    0x808080 => "gray",
445    0xa0522d => "sienna",
446    0xa52a2a => "brown",
447    0xc0c0c0 => "silver",
448    0xcd853f => "peru",
449    0xd2b48c => "tan",
450    0xda70d6 => "orchid",
451    0xdda0dd => "plum",
452    0xee82ee => "violet",
453    0xf0e68c => "khaki",
454    0xf0ffff => "azure",
455    0xf5deb3 => "wheat",
456    0xf5f5dc => "beige",
457    0xfa8072 => "salmon",
458    0xfaf0e6 => "linen",
459    0xff0000 => "red",
460    0xff6347 => "tomato",
461    0xff7f50 => "coral",
462    0xffa500 => "orange",
463    0xffc0cb => "pink",
464    0xffd700 => "gold",
465    0xffe4c4 => "bisque",
466    0xfffafa => "snow",
467    0xfffff0 => "ivory",
468    _ => return None,
469  };
470
471  Some(s)
472}
473
474pub(crate) struct ComponentParser {
475  pub allow_none: bool,
476}
477
478impl<'i> ColorComponentParser<'i> for ComponentParser {
479  type Error = ParserError<'i>;
480
481  fn parse_angle_or_number<'t>(
482    &self,
483    input: &mut Parser<'i, 't>,
484  ) -> Result<AngleOrNumber, ParseError<'i, Self::Error>> {
485    if let Ok(angle) = input.try_parse(Angle::parse) {
486      Ok(AngleOrNumber::Angle {
487        degrees: angle.to_degrees(),
488      })
489    } else if let Ok(value) = input.try_parse(CSSNumber::parse) {
490      Ok(AngleOrNumber::Number { value })
491    } else if self.allow_none {
492      input.expect_ident_matching("none")?;
493      Ok(AngleOrNumber::Number { value: f32::NAN })
494    } else {
495      Err(input.new_custom_error(ParserError::InvalidValue))
496    }
497  }
498
499  fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {
500    if let Ok(val) = input.try_parse(CSSNumber::parse) {
501      return Ok(val);
502    } else if self.allow_none {
503      input.expect_ident_matching("none")?;
504      Ok(f32::NAN)
505    } else {
506      Err(input.new_custom_error(ParserError::InvalidValue))
507    }
508  }
509
510  fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {
511    if let Ok(val) = input.try_parse(Percentage::parse) {
512      return Ok(val.0);
513    } else if self.allow_none {
514      input.expect_ident_matching("none")?;
515      Ok(f32::NAN)
516    } else {
517      Err(input.new_custom_error(ParserError::InvalidValue))
518    }
519  }
520
521  fn parse_number_or_percentage<'t>(
522    &self,
523    input: &mut Parser<'i, 't>,
524  ) -> Result<NumberOrPercentage, ParseError<'i, Self::Error>> {
525    if let Ok(value) = input.try_parse(CSSNumber::parse) {
526      Ok(NumberOrPercentage::Number { value })
527    } else if let Ok(value) = input.try_parse(Percentage::parse) {
528      Ok(NumberOrPercentage::Percentage { unit_value: value.0 })
529    } else if self.allow_none {
530      input.expect_ident_matching("none")?;
531      Ok(NumberOrPercentage::Number { value: f32::NAN })
532    } else {
533      Err(input.new_custom_error(ParserError::InvalidValue))
534    }
535  }
536}
537
538// https://www.w3.org/TR/css-color-4/#lab-colors
539fn parse_color_function<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
540  let location = input.current_source_location();
541  let function = input.expect_function()?;
542  let parser = ComponentParser { allow_none: true };
543
544  match_ignore_ascii_case! {&*function,
545    "lab" => {
546      let (l, a, b, alpha) = parse_lab(input, &parser)?;
547      let lab = LABColor::LAB(LAB { l, a, b, alpha });
548      Ok(CssColor::LAB(Box::new(lab)))
549    },
550    "oklab" => {
551      let (l, a, b, alpha) = parse_lab(input, &parser)?;
552      let lab = LABColor::OKLAB(OKLAB { l, a, b, alpha });
553      Ok(CssColor::LAB(Box::new(lab)))
554    },
555    "lch" => {
556      let (l, c, h, alpha) = parse_lch(input, &parser)?;
557      let lab = LABColor::LCH(LCH { l, c, h, alpha });
558      Ok(CssColor::LAB(Box::new(lab)))
559    },
560    "oklch" => {
561      let (l, c, h, alpha) = parse_lch(input, &parser)?;
562      let lab = LABColor::OKLCH(OKLCH { l, c, h, alpha });
563      Ok(CssColor::LAB(Box::new(lab)))
564    },
565    "color" => {
566      let predefined = parse_predefined(input, &parser)?;
567      Ok(CssColor::Predefined(Box::new(predefined)))
568    },
569    "hsl" => {
570      let (h, s, l, a) = parse_hsl_hwb(input, &parser)?;
571      Ok(CssColor::Float(Box::new(FloatColor::HSL(HSL { h, s, l, alpha: a }))))
572    },
573    "hwb" => {
574      let (h, w, b, a) = parse_hsl_hwb(input, &parser)?;
575      Ok(CssColor::Float(Box::new(FloatColor::HWB(HWB { h, w, b, alpha: a }))))
576    },
577    "rgb" => {
578      let (r, g, b, a) = parse_rgb(input, &parser)?;
579      Ok(CssColor::Float(Box::new(FloatColor::RGB(SRGB { r, g, b, alpha: a }))))
580    },
581    "color-mix" => {
582      input.parse_nested_block(parse_color_mix)
583    },
584    _ => Err(location.new_unexpected_token_error(
585      cssparser::Token::Ident(function.clone())
586    ))
587  }
588}
589
590/// Parses the lab() and oklab() functions.
591#[inline]
592fn parse_lab<'i, 't>(
593  input: &mut Parser<'i, 't>,
594  parser: &ComponentParser,
595) -> Result<(f32, f32, f32, f32), ParseError<'i, ParserError<'i>>> {
596  // https://www.w3.org/TR/css-color-4/#funcdef-lab
597  let res = input.parse_nested_block(|input| {
598    // f32::max() does not propagate NaN, so use clamp for now until f32::maximum() is stable.
599    let l = parser.parse_percentage(input)?.clamp(0.0, f32::MAX);
600    let a = parser.parse_number(input)?;
601    let b = parser.parse_number(input)?;
602    let alpha = parse_alpha(input, parser)?;
603
604    Ok((l, a, b, alpha))
605  })?;
606
607  Ok(res)
608}
609
610/// Parses the lch() and oklch() functions.
611#[inline]
612fn parse_lch<'i, 't>(
613  input: &mut Parser<'i, 't>,
614  parser: &ComponentParser,
615) -> Result<(f32, f32, f32, f32), ParseError<'i, ParserError<'i>>> {
616  // https://www.w3.org/TR/css-color-4/#funcdef-lch
617  let res = input.parse_nested_block(|input| {
618    let l = parser.parse_percentage(input)?.clamp(0.0, f32::MAX);
619    let c = parser.parse_number(input)?.clamp(0.0, f32::MAX);
620    let h = parse_angle_or_number(input, parser)?;
621    let alpha = parse_alpha(input, parser)?;
622
623    Ok((l, c, h, alpha))
624  })?;
625
626  Ok(res)
627}
628
629#[inline]
630fn parse_predefined<'i, 't>(
631  input: &mut Parser<'i, 't>,
632  parser: &ComponentParser,
633) -> Result<PredefinedColor, ParseError<'i, ParserError<'i>>> {
634  // https://www.w3.org/TR/css-color-4/#color-function
635  let res = input.parse_nested_block(|input| {
636    let location = input.current_source_location();
637    let colorspace = input.expect_ident_cloned()?;
638
639    // Out of gamut values should not be clamped, i.e. values < 0 or > 1 should be preserved.
640    // The browser will gamut-map the color for the target device that it is rendered on.
641    let a = input
642      .try_parse(|input| parse_number_or_percentage(input, parser))
643      .unwrap_or(0.0);
644    let b = input
645      .try_parse(|input| parse_number_or_percentage(input, parser))
646      .unwrap_or(0.0);
647    let c = input
648      .try_parse(|input| parse_number_or_percentage(input, parser))
649      .unwrap_or(0.0);
650    let alpha = parse_alpha(input, parser)?;
651
652    let res = match_ignore_ascii_case! { &*&colorspace,
653      "srgb" => PredefinedColor::SRGB(SRGB { r: a, g: b, b: c, alpha }),
654      "srgb-linear" => PredefinedColor::SRGBLinear(SRGBLinear { r: a, g: b, b: c, alpha }),
655      "display-p3" => PredefinedColor::DisplayP3(P3 { r: a, g: b, b: c, alpha }),
656      "a98-rgb" => PredefinedColor::A98(A98 { r: a, g: b, b: c, alpha }),
657      "prophoto-rgb" => PredefinedColor::ProPhoto(ProPhoto { r: a, g: b, b: c, alpha }),
658      "rec2020" => PredefinedColor::Rec2020(Rec2020 { r: a, g: b, b: c, alpha }),
659      "xyz-d50" => PredefinedColor::XYZd50(XYZd50 { x: a, y: b, z: c, alpha}),
660      "xyz" | "xyz-d65" => PredefinedColor::XYZd65(XYZd65 { x: a, y: b, z: c, alpha }),
661      _ => return Err(location.new_unexpected_token_error(
662        cssparser::Token::Ident(colorspace.clone())
663      ))
664    };
665
666    Ok(res)
667  })?;
668
669  Ok(res)
670}
671
672/// Parses the hsl() and hwb() functions.
673/// Only the modern syntax with no commas is handled here, cssparser handles the legacy syntax.
674/// The results of this function are stored as floating point if there are any `none` components.
675#[inline]
676fn parse_hsl_hwb<'i, 't>(
677  input: &mut Parser<'i, 't>,
678  parser: &ComponentParser,
679) -> Result<(f32, f32, f32, f32), ParseError<'i, ParserError<'i>>> {
680  // https://drafts.csswg.org/css-color-4/#the-hsl-notation
681  let res = input.parse_nested_block(|input| {
682    let (h, a, b) = parse_hsl_hwb_components(input, parser)?;
683    let alpha = parse_alpha(input, parser)?;
684
685    Ok((h, a, b, alpha))
686  })?;
687
688  Ok(res)
689}
690
691#[inline]
692pub(crate) fn parse_hsl_hwb_components<'i, 't>(
693  input: &mut Parser<'i, 't>,
694  parser: &ComponentParser,
695) -> Result<(f32, f32, f32), ParseError<'i, ParserError<'i>>> {
696  let h = parse_angle_or_number(input, parser)?;
697  let a = parser.parse_percentage(input)?.clamp(0.0, 1.0);
698  let b = parser.parse_percentage(input)?.clamp(0.0, 1.0);
699  Ok((h, a, b))
700}
701
702#[inline]
703fn parse_rgb<'i, 't>(
704  input: &mut Parser<'i, 't>,
705  parser: &ComponentParser,
706) -> Result<(f32, f32, f32, f32), ParseError<'i, ParserError<'i>>> {
707  // https://drafts.csswg.org/css-color-4/#rgb-functions
708  let res = input.parse_nested_block(|input| {
709    let (r, g, b) = parse_rgb_components(input, parser)?;
710    let alpha = parse_alpha(input, parser)?;
711    Ok((r, g, b, alpha))
712  })?;
713
714  Ok(res)
715}
716
717#[inline]
718pub(crate) fn parse_rgb_components<'i, 't>(
719  input: &mut Parser<'i, 't>,
720  parser: &ComponentParser,
721) -> Result<(f32, f32, f32), ParseError<'i, ParserError<'i>>> {
722  // percentages and numbers cannot be mixed, but we might not know
723  // what kind of components to expect until later if there are `none` values.
724  #[derive(PartialEq)]
725  enum Kind {
726    Unknown,
727    Number,
728    Percentage,
729  }
730
731  #[inline]
732  fn parse_component<'i, 't>(
733    input: &mut Parser<'i, 't>,
734    parser: &ComponentParser,
735    kind: Kind,
736  ) -> Result<(f32, Kind), ParseError<'i, ParserError<'i>>> {
737    Ok(match parser.parse_number_or_percentage(input)? {
738      NumberOrPercentage::Number { value } if value.is_nan() => (value, kind),
739      NumberOrPercentage::Number { value } if kind != Kind::Percentage => {
740        (value.round().clamp(0.0, 255.0) / 255.0, Kind::Number)
741      }
742      NumberOrPercentage::Percentage { unit_value } if kind != Kind::Number => {
743        (unit_value.clamp(0.0, 1.0), Kind::Percentage)
744      }
745      _ => return Err(input.new_custom_error(ParserError::InvalidValue)),
746    })
747  }
748
749  let (r, kind) = parse_component(input, parser, Kind::Unknown)?;
750  let (g, kind) = parse_component(input, parser, kind)?;
751  let (b, _) = parse_component(input, parser, kind)?;
752  Ok((r, g, b))
753}
754
755#[inline]
756fn parse_angle_or_number<'i, 't>(
757  input: &mut Parser<'i, 't>,
758  parser: &ComponentParser,
759) -> Result<f32, ParseError<'i, ParserError<'i>>> {
760  Ok(match parser.parse_angle_or_number(input)? {
761    AngleOrNumber::Number { value } => value,
762    AngleOrNumber::Angle { degrees } => degrees,
763  })
764}
765
766#[inline]
767fn parse_number_or_percentage<'i, 't>(
768  input: &mut Parser<'i, 't>,
769  parser: &ComponentParser,
770) -> Result<f32, ParseError<'i, ParserError<'i>>> {
771  Ok(match parser.parse_number_or_percentage(input)? {
772    NumberOrPercentage::Number { value } => value,
773    NumberOrPercentage::Percentage { unit_value } => unit_value,
774  })
775}
776
777#[inline]
778fn parse_alpha<'i, 't>(
779  input: &mut Parser<'i, 't>,
780  parser: &ComponentParser,
781) -> Result<f32, ParseError<'i, ParserError<'i>>> {
782  let res = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
783    parse_number_or_percentage(input, parser)?.clamp(0.0, 1.0)
784  } else {
785    1.0
786  };
787  Ok(res)
788}
789
790#[inline]
791fn write_components<W>(
792  name: &str,
793  a: f32,
794  b: f32,
795  c: f32,
796  alpha: f32,
797  dest: &mut Printer<W>,
798) -> Result<(), PrinterError>
799where
800  W: std::fmt::Write,
801{
802  dest.write_str(name)?;
803  dest.write_char('(')?;
804  if a.is_nan() {
805    dest.write_str("none")?;
806  } else {
807    Percentage(a).to_css(dest)?;
808  }
809  dest.write_char(' ')?;
810  write_component(b, dest)?;
811  dest.write_char(' ')?;
812  write_component(c, dest)?;
813  if alpha.is_nan() || (alpha - 1.0).abs() > f32::EPSILON {
814    dest.delim('/', true)?;
815    write_component(alpha, dest)?;
816  }
817
818  dest.write_char(')')
819}
820
821#[inline]
822fn write_component<W>(c: f32, dest: &mut Printer<W>) -> Result<(), PrinterError>
823where
824  W: std::fmt::Write,
825{
826  if c.is_nan() {
827    dest.write_str("none")?;
828  } else {
829    c.to_css(dest)?;
830  }
831  Ok(())
832}
833
834#[inline]
835fn write_predefined<W>(predefined: &PredefinedColor, dest: &mut Printer<W>) -> Result<(), PrinterError>
836where
837  W: std::fmt::Write,
838{
839  use PredefinedColor::*;
840
841  let (name, a, b, c, alpha) = match predefined {
842    SRGB(rgb) => ("srgb", rgb.r, rgb.g, rgb.b, rgb.alpha),
843    SRGBLinear(rgb) => ("srgb-linear", rgb.r, rgb.g, rgb.b, rgb.alpha),
844    DisplayP3(rgb) => ("display-p3", rgb.r, rgb.g, rgb.b, rgb.alpha),
845    A98(rgb) => ("a98-rgb", rgb.r, rgb.g, rgb.b, rgb.alpha),
846    ProPhoto(rgb) => ("prophoto-rgb", rgb.r, rgb.g, rgb.b, rgb.alpha),
847    Rec2020(rgb) => ("rec2020", rgb.r, rgb.g, rgb.b, rgb.alpha),
848    XYZd50(xyz) => ("xyz-d50", xyz.x, xyz.y, xyz.z, xyz.alpha),
849    // "xyz" has better compatibility (Safari 15) than "xyz-d65", and it is shorter.
850    XYZd65(xyz) => ("xyz", xyz.x, xyz.y, xyz.z, xyz.alpha),
851  };
852
853  dest.write_str("color(")?;
854  dest.write_str(name)?;
855  if !dest.minify || a != 0.0 || b != 0.0 || c != 0.0 {
856    dest.write_char(' ')?;
857    write_component(a, dest)?;
858    if !dest.minify || b != 0.0 || c != 0.0 {
859      dest.write_char(' ')?;
860      write_component(b, dest)?;
861      if !dest.minify || c != 0.0 {
862        dest.write_char(' ')?;
863        write_component(c, dest)?;
864      }
865    }
866  }
867
868  if alpha.is_nan() || (alpha - 1.0).abs() > f32::EPSILON {
869    dest.delim('/', true)?;
870    write_component(alpha, dest)?;
871  }
872
873  dest.write_char(')')
874}
875
876macro_rules! define_colorspace {
877  (
878    $(#[$outer:meta])*
879    $vis:vis struct $name:ident {
880      $(#[$a_meta: meta])*
881      $a: ident,
882      $(#[$b_meta: meta])*
883      $b: ident,
884      $(#[$c_meta: meta])*
885      $c: ident
886    }
887  ) => {
888    $(#[$outer])*
889    #[derive(Debug, Clone, Copy, PartialEq)]
890    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
891    pub struct $name {
892      $(#[$a_meta])*
893      pub $a: f32,
894      $(#[$b_meta])*
895      pub $b: f32,
896      $(#[$c_meta])*
897      pub $c: f32,
898      /// The alpha component.
899      pub alpha: f32,
900    }
901
902    impl $name {
903      #[inline]
904      fn resolve_missing(&self) -> Self {
905        Self {
906          $a: if self.$a.is_nan() { 0.0 } else { self.$a },
907          $b: if self.$b.is_nan() { 0.0 } else { self.$b },
908          $c: if self.$c.is_nan() { 0.0 } else { self.$c },
909          alpha: if self.alpha.is_nan() { 0.0 } else { self.alpha },
910        }
911      }
912
913      /// Returns a resolved color by replacing missing (i.e. `none`) components with zero,
914      /// and performing gamut mapping to ensure the color can be represented within the color space.
915      #[inline]
916      pub fn resolve(&self) -> Self {
917        let mut resolved = self.resolve_missing();
918        if !resolved.in_gamut() {
919          resolved = map_gamut(resolved);
920        }
921        resolved
922      }
923    }
924  };
925}
926
927define_colorspace! {
928  /// A color in the [`sRGB`](https://www.w3.org/TR/css-color-4/#predefined-sRGB) color space.
929  pub struct SRGB {
930    /// The red component.
931    r,
932    /// The green component.
933    g,
934    /// The blue component.
935    b
936  }
937}
938
939define_colorspace! {
940  /// A color in the [`sRGB-linear`](https://www.w3.org/TR/css-color-4/#predefined-sRGB-linear) color space.
941  pub struct SRGBLinear {
942    /// The red component.
943    r,
944    /// The green component.
945    g,
946    /// The blue component.
947    b
948  }
949}
950
951define_colorspace! {
952  /// A color in the [`display-p3`](https://www.w3.org/TR/css-color-4/#predefined-display-p3) color space.
953  pub struct P3 {
954    /// The red component.
955    r,
956    /// The green component.
957    g,
958    /// The blue component.
959    b
960  }
961}
962
963define_colorspace! {
964  /// A color in the [`a98-rgb`](https://www.w3.org/TR/css-color-4/#predefined-a98-rgb) color space.
965  pub struct A98 {
966    /// The red component.
967    r,
968    /// The green component.
969    g,
970    /// The blue component.
971    b
972  }
973}
974
975define_colorspace! {
976  /// A color in the [`prophoto-rgb`](https://www.w3.org/TR/css-color-4/#predefined-prophoto-rgb) color space.
977  pub struct ProPhoto {
978    /// The red component.
979    r,
980    /// The green component.
981    g,
982    /// The blue component.
983    b
984  }
985}
986
987define_colorspace! {
988  /// A color in the [`rec2020`](https://www.w3.org/TR/css-color-4/#predefined-rec2020) color space.
989  pub struct Rec2020 {
990    /// The red component.
991    r,
992    /// The green component.
993    g,
994    /// The blue component.
995    b
996  }
997}
998
999define_colorspace! {
1000  /// A color in the [CIE Lab](https://www.w3.org/TR/css-color-4/#cie-lab) color space.
1001  pub struct LAB {
1002    /// The lightness component.
1003    l,
1004    /// The a component.
1005    a,
1006    /// The b component.
1007    b
1008  }
1009}
1010
1011define_colorspace! {
1012  /// A color in the [CIE LCH](https://www.w3.org/TR/css-color-4/#cie-lab) color space.
1013  pub struct LCH {
1014    /// The lightness component.
1015    l,
1016    /// The chroma component.
1017    c,
1018    /// The hue component.
1019    h
1020  }
1021}
1022
1023define_colorspace! {
1024  /// A color in the [OKLab](https://www.w3.org/TR/css-color-4/#ok-lab) color space.
1025  pub struct OKLAB {
1026    /// The lightness component.
1027    l,
1028    /// The a component.
1029    a,
1030    /// The b component.
1031    b
1032  }
1033}
1034
1035define_colorspace! {
1036  /// A color in the [OKLCH](https://www.w3.org/TR/css-color-4/#ok-lab) color space.
1037  pub struct OKLCH {
1038    /// The lightness component.
1039    l,
1040    /// The chroma component.
1041    c,
1042    /// The hue component.
1043    h
1044  }
1045}
1046
1047define_colorspace! {
1048  /// A color in the [`xyz-d50`](https://www.w3.org/TR/css-color-4/#predefined-xyz) color space.
1049  pub struct XYZd50 {
1050    /// The x component.
1051    x,
1052    /// The y component.
1053    y,
1054    /// The z component.
1055    z
1056  }
1057}
1058
1059define_colorspace! {
1060  /// A color in the [`xyz-d65`](https://www.w3.org/TR/css-color-4/#predefined-xyz) color space.
1061  pub struct XYZd65 {
1062    /// The x component.
1063    x,
1064    /// The y component.
1065    y,
1066    /// The z component.
1067    z
1068  }
1069}
1070
1071define_colorspace! {
1072  /// A color in the [`hsl`](https://www.w3.org/TR/css-color-4/#the-hsl-notation) color space.
1073  pub struct HSL {
1074    /// The hue component.
1075    h,
1076    /// The saturation component.
1077    s,
1078    /// The lightness component.
1079    l
1080  }
1081}
1082
1083define_colorspace! {
1084  /// A color in the [`hwb`](https://www.w3.org/TR/css-color-4/#the-hwb-notation) color space.
1085  pub struct HWB {
1086    /// The hue component.
1087    h,
1088    /// The whiteness component.
1089    w,
1090    /// The blackness component.
1091    b
1092  }
1093}
1094
1095macro_rules! via {
1096  ($t: ident -> $u: ident -> $v: ident) => {
1097    impl From<$t> for $v {
1098      #[inline]
1099      fn from(t: $t) -> $v {
1100        let xyz: $u = t.into();
1101        xyz.into()
1102      }
1103    }
1104
1105    impl From<$v> for $t {
1106      #[inline]
1107      fn from(t: $v) -> $t {
1108        let xyz: $u = t.into();
1109        xyz.into()
1110      }
1111    }
1112  };
1113}
1114
1115#[inline]
1116fn rectangular_to_polar(l: f32, a: f32, b: f32) -> (f32, f32, f32) {
1117  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L375
1118  let mut h = b.atan2(a) * 180.0 / PI;
1119  if h < 0.0 {
1120    h += 360.0;
1121  }
1122  let c = (a.powi(2) + b.powi(2)).sqrt();
1123  h = h % 360.0;
1124  (l, c, h)
1125}
1126
1127#[inline]
1128fn polar_to_rectangular(l: f32, c: f32, h: f32) -> (f32, f32, f32) {
1129  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L385
1130  let a = c * (h * PI / 180.0).cos();
1131  let b = c * (h * PI / 180.0).sin();
1132  (l, a, b)
1133}
1134
1135impl From<LCH> for LAB {
1136  fn from(lch: LCH) -> LAB {
1137    let lch = lch.resolve_missing();
1138    let (l, a, b) = polar_to_rectangular(lch.l, lch.c, lch.h);
1139    LAB {
1140      l,
1141      a,
1142      b,
1143      alpha: lch.alpha,
1144    }
1145  }
1146}
1147
1148impl From<LAB> for LCH {
1149  fn from(lab: LAB) -> LCH {
1150    let lab = lab.resolve_missing();
1151    let (l, c, h) = rectangular_to_polar(lab.l, lab.a, lab.b);
1152    LCH {
1153      l,
1154      c,
1155      h,
1156      alpha: lab.alpha,
1157    }
1158  }
1159}
1160
1161impl From<OKLCH> for OKLAB {
1162  fn from(lch: OKLCH) -> OKLAB {
1163    let lch = lch.resolve_missing();
1164    let (l, a, b) = polar_to_rectangular(lch.l, lch.c, lch.h);
1165    OKLAB {
1166      l,
1167      a,
1168      b,
1169      alpha: lch.alpha,
1170    }
1171  }
1172}
1173
1174impl From<OKLAB> for OKLCH {
1175  fn from(lab: OKLAB) -> OKLCH {
1176    let lab = lab.resolve_missing();
1177    let (l, c, h) = rectangular_to_polar(lab.l, lab.a, lab.b);
1178    OKLCH {
1179      l,
1180      c,
1181      h,
1182      alpha: lab.alpha,
1183    }
1184  }
1185}
1186
1187const D50: &[f32] = &[0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585];
1188
1189impl From<LAB> for XYZd50 {
1190  fn from(lab: LAB) -> XYZd50 {
1191    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L352
1192    const K: f32 = 24389.0 / 27.0; // 29^3/3^3
1193    const E: f32 = 216.0 / 24389.0; // 6^3/29^3
1194
1195    let lab = lab.resolve_missing();
1196    let l = lab.l * 100.0;
1197    let a = lab.a;
1198    let b = lab.b;
1199
1200    // compute f, starting with the luminance-related term
1201    let f1 = (l + 16.0) / 116.0;
1202    let f0 = a / 500.0 + f1;
1203    let f2 = f1 - b / 200.0;
1204
1205    // compute xyz
1206    let x = if f0.powi(3) > E {
1207      f0.powi(3)
1208    } else {
1209      (116.0 * f0 - 16.0) / K
1210    };
1211
1212    let y = if l > K * E { ((l + 16.0) / 116.0).powi(3) } else { l / K };
1213
1214    let z = if f2.powi(3) > E {
1215      f2.powi(3)
1216    } else {
1217      (116.0 * f2 - 16.0) / K
1218    };
1219
1220    // Compute XYZ by scaling xyz by reference white
1221    XYZd50 {
1222      x: x * D50[0],
1223      y: y * D50[1],
1224      z: z * D50[2],
1225      alpha: lab.alpha,
1226    }
1227  }
1228}
1229
1230impl From<XYZd50> for XYZd65 {
1231  fn from(xyz: XYZd50) -> XYZd65 {
1232    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L319
1233    const MATRIX: &[f32] = &[
1234      0.9554734527042182,
1235      -0.023098536874261423,
1236      0.0632593086610217,
1237      -0.028369706963208136,
1238      1.0099954580058226,
1239      0.021041398966943008,
1240      0.012314001688319899,
1241      -0.020507696433477912,
1242      1.3303659366080753,
1243    ];
1244
1245    let xyz = xyz.resolve_missing();
1246    let (x, y, z) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
1247    XYZd65 {
1248      x,
1249      y,
1250      z,
1251      alpha: xyz.alpha,
1252    }
1253  }
1254}
1255
1256impl From<XYZd65> for XYZd50 {
1257  fn from(xyz: XYZd65) -> XYZd50 {
1258    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L319
1259    const MATRIX: &[f32] = &[
1260      1.0479298208405488,
1261      0.022946793341019088,
1262      -0.05019222954313557,
1263      0.029627815688159344,
1264      0.990434484573249,
1265      -0.01707382502938514,
1266      -0.009243058152591178,
1267      0.015055144896577895,
1268      0.7518742899580008,
1269    ];
1270
1271    let xyz = xyz.resolve_missing();
1272    let (x, y, z) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
1273    XYZd50 {
1274      x,
1275      y,
1276      z,
1277      alpha: xyz.alpha,
1278    }
1279  }
1280}
1281
1282impl From<XYZd65> for SRGBLinear {
1283  fn from(xyz: XYZd65) -> SRGBLinear {
1284    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L62
1285    const MATRIX: &[f32] = &[
1286      3.2409699419045226,
1287      -1.537383177570094,
1288      -0.4986107602930034,
1289      -0.9692436362808796,
1290      1.8759675015077202,
1291      0.04155505740717559,
1292      0.05563007969699366,
1293      -0.20397695888897652,
1294      1.0569715142428786,
1295    ];
1296
1297    let xyz = xyz.resolve_missing();
1298    let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
1299    SRGBLinear {
1300      r,
1301      g,
1302      b,
1303      alpha: xyz.alpha,
1304    }
1305  }
1306}
1307
1308#[inline]
1309fn multiply_matrix(m: &[f32], x: f32, y: f32, z: f32) -> (f32, f32, f32) {
1310  let a = m[0] * x + m[1] * y + m[2] * z;
1311  let b = m[3] * x + m[4] * y + m[5] * z;
1312  let c = m[6] * x + m[7] * y + m[8] * z;
1313  (a, b, c)
1314}
1315
1316impl From<SRGBLinear> for SRGB {
1317  #[inline]
1318  fn from(rgb: SRGBLinear) -> SRGB {
1319    let rgb = rgb.resolve_missing();
1320    let (r, g, b) = gam_srgb(rgb.r, rgb.g, rgb.b);
1321    SRGB {
1322      r,
1323      g,
1324      b,
1325      alpha: rgb.alpha,
1326    }
1327  }
1328}
1329
1330fn gam_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
1331  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L31
1332  // convert an array of linear-light sRGB values in the range 0.0-1.0
1333  // to gamma corrected form
1334  // https://en.wikipedia.org/wiki/SRGB
1335  // Extended transfer function:
1336  // For negative values, linear portion extends on reflection
1337  // of axis, then uses reflected pow below that
1338
1339  #[inline]
1340  fn gam_srgb_component(c: f32) -> f32 {
1341    let abs = c.abs();
1342    if abs > 0.0031308 {
1343      let sign = if c < 0.0 { -1.0 } else { 1.0 };
1344      return sign * (1.055 * abs.powf(1.0 / 2.4) - 0.055);
1345    }
1346
1347    return 12.92 * c;
1348  }
1349
1350  let r = gam_srgb_component(r);
1351  let g = gam_srgb_component(g);
1352  let b = gam_srgb_component(b);
1353  (r, g, b)
1354}
1355
1356impl From<OKLAB> for XYZd65 {
1357  fn from(lab: OKLAB) -> XYZd65 {
1358    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L418
1359    const LMS_TO_XYZ: &[f32] = &[
1360      1.2268798733741557,
1361      -0.5578149965554813,
1362      0.28139105017721583,
1363      -0.04057576262431372,
1364      1.1122868293970594,
1365      -0.07171106666151701,
1366      -0.07637294974672142,
1367      -0.4214933239627914,
1368      1.5869240244272418,
1369    ];
1370
1371    const OKLAB_TO_LMS: &[f32] = &[
1372      0.99999999845051981432,
1373      0.39633779217376785678,
1374      0.21580375806075880339,
1375      1.0000000088817607767,
1376      -0.1055613423236563494,
1377      -0.063854174771705903402,
1378      1.0000000546724109177,
1379      -0.089484182094965759684,
1380      -1.2914855378640917399,
1381    ];
1382
1383    let lab = lab.resolve_missing();
1384    let (a, b, c) = multiply_matrix(OKLAB_TO_LMS, lab.l, lab.a, lab.b);
1385    let (x, y, z) = multiply_matrix(LMS_TO_XYZ, a.powi(3), b.powi(3), c.powi(3));
1386    XYZd65 {
1387      x,
1388      y,
1389      z,
1390      alpha: lab.alpha,
1391    }
1392  }
1393}
1394
1395impl From<XYZd65> for OKLAB {
1396  fn from(xyz: XYZd65) -> OKLAB {
1397    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L400
1398    const XYZ_TO_LMS: &[f32] = &[
1399      0.8190224432164319,
1400      0.3619062562801221,
1401      -0.12887378261216414,
1402      0.0329836671980271,
1403      0.9292868468965546,
1404      0.03614466816999844,
1405      0.048177199566046255,
1406      0.26423952494422764,
1407      0.6335478258136937,
1408    ];
1409
1410    const LMS_TO_OKLAB: &[f32] = &[
1411      0.2104542553,
1412      0.7936177850,
1413      -0.0040720468,
1414      1.9779984951,
1415      -2.4285922050,
1416      0.4505937099,
1417      0.0259040371,
1418      0.7827717662,
1419      -0.8086757660,
1420    ];
1421
1422    let xyz = xyz.resolve_missing();
1423    let (a, b, c) = multiply_matrix(XYZ_TO_LMS, xyz.x, xyz.y, xyz.z);
1424    let (l, a, b) = multiply_matrix(LMS_TO_OKLAB, a.cbrt(), b.cbrt(), c.cbrt());
1425    OKLAB {
1426      l,
1427      a,
1428      b,
1429      alpha: xyz.alpha,
1430    }
1431  }
1432}
1433
1434impl From<XYZd50> for LAB {
1435  fn from(xyz: XYZd50) -> LAB {
1436    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L332
1437    // Assuming XYZ is relative to D50, convert to CIE LAB
1438    // from CIE standard, which now defines these as a rational fraction
1439    const E: f32 = 216.0 / 24389.0; // 6^3/29^3
1440    const K: f32 = 24389.0 / 27.0; // 29^3/3^3
1441
1442    // compute xyz, which is XYZ scaled relative to reference white
1443    let xyz = xyz.resolve_missing();
1444    let x = xyz.x / D50[0];
1445    let y = xyz.y / D50[1];
1446    let z = xyz.z / D50[2];
1447
1448    // now compute f
1449    let f0 = if x > E { x.cbrt() } else { (K * x + 16.0) / 116.0 };
1450
1451    let f1 = if y > E { y.cbrt() } else { (K * y + 16.0) / 116.0 };
1452
1453    let f2 = if z > E { z.cbrt() } else { (K * z + 16.0) / 116.0 };
1454
1455    let l = ((116.0 * f1) - 16.0) / 100.0;
1456    let a = 500.0 * (f0 - f1);
1457    let b = 200.0 * (f1 - f2);
1458    LAB {
1459      l,
1460      a,
1461      b,
1462      alpha: xyz.alpha,
1463    }
1464  }
1465}
1466
1467impl From<SRGB> for SRGBLinear {
1468  fn from(rgb: SRGB) -> SRGBLinear {
1469    let rgb = rgb.resolve_missing();
1470    let (r, g, b) = lin_srgb(rgb.r, rgb.g, rgb.b);
1471    SRGBLinear {
1472      r,
1473      g,
1474      b,
1475      alpha: rgb.alpha,
1476    }
1477  }
1478}
1479
1480fn lin_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
1481  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L11
1482  // convert sRGB values where in-gamut values are in the range [0 - 1]
1483  // to linear light (un-companded) form.
1484  // https://en.wikipedia.org/wiki/SRGB
1485  // Extended transfer function:
1486  // for negative values, linear portion is extended on reflection of axis,
1487  // then reflected power function is used.
1488
1489  #[inline]
1490  fn lin_srgb_component(c: f32) -> f32 {
1491    let abs = c.abs();
1492    if abs < 0.04045 {
1493      return c / 12.92;
1494    }
1495
1496    let sign = if c < 0.0 { -1.0 } else { 1.0 };
1497    sign * ((abs + 0.055) / 1.055).powf(2.4)
1498  }
1499
1500  let r = lin_srgb_component(r);
1501  let g = lin_srgb_component(g);
1502  let b = lin_srgb_component(b);
1503  (r, g, b)
1504}
1505
1506impl From<SRGBLinear> for XYZd65 {
1507  fn from(rgb: SRGBLinear) -> XYZd65 {
1508    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L50
1509    // convert an array of linear-light sRGB values to CIE XYZ
1510    // using sRGB's own white, D65 (no chromatic adaptation)
1511    const MATRIX: &[f32] = &[
1512      0.41239079926595934,
1513      0.357584339383878,
1514      0.1804807884018343,
1515      0.21263900587151027,
1516      0.715168678767756,
1517      0.07219231536073371,
1518      0.01933081871559182,
1519      0.11919477979462598,
1520      0.9505321522496607,
1521    ];
1522
1523    let rgb = rgb.resolve_missing();
1524    let (x, y, z) = multiply_matrix(MATRIX, rgb.r, rgb.g, rgb.b);
1525    XYZd65 {
1526      x,
1527      y,
1528      z,
1529      alpha: rgb.alpha,
1530    }
1531  }
1532}
1533
1534impl From<XYZd65> for P3 {
1535  fn from(xyz: XYZd65) -> P3 {
1536    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L105
1537    const MATRIX: &[f32] = &[
1538      2.493496911941425,
1539      -0.9313836179191239,
1540      -0.40271078445071684,
1541      -0.8294889695615747,
1542      1.7626640603183463,
1543      0.023624685841943577,
1544      0.03584583024378447,
1545      -0.07617238926804182,
1546      0.9568845240076872,
1547    ];
1548
1549    let xyz = xyz.resolve_missing();
1550    let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
1551    let (r, g, b) = gam_srgb(r, g, b); // same as sRGB
1552    P3 {
1553      r,
1554      g,
1555      b,
1556      alpha: xyz.alpha,
1557    }
1558  }
1559}
1560
1561impl From<P3> for XYZd65 {
1562  fn from(p3: P3) -> XYZd65 {
1563    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L91
1564    // convert linear-light display-p3 values to CIE XYZ
1565    // using D65 (no chromatic adaptation)
1566    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
1567    const MATRIX: &[f32] = &[
1568      0.4865709486482162,
1569      0.26566769316909306,
1570      0.1982172852343625,
1571      0.2289745640697488,
1572      0.6917385218365064,
1573      0.079286914093745,
1574      0.0000000000000000,
1575      0.04511338185890264,
1576      1.043944368900976,
1577    ];
1578
1579    let p3 = p3.resolve_missing();
1580    let (r, g, b) = lin_srgb(p3.r, p3.g, p3.b);
1581    let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
1582    XYZd65 {
1583      x,
1584      y,
1585      z,
1586      alpha: p3.alpha,
1587    }
1588  }
1589}
1590
1591impl From<A98> for XYZd65 {
1592  fn from(a98: A98) -> XYZd65 {
1593    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L181
1594    #[inline]
1595    fn lin_a98rgb_component(c: f32) -> f32 {
1596      let sign = if c < 0.0 { -1.0 } else { 1.0 };
1597      sign * c.abs().powf(563.0 / 256.0)
1598    }
1599
1600    // convert an array of a98-rgb values in the range 0.0 - 1.0
1601    // to linear light (un-companded) form.
1602    // negative values are also now accepted
1603    let a98 = a98.resolve_missing();
1604    let r = lin_a98rgb_component(a98.r);
1605    let g = lin_a98rgb_component(a98.g);
1606    let b = lin_a98rgb_component(a98.b);
1607
1608    // convert an array of linear-light a98-rgb values to CIE XYZ
1609    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
1610    // has greater numerical precision than section 4.3.5.3 of
1611    // https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
1612    // but the values below were calculated from first principles
1613    // from the chromaticity coordinates of R G B W
1614    // see matrixmaker.html
1615    const MATRIX: &[f32] = &[
1616      0.5766690429101305,
1617      0.1855582379065463,
1618      0.1882286462349947,
1619      0.29734497525053605,
1620      0.6273635662554661,
1621      0.07529145849399788,
1622      0.02703136138641234,
1623      0.07068885253582723,
1624      0.9913375368376388,
1625    ];
1626
1627    let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
1628    XYZd65 {
1629      x,
1630      y,
1631      z,
1632      alpha: a98.alpha,
1633    }
1634  }
1635}
1636
1637impl From<XYZd65> for A98 {
1638  fn from(xyz: XYZd65) -> A98 {
1639    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L222
1640    // convert XYZ to linear-light a98-rgb
1641    const MATRIX: &[f32] = &[
1642      2.0415879038107465,
1643      -0.5650069742788596,
1644      -0.34473135077832956,
1645      -0.9692436362808795,
1646      1.8759675015077202,
1647      0.04155505740717557,
1648      0.013444280632031142,
1649      -0.11836239223101838,
1650      1.0151749943912054,
1651    ];
1652
1653    #[inline]
1654    fn gam_a98_component(c: f32) -> f32 {
1655      // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L193
1656      // convert linear-light a98-rgb  in the range 0.0-1.0
1657      // to gamma corrected form
1658      // negative values are also now accepted
1659      let sign = if c < 0.0 { -1.0 } else { 1.0 };
1660      sign * c.abs().powf(256.0 / 563.0)
1661    }
1662
1663    let xyz = xyz.resolve_missing();
1664    let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
1665    let r = gam_a98_component(r);
1666    let g = gam_a98_component(g);
1667    let b = gam_a98_component(b);
1668    A98 {
1669      r,
1670      g,
1671      b,
1672      alpha: xyz.alpha,
1673    }
1674  }
1675}
1676
1677impl From<ProPhoto> for XYZd50 {
1678  fn from(prophoto: ProPhoto) -> XYZd50 {
1679    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L118
1680    // convert an array of prophoto-rgb values
1681    // where in-gamut colors are in the range [0.0 - 1.0]
1682    // to linear light (un-companded) form.
1683    // Transfer curve is gamma 1.8 with a small linear portion
1684    // Extended transfer function
1685
1686    #[inline]
1687    fn lin_prophoto_component(c: f32) -> f32 {
1688      const ET2: f32 = 16.0 / 512.0;
1689      let abs = c.abs();
1690      if abs <= ET2 {
1691        return c / 16.0;
1692      }
1693
1694      let sign = if c < 0.0 { -1.0 } else { 1.0 };
1695      sign * c.powf(1.8)
1696    }
1697
1698    let prophoto = prophoto.resolve_missing();
1699    let r = lin_prophoto_component(prophoto.r);
1700    let g = lin_prophoto_component(prophoto.g);
1701    let b = lin_prophoto_component(prophoto.b);
1702
1703    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L155
1704    // convert an array of linear-light prophoto-rgb values to CIE XYZ
1705    // using  D50 (so no chromatic adaptation needed afterwards)
1706    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
1707    const MATRIX: &[f32] = &[
1708      0.7977604896723027,
1709      0.13518583717574031,
1710      0.0313493495815248,
1711      0.2880711282292934,
1712      0.7118432178101014,
1713      0.00008565396060525902,
1714      0.0,
1715      0.0,
1716      0.8251046025104601,
1717    ];
1718
1719    let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
1720    XYZd50 {
1721      x,
1722      y,
1723      z,
1724      alpha: prophoto.alpha,
1725    }
1726  }
1727}
1728
1729impl From<XYZd50> for ProPhoto {
1730  fn from(xyz: XYZd50) -> ProPhoto {
1731    // convert XYZ to linear-light prophoto-rgb
1732    const MATRIX: &[f32] = &[
1733      1.3457989731028281,
1734      -0.25558010007997534,
1735      -0.05110628506753401,
1736      -0.5446224939028347,
1737      1.5082327413132781,
1738      0.02053603239147973,
1739      0.0,
1740      0.0,
1741      1.2119675456389454,
1742    ];
1743
1744    #[inline]
1745    fn gam_prophoto_component(c: f32) -> f32 {
1746      // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L137
1747      // convert linear-light prophoto-rgb  in the range 0.0-1.0
1748      // to gamma corrected form
1749      // Transfer curve is gamma 1.8 with a small linear portion
1750      // TODO for negative values, extend linear portion on reflection of axis, then add pow below that
1751      const ET: f32 = 1.0 / 512.0;
1752      let abs = c.abs();
1753      if abs >= ET {
1754        let sign = if c < 0.0 { -1.0 } else { 1.0 };
1755        return sign * abs.powf(1.0 / 1.8);
1756      }
1757
1758      16.0 * c
1759    }
1760
1761    let xyz = xyz.resolve_missing();
1762    let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
1763    let r = gam_prophoto_component(r);
1764    let g = gam_prophoto_component(g);
1765    let b = gam_prophoto_component(b);
1766    ProPhoto {
1767      r,
1768      g,
1769      b,
1770      alpha: xyz.alpha,
1771    }
1772  }
1773}
1774
1775impl From<Rec2020> for XYZd65 {
1776  fn from(rec2020: Rec2020) -> XYZd65 {
1777    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L235
1778    // convert an array of rec2020 RGB values in the range 0.0 - 1.0
1779    // to linear light (un-companded) form.
1780    // ITU-R BT.2020-2 p.4
1781
1782    #[inline]
1783    fn lin_rec2020_component(c: f32) -> f32 {
1784      const A: f32 = 1.09929682680944;
1785      const B: f32 = 0.018053968510807;
1786
1787      let abs = c.abs();
1788      if abs < B * 4.5 {
1789        return c / 4.5;
1790      }
1791
1792      let sign = if c < 0.0 { -1.0 } else { 1.0 };
1793      sign * ((abs + A - 1.0) / A).powf(1.0 / 0.45)
1794    }
1795
1796    let rec2020 = rec2020.resolve_missing();
1797    let r = lin_rec2020_component(rec2020.r);
1798    let g = lin_rec2020_component(rec2020.g);
1799    let b = lin_rec2020_component(rec2020.b);
1800
1801    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L276
1802    // convert an array of linear-light rec2020 values to CIE XYZ
1803    // using  D65 (no chromatic adaptation)
1804    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
1805    const MATRIX: &[f32] = &[
1806      0.6369580483012914,
1807      0.14461690358620832,
1808      0.1688809751641721,
1809      0.2627002120112671,
1810      0.6779980715188708,
1811      0.05930171646986196,
1812      0.000000000000000,
1813      0.028072693049087428,
1814      1.060985057710791,
1815    ];
1816
1817    let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
1818    XYZd65 {
1819      x,
1820      y,
1821      z,
1822      alpha: rec2020.alpha,
1823    }
1824  }
1825}
1826
1827impl From<XYZd65> for Rec2020 {
1828  fn from(xyz: XYZd65) -> Rec2020 {
1829    // convert XYZ to linear-light rec2020
1830    const MATRIX: &[f32] = &[
1831      1.7166511879712674,
1832      -0.35567078377639233,
1833      -0.25336628137365974,
1834      -0.6666843518324892,
1835      1.6164812366349395,
1836      0.01576854581391113,
1837      0.017639857445310783,
1838      -0.042770613257808524,
1839      0.9421031212354738,
1840    ];
1841
1842    #[inline]
1843    fn gam_rec2020_component(c: f32) -> f32 {
1844      // convert linear-light rec2020 RGB  in the range 0.0-1.0
1845      // to gamma corrected form
1846      // ITU-R BT.2020-2 p.4
1847
1848      const A: f32 = 1.09929682680944;
1849      const B: f32 = 0.018053968510807;
1850
1851      let abs = c.abs();
1852      if abs > B {
1853        let sign = if c < 0.0 { -1.0 } else { 1.0 };
1854        return sign * (A * abs.powf(0.45) - (A - 1.0));
1855      }
1856
1857      4.5 * c
1858    }
1859
1860    let xyz = xyz.resolve_missing();
1861    let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
1862    let r = gam_rec2020_component(r);
1863    let g = gam_rec2020_component(g);
1864    let b = gam_rec2020_component(b);
1865    Rec2020 {
1866      r,
1867      g,
1868      b,
1869      alpha: xyz.alpha,
1870    }
1871  }
1872}
1873
1874impl From<SRGB> for HSL {
1875  fn from(rgb: SRGB) -> HSL {
1876    // https://drafts.csswg.org/css-color/#rgb-to-hsl
1877    let rgb = rgb.resolve();
1878    let r = rgb.r;
1879    let g = rgb.g;
1880    let b = rgb.b;
1881    let max = r.max(g).max(b);
1882    let min = r.min(g).min(b);
1883    let mut h = f32::NAN;
1884    let mut s: f32 = 0.0;
1885    let l = (min + max) / 2.0;
1886    let d = max - min;
1887
1888    if d != 0.0 {
1889      s = if l == 0.0 || l == 1.0 {
1890        0.0
1891      } else {
1892        (max - l) / l.min(1.0 - l)
1893      };
1894
1895      if max == r {
1896        h = (g - b) / d + (if g < b { 6.0 } else { 0.0 });
1897      } else if max == g {
1898        h = (b - r) / d + 2.0;
1899      } else if max == b {
1900        h = (r - g) / d + 4.0;
1901      }
1902
1903      h = h * 60.0;
1904    }
1905
1906    HSL {
1907      h,
1908      s,
1909      l,
1910      alpha: rgb.alpha,
1911    }
1912  }
1913}
1914
1915impl From<HSL> for SRGB {
1916  fn from(hsl: HSL) -> SRGB {
1917    // https://drafts.csswg.org/css-color/#hsl-to-rgb
1918    let hsl = hsl.resolve_missing();
1919    let mut h = hsl.h % 360.0;
1920    if h < 0.0 {
1921      h += 360.0;
1922    }
1923
1924    #[inline]
1925    fn hue_to_rgb(n: f32, h: f32, s: f32, l: f32) -> f32 {
1926      let k = (n + h / 30.0) % 12.0;
1927      let a = s * l.min(1.0 - l);
1928      l - a * (k - 3.0).min(9.0 - k).clamp(-1.0, 1.0)
1929    }
1930
1931    let r = hue_to_rgb(0.0, h, hsl.s, hsl.l);
1932    let g = hue_to_rgb(8.0, h, hsl.s, hsl.l);
1933    let b = hue_to_rgb(4.0, h, hsl.s, hsl.l);
1934    SRGB {
1935      r,
1936      g,
1937      b,
1938      alpha: hsl.alpha,
1939    }
1940  }
1941}
1942
1943impl From<SRGB> for HWB {
1944  fn from(rgb: SRGB) -> HWB {
1945    let rgb = rgb.resolve();
1946    let hsl = HSL::from(rgb);
1947    let r = rgb.r;
1948    let g = rgb.g;
1949    let b = rgb.b;
1950    let w = r.min(g).min(b);
1951    let b = 1.0 - r.max(g).max(b);
1952    HWB {
1953      h: hsl.h,
1954      w,
1955      b,
1956      alpha: rgb.alpha,
1957    }
1958  }
1959}
1960
1961impl From<HWB> for SRGB {
1962  fn from(hwb: HWB) -> SRGB {
1963    // https://drafts.csswg.org/css-color/#hwb-to-rgb
1964    let hwb = hwb.resolve_missing();
1965    let h = hwb.h;
1966    let w = hwb.w;
1967    let b = hwb.b;
1968
1969    if w + b >= 1.0 {
1970      let gray = w / (w + b);
1971      return SRGB {
1972        r: gray,
1973        g: gray,
1974        b: gray,
1975        alpha: hwb.alpha,
1976      };
1977    }
1978
1979    let mut rgba = SRGB::from(HSL {
1980      h,
1981      s: 1.0,
1982      l: 0.5,
1983      alpha: hwb.alpha,
1984    });
1985    let x = 1.0 - w - b;
1986    rgba.r = rgba.r * x + w;
1987    rgba.g = rgba.g * x + w;
1988    rgba.b = rgba.b * x + w;
1989    rgba
1990  }
1991}
1992
1993impl From<RGBA> for SRGB {
1994  fn from(rgb: RGBA) -> SRGB {
1995    SRGB {
1996      r: rgb.red_f32(),
1997      g: rgb.green_f32(),
1998      b: rgb.blue_f32(),
1999      alpha: rgb.alpha_f32(),
2000    }
2001  }
2002}
2003
2004impl From<SRGB> for RGBA {
2005  fn from(rgb: SRGB) -> RGBA {
2006    let rgb = rgb.resolve();
2007    RGBA::from_floats(rgb.r, rgb.g, rgb.b, rgb.alpha)
2008  }
2009}
2010
2011// Once Rust specialization is stable, this could be simplified.
2012via!(LAB -> XYZd50 -> XYZd65);
2013via!(ProPhoto -> XYZd50 -> XYZd65);
2014via!(OKLCH -> OKLAB -> XYZd65);
2015
2016via!(LAB -> XYZd65 -> OKLAB);
2017via!(LAB -> XYZd65 -> OKLCH);
2018via!(LAB -> XYZd65 -> SRGB);
2019via!(LAB -> XYZd65 -> SRGBLinear);
2020via!(LAB -> XYZd65 -> P3);
2021via!(LAB -> XYZd65 -> A98);
2022via!(LAB -> XYZd65 -> ProPhoto);
2023via!(LAB -> XYZd65 -> Rec2020);
2024via!(LAB -> XYZd65 -> HSL);
2025via!(LAB -> XYZd65 -> HWB);
2026
2027via!(LCH -> LAB -> XYZd65);
2028via!(LCH -> XYZd65 -> OKLAB);
2029via!(LCH -> XYZd65 -> OKLCH);
2030via!(LCH -> XYZd65 -> SRGB);
2031via!(LCH -> XYZd65 -> SRGBLinear);
2032via!(LCH -> XYZd65 -> P3);
2033via!(LCH -> XYZd65 -> A98);
2034via!(LCH -> XYZd65 -> ProPhoto);
2035via!(LCH -> XYZd65 -> Rec2020);
2036via!(LCH -> XYZd65 -> XYZd50);
2037via!(LCH -> XYZd65 -> HSL);
2038via!(LCH -> XYZd65 -> HWB);
2039
2040via!(SRGB -> SRGBLinear -> XYZd65);
2041via!(SRGB -> XYZd65 -> OKLAB);
2042via!(SRGB -> XYZd65 -> OKLCH);
2043via!(SRGB -> XYZd65 -> P3);
2044via!(SRGB -> XYZd65 -> A98);
2045via!(SRGB -> XYZd65 -> ProPhoto);
2046via!(SRGB -> XYZd65 -> Rec2020);
2047via!(SRGB -> XYZd65 -> XYZd50);
2048
2049via!(P3 -> XYZd65 -> SRGBLinear);
2050via!(P3 -> XYZd65 -> OKLAB);
2051via!(P3 -> XYZd65 -> OKLCH);
2052via!(P3 -> XYZd65 -> A98);
2053via!(P3 -> XYZd65 -> ProPhoto);
2054via!(P3 -> XYZd65 -> Rec2020);
2055via!(P3 -> XYZd65 -> XYZd50);
2056via!(P3 -> XYZd65 -> HSL);
2057via!(P3 -> XYZd65 -> HWB);
2058
2059via!(SRGBLinear -> XYZd65 -> OKLAB);
2060via!(SRGBLinear -> XYZd65 -> OKLCH);
2061via!(SRGBLinear -> XYZd65 -> A98);
2062via!(SRGBLinear -> XYZd65 -> ProPhoto);
2063via!(SRGBLinear -> XYZd65 -> Rec2020);
2064via!(SRGBLinear -> XYZd65 -> XYZd50);
2065via!(SRGBLinear -> XYZd65 -> HSL);
2066via!(SRGBLinear -> XYZd65 -> HWB);
2067
2068via!(A98 -> XYZd65 -> OKLAB);
2069via!(A98 -> XYZd65 -> OKLCH);
2070via!(A98 -> XYZd65 -> ProPhoto);
2071via!(A98 -> XYZd65 -> Rec2020);
2072via!(A98 -> XYZd65 -> XYZd50);
2073via!(A98 -> XYZd65 -> HSL);
2074via!(A98 -> XYZd65 -> HWB);
2075
2076via!(ProPhoto -> XYZd65 -> OKLAB);
2077via!(ProPhoto -> XYZd65 -> OKLCH);
2078via!(ProPhoto -> XYZd65 -> Rec2020);
2079via!(ProPhoto -> XYZd65 -> HSL);
2080via!(ProPhoto -> XYZd65 -> HWB);
2081
2082via!(XYZd50 -> XYZd65 -> OKLAB);
2083via!(XYZd50 -> XYZd65 -> OKLCH);
2084via!(XYZd50 -> XYZd65 -> Rec2020);
2085via!(XYZd50 -> XYZd65 -> HSL);
2086via!(XYZd50 -> XYZd65 -> HWB);
2087
2088via!(Rec2020 -> XYZd65 -> OKLAB);
2089via!(Rec2020 -> XYZd65 -> OKLCH);
2090via!(Rec2020 -> XYZd65 -> HSL);
2091via!(Rec2020 -> XYZd65 -> HWB);
2092
2093via!(HSL -> XYZd65 -> OKLAB);
2094via!(HSL -> XYZd65 -> OKLCH);
2095via!(HSL -> SRGB -> XYZd65);
2096via!(HSL -> SRGB -> HWB);
2097
2098via!(HWB -> SRGB -> XYZd65);
2099via!(HWB -> XYZd65 -> OKLAB);
2100via!(HWB -> XYZd65 -> OKLCH);
2101
2102// RGBA is an 8-bit version. Convert to SRGB, which is a
2103// more accurate floating point representation for all operations.
2104via!(RGBA -> SRGB -> LAB);
2105via!(RGBA -> SRGB -> LCH);
2106via!(RGBA -> SRGB -> OKLAB);
2107via!(RGBA -> SRGB -> OKLCH);
2108via!(RGBA -> SRGB -> P3);
2109via!(RGBA -> SRGB -> SRGBLinear);
2110via!(RGBA -> SRGB -> A98);
2111via!(RGBA -> SRGB -> ProPhoto);
2112via!(RGBA -> SRGB -> XYZd50);
2113via!(RGBA -> SRGB -> XYZd65);
2114via!(RGBA -> SRGB -> Rec2020);
2115via!(RGBA -> SRGB -> HSL);
2116via!(RGBA -> SRGB -> HWB);
2117
2118macro_rules! color_space {
2119  ($space: ty) => {
2120    impl From<LABColor> for $space {
2121      fn from(color: LABColor) -> $space {
2122        use LABColor::*;
2123
2124        match color {
2125          LAB(v) => v.into(),
2126          LCH(v) => v.into(),
2127          OKLAB(v) => v.into(),
2128          OKLCH(v) => v.into(),
2129        }
2130      }
2131    }
2132
2133    impl From<PredefinedColor> for $space {
2134      fn from(color: PredefinedColor) -> $space {
2135        use PredefinedColor::*;
2136
2137        match color {
2138          SRGB(v) => v.into(),
2139          SRGBLinear(v) => v.into(),
2140          DisplayP3(v) => v.into(),
2141          A98(v) => v.into(),
2142          ProPhoto(v) => v.into(),
2143          Rec2020(v) => v.into(),
2144          XYZd50(v) => v.into(),
2145          XYZd65(v) => v.into(),
2146        }
2147      }
2148    }
2149
2150    impl From<FloatColor> for $space {
2151      fn from(color: FloatColor) -> $space {
2152        use FloatColor::*;
2153
2154        match color {
2155          RGB(v) => v.into(),
2156          HSL(v) => v.into(),
2157          HWB(v) => v.into(),
2158        }
2159      }
2160    }
2161
2162    impl From<&CssColor> for $space {
2163      fn from(color: &CssColor) -> $space {
2164        match color {
2165          CssColor::RGBA(rgba) => (*rgba).into(),
2166          CssColor::LAB(lab) => (**lab).into(),
2167          CssColor::Predefined(predefined) => (**predefined).into(),
2168          CssColor::Float(float) => (**float).into(),
2169          CssColor::CurrentColor => unreachable!(),
2170        }
2171      }
2172    }
2173  };
2174}
2175
2176color_space!(LAB);
2177color_space!(LCH);
2178color_space!(OKLAB);
2179color_space!(OKLCH);
2180color_space!(SRGB);
2181color_space!(SRGBLinear);
2182color_space!(XYZd50);
2183color_space!(XYZd65);
2184color_space!(P3);
2185color_space!(A98);
2186color_space!(ProPhoto);
2187color_space!(Rec2020);
2188color_space!(HSL);
2189color_space!(HWB);
2190color_space!(RGBA);
2191
2192macro_rules! predefined {
2193  ($key: ident, $t: ty) => {
2194    impl From<$t> for PredefinedColor {
2195      fn from(color: $t) -> PredefinedColor {
2196        PredefinedColor::$key(color)
2197      }
2198    }
2199
2200    impl From<$t> for CssColor {
2201      fn from(color: $t) -> CssColor {
2202        CssColor::Predefined(Box::new(PredefinedColor::$key(color)))
2203      }
2204    }
2205  };
2206}
2207
2208predefined!(SRGBLinear, SRGBLinear);
2209predefined!(XYZd50, XYZd50);
2210predefined!(XYZd65, XYZd65);
2211predefined!(DisplayP3, P3);
2212predefined!(A98, A98);
2213predefined!(ProPhoto, ProPhoto);
2214predefined!(Rec2020, Rec2020);
2215
2216macro_rules! lab {
2217  ($key: ident, $t: ty) => {
2218    impl From<$t> for LABColor {
2219      fn from(color: $t) -> LABColor {
2220        LABColor::$key(color)
2221      }
2222    }
2223
2224    impl From<$t> for CssColor {
2225      fn from(color: $t) -> CssColor {
2226        CssColor::LAB(Box::new(LABColor::$key(color)))
2227      }
2228    }
2229  };
2230}
2231
2232lab!(LAB, LAB);
2233lab!(LCH, LCH);
2234lab!(OKLAB, OKLAB);
2235lab!(OKLCH, OKLCH);
2236
2237macro_rules! rgb {
2238  ($t: ty) => {
2239    impl From<$t> for CssColor {
2240      fn from(color: $t) -> CssColor {
2241        // TODO: should we serialize as color(srgb, ...)?
2242        // would be more precise than 8-bit color.
2243        CssColor::RGBA(color.into())
2244      }
2245    }
2246  };
2247}
2248
2249rgb!(SRGB);
2250rgb!(HSL);
2251rgb!(HWB);
2252
2253impl From<RGBA> for CssColor {
2254  fn from(color: RGBA) -> CssColor {
2255    CssColor::RGBA(color)
2256  }
2257}
2258
2259/// A trait that colors implement to support [gamut mapping](https://www.w3.org/TR/css-color-4/#gamut-mapping).
2260pub trait ColorGamut {
2261  /// Returns whether the color is within the gamut of the color space.
2262  fn in_gamut(&self) -> bool;
2263  /// Clips the color so that it is within the gamut of the color space.
2264  fn clip(&self) -> Self;
2265}
2266
2267macro_rules! bounded_color_gamut {
2268  ($t: ty, $a: ident, $b: ident, $c: ident) => {
2269    impl ColorGamut for $t {
2270      #[inline]
2271      fn in_gamut(&self) -> bool {
2272        self.$a >= 0.0 && self.$a <= 1.0 && self.$b >= 0.0 && self.$b <= 1.0 && self.$c >= 0.0 && self.$c <= 1.0
2273      }
2274
2275      #[inline]
2276      fn clip(&self) -> Self {
2277        Self {
2278          $a: self.$a.clamp(0.0, 1.0),
2279          $b: self.$b.clamp(0.0, 1.0),
2280          $c: self.$c.clamp(0.0, 1.0),
2281          alpha: self.alpha.clamp(0.0, 1.0),
2282        }
2283      }
2284    }
2285  };
2286}
2287
2288macro_rules! unbounded_color_gamut {
2289  ($t: ty, $a: ident, $b: ident, $c: ident) => {
2290    impl ColorGamut for $t {
2291      #[inline]
2292      fn in_gamut(&self) -> bool {
2293        true
2294      }
2295
2296      #[inline]
2297      fn clip(&self) -> Self {
2298        *self
2299      }
2300    }
2301  };
2302}
2303
2304macro_rules! hsl_hwb_color_gamut {
2305  ($t: ty, $a: ident, $b: ident) => {
2306    impl ColorGamut for $t {
2307      #[inline]
2308      fn in_gamut(&self) -> bool {
2309        self.$a >= 0.0 && self.$a <= 1.0 && self.$b >= 0.0 && self.$b <= 1.0
2310      }
2311
2312      #[inline]
2313      fn clip(&self) -> Self {
2314        Self {
2315          h: self.h % 360.0,
2316          $a: self.$a.clamp(0.0, 1.0),
2317          $b: self.$b.clamp(0.0, 1.0),
2318          alpha: self.alpha.clamp(0.0, 1.0),
2319        }
2320      }
2321    }
2322  };
2323}
2324
2325bounded_color_gamut!(SRGB, r, g, b);
2326bounded_color_gamut!(SRGBLinear, r, g, b);
2327bounded_color_gamut!(P3, r, g, b);
2328bounded_color_gamut!(A98, r, g, b);
2329bounded_color_gamut!(ProPhoto, r, g, b);
2330bounded_color_gamut!(Rec2020, r, g, b);
2331unbounded_color_gamut!(LAB, l, a, b);
2332unbounded_color_gamut!(OKLAB, l, a, b);
2333unbounded_color_gamut!(XYZd50, x, y, z);
2334unbounded_color_gamut!(XYZd65, x, y, z);
2335unbounded_color_gamut!(LCH, l, c, h);
2336unbounded_color_gamut!(OKLCH, l, c, h);
2337hsl_hwb_color_gamut!(HSL, s, l);
2338hsl_hwb_color_gamut!(HWB, w, b);
2339
2340fn delta_eok<T: Into<OKLAB>>(a: T, b: OKLCH) -> f32 {
2341  // https://www.w3.org/TR/css-color-4/#color-difference-OK
2342  let a: OKLAB = a.into();
2343  let b: OKLAB = b.into();
2344  let delta_l = a.l - b.l;
2345  let delta_a = a.a - b.a;
2346  let delta_b = a.b - b.b;
2347
2348  (delta_l.powi(2) + delta_a.powi(2) + delta_b.powi(2)).sqrt()
2349}
2350
2351fn map_gamut<T>(color: T) -> T
2352where
2353  T: Into<OKLCH> + ColorGamut + Into<OKLAB> + From<OKLCH> + Copy,
2354{
2355  const JND: f32 = 0.02;
2356  const EPSILON: f32 = 0.00001;
2357
2358  // https://www.w3.org/TR/css-color-4/#binsearch
2359  let mut current: OKLCH = color.into();
2360
2361  // If lightness is >= 100%, return pure white.
2362  if (current.l - 1.0).abs() < EPSILON || current.l > 1.0 {
2363    return OKLCH {
2364      l: 1.0,
2365      c: 0.0,
2366      h: 0.0,
2367      alpha: current.alpha,
2368    }
2369    .into();
2370  }
2371
2372  // If lightness <= 0%, return pure black.
2373  if current.l < EPSILON {
2374    return OKLCH {
2375      l: 0.0,
2376      c: 0.0,
2377      h: 0.0,
2378      alpha: current.alpha,
2379    }
2380    .into();
2381  }
2382
2383  let mut min = 0.0;
2384  let mut max = current.c;
2385
2386  while min < max {
2387    let chroma = (min + max) / 2.0;
2388    current.c = chroma;
2389
2390    let converted = T::from(current);
2391    if converted.in_gamut() {
2392      min = chroma;
2393      continue;
2394    }
2395
2396    let clipped = converted.clip();
2397    let delta_e = delta_eok(clipped, current);
2398    if delta_e < JND {
2399      return clipped;
2400    }
2401
2402    max = chroma;
2403  }
2404
2405  current.into()
2406}
2407
2408fn parse_color_mix<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
2409  input.expect_ident_matching("in")?;
2410  let method = ColorSpace::parse(input)?;
2411
2412  let hue_method = if matches!(
2413    method,
2414    ColorSpace::Hsl | ColorSpace::Hwb | ColorSpace::LCH | ColorSpace::OKLCH
2415  ) {
2416    let hue_method = input.try_parse(HueInterpolationMethod::parse);
2417    if hue_method.is_ok() {
2418      input.expect_ident_matching("hue")?;
2419    }
2420    hue_method
2421  } else {
2422    Ok(HueInterpolationMethod::Shorter)
2423  };
2424
2425  let hue_method = hue_method.unwrap_or(HueInterpolationMethod::Shorter);
2426  input.expect_comma()?;
2427
2428  let first_percent = input.try_parse(|input| input.expect_percentage());
2429  let first_color = CssColor::parse(input)?;
2430  let first_percent = first_percent
2431    .or_else(|_| input.try_parse(|input| input.expect_percentage()))
2432    .ok();
2433  input.expect_comma()?;
2434
2435  let second_percent = input.try_parse(|input| input.expect_percentage());
2436  let second_color = CssColor::parse(input)?;
2437  let second_percent = second_percent
2438    .or_else(|_| input.try_parse(|input| input.expect_percentage()))
2439    .ok();
2440
2441  // https://drafts.csswg.org/css-color-5/#color-mix-percent-norm
2442  let (p1, p2) = if first_percent.is_none() && second_percent.is_none() {
2443    (0.5, 0.5)
2444  } else {
2445    let p2 = second_percent.unwrap_or_else(|| 1.0 - first_percent.unwrap());
2446    let p1 = first_percent.unwrap_or_else(|| 1.0 - second_percent.unwrap());
2447    (p1, p2)
2448  };
2449
2450  if (p1 + p2) == 0.0 {
2451    return Err(input.new_custom_error(ParserError::InvalidValue));
2452  }
2453
2454  Ok(match method {
2455    ColorSpace::SRGB => first_color.interpolate::<SRGB>(p1, &second_color, p2, hue_method),
2456    ColorSpace::SRGBLinear => first_color.interpolate::<SRGBLinear>(p1, &second_color, p2, hue_method),
2457    ColorSpace::Hsl => first_color.interpolate::<HSL>(p1, &second_color, p2, hue_method),
2458    ColorSpace::Hwb => first_color.interpolate::<HWB>(p1, &second_color, p2, hue_method),
2459    ColorSpace::LAB => first_color.interpolate::<LAB>(p1, &second_color, p2, hue_method),
2460    ColorSpace::LCH => first_color.interpolate::<LCH>(p1, &second_color, p2, hue_method),
2461    ColorSpace::OKLAB => first_color.interpolate::<OKLAB>(p1, &second_color, p2, hue_method),
2462    ColorSpace::OKLCH => first_color.interpolate::<OKLCH>(p1, &second_color, p2, hue_method),
2463    ColorSpace::XYZ | ColorSpace::XYZd65 => first_color.interpolate::<XYZd65>(p1, &second_color, p2, hue_method),
2464    ColorSpace::XYZd50 => first_color.interpolate::<XYZd50>(p1, &second_color, p2, hue_method),
2465  })
2466}
2467
2468impl CssColor {
2469  fn get_type_id(&self) -> TypeId {
2470    match self {
2471      CssColor::RGBA(..) => TypeId::of::<SRGB>(),
2472      CssColor::LAB(lab) => match &**lab {
2473        LABColor::LAB(..) => TypeId::of::<LAB>(),
2474        LABColor::LCH(..) => TypeId::of::<LCH>(),
2475        LABColor::OKLAB(..) => TypeId::of::<OKLAB>(),
2476        LABColor::OKLCH(..) => TypeId::of::<OKLCH>(),
2477      },
2478      CssColor::Predefined(predefined) => match &**predefined {
2479        PredefinedColor::SRGB(..) => TypeId::of::<SRGB>(),
2480        PredefinedColor::SRGBLinear(..) => TypeId::of::<SRGBLinear>(),
2481        PredefinedColor::DisplayP3(..) => TypeId::of::<P3>(),
2482        PredefinedColor::A98(..) => TypeId::of::<A98>(),
2483        PredefinedColor::ProPhoto(..) => TypeId::of::<ProPhoto>(),
2484        PredefinedColor::Rec2020(..) => TypeId::of::<Rec2020>(),
2485        PredefinedColor::XYZd50(..) => TypeId::of::<XYZd50>(),
2486        PredefinedColor::XYZd65(..) => TypeId::of::<XYZd65>(),
2487      },
2488      CssColor::Float(float) => match &**float {
2489        FloatColor::RGB(..) => TypeId::of::<SRGB>(),
2490        FloatColor::HSL(..) => TypeId::of::<HSL>(),
2491        FloatColor::HWB(..) => TypeId::of::<HWB>(),
2492      },
2493      _ => unreachable!(),
2494    }
2495  }
2496
2497  /// Mixes this color with another color, including the specified amount of each.
2498  /// Implemented according to the [`color-mix()`](https://www.w3.org/TR/css-color-5/#color-mix) function.
2499  pub fn interpolate<'a, T>(
2500    &'a self,
2501    mut p1: f32,
2502    other: &'a CssColor,
2503    mut p2: f32,
2504    method: HueInterpolationMethod,
2505  ) -> CssColor
2506  where
2507    T: 'static
2508      + From<&'a CssColor>
2509      + Interpolate
2510      + Into<CssColor>
2511      + Into<OKLCH>
2512      + ColorGamut
2513      + Into<OKLAB>
2514      + From<OKLCH>
2515      + Copy,
2516  {
2517    let type_id = TypeId::of::<T>();
2518    let converted_first = self.get_type_id() != type_id;
2519    let converted_second = other.get_type_id() != type_id;
2520
2521    // https://drafts.csswg.org/css-color-5/#color-mix-result
2522    let mut first_color = T::from(self);
2523    let mut second_color = T::from(other);
2524
2525    if converted_first && !first_color.in_gamut() {
2526      first_color = map_gamut(first_color);
2527    }
2528
2529    if converted_second && !second_color.in_gamut() {
2530      second_color = map_gamut(second_color);
2531    }
2532
2533    // https://www.w3.org/TR/css-color-4/#powerless
2534    if converted_first {
2535      first_color.adjust_powerless_components();
2536    }
2537
2538    if converted_second {
2539      second_color.adjust_powerless_components();
2540    }
2541
2542    // https://drafts.csswg.org/css-color-4/#interpolation-missing
2543    first_color.fill_missing_components(&second_color);
2544    second_color.fill_missing_components(&first_color);
2545
2546    // https://www.w3.org/TR/css-color-4/#hue-interpolation
2547    first_color.adjust_hue(&mut second_color, method);
2548
2549    // https://www.w3.org/TR/css-color-4/#interpolation-alpha
2550    first_color.premultiply();
2551    second_color.premultiply();
2552
2553    // https://drafts.csswg.org/css-color-5/#color-mix-percent-norm
2554    let mut alpha_multiplier = p1 + p2;
2555    if alpha_multiplier != 1.0 {
2556      p1 = p1 / alpha_multiplier;
2557      p2 = p2 / alpha_multiplier;
2558      if alpha_multiplier > 1.0 {
2559        alpha_multiplier = 1.0;
2560      }
2561    }
2562
2563    let mut result_color = first_color.interpolate(p1, &second_color, p2);
2564    result_color.unpremultiply(alpha_multiplier);
2565
2566    result_color.into()
2567  }
2568}
2569
2570/// A trait that colors implement to support interpolation.
2571pub trait Interpolate {
2572  /// Adjusts components that are powerless to be NaN.
2573  fn adjust_powerless_components(&mut self) {}
2574  /// Fills missing components (represented as NaN) to match the other color to interpolate with.
2575  fn fill_missing_components(&mut self, other: &Self);
2576  /// Adjusts the color hue according to the given hue interpolation method.
2577  fn adjust_hue(&mut self, _: &mut Self, _: HueInterpolationMethod) {}
2578  /// Premultiplies the color by its alpha value.
2579  fn premultiply(&mut self);
2580  /// Un-premultiplies the color by the given alpha multiplier.
2581  fn unpremultiply(&mut self, alpha_multiplier: f32);
2582  /// Interpolates the color with another using the given amounts of each.
2583  fn interpolate(&self, p1: f32, other: &Self, p2: f32) -> Self;
2584}
2585
2586macro_rules! interpolate {
2587  ($a: ident, $b: ident, $c: ident) => {
2588    fn fill_missing_components(&mut self, other: &Self) {
2589      if self.$a.is_nan() {
2590        self.$a = other.$a;
2591      }
2592
2593      if self.$b.is_nan() {
2594        self.$b = other.$b;
2595      }
2596
2597      if self.$c.is_nan() {
2598        self.$c = other.$c;
2599      }
2600
2601      if self.alpha.is_nan() {
2602        self.alpha = other.alpha;
2603      }
2604    }
2605
2606    fn interpolate(&self, p1: f32, other: &Self, p2: f32) -> Self {
2607      Self {
2608        $a: self.$a * p1 + other.$a * p2,
2609        $b: self.$b * p1 + other.$b * p2,
2610        $c: self.$c * p1 + other.$c * p2,
2611        alpha: self.alpha * p1 + other.alpha * p2,
2612      }
2613    }
2614  };
2615}
2616
2617macro_rules! rectangular_premultiply {
2618  ($a: ident, $b: ident, $c: ident) => {
2619    fn premultiply(&mut self) {
2620      if !self.alpha.is_nan() {
2621        self.$a *= self.alpha;
2622        self.$b *= self.alpha;
2623        self.$c *= self.alpha;
2624      }
2625    }
2626
2627    fn unpremultiply(&mut self, alpha_multiplier: f32) {
2628      if !self.alpha.is_nan() && self.alpha != 0.0 {
2629        self.$a /= self.alpha;
2630        self.$b /= self.alpha;
2631        self.$c /= self.alpha;
2632        self.alpha *= alpha_multiplier;
2633      }
2634    }
2635  };
2636}
2637
2638macro_rules! polar_premultiply {
2639  ($a: ident, $b: ident) => {
2640    fn premultiply(&mut self) {
2641      if !self.alpha.is_nan() {
2642        self.$a *= self.alpha;
2643        self.$b *= self.alpha;
2644      }
2645    }
2646
2647    fn unpremultiply(&mut self, alpha_multiplier: f32) {
2648      self.h %= 360.0;
2649      if !self.alpha.is_nan() {
2650        self.$a /= self.alpha;
2651        self.$b /= self.alpha;
2652        self.alpha *= alpha_multiplier;
2653      }
2654    }
2655  };
2656}
2657
2658macro_rules! adjust_powerless_lab {
2659  () => {
2660    fn adjust_powerless_components(&mut self) {
2661      // If the lightness of a LAB color is 0%, both the a and b components are powerless.
2662      if self.l.abs() < f32::EPSILON {
2663        self.a = f32::NAN;
2664        self.b = f32::NAN;
2665      }
2666    }
2667  };
2668}
2669
2670macro_rules! adjust_powerless_lch {
2671  () => {
2672    fn adjust_powerless_components(&mut self) {
2673      // If the chroma of an LCH color is 0%, the hue component is powerless.
2674      // If the lightness of an LCH color is 0%, both the hue and chroma components are powerless.
2675      if self.c.abs() < f32::EPSILON {
2676        self.h = f32::NAN;
2677      }
2678
2679      if self.l.abs() < f32::EPSILON {
2680        self.c = f32::NAN;
2681        self.h = f32::NAN;
2682      }
2683    }
2684
2685    fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {
2686      method.interpolate(&mut self.h, &mut other.h);
2687    }
2688  };
2689}
2690
2691impl Interpolate for SRGB {
2692  rectangular_premultiply!(r, g, b);
2693  interpolate!(r, g, b);
2694}
2695
2696impl Interpolate for SRGBLinear {
2697  rectangular_premultiply!(r, g, b);
2698  interpolate!(r, g, b);
2699}
2700
2701impl Interpolate for XYZd50 {
2702  rectangular_premultiply!(x, y, z);
2703  interpolate!(x, y, z);
2704}
2705
2706impl Interpolate for XYZd65 {
2707  rectangular_premultiply!(x, y, z);
2708  interpolate!(x, y, z);
2709}
2710
2711impl Interpolate for LAB {
2712  adjust_powerless_lab!();
2713  rectangular_premultiply!(l, a, b);
2714  interpolate!(l, a, b);
2715}
2716
2717impl Interpolate for OKLAB {
2718  adjust_powerless_lab!();
2719  rectangular_premultiply!(l, a, b);
2720  interpolate!(l, a, b);
2721}
2722
2723impl Interpolate for LCH {
2724  adjust_powerless_lch!();
2725  polar_premultiply!(l, c);
2726  interpolate!(l, c, h);
2727}
2728
2729impl Interpolate for OKLCH {
2730  adjust_powerless_lch!();
2731  polar_premultiply!(l, c);
2732  interpolate!(l, c, h);
2733}
2734
2735impl Interpolate for HSL {
2736  polar_premultiply!(s, l);
2737
2738  fn adjust_powerless_components(&mut self) {
2739    // If the saturation of an HSL color is 0%, then the hue component is powerless.
2740    // If the lightness of an HSL color is 0% or 100%, both the saturation and hue components are powerless.
2741    if self.s.abs() < f32::EPSILON {
2742      self.h = f32::NAN;
2743    }
2744
2745    if self.l.abs() < f32::EPSILON || (self.l - 1.0).abs() < f32::EPSILON {
2746      self.h = f32::NAN;
2747      self.s = f32::NAN;
2748    }
2749  }
2750
2751  fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {
2752    method.interpolate(&mut self.h, &mut other.h);
2753  }
2754
2755  interpolate!(h, s, l);
2756}
2757
2758impl Interpolate for HWB {
2759  polar_premultiply!(w, b);
2760
2761  fn adjust_powerless_components(&mut self) {
2762    // If white+black is equal to 100% (after normalization), it defines an achromatic color,
2763    // i.e. some shade of gray, without any hint of the chosen hue. In this case, the hue component is powerless.
2764    if (self.w + self.b - 1.0).abs() < f32::EPSILON {
2765      self.h = f32::NAN;
2766    }
2767  }
2768
2769  fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {
2770    method.interpolate(&mut self.h, &mut other.h);
2771  }
2772
2773  interpolate!(h, w, b);
2774}
2775
2776impl HueInterpolationMethod {
2777  fn interpolate(&self, a: &mut f32, b: &mut f32) {
2778    // https://drafts.csswg.org/css-color/#hue-interpolation
2779    if *self != HueInterpolationMethod::Specified {
2780      *a = ((*a % 360.0) + 360.0) % 360.0;
2781      *b = ((*b % 360.0) + 360.0) % 360.0;
2782    }
2783
2784    match self {
2785      HueInterpolationMethod::Shorter => {
2786        // https://www.w3.org/TR/css-color-4/#hue-shorter
2787        let delta = *b - *a;
2788        if delta > 180.0 {
2789          *a += 360.0;
2790        } else if delta < -180.0 {
2791          *b += 360.0;
2792        }
2793      }
2794      HueInterpolationMethod::Longer => {
2795        // https://www.w3.org/TR/css-color-4/#hue-longer
2796        let delta = *b - *a;
2797        if 0.0 < delta && delta < 180.0 {
2798          *a += 360.0;
2799        } else if -180.0 < delta && delta < 0.0 {
2800          *b += 360.0;
2801        }
2802      }
2803      HueInterpolationMethod::Increasing => {
2804        // https://www.w3.org/TR/css-color-4/#hue-increasing
2805        if *b < *a {
2806          *b += 360.0;
2807        }
2808      }
2809      HueInterpolationMethod::Decreasing => {
2810        // https://www.w3.org/TR/css-color-4/#hue-decreasing
2811        if *a < *b {
2812          *a += 360.0;
2813        }
2814      }
2815      HueInterpolationMethod::Specified => {}
2816    }
2817  }
2818}