1#![allow(non_upper_case_globals)]
4
5use super::angle::Angle;
6use super::calc::Calc;
7use super::number::CSSNumber;
8use super::percentage::Percentage;
9use crate::compat::Feature;
10use crate::error::{ParserError, PrinterError};
11use crate::macros::enum_property;
12use crate::printer::Printer;
13use crate::properties::PropertyId;
14use crate::rules::supports::SupportsCondition;
15use crate::targets::{should_compile, Browsers, Features, Targets};
16use crate::traits::{FallbackValues, IsCompatible, Parse, ToCss};
17#[cfg(feature = "visitor")]
18use crate::visitor::{Visit, VisitTypes, Visitor};
19use bitflags::bitflags;
20use cssparser::color::{parse_hash_color, parse_named_color};
21use cssparser::*;
22use cssparser_color::{hsl_to_rgb, AngleOrNumber, ColorParser, NumberOrPercentage};
23use std::any::TypeId;
24use std::f32::consts::PI;
25use std::fmt::Write;
26
27#[derive(Debug, Clone, PartialEq)]
37#[cfg_attr(feature = "visitor", derive(Visit))]
38#[cfg_attr(feature = "visitor", visit(visit_color, COLORS))]
39#[cfg_attr(
40 feature = "serde",
41 derive(serde::Serialize, serde::Deserialize),
42 serde(untagged, rename_all = "lowercase")
43)]
44#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
45#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
46pub enum CssColor {
47 #[cfg_attr(feature = "serde", serde(with = "CurrentColor"))]
49 CurrentColor,
50 #[cfg_attr(
52 feature = "serde",
53 serde(serialize_with = "serialize_rgba", deserialize_with = "deserialize_rgba")
54 )]
55 #[cfg_attr(feature = "jsonschema", schemars(with = "RGBColor"))]
56 RGBA(RGBA),
57 LAB(Box<LABColor>),
59 Predefined(Box<PredefinedColor>),
61 Float(Box<FloatColor>),
63 #[cfg_attr(feature = "visitor", skip_type)]
65 #[cfg_attr(feature = "serde", serde(with = "LightDark"))]
66 LightDark(Box<CssColor>, Box<CssColor>),
67 System(SystemColor),
69}
70
71#[cfg(feature = "serde")]
72#[derive(serde::Serialize, serde::Deserialize)]
73#[serde(tag = "type", rename_all = "lowercase")]
74#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
75enum CurrentColor {
76 CurrentColor,
77}
78
79#[cfg(feature = "serde")]
80impl CurrentColor {
81 fn serialize<S>(serializer: S) -> Result<S::Ok, S::Error>
82 where
83 S: serde::Serializer,
84 {
85 serde::Serialize::serialize(&CurrentColor::CurrentColor, serializer)
86 }
87
88 fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error>
89 where
90 D: serde::Deserializer<'de>,
91 {
92 use serde::Deserialize;
93 let _: CurrentColor = Deserialize::deserialize(deserializer)?;
94 Ok(())
95 }
96}
97
98#[cfg(feature = "serde")]
100#[derive(serde::Serialize, serde::Deserialize)]
101#[serde(tag = "type", rename_all = "lowercase")]
102#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
103enum RGBColor {
104 RGB(RGB),
105}
106
107#[cfg(feature = "serde")]
108fn serialize_rgba<S>(rgba: &RGBA, serializer: S) -> Result<S::Ok, S::Error>
109where
110 S: serde::Serializer,
111{
112 use serde::Serialize;
113 RGBColor::RGB(rgba.into()).serialize(serializer)
114}
115
116#[cfg(feature = "serde")]
117fn deserialize_rgba<'de, D>(deserializer: D) -> Result<RGBA, D::Error>
118where
119 D: serde::Deserializer<'de>,
120{
121 use serde::Deserialize;
122 match RGBColor::deserialize(deserializer)? {
123 RGBColor::RGB(srgb) => Ok(srgb.into()),
124 }
125}
126
127#[cfg(feature = "serde")]
129#[derive(serde::Serialize, serde::Deserialize)]
130#[serde(tag = "type", rename_all = "kebab-case")]
131#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
132enum LightDark {
133 LightDark { light: CssColor, dark: CssColor },
134}
135
136#[cfg(feature = "serde")]
137impl<'de> LightDark {
138 pub fn serialize<S>(light: &Box<CssColor>, dark: &Box<CssColor>, serializer: S) -> Result<S::Ok, S::Error>
139 where
140 S: serde::Serializer,
141 {
142 let wrapper = LightDark::LightDark {
143 light: (**light).clone(),
144 dark: (**dark).clone(),
145 };
146 serde::Serialize::serialize(&wrapper, serializer)
147 }
148
149 pub fn deserialize<D>(deserializer: D) -> Result<(Box<CssColor>, Box<CssColor>), D::Error>
150 where
151 D: serde::Deserializer<'de>,
152 {
153 let v: LightDark = serde::Deserialize::deserialize(deserializer)?;
154 match v {
155 LightDark::LightDark { light, dark } => Ok((Box::new(light), Box::new(dark))),
156 }
157 }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq)]
162#[cfg_attr(feature = "visitor", derive(Visit))]
163#[cfg_attr(
164 feature = "serde",
165 derive(serde::Serialize, serde::Deserialize),
166 serde(tag = "type", rename_all = "lowercase")
167)]
168#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
169pub enum LABColor {
170 LAB(LAB),
172 LCH(LCH),
174 OKLAB(OKLAB),
176 OKLCH(OKLCH),
178}
179
180#[derive(Debug, Clone, Copy, PartialEq)]
182#[cfg_attr(feature = "visitor", derive(Visit))]
183#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type"))]
184#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
185pub enum PredefinedColor {
186 #[cfg_attr(feature = "serde", serde(rename = "srgb"))]
188 SRGB(SRGB),
189 #[cfg_attr(feature = "serde", serde(rename = "srgb-linear"))]
191 SRGBLinear(SRGBLinear),
192 #[cfg_attr(feature = "serde", serde(rename = "display-p3"))]
194 DisplayP3(P3),
195 #[cfg_attr(feature = "serde", serde(rename = "a98-rgb"))]
197 A98(A98),
198 #[cfg_attr(feature = "serde", serde(rename = "prophoto-rgb"))]
200 ProPhoto(ProPhoto),
201 #[cfg_attr(feature = "serde", serde(rename = "rec2020"))]
203 Rec2020(Rec2020),
204 #[cfg_attr(feature = "serde", serde(rename = "xyz-d50"))]
206 XYZd50(XYZd50),
207 #[cfg_attr(feature = "serde", serde(rename = "xyz-d65"))]
209 XYZd65(XYZd65),
210}
211
212#[derive(Debug, Clone, Copy, PartialEq)]
216#[cfg_attr(feature = "visitor", derive(Visit))]
217#[cfg_attr(
218 feature = "serde",
219 derive(serde::Serialize, serde::Deserialize),
220 serde(tag = "type", rename_all = "lowercase")
221)]
222#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
223pub enum FloatColor {
224 RGB(RGB),
226 HSL(HSL),
228 HWB(HWB),
230}
231
232bitflags! {
233 #[derive(PartialEq, Eq, Clone, Copy)]
235 pub struct ColorFallbackKind: u8 {
236 const RGB = 0b01;
238 const P3 = 0b10;
240 const LAB = 0b100;
242 const OKLAB = 0b1000;
244 }
245}
246
247enum_property! {
248 enum ColorSpaceName {
251 "srgb": SRGB,
252 "srgb-linear": SRGBLinear,
253 "lab": LAB,
254 "oklab": OKLAB,
255 "xyz": XYZ,
256 "xyz-d50": XYZd50,
257 "xyz-d65": XYZd65,
258 "hsl": Hsl,
259 "hwb": Hwb,
260 "lch": LCH,
261 "oklch": OKLCH,
262 }
263}
264
265enum_property! {
266 pub enum HueInterpolationMethod {
269 Shorter,
271 Longer,
273 Increasing,
275 Decreasing,
277 Specified,
279 }
280}
281
282impl ColorFallbackKind {
283 pub(crate) fn lowest(&self) -> ColorFallbackKind {
284 *self & ColorFallbackKind::from_bits_truncate(self.bits().wrapping_neg())
286 }
287
288 pub(crate) fn highest(&self) -> ColorFallbackKind {
289 if self.is_empty() {
291 return ColorFallbackKind::empty();
292 }
293
294 let zeros = 7 - self.bits().leading_zeros();
295 ColorFallbackKind::from_bits_truncate(1 << zeros)
296 }
297
298 pub(crate) fn and_below(&self) -> ColorFallbackKind {
299 if self.is_empty() {
300 return ColorFallbackKind::empty();
301 }
302
303 *self | ColorFallbackKind::from_bits_truncate(self.bits() - 1)
304 }
305
306 pub(crate) fn supports_condition<'i>(&self) -> SupportsCondition<'i> {
307 let s = match *self {
308 ColorFallbackKind::P3 => "color(display-p3 0 0 0)",
309 ColorFallbackKind::LAB => "lab(0% 0 0)",
310 _ => unreachable!(),
311 };
312
313 SupportsCondition::Declaration {
314 property_id: PropertyId::Color,
315 value: s.into(),
316 }
317 }
318}
319
320impl CssColor {
321 pub fn current_color() -> CssColor {
323 CssColor::CurrentColor
324 }
325
326 pub fn transparent() -> CssColor {
328 CssColor::RGBA(RGBA::transparent())
329 }
330
331 fn alpha(&self) -> Result<f32, ()> {
332 Ok(match self {
333 CssColor::RGBA(rgba) => rgba.alpha_f32(),
334 CssColor::LAB(lab) => match &**lab {
335 LABColor::LAB(lab) => lab.alpha,
336 LABColor::LCH(lch) => lch.alpha,
337 LABColor::OKLAB(lab) => lab.alpha,
338 LABColor::OKLCH(lch) => lch.alpha,
339 },
340 CssColor::Predefined(predefined) => match &**predefined {
341 PredefinedColor::SRGB(rgb) => rgb.alpha,
342 PredefinedColor::SRGBLinear(rgb) => rgb.alpha,
343 PredefinedColor::DisplayP3(rgb) => rgb.alpha,
344 PredefinedColor::A98(rgb) => rgb.alpha,
345 PredefinedColor::ProPhoto(rgb) => rgb.alpha,
346 PredefinedColor::Rec2020(rgb) => rgb.alpha,
347 PredefinedColor::XYZd50(xyz) => xyz.alpha,
348 PredefinedColor::XYZd65(xyz) => xyz.alpha,
349 },
350 CssColor::Float(float) => match &**float {
351 FloatColor::RGB(rgb) => rgb.alpha,
352 FloatColor::HSL(hsl) => hsl.alpha,
353 FloatColor::HWB(hwb) => hwb.alpha,
354 },
355 CssColor::LightDark(..) | CssColor::CurrentColor | CssColor::System(..) => return Err(()),
356 })
357 }
358
359 fn with_alpha(self, alpha: f32) -> CssColor {
360 match self {
361 CssColor::RGBA(rgba) => {
362 let mut rgb = RGB::from(rgba);
366 rgb.alpha = alpha;
367 CssColor::Float(Box::new(FloatColor::RGB(rgb)))
368 }
369 CssColor::LAB(mut lab) => {
370 match &mut *lab {
371 LABColor::LAB(lab) => lab.alpha = alpha,
372 LABColor::LCH(lch) => lch.alpha = alpha,
373 LABColor::OKLAB(lab) => lab.alpha = alpha,
374 LABColor::OKLCH(lch) => lch.alpha = alpha,
375 }
376 CssColor::LAB(lab)
377 }
378 CssColor::Predefined(mut predefined) => {
379 match &mut *predefined {
380 PredefinedColor::SRGB(rgb) => rgb.alpha = alpha,
381 PredefinedColor::SRGBLinear(rgb) => rgb.alpha = alpha,
382 PredefinedColor::DisplayP3(rgb) => rgb.alpha = alpha,
383 PredefinedColor::A98(rgb) => rgb.alpha = alpha,
384 PredefinedColor::ProPhoto(rgb) => rgb.alpha = alpha,
385 PredefinedColor::Rec2020(rgb) => rgb.alpha = alpha,
386 PredefinedColor::XYZd50(xyz) => xyz.alpha = alpha,
387 PredefinedColor::XYZd65(xyz) => xyz.alpha = alpha,
388 }
389 CssColor::Predefined(predefined)
390 }
391 CssColor::Float(mut float) => {
392 match &mut *float {
393 FloatColor::RGB(rgb) => rgb.alpha = alpha,
394 FloatColor::HSL(hsl) => hsl.alpha = alpha,
395 FloatColor::HWB(hwb) => hwb.alpha = alpha,
396 }
397 CssColor::Float(float)
398 }
399 CssColor::LightDark(light, dark) => CssColor::LightDark(
400 Box::new((*light).with_alpha(alpha)),
401 Box::new((*dark).with_alpha(alpha)),
402 ),
403 color => color,
404 }
405 }
406
407 pub fn to_rgb(&self) -> Result<CssColor, ()> {
409 match self {
410 CssColor::LightDark(light, dark) => {
411 Ok(CssColor::LightDark(Box::new(light.to_rgb()?), Box::new(dark.to_rgb()?)))
412 }
413 _ => Ok(RGBA::try_from(self)?.into()),
414 }
415 }
416
417 pub fn to_lab(&self) -> Result<CssColor, ()> {
419 match self {
420 CssColor::LightDark(light, dark) => {
421 Ok(CssColor::LightDark(Box::new(light.to_lab()?), Box::new(dark.to_lab()?)))
422 }
423 _ => Ok(LAB::try_from(self)?.into()),
424 }
425 }
426
427 pub fn to_p3(&self) -> Result<CssColor, ()> {
429 match self {
430 CssColor::LightDark(light, dark) => {
431 Ok(CssColor::LightDark(Box::new(light.to_p3()?), Box::new(dark.to_p3()?)))
432 }
433 _ => Ok(P3::try_from(self)?.into()),
434 }
435 }
436
437 pub(crate) fn get_possible_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
438 let mut fallbacks = match self {
442 CssColor::CurrentColor | CssColor::RGBA(_) | CssColor::Float(..) | CssColor::System(..) => {
443 return ColorFallbackKind::empty()
444 }
445 CssColor::LAB(lab) => match &**lab {
446 LABColor::LAB(..) | LABColor::LCH(..) if should_compile!(targets, LabColors) => {
447 ColorFallbackKind::LAB.and_below()
448 }
449 LABColor::OKLAB(..) | LABColor::OKLCH(..) if should_compile!(targets, OklabColors) => {
450 ColorFallbackKind::OKLAB.and_below()
451 }
452 _ => return ColorFallbackKind::empty(),
453 },
454 CssColor::Predefined(predefined) => match &**predefined {
455 PredefinedColor::DisplayP3(..) if should_compile!(targets, P3Colors) => ColorFallbackKind::P3.and_below(),
456 _ if should_compile!(targets, ColorFunction) => ColorFallbackKind::LAB.and_below(),
457 _ => return ColorFallbackKind::empty(),
458 },
459 CssColor::LightDark(light, dark) => {
460 return light.get_possible_fallbacks(targets) | dark.get_possible_fallbacks(targets);
461 }
462 };
463
464 if fallbacks.contains(ColorFallbackKind::OKLAB) {
465 if !should_compile!(targets, OklabColors) {
466 fallbacks.remove(ColorFallbackKind::LAB.and_below());
467 }
468 }
469
470 if fallbacks.contains(ColorFallbackKind::LAB) {
471 if !should_compile!(targets, LabColors) {
472 fallbacks.remove(ColorFallbackKind::P3.and_below());
473 } else if targets
474 .browsers
475 .map(|targets| Feature::LabColors.is_partially_compatible(targets))
476 .unwrap_or(false)
477 {
478 fallbacks.remove(ColorFallbackKind::P3);
481 }
482 }
483
484 if fallbacks.contains(ColorFallbackKind::P3) {
485 if !should_compile!(targets, P3Colors) {
486 fallbacks.remove(ColorFallbackKind::RGB);
487 } else if fallbacks.highest() != ColorFallbackKind::P3
488 && !targets
489 .browsers
490 .map(|targets| Feature::P3Colors.is_partially_compatible(targets))
491 .unwrap_or(false)
492 {
493 fallbacks.remove(ColorFallbackKind::P3);
496 }
497 }
498
499 fallbacks
500 }
501
502 pub fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
504 let fallbacks = self.get_possible_fallbacks(targets);
507 fallbacks - fallbacks.highest()
508 }
509
510 pub fn get_fallback(&self, kind: ColorFallbackKind) -> CssColor {
512 if matches!(self, CssColor::RGBA(_)) {
513 return self.clone();
514 }
515
516 match kind {
517 ColorFallbackKind::RGB => self.to_rgb().unwrap(),
518 ColorFallbackKind::P3 => self.to_p3().unwrap(),
519 ColorFallbackKind::LAB => self.to_lab().unwrap(),
520 _ => unreachable!(),
521 }
522 }
523
524 pub(crate) fn get_features(&self) -> Features {
525 let mut features = Features::empty();
526 match self {
527 CssColor::LAB(labcolor) => match &**labcolor {
528 LABColor::LAB(_) | LABColor::LCH(_) => {
529 features |= Features::LabColors;
530 }
531 LABColor::OKLAB(_) | LABColor::OKLCH(_) => {
532 features |= Features::OklabColors;
533 }
534 },
535 CssColor::Predefined(predefined_color) => {
536 features |= Features::ColorFunction;
537 match &**predefined_color {
538 PredefinedColor::DisplayP3(_) => {
539 features |= Features::P3Colors;
540 }
541 _ => {}
542 }
543 }
544 CssColor::Float(_) => {
545 features |= Features::SpaceSeparatedColorNotation;
546 }
547 CssColor::LightDark(light, dark) => {
548 features |= Features::LightDark;
549 features |= light.get_features();
550 features |= dark.get_features();
551 }
552 _ => {}
553 }
554
555 features
556 }
557}
558
559impl IsCompatible for CssColor {
560 fn is_compatible(&self, browsers: Browsers) -> bool {
561 match self {
562 CssColor::CurrentColor | CssColor::RGBA(_) | CssColor::Float(..) => true,
563 CssColor::LAB(lab) => match &**lab {
564 LABColor::LAB(..) | LABColor::LCH(..) => Feature::LabColors.is_compatible(browsers),
565 LABColor::OKLAB(..) | LABColor::OKLCH(..) => Feature::OklabColors.is_compatible(browsers),
566 },
567 CssColor::Predefined(predefined) => match &**predefined {
568 PredefinedColor::DisplayP3(..) => Feature::P3Colors.is_compatible(browsers),
569 _ => Feature::ColorFunction.is_compatible(browsers),
570 },
571 CssColor::LightDark(light, dark) => {
572 Feature::LightDark.is_compatible(browsers) && light.is_compatible(browsers) && dark.is_compatible(browsers)
573 }
574 CssColor::System(system) => system.is_compatible(browsers),
575 }
576 }
577}
578
579impl FallbackValues for CssColor {
580 fn get_fallbacks(&mut self, targets: Targets) -> Vec<CssColor> {
581 let fallbacks = self.get_necessary_fallbacks(targets);
582
583 let mut res = Vec::new();
584 if fallbacks.contains(ColorFallbackKind::RGB) {
585 res.push(self.to_rgb().unwrap());
586 }
587
588 if fallbacks.contains(ColorFallbackKind::P3) {
589 res.push(self.to_p3().unwrap());
590 }
591
592 if fallbacks.contains(ColorFallbackKind::LAB) {
593 *self = self.to_lab().unwrap();
594 }
595
596 res
597 }
598}
599
600impl Default for CssColor {
601 fn default() -> CssColor {
602 CssColor::transparent()
603 }
604}
605
606impl<'i> Parse<'i> for CssColor {
607 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
608 let location = input.current_source_location();
609 let token = input.next()?;
610 match *token {
611 Token::Hash(ref value) | Token::IDHash(ref value) => parse_hash_color(value.as_bytes())
612 .map(|(r, g, b, a)| CssColor::RGBA(RGBA::new(r, g, b, a)))
613 .map_err(|_| location.new_unexpected_token_error(token.clone())),
614 Token::Ident(ref value) => Ok(match_ignore_ascii_case! { value,
615 "currentcolor" => CssColor::CurrentColor,
616 "transparent" => CssColor::RGBA(RGBA::transparent()),
617 _ => {
618 if let Ok((r, g, b)) = parse_named_color(value) {
619 CssColor::RGBA(RGBA { red: r, green: g, blue: b, alpha: 255 })
620 } else if let Ok(system_color) = SystemColor::parse_string(&value) {
621 CssColor::System(system_color)
622 } else {
623 return Err(location.new_unexpected_token_error(token.clone()))
624 }
625 }
626 }),
627 Token::Function(ref name) => parse_color_function(location, name.clone(), input),
628 _ => Err(location.new_unexpected_token_error(token.clone())),
629 }
630 }
631}
632
633impl ToCss for CssColor {
634 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
635 where
636 W: std::fmt::Write,
637 {
638 match self {
639 CssColor::CurrentColor => dest.write_str("currentColor"),
640 CssColor::RGBA(color) => {
641 if color.alpha == 255 {
642 let hex: u32 = ((color.red as u32) << 16) | ((color.green as u32) << 8) | (color.blue as u32);
643 if let Some(name) = short_color_name(hex) {
644 return dest.write_str(name);
645 }
646
647 let compact = compact_hex(hex);
648 if hex == expand_hex(compact) {
649 write!(dest, "#{:03x}", compact)?;
650 } else {
651 write!(dest, "#{:06x}", hex)?;
652 }
653 } else {
654 if should_compile!(dest.targets.current, HexAlphaColors) {
656 if dest.minify && color.red == 0 && color.green == 0 && color.blue == 0 && color.alpha == 0 {
659 return dest.write_str("transparent");
660 } else {
661 dest.write_str("rgba(")?;
662 write!(dest, "{}", color.red)?;
663 dest.delim(',', false)?;
664 write!(dest, "{}", color.green)?;
665 dest.delim(',', false)?;
666 write!(dest, "{}", color.blue)?;
667 dest.delim(',', false)?;
668
669 let mut rounded_alpha = (color.alpha_f32() * 100.0).round() / 100.0;
671 let clamped = (rounded_alpha * 255.0).round().max(0.).min(255.0) as u8;
672 if clamped != color.alpha {
673 rounded_alpha = (color.alpha_f32() * 1000.).round() / 1000.;
674 }
675
676 rounded_alpha.to_css(dest)?;
677 dest.write_char(')')?;
678 return Ok(());
679 }
680 }
681
682 let hex: u32 = ((color.red as u32) << 24)
683 | ((color.green as u32) << 16)
684 | ((color.blue as u32) << 8)
685 | (color.alpha as u32);
686 let compact = compact_hex(hex);
687 if hex == expand_hex(compact) {
688 write!(dest, "#{:04x}", compact)?;
689 } else {
690 write!(dest, "#{:08x}", hex)?;
691 }
692 }
693 Ok(())
694 }
695 CssColor::LAB(lab) => match &**lab {
696 LABColor::LAB(lab) => write_components("lab", lab.l / 100.0, lab.a, lab.b, lab.alpha, dest),
697 LABColor::LCH(lch) => write_components("lch", lch.l / 100.0, lch.c, lch.h, lch.alpha, dest),
698 LABColor::OKLAB(lab) => write_components("oklab", lab.l, lab.a, lab.b, lab.alpha, dest),
699 LABColor::OKLCH(lch) => write_components("oklch", lch.l, lch.c, lch.h, lch.alpha, dest),
700 },
701 CssColor::Predefined(predefined) => write_predefined(predefined, dest),
702 CssColor::Float(float) => {
703 let rgb = RGB::from(**float);
705 CssColor::from(rgb).to_css(dest)
706 }
707 CssColor::LightDark(light, dark) => {
708 if should_compile!(dest.targets.current, LightDark) {
709 dest.write_str("var(--lightningcss-light")?;
710 dest.delim(',', false)?;
711 light.to_css(dest)?;
712 dest.write_char(')')?;
713 dest.whitespace()?;
714 dest.write_str("var(--lightningcss-dark")?;
715 dest.delim(',', false)?;
716 dark.to_css(dest)?;
717 return dest.write_char(')');
718 }
719
720 dest.write_str("light-dark(")?;
721 light.to_css(dest)?;
722 dest.delim(',', false)?;
723 dark.to_css(dest)?;
724 dest.write_char(')')
725 }
726 CssColor::System(system) => system.to_css(dest),
727 }
728 }
729}
730
731fn compact_hex(v: u32) -> u32 {
734 return ((v & 0x0FF00000) >> 12) | ((v & 0x00000FF0) >> 4);
735}
736
737fn expand_hex(v: u32) -> u32 {
739 return ((v & 0xF000) << 16) | ((v & 0xFF00) << 12) | ((v & 0x0FF0) << 8) | ((v & 0x00FF) << 4) | (v & 0x000F);
740}
741
742fn short_color_name(v: u32) -> Option<&'static str> {
743 let s = match v {
745 0x000080 => "navy",
746 0x008000 => "green",
747 0x008080 => "teal",
748 0x4b0082 => "indigo",
749 0x800000 => "maroon",
750 0x800080 => "purple",
751 0x808000 => "olive",
752 0x808080 => "gray",
753 0xa0522d => "sienna",
754 0xa52a2a => "brown",
755 0xc0c0c0 => "silver",
756 0xcd853f => "peru",
757 0xd2b48c => "tan",
758 0xda70d6 => "orchid",
759 0xdda0dd => "plum",
760 0xee82ee => "violet",
761 0xf0e68c => "khaki",
762 0xf0ffff => "azure",
763 0xf5deb3 => "wheat",
764 0xf5f5dc => "beige",
765 0xfa8072 => "salmon",
766 0xfaf0e6 => "linen",
767 0xff0000 => "red",
768 0xff6347 => "tomato",
769 0xff7f50 => "coral",
770 0xffa500 => "orange",
771 0xffc0cb => "pink",
772 0xffd700 => "gold",
773 0xffe4c4 => "bisque",
774 0xfffafa => "snow",
775 0xfffff0 => "ivory",
776 _ => return None,
777 };
778
779 Some(s)
780}
781
782struct RelativeComponentParser {
783 names: (&'static str, &'static str, &'static str),
784 components: (f32, f32, f32, f32),
785 types: (ChannelType, ChannelType, ChannelType),
786}
787
788impl RelativeComponentParser {
789 fn new<T: ColorSpace>(color: &T) -> Self {
790 Self {
791 names: color.channels(),
792 components: color.components(),
793 types: color.types(),
794 }
795 }
796
797 fn alpha_only(alpha: f32) -> Self {
798 Self {
799 names: ("", "", ""),
800 components: (0.0, 0.0, 0.0, alpha),
801 types: (ChannelType::empty(), ChannelType::empty(), ChannelType::empty()),
802 }
803 }
804
805 fn get_ident(&self, ident: &str, allowed_types: ChannelType) -> Option<(f32, ChannelType)> {
806 if ident.eq_ignore_ascii_case(self.names.0) && allowed_types.intersects(self.types.0) {
807 return Some((self.components.0, self.types.0));
808 }
809
810 if ident.eq_ignore_ascii_case(self.names.1) && allowed_types.intersects(self.types.1) {
811 return Some((self.components.1, self.types.1));
812 }
813
814 if ident.eq_ignore_ascii_case(self.names.2) && allowed_types.intersects(self.types.2) {
815 return Some((self.components.2, self.types.2));
816 }
817
818 if ident.eq_ignore_ascii_case("alpha")
819 && allowed_types.intersects(ChannelType::Number | ChannelType::Percentage)
820 {
821 return Some((self.components.3, ChannelType::Number));
822 }
823
824 None
825 }
826
827 fn parse_ident<'i, 't>(
828 &self,
829 input: &mut Parser<'i, 't>,
830 allowed_types: ChannelType,
831 ) -> Result<(f32, ChannelType), ParseError<'i, ParserError<'i>>> {
832 match self.get_ident(input.expect_ident()?.as_ref(), allowed_types) {
833 Some(v) => Ok(v),
834 None => Err(input.new_error_for_next_token()),
835 }
836 }
837}
838
839impl<'i> ColorParser<'i> for RelativeComponentParser {
840 type Output = cssparser_color::Color;
841 type Error = ParserError<'i>;
842
843 fn parse_angle_or_number<'t>(
844 &self,
845 input: &mut Parser<'i, 't>,
846 ) -> Result<AngleOrNumber, ParseError<'i, Self::Error>> {
847 if let Ok((value, ty)) =
848 input.try_parse(|input| self.parse_ident(input, ChannelType::Angle | ChannelType::Number))
849 {
850 return Ok(match ty {
851 ChannelType::Angle => AngleOrNumber::Angle { degrees: value },
852 ChannelType::Number => AngleOrNumber::Number { value },
853 _ => unreachable!(),
854 });
855 }
856
857 if let Ok(value) = input.try_parse(|input| -> Result<AngleOrNumber, ParseError<'i, ParserError<'i>>> {
858 match Calc::parse_with(input, |ident| {
859 self
860 .get_ident(ident, ChannelType::Angle | ChannelType::Number)
861 .map(|(value, ty)| match ty {
862 ChannelType::Angle => Calc::Value(Box::new(Angle::Deg(value))),
863 ChannelType::Number => Calc::Number(value),
864 _ => unreachable!(),
865 })
866 }) {
867 Ok(Calc::Value(v)) => Ok(AngleOrNumber::Angle {
868 degrees: v.to_degrees(),
869 }),
870 Ok(Calc::Number(v)) => Ok(AngleOrNumber::Number { value: v }),
871 _ => Err(input.new_custom_error(ParserError::InvalidValue)),
872 }
873 }) {
874 return Ok(value);
875 }
876
877 Err(input.new_error_for_next_token())
878 }
879
880 fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {
881 if let Ok((value, _)) = input.try_parse(|input| self.parse_ident(input, ChannelType::Number)) {
882 return Ok(value);
883 }
884
885 match Calc::parse_with(input, |ident| {
886 self.get_ident(ident, ChannelType::Number).map(|(v, _)| Calc::Number(v))
887 }) {
888 Ok(Calc::Value(v)) => Ok(*v),
889 Ok(Calc::Number(n)) => Ok(n),
890 _ => Err(input.new_error_for_next_token()),
891 }
892 }
893
894 fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {
895 if let Ok((value, _)) = input.try_parse(|input| self.parse_ident(input, ChannelType::Percentage)) {
896 return Ok(value);
897 }
898
899 if let Ok(value) = input.try_parse(|input| -> Result<Percentage, ParseError<'i, ParserError<'i>>> {
900 match Calc::parse_with(input, |ident| {
901 self
902 .get_ident(ident, ChannelType::Percentage)
903 .map(|(v, _)| Calc::Value(Box::new(Percentage(v))))
904 }) {
905 Ok(Calc::Value(v)) => Ok(*v),
906 _ => Err(input.new_custom_error(ParserError::InvalidValue)),
907 }
908 }) {
909 return Ok(value.0);
910 }
911
912 Err(input.new_error_for_next_token())
913 }
914
915 fn parse_number_or_percentage<'t>(
916 &self,
917 input: &mut Parser<'i, 't>,
918 ) -> Result<NumberOrPercentage, ParseError<'i, Self::Error>> {
919 if let Ok((value, ty)) =
920 input.try_parse(|input| self.parse_ident(input, ChannelType::Percentage | ChannelType::Number))
921 {
922 return Ok(match ty {
923 ChannelType::Percentage => NumberOrPercentage::Percentage { unit_value: value },
924 ChannelType::Number => NumberOrPercentage::Number { value },
925 _ => unreachable!(),
926 });
927 }
928
929 if let Ok(value) = input.try_parse(|input| -> Result<NumberOrPercentage, ParseError<'i, ParserError<'i>>> {
930 match Calc::parse_with(input, |ident| {
931 self
932 .get_ident(ident, ChannelType::Percentage | ChannelType::Number)
933 .map(|(value, ty)| match ty {
934 ChannelType::Percentage => Calc::Value(Box::new(Percentage(value))),
935 ChannelType::Number => Calc::Number(value),
936 _ => unreachable!(),
937 })
938 }) {
939 Ok(Calc::Value(v)) => Ok(NumberOrPercentage::Percentage { unit_value: v.0 }),
940 Ok(Calc::Number(v)) => Ok(NumberOrPercentage::Number { value: v }),
941 _ => Err(input.new_custom_error(ParserError::InvalidValue)),
942 }
943 }) {
944 return Ok(value);
945 }
946
947 Err(input.new_error_for_next_token())
948 }
949}
950
951pub(crate) trait LightDarkColor {
952 fn light_dark(light: Self, dark: Self) -> Self;
953}
954
955impl LightDarkColor for CssColor {
956 #[inline]
957 fn light_dark(light: Self, dark: Self) -> Self {
958 CssColor::LightDark(Box::new(light), Box::new(dark))
959 }
960}
961
962pub(crate) struct ComponentParser {
963 pub allow_none: bool,
964 from: Option<RelativeComponentParser>,
965}
966
967impl ComponentParser {
968 pub fn new(allow_none: bool) -> Self {
969 Self { allow_none, from: None }
970 }
971
972 pub fn parse_relative<
973 'i,
974 't,
975 T: TryFrom<CssColor> + ColorSpace,
976 C: LightDarkColor,
977 P: Fn(&mut Parser<'i, 't>, &mut Self) -> Result<C, ParseError<'i, ParserError<'i>>>,
978 >(
979 &mut self,
980 input: &mut Parser<'i, 't>,
981 parse: P,
982 ) -> Result<C, ParseError<'i, ParserError<'i>>> {
983 if input.try_parse(|input| input.expect_ident_matching("from")).is_ok() {
984 let from = CssColor::parse(input)?;
985 return self.parse_from::<T, C, P>(from, input, &parse);
986 }
987
988 parse(input, self)
989 }
990
991 fn parse_from<
992 'i,
993 't,
994 T: TryFrom<CssColor> + ColorSpace,
995 C: LightDarkColor,
996 P: Fn(&mut Parser<'i, 't>, &mut Self) -> Result<C, ParseError<'i, ParserError<'i>>>,
997 >(
998 &mut self,
999 from: CssColor,
1000 input: &mut Parser<'i, 't>,
1001 parse: &P,
1002 ) -> Result<C, ParseError<'i, ParserError<'i>>> {
1003 if let CssColor::LightDark(light, dark) = from {
1004 let state = input.state();
1005 let light = self.parse_from::<T, C, P>(*light, input, parse)?;
1006 input.reset(&state);
1007 let dark = self.parse_from::<T, C, P>(*dark, input, parse)?;
1008 return Ok(C::light_dark(light, dark));
1009 }
1010
1011 let from = T::try_from(from)
1012 .map_err(|_| input.new_custom_error(ParserError::InvalidValue))?
1013 .resolve();
1014 self.from = Some(RelativeComponentParser::new(&from));
1015
1016 parse(input, self)
1017 }
1018}
1019
1020impl<'i> ColorParser<'i> for ComponentParser {
1021 type Output = cssparser_color::Color;
1022 type Error = ParserError<'i>;
1023
1024 fn parse_angle_or_number<'t>(
1025 &self,
1026 input: &mut Parser<'i, 't>,
1027 ) -> Result<AngleOrNumber, ParseError<'i, Self::Error>> {
1028 if let Some(from) = &self.from {
1029 if let Ok(res) = input.try_parse(|input| from.parse_angle_or_number(input)) {
1030 return Ok(res);
1031 }
1032 }
1033
1034 if let Ok(angle) = input.try_parse(Angle::parse) {
1035 Ok(AngleOrNumber::Angle {
1036 degrees: angle.to_degrees(),
1037 })
1038 } else if let Ok(value) = input.try_parse(CSSNumber::parse) {
1039 Ok(AngleOrNumber::Number { value })
1040 } else if self.allow_none {
1041 input.expect_ident_matching("none")?;
1042 Ok(AngleOrNumber::Number { value: f32::NAN })
1043 } else {
1044 Err(input.new_custom_error(ParserError::InvalidValue))
1045 }
1046 }
1047
1048 fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {
1049 if let Some(from) = &self.from {
1050 if let Ok(res) = input.try_parse(|input| from.parse_number(input)) {
1051 return Ok(res);
1052 }
1053 }
1054
1055 if let Ok(val) = input.try_parse(CSSNumber::parse) {
1056 return Ok(val);
1057 } else if self.allow_none {
1058 input.expect_ident_matching("none")?;
1059 Ok(f32::NAN)
1060 } else {
1061 Err(input.new_custom_error(ParserError::InvalidValue))
1062 }
1063 }
1064
1065 fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {
1066 if let Some(from) = &self.from {
1067 if let Ok(res) = input.try_parse(|input| from.parse_percentage(input)) {
1068 return Ok(res);
1069 }
1070 }
1071
1072 if let Ok(val) = input.try_parse(Percentage::parse) {
1073 return Ok(val.0);
1074 } else if self.allow_none {
1075 input.expect_ident_matching("none")?;
1076 Ok(f32::NAN)
1077 } else {
1078 Err(input.new_custom_error(ParserError::InvalidValue))
1079 }
1080 }
1081
1082 fn parse_number_or_percentage<'t>(
1083 &self,
1084 input: &mut Parser<'i, 't>,
1085 ) -> Result<NumberOrPercentage, ParseError<'i, Self::Error>> {
1086 if let Some(from) = &self.from {
1087 if let Ok(res) = input.try_parse(|input| from.parse_number_or_percentage(input)) {
1088 return Ok(res);
1089 }
1090 }
1091
1092 if let Ok(value) = input.try_parse(CSSNumber::parse) {
1093 Ok(NumberOrPercentage::Number { value })
1094 } else if let Ok(value) = input.try_parse(Percentage::parse) {
1095 Ok(NumberOrPercentage::Percentage { unit_value: value.0 })
1096 } else if self.allow_none {
1097 input.expect_ident_matching("none")?;
1098 Ok(NumberOrPercentage::Number { value: f32::NAN })
1099 } else {
1100 Err(input.new_custom_error(ParserError::InvalidValue))
1101 }
1102 }
1103}
1104
1105fn parse_color_function<'i, 't>(
1107 location: SourceLocation,
1108 function: CowRcStr<'i>,
1109 input: &mut Parser<'i, 't>,
1110) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1111 let mut parser = ComponentParser::new(true);
1112
1113 match_ignore_ascii_case! {&*function,
1114 "lab" => {
1115 parse_lab::<LAB, _>(input, &mut parser, 100.0, 125.0, |l, a, b, alpha| {
1116 LABColor::LAB(LAB { l, a, b, alpha })
1117 })
1118 },
1119 "oklab" => {
1120 parse_lab::<OKLAB, _>(input, &mut parser, 1.0, 0.4, |l, a, b, alpha| {
1121 LABColor::OKLAB(OKLAB { l, a, b, alpha })
1122 })
1123 },
1124 "lch" => {
1125 parse_lch::<LCH, _>(input, &mut parser, 100.0, 150.0, |l, c, h, alpha| {
1126 LABColor::LCH(LCH { l, c, h, alpha })
1127 })
1128 },
1129 "oklch" => {
1130 parse_lch::<OKLCH, _>(input, &mut parser, 1.0, 0.4, |l, c, h, alpha| {
1131 LABColor::OKLCH(OKLCH { l, c, h, alpha })
1132 })
1133 },
1134 "color" => {
1135 let predefined = parse_predefined(input, &mut parser)?;
1136 Ok(predefined)
1137 },
1138 "hsl" | "hsla" => {
1139 parse_hsl_hwb::<HSL, _>(input, &mut parser, true, |h, s, l, a| {
1140 let hsl = HSL { h, s, l, alpha: a };
1141 if !h.is_nan() && !s.is_nan() && !l.is_nan() && !a.is_nan() {
1142 CssColor::RGBA(hsl.into())
1143 } else {
1144 CssColor::Float(Box::new(FloatColor::HSL(hsl)))
1145 }
1146 })
1147 },
1148 "hwb" => {
1149 parse_hsl_hwb::<HWB, _>(input, &mut parser, false, |h, w, b, a| {
1150 let hwb = HWB { h, w, b, alpha: a };
1151 if !h.is_nan() && !w.is_nan() && !b.is_nan() && !a.is_nan() {
1152 CssColor::RGBA(hwb.into())
1153 } else {
1154 CssColor::Float(Box::new(FloatColor::HWB(hwb)))
1155 }
1156 })
1157 },
1158 "rgb" | "rgba" => {
1159 parse_rgb(input, &mut parser)
1160 },
1161 "alpha" => {
1162 parse_relative_alpha(input)
1163 },
1164 "color-mix" => {
1165 input.parse_nested_block(parse_color_mix)
1166 },
1167 "light-dark" => {
1168 input.parse_nested_block(|input| {
1169 let light = match CssColor::parse(input)? {
1170 CssColor::LightDark(light, _) => light,
1171 light => Box::new(light)
1172 };
1173 input.expect_comma()?;
1174 let dark = match CssColor::parse(input)? {
1175 CssColor::LightDark(_, dark) => dark,
1176 dark => Box::new(dark)
1177 };
1178
1179 if light==dark {
1180 return Ok(*light);
1181 }
1182
1183 Ok(CssColor::LightDark(light, dark))
1184 })
1185 },
1186 _ => Err(location.new_unexpected_token_error(
1187 cssparser::Token::Ident(function.clone())
1188 ))
1189 }
1190}
1191
1192#[inline]
1194fn parse_lab<'i, 't, T: TryFrom<CssColor> + ColorSpace, F: Fn(f32, f32, f32, f32) -> LABColor>(
1195 input: &mut Parser<'i, 't>,
1196 parser: &mut ComponentParser,
1197 l_basis: f32,
1198 ab_basis: f32,
1199 f: F,
1200) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1201 input.parse_nested_block(|input| {
1203 parser.parse_relative::<T, _, _>(input, |input, parser| {
1204 let l = parse_number_or_percentage(input, parser, l_basis)?.clamp(0.0, f32::MAX);
1206 let a = parse_number_or_percentage(input, parser, ab_basis)?;
1207 let b = parse_number_or_percentage(input, parser, ab_basis)?;
1208 let alpha = parse_alpha(input, parser)?;
1209 let lab = f(l, a, b, alpha);
1210
1211 Ok(CssColor::LAB(Box::new(lab)))
1212 })
1213 })
1214}
1215
1216#[inline]
1218fn parse_lch<'i, 't, T: TryFrom<CssColor> + ColorSpace, F: Fn(f32, f32, f32, f32) -> LABColor>(
1219 input: &mut Parser<'i, 't>,
1220 parser: &mut ComponentParser,
1221 l_basis: f32,
1222 c_basis: f32,
1223 f: F,
1224) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1225 input.parse_nested_block(|input| {
1227 parser.parse_relative::<T, _, _>(input, |input, parser| {
1228 if let Some(from) = &mut parser.from {
1229 from.components.2 %= 360.0;
1232 if from.components.2 < 0.0 {
1233 from.components.2 += 360.0;
1234 }
1235 }
1236
1237 let l = parse_number_or_percentage(input, parser, l_basis)?.clamp(0.0, f32::MAX);
1238 let c = parse_number_or_percentage(input, parser, c_basis)?.clamp(0.0, f32::MAX);
1239 let h = parse_angle_or_number(input, parser)?;
1240 let alpha = parse_alpha(input, parser)?;
1241 let lab = f(l, c, h, alpha);
1242
1243 Ok(CssColor::LAB(Box::new(lab)))
1244 })
1245 })
1246}
1247
1248#[inline]
1251fn parse_relative_alpha<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1252 input.parse_nested_block(|input| {
1254 input.expect_ident_matching("from")?;
1255 let from = CssColor::parse(input)?;
1256 let res = parse_relative_alpha_from(from, input)?;
1257 input.expect_exhausted()?;
1258 Ok(res)
1259 })
1260}
1261
1262#[inline]
1263fn parse_relative_alpha_from<'i, 't>(
1264 from: CssColor,
1265 input: &mut Parser<'i, 't>,
1266) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1267 if let CssColor::LightDark(light, dark) = from {
1268 let state = input.state();
1269 let light = parse_relative_alpha_from(*light, input)?;
1270 input.reset(&state);
1271 let dark = parse_relative_alpha_from(*dark, input)?;
1272 return Ok(CssColor::LightDark(Box::new(light), Box::new(dark)));
1273 }
1274
1275 let from_alpha = from.alpha().map_err(|_| input.new_custom_error(ParserError::InvalidValue))?;
1276 let alpha = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1277 let parser = ComponentParser {
1278 allow_none: true,
1279 from: Some(RelativeComponentParser::alpha_only(from_alpha)),
1280 };
1281 parse_number_or_percentage(input, &parser, 1.0)?.clamp(0.0, 1.0)
1282 } else {
1283 from_alpha
1284 };
1285
1286 Ok(from.with_alpha(alpha))
1287}
1288
1289#[inline]
1290fn parse_predefined<'i, 't>(
1291 input: &mut Parser<'i, 't>,
1292 parser: &mut ComponentParser,
1293) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1294 let res = input.parse_nested_block(|input| {
1296 let from = if input.try_parse(|input| input.expect_ident_matching("from")).is_ok() {
1297 Some(CssColor::parse(input)?)
1298 } else {
1299 None
1300 };
1301
1302 let colorspace = input.expect_ident_cloned()?;
1303
1304 if let Some(CssColor::LightDark(light, dark)) = from {
1305 let state = input.state();
1306 let light = parse_predefined_relative(input, parser, &colorspace, Some(&*light))?;
1307 input.reset(&state);
1308 let dark = parse_predefined_relative(input, parser, &colorspace, Some(&*dark))?;
1309 return Ok(CssColor::LightDark(Box::new(light), Box::new(dark)));
1310 }
1311
1312 parse_predefined_relative(input, parser, &colorspace, from.as_ref())
1313 })?;
1314
1315 Ok(res)
1316}
1317
1318#[inline]
1319fn parse_predefined_relative<'i, 't>(
1320 input: &mut Parser<'i, 't>,
1321 parser: &mut ComponentParser,
1322 colorspace: &CowRcStr<'i>,
1323 from: Option<&CssColor>,
1324) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1325 let location = input.current_source_location();
1326 if let Some(from) = from {
1327 let handle_error = |_| input.new_custom_error(ParserError::InvalidValue);
1328 parser.from = Some(match_ignore_ascii_case! { &*&colorspace,
1329 "srgb" => RelativeComponentParser::new(&SRGB::try_from(from).map_err(handle_error)?.resolve_missing()),
1330 "srgb-linear" => RelativeComponentParser::new(&SRGBLinear::try_from(from).map_err(handle_error)?.resolve_missing()),
1331 "display-p3" => RelativeComponentParser::new(&P3::try_from(from).map_err(handle_error)?.resolve_missing()),
1332 "a98-rgb" => RelativeComponentParser::new(&A98::try_from(from).map_err(handle_error)?.resolve_missing()),
1333 "prophoto-rgb" => RelativeComponentParser::new(&ProPhoto::try_from(from).map_err(handle_error)?.resolve_missing()),
1334 "rec2020" => RelativeComponentParser::new(&Rec2020::try_from(from).map_err(handle_error)?.resolve_missing()),
1335 "xyz-d50" => RelativeComponentParser::new(&XYZd50::try_from(from).map_err(handle_error)?.resolve_missing()),
1336 "xyz" | "xyz-d65" => RelativeComponentParser::new(&XYZd65::try_from(from).map_err(handle_error)?.resolve_missing()),
1337 _ => return Err(location.new_unexpected_token_error(
1338 cssparser::Token::Ident(colorspace.clone())
1339 ))
1340 });
1341 }
1342
1343 let a = input.try_parse(|input| parse_number_or_percentage(input, parser, 1.0))?;
1346 let b = input.try_parse(|input| parse_number_or_percentage(input, parser, 1.0))?;
1347 let c = input.try_parse(|input| parse_number_or_percentage(input, parser, 1.0))?;
1348 let alpha = parse_alpha(input, parser)?;
1349
1350 let res = match_ignore_ascii_case! { &*&colorspace,
1351 "srgb" => PredefinedColor::SRGB(SRGB { r: a, g: b, b: c, alpha }),
1352 "srgb-linear" => PredefinedColor::SRGBLinear(SRGBLinear { r: a, g: b, b: c, alpha }),
1353 "display-p3" => PredefinedColor::DisplayP3(P3 { r: a, g: b, b: c, alpha }),
1354 "a98-rgb" => PredefinedColor::A98(A98 { r: a, g: b, b: c, alpha }),
1355 "prophoto-rgb" => PredefinedColor::ProPhoto(ProPhoto { r: a, g: b, b: c, alpha }),
1356 "rec2020" => PredefinedColor::Rec2020(Rec2020 { r: a, g: b, b: c, alpha }),
1357 "xyz-d50" => PredefinedColor::XYZd50(XYZd50 { x: a, y: b, z: c, alpha}),
1358 "xyz" | "xyz-d65" => PredefinedColor::XYZd65(XYZd65 { x: a, y: b, z: c, alpha }),
1359 _ => return Err(location.new_unexpected_token_error(
1360 cssparser::Token::Ident(colorspace.clone())
1361 ))
1362 };
1363
1364 Ok(CssColor::Predefined(Box::new(res)))
1365}
1366
1367#[inline]
1370fn parse_hsl_hwb<'i, 't, T: TryFrom<CssColor> + ColorSpace, F: Fn(f32, f32, f32, f32) -> CssColor>(
1371 input: &mut Parser<'i, 't>,
1372 parser: &mut ComponentParser,
1373 allows_legacy: bool,
1374 f: F,
1375) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1376 input.parse_nested_block(|input| {
1378 parser.parse_relative::<T, _, _>(input, |input, parser| {
1379 let (h, a, b, is_legacy) = parse_hsl_hwb_components::<T>(input, parser, allows_legacy)?;
1380 let alpha = if is_legacy {
1381 parse_legacy_alpha(input, parser)?
1382 } else {
1383 parse_alpha(input, parser)?
1384 };
1385
1386 Ok(f(h, a, b, alpha))
1387 })
1388 })
1389}
1390
1391#[inline]
1392pub(crate) fn parse_hsl_hwb_components<'i, 't, T: TryFrom<CssColor> + ColorSpace>(
1393 input: &mut Parser<'i, 't>,
1394 parser: &mut ComponentParser,
1395 allows_legacy: bool,
1396) -> Result<(f32, f32, f32, bool), ParseError<'i, ParserError<'i>>> {
1397 let h = parse_angle_or_number(input, parser)?;
1398 let is_legacy_syntax =
1399 allows_legacy && parser.from.is_none() && !h.is_nan() && input.try_parse(|p| p.expect_comma()).is_ok();
1400 let a = parse_number_or_percentage(input, parser, 100.0)?.clamp(0.0, 100.0);
1401 if is_legacy_syntax {
1402 input.expect_comma()?;
1403 }
1404 let b = parse_number_or_percentage(input, parser, 100.0)?.clamp(0.0, 100.0);
1405 if is_legacy_syntax && (a.is_nan() || b.is_nan()) {
1406 return Err(input.new_custom_error(ParserError::InvalidValue));
1407 }
1408 Ok((h, a, b, is_legacy_syntax))
1409}
1410
1411#[inline]
1412fn parse_rgb<'i, 't>(
1413 input: &mut Parser<'i, 't>,
1414 parser: &mut ComponentParser,
1415) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1416 input.parse_nested_block(|input| {
1418 parser.parse_relative::<RGB, _, _>(input, |input, parser| {
1419 let (r, g, b, is_legacy) = parse_rgb_components(input, parser)?;
1420 let alpha = if is_legacy {
1421 parse_legacy_alpha(input, parser)?
1422 } else {
1423 parse_alpha(input, parser)?
1424 };
1425
1426 if !r.is_nan() && !g.is_nan() && !b.is_nan() && !alpha.is_nan() {
1427 if is_legacy {
1428 Ok(CssColor::RGBA(RGBA::new(r as u8, g as u8, b as u8, alpha)))
1429 } else {
1430 Ok(CssColor::RGBA(RGBA::from_floats(
1431 r / 255.0,
1432 g / 255.0,
1433 b / 255.0,
1434 alpha,
1435 )))
1436 }
1437 } else {
1438 Ok(CssColor::Float(Box::new(FloatColor::RGB(RGB { r, g, b, alpha }))))
1439 }
1440 })
1441 })
1442}
1443
1444#[inline]
1445pub(crate) fn parse_rgb_components<'i, 't>(
1446 input: &mut Parser<'i, 't>,
1447 parser: &mut ComponentParser,
1448) -> Result<(f32, f32, f32, bool), ParseError<'i, ParserError<'i>>> {
1449 let red = parser.parse_number_or_percentage(input)?;
1450 let is_legacy_syntax =
1451 parser.from.is_none() && !red.unit_value().is_nan() && input.try_parse(|p| p.expect_comma()).is_ok();
1452 let (r, g, b) = if is_legacy_syntax {
1453 match red {
1454 NumberOrPercentage::Number { value } => {
1455 let r = value.round().clamp(0.0, 255.0);
1456 let g = parser.parse_number(input)?.round().clamp(0.0, 255.0);
1457 input.expect_comma()?;
1458 let b = parser.parse_number(input)?.round().clamp(0.0, 255.0);
1459 (r, g, b)
1460 }
1461 NumberOrPercentage::Percentage { unit_value } => {
1462 let r = (unit_value * 255.0).round().clamp(0.0, 255.0);
1463 let g = (parser.parse_percentage(input)? * 255.0).round().clamp(0.0, 255.0);
1464 input.expect_comma()?;
1465 let b = (parser.parse_percentage(input)? * 255.0).round().clamp(0.0, 255.0);
1466 (r, g, b)
1467 }
1468 }
1469 } else {
1470 #[inline]
1471 fn get_component<'i, 't>(value: NumberOrPercentage) -> f32 {
1472 match value {
1473 NumberOrPercentage::Number { value } if value.is_nan() => value,
1474 NumberOrPercentage::Number { value } => value.round().clamp(0.0, 255.0),
1475 NumberOrPercentage::Percentage { unit_value } => (unit_value * 255.0).round().clamp(0.0, 255.0),
1476 }
1477 }
1478
1479 let r = get_component(red);
1480 let g = get_component(parser.parse_number_or_percentage(input)?);
1481 let b = get_component(parser.parse_number_or_percentage(input)?);
1482 (r, g, b)
1483 };
1484
1485 if is_legacy_syntax && (g.is_nan() || b.is_nan()) {
1486 return Err(input.new_custom_error(ParserError::InvalidValue));
1487 }
1488 Ok((r, g, b, is_legacy_syntax))
1489}
1490
1491#[inline]
1492fn parse_angle_or_number<'i, 't>(
1493 input: &mut Parser<'i, 't>,
1494 parser: &ComponentParser,
1495) -> Result<f32, ParseError<'i, ParserError<'i>>> {
1496 Ok(match parser.parse_angle_or_number(input)? {
1497 AngleOrNumber::Number { value } => value,
1498 AngleOrNumber::Angle { degrees } => degrees,
1499 })
1500}
1501
1502#[inline]
1503fn parse_number_or_percentage<'i, 't>(
1504 input: &mut Parser<'i, 't>,
1505 parser: &ComponentParser,
1506 percent_basis: f32,
1507) -> Result<f32, ParseError<'i, ParserError<'i>>> {
1508 Ok(match parser.parse_number_or_percentage(input)? {
1509 NumberOrPercentage::Number { value } => value,
1510 NumberOrPercentage::Percentage { unit_value } => unit_value * percent_basis,
1511 })
1512}
1513
1514#[inline]
1515fn parse_alpha<'i, 't>(
1516 input: &mut Parser<'i, 't>,
1517 parser: &ComponentParser,
1518) -> Result<f32, ParseError<'i, ParserError<'i>>> {
1519 let res = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1520 parse_number_or_percentage(input, parser, 1.0)?.clamp(0.0, 1.0)
1521 } else {
1522 1.0
1523 };
1524 Ok(res)
1525}
1526
1527#[inline]
1528fn parse_legacy_alpha<'i, 't>(
1529 input: &mut Parser<'i, 't>,
1530 parser: &ComponentParser,
1531) -> Result<f32, ParseError<'i, ParserError<'i>>> {
1532 Ok(if !input.is_exhausted() {
1533 input.expect_comma()?;
1534 parse_number_or_percentage(input, parser, 1.0)?.clamp(0.0, 1.0)
1535 } else {
1536 1.0
1537 })
1538}
1539
1540#[inline]
1541fn write_components<W>(
1542 name: &str,
1543 a: f32,
1544 b: f32,
1545 c: f32,
1546 alpha: f32,
1547 dest: &mut Printer<W>,
1548) -> Result<(), PrinterError>
1549where
1550 W: std::fmt::Write,
1551{
1552 dest.write_str(name)?;
1553 dest.write_char('(')?;
1554 if a.is_nan() {
1555 dest.write_str("none")?;
1556 } else {
1557 Percentage(a).to_css(dest)?;
1558 }
1559 dest.write_char(' ')?;
1560 write_component(b, dest)?;
1561 dest.write_char(' ')?;
1562 write_component(c, dest)?;
1563 if alpha.is_nan() || (alpha - 1.0).abs() > f32::EPSILON {
1564 dest.delim('/', true)?;
1565 write_component(alpha, dest)?;
1566 }
1567
1568 dest.write_char(')')
1569}
1570
1571#[inline]
1572fn write_component<W>(c: f32, dest: &mut Printer<W>) -> Result<(), PrinterError>
1573where
1574 W: std::fmt::Write,
1575{
1576 if c.is_nan() {
1577 dest.write_str("none")?;
1578 } else {
1579 c.to_css(dest)?;
1580 }
1581 Ok(())
1582}
1583
1584#[inline]
1585fn write_predefined<W>(predefined: &PredefinedColor, dest: &mut Printer<W>) -> Result<(), PrinterError>
1586where
1587 W: std::fmt::Write,
1588{
1589 use PredefinedColor::*;
1590
1591 let (name, a, b, c, alpha) = match predefined {
1592 SRGB(rgb) => ("srgb", rgb.r, rgb.g, rgb.b, rgb.alpha),
1593 SRGBLinear(rgb) => ("srgb-linear", rgb.r, rgb.g, rgb.b, rgb.alpha),
1594 DisplayP3(rgb) => ("display-p3", rgb.r, rgb.g, rgb.b, rgb.alpha),
1595 A98(rgb) => ("a98-rgb", rgb.r, rgb.g, rgb.b, rgb.alpha),
1596 ProPhoto(rgb) => ("prophoto-rgb", rgb.r, rgb.g, rgb.b, rgb.alpha),
1597 Rec2020(rgb) => ("rec2020", rgb.r, rgb.g, rgb.b, rgb.alpha),
1598 XYZd50(xyz) => ("xyz-d50", xyz.x, xyz.y, xyz.z, xyz.alpha),
1599 XYZd65(xyz) => ("xyz", xyz.x, xyz.y, xyz.z, xyz.alpha),
1601 };
1602
1603 dest.write_str("color(")?;
1604 dest.write_str(name)?;
1605 dest.write_char(' ')?;
1606 write_component(a, dest)?;
1607 dest.write_char(' ')?;
1608 write_component(b, dest)?;
1609 dest.write_char(' ')?;
1610 write_component(c, dest)?;
1611
1612 if alpha.is_nan() || (alpha - 1.0).abs() > f32::EPSILON {
1613 dest.delim('/', true)?;
1614 write_component(alpha, dest)?;
1615 }
1616
1617 dest.write_char(')')
1618}
1619
1620bitflags! {
1621 #[derive(PartialEq, Eq, Clone, Copy)]
1623 pub struct ChannelType: u8 {
1624 const Percentage = 0b001;
1626 const Angle = 0b010;
1628 const Number = 0b100;
1630 }
1631}
1632
1633pub trait ColorSpace {
1635 fn components(&self) -> (f32, f32, f32, f32);
1637 fn channels(&self) -> (&'static str, &'static str, &'static str);
1639 fn types(&self) -> (ChannelType, ChannelType, ChannelType);
1641 fn resolve_missing(&self) -> Self;
1643 fn resolve(&self) -> Self;
1646}
1647
1648macro_rules! define_colorspace {
1649 (
1650 $(#[$outer:meta])*
1651 $vis:vis struct $name:ident {
1652 $(#[$a_meta: meta])*
1653 $a: ident: $at: ident,
1654 $(#[$b_meta: meta])*
1655 $b: ident: $bt: ident,
1656 $(#[$c_meta: meta])*
1657 $c: ident: $ct: ident
1658 }
1659 ) => {
1660 $(#[$outer])*
1661 #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))]
1662 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1663 #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1664 pub struct $name {
1665 $(#[$a_meta])*
1666 pub $a: f32,
1667 $(#[$b_meta])*
1668 pub $b: f32,
1669 $(#[$c_meta])*
1670 pub $c: f32,
1671 pub alpha: f32,
1673 }
1674
1675 impl ColorSpace for $name {
1676 fn components(&self) -> (f32, f32, f32, f32) {
1677 (self.$a, self.$b, self.$c, self.alpha)
1678 }
1679
1680 fn channels(&self) -> (&'static str, &'static str, &'static str) {
1681 (stringify!($a), stringify!($b), stringify!($c))
1682 }
1683
1684 fn types(&self) -> (ChannelType, ChannelType, ChannelType) {
1685 (ChannelType::$at, ChannelType::$bt, ChannelType::$ct)
1686 }
1687
1688 #[inline]
1689 fn resolve_missing(&self) -> Self {
1690 Self {
1691 $a: if self.$a.is_nan() { 0.0 } else { self.$a },
1692 $b: if self.$b.is_nan() { 0.0 } else { self.$b },
1693 $c: if self.$c.is_nan() { 0.0 } else { self.$c },
1694 alpha: if self.alpha.is_nan() { 0.0 } else { self.alpha },
1695 }
1696 }
1697
1698 #[inline]
1699 fn resolve(&self) -> Self {
1700 let mut resolved = self.resolve_missing();
1701 if !resolved.in_gamut() {
1702 resolved = map_gamut(resolved);
1703 }
1704 resolved
1705 }
1706 }
1707 };
1708}
1709
1710define_colorspace! {
1711 pub struct SRGB {
1713 r: Number,
1715 g: Number,
1717 b: Number
1719 }
1720}
1721
1722#[derive(Clone, Copy, PartialEq, Debug)]
1725pub struct RGBA {
1726 pub red: u8,
1728 pub green: u8,
1730 pub blue: u8,
1732 pub alpha: u8,
1734}
1735
1736impl RGBA {
1737 #[inline]
1741 pub fn from_floats(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
1742 Self::new(clamp_unit_f32(red), clamp_unit_f32(green), clamp_unit_f32(blue), alpha)
1743 }
1744
1745 #[inline]
1747 pub fn transparent() -> Self {
1748 Self::new(0, 0, 0, 0.0)
1749 }
1750
1751 #[inline]
1753 pub fn new(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
1754 RGBA {
1755 red,
1756 green,
1757 blue,
1758 alpha: clamp_unit_f32(alpha),
1759 }
1760 }
1761
1762 #[inline]
1764 pub fn red_f32(&self) -> f32 {
1765 self.red as f32 / 255.0
1766 }
1767
1768 #[inline]
1770 pub fn green_f32(&self) -> f32 {
1771 self.green as f32 / 255.0
1772 }
1773
1774 #[inline]
1776 pub fn blue_f32(&self) -> f32 {
1777 self.blue as f32 / 255.0
1778 }
1779
1780 #[inline]
1782 pub fn alpha_f32(&self) -> f32 {
1783 self.alpha as f32 / 255.0
1784 }
1785}
1786
1787fn clamp_unit_f32(val: f32) -> u8 {
1788 clamp_floor_256_f32(val * 255.)
1803}
1804
1805fn clamp_floor_256_f32(val: f32) -> u8 {
1806 val.round().max(0.).min(255.) as u8
1807}
1808
1809define_colorspace! {
1810 pub struct RGB {
1813 r: Number,
1815 g: Number,
1817 b: Number
1819 }
1820}
1821
1822define_colorspace! {
1823 pub struct SRGBLinear {
1825 r: Number,
1827 g: Number,
1829 b: Number
1831 }
1832}
1833
1834define_colorspace! {
1835 pub struct P3 {
1837 r: Number,
1839 g: Number,
1841 b: Number
1843 }
1844}
1845
1846define_colorspace! {
1847 pub struct A98 {
1849 r: Number,
1851 g: Number,
1853 b: Number
1855 }
1856}
1857
1858define_colorspace! {
1859 pub struct ProPhoto {
1861 r: Number,
1863 g: Number,
1865 b: Number
1867 }
1868}
1869
1870define_colorspace! {
1871 pub struct Rec2020 {
1873 r: Number,
1875 g: Number,
1877 b: Number
1879 }
1880}
1881
1882define_colorspace! {
1883 pub struct LAB {
1885 l: Number,
1887 a: Number,
1889 b: Number
1891 }
1892}
1893
1894define_colorspace! {
1895 pub struct LCH {
1897 l: Number,
1899 c: Number,
1901 h: Angle
1903 }
1904}
1905
1906define_colorspace! {
1907 pub struct OKLAB {
1909 l: Number,
1911 a: Number,
1913 b: Number
1915 }
1916}
1917
1918define_colorspace! {
1919 pub struct OKLCH {
1921 l: Number,
1923 c: Number,
1925 h: Angle
1927 }
1928}
1929
1930define_colorspace! {
1931 pub struct XYZd50 {
1933 x: Number,
1935 y: Number,
1937 z: Number
1939 }
1940}
1941
1942define_colorspace! {
1943 pub struct XYZd65 {
1945 x: Number,
1947 y: Number,
1949 z: Number
1951 }
1952}
1953
1954define_colorspace! {
1955 pub struct HSL {
1957 h: Angle,
1959 s: Number,
1961 l: Number
1963 }
1964}
1965
1966define_colorspace! {
1967 pub struct HWB {
1969 h: Angle,
1971 w: Number,
1973 b: Number
1975 }
1976}
1977
1978macro_rules! via {
1979 ($t: ident -> $u: ident -> $v: ident) => {
1980 impl From<$t> for $v {
1981 #[inline]
1982 fn from(t: $t) -> $v {
1983 let xyz: $u = t.into();
1984 xyz.into()
1985 }
1986 }
1987
1988 impl From<$v> for $t {
1989 #[inline]
1990 fn from(t: $v) -> $t {
1991 let xyz: $u = t.into();
1992 xyz.into()
1993 }
1994 }
1995 };
1996}
1997
1998#[inline]
1999fn rectangular_to_polar(l: f32, a: f32, b: f32) -> (f32, f32, f32) {
2000 let mut h = b.atan2(a) * 180.0 / PI;
2002 if h < 0.0 {
2003 h += 360.0;
2004 }
2005 let c = (a.powi(2) + b.powi(2)).sqrt();
2006 h = h % 360.0;
2007 (l, c, h)
2008}
2009
2010#[inline]
2011fn polar_to_rectangular(l: f32, c: f32, h: f32) -> (f32, f32, f32) {
2012 let a = c * (h * PI / 180.0).cos();
2014 let b = c * (h * PI / 180.0).sin();
2015 (l, a, b)
2016}
2017
2018impl From<LCH> for LAB {
2019 fn from(lch: LCH) -> LAB {
2020 let lch = lch.resolve_missing();
2021 let (l, a, b) = polar_to_rectangular(lch.l, lch.c, lch.h);
2022 LAB {
2023 l,
2024 a,
2025 b,
2026 alpha: lch.alpha,
2027 }
2028 }
2029}
2030
2031impl From<LAB> for LCH {
2032 fn from(lab: LAB) -> LCH {
2033 let lab = lab.resolve_missing();
2034 let (l, c, h) = rectangular_to_polar(lab.l, lab.a, lab.b);
2035 LCH {
2036 l,
2037 c,
2038 h,
2039 alpha: lab.alpha,
2040 }
2041 }
2042}
2043
2044impl From<OKLCH> for OKLAB {
2045 fn from(lch: OKLCH) -> OKLAB {
2046 let lch = lch.resolve_missing();
2047 let (l, a, b) = polar_to_rectangular(lch.l, lch.c, lch.h);
2048 OKLAB {
2049 l,
2050 a,
2051 b,
2052 alpha: lch.alpha,
2053 }
2054 }
2055}
2056
2057impl From<OKLAB> for OKLCH {
2058 fn from(lab: OKLAB) -> OKLCH {
2059 let lab = lab.resolve_missing();
2060 let (l, c, h) = rectangular_to_polar(lab.l, lab.a, lab.b);
2061 OKLCH {
2062 l,
2063 c,
2064 h,
2065 alpha: lab.alpha,
2066 }
2067 }
2068}
2069
2070const D50: &[f32] = &[0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585];
2071
2072impl From<LAB> for XYZd50 {
2073 fn from(lab: LAB) -> XYZd50 {
2074 const K: f32 = 24389.0 / 27.0; const E: f32 = 216.0 / 24389.0; let lab = lab.resolve_missing();
2079 let l = lab.l;
2080 let a = lab.a;
2081 let b = lab.b;
2082
2083 let f1 = (l + 16.0) / 116.0;
2085 let f0 = a / 500.0 + f1;
2086 let f2 = f1 - b / 200.0;
2087
2088 let x = if f0.powi(3) > E {
2090 f0.powi(3)
2091 } else {
2092 (116.0 * f0 - 16.0) / K
2093 };
2094
2095 let y = if l > K * E { ((l + 16.0) / 116.0).powi(3) } else { l / K };
2096
2097 let z = if f2.powi(3) > E {
2098 f2.powi(3)
2099 } else {
2100 (116.0 * f2 - 16.0) / K
2101 };
2102
2103 XYZd50 {
2105 x: x * D50[0],
2106 y: y * D50[1],
2107 z: z * D50[2],
2108 alpha: lab.alpha,
2109 }
2110 }
2111}
2112
2113impl From<XYZd50> for XYZd65 {
2114 fn from(xyz: XYZd50) -> XYZd65 {
2115 const MATRIX: &[f32] = &[
2117 0.9554734527042182,
2118 -0.023098536874261423,
2119 0.0632593086610217,
2120 -0.028369706963208136,
2121 1.0099954580058226,
2122 0.021041398966943008,
2123 0.012314001688319899,
2124 -0.020507696433477912,
2125 1.3303659366080753,
2126 ];
2127
2128 let xyz = xyz.resolve_missing();
2129 let (x, y, z) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2130 XYZd65 {
2131 x,
2132 y,
2133 z,
2134 alpha: xyz.alpha,
2135 }
2136 }
2137}
2138
2139impl From<XYZd65> for XYZd50 {
2140 fn from(xyz: XYZd65) -> XYZd50 {
2141 const MATRIX: &[f32] = &[
2143 1.0479298208405488,
2144 0.022946793341019088,
2145 -0.05019222954313557,
2146 0.029627815688159344,
2147 0.990434484573249,
2148 -0.01707382502938514,
2149 -0.009243058152591178,
2150 0.015055144896577895,
2151 0.7518742899580008,
2152 ];
2153
2154 let xyz = xyz.resolve_missing();
2155 let (x, y, z) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2156 XYZd50 {
2157 x,
2158 y,
2159 z,
2160 alpha: xyz.alpha,
2161 }
2162 }
2163}
2164
2165impl From<XYZd65> for SRGBLinear {
2166 fn from(xyz: XYZd65) -> SRGBLinear {
2167 const MATRIX: &[f32] = &[
2169 3.2409699419045226,
2170 -1.537383177570094,
2171 -0.4986107602930034,
2172 -0.9692436362808796,
2173 1.8759675015077202,
2174 0.04155505740717559,
2175 0.05563007969699366,
2176 -0.20397695888897652,
2177 1.0569715142428786,
2178 ];
2179
2180 let xyz = xyz.resolve_missing();
2181 let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2182 SRGBLinear {
2183 r,
2184 g,
2185 b,
2186 alpha: xyz.alpha,
2187 }
2188 }
2189}
2190
2191#[inline]
2192fn multiply_matrix(m: &[f32], x: f32, y: f32, z: f32) -> (f32, f32, f32) {
2193 let a = m[0] * x + m[1] * y + m[2] * z;
2194 let b = m[3] * x + m[4] * y + m[5] * z;
2195 let c = m[6] * x + m[7] * y + m[8] * z;
2196 (a, b, c)
2197}
2198
2199impl From<SRGBLinear> for SRGB {
2200 #[inline]
2201 fn from(rgb: SRGBLinear) -> SRGB {
2202 let rgb = rgb.resolve_missing();
2203 let (r, g, b) = gam_srgb(rgb.r, rgb.g, rgb.b);
2204 SRGB {
2205 r,
2206 g,
2207 b,
2208 alpha: rgb.alpha,
2209 }
2210 }
2211}
2212
2213fn gam_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
2214 #[inline]
2223 fn gam_srgb_component(c: f32) -> f32 {
2224 let abs = c.abs();
2225 if abs > 0.0031308 {
2226 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2227 return sign * (1.055 * abs.powf(1.0 / 2.4) - 0.055);
2228 }
2229
2230 return 12.92 * c;
2231 }
2232
2233 let r = gam_srgb_component(r);
2234 let g = gam_srgb_component(g);
2235 let b = gam_srgb_component(b);
2236 (r, g, b)
2237}
2238
2239impl From<OKLAB> for XYZd65 {
2240 fn from(lab: OKLAB) -> XYZd65 {
2241 const LMS_TO_XYZ: &[f32] = &[
2243 1.2268798733741557,
2244 -0.5578149965554813,
2245 0.28139105017721583,
2246 -0.04057576262431372,
2247 1.1122868293970594,
2248 -0.07171106666151701,
2249 -0.07637294974672142,
2250 -0.4214933239627914,
2251 1.5869240244272418,
2252 ];
2253
2254 const OKLAB_TO_LMS: &[f32] = &[
2255 0.99999999845051981432,
2256 0.39633779217376785678,
2257 0.21580375806075880339,
2258 1.0000000088817607767,
2259 -0.1055613423236563494,
2260 -0.063854174771705903402,
2261 1.0000000546724109177,
2262 -0.089484182094965759684,
2263 -1.2914855378640917399,
2264 ];
2265
2266 let lab = lab.resolve_missing();
2267 let (a, b, c) = multiply_matrix(OKLAB_TO_LMS, lab.l, lab.a, lab.b);
2268 let (x, y, z) = multiply_matrix(LMS_TO_XYZ, a.powi(3), b.powi(3), c.powi(3));
2269 XYZd65 {
2270 x,
2271 y,
2272 z,
2273 alpha: lab.alpha,
2274 }
2275 }
2276}
2277
2278impl From<XYZd65> for OKLAB {
2279 fn from(xyz: XYZd65) -> OKLAB {
2280 const XYZ_TO_LMS: &[f32] = &[
2282 0.8190224432164319,
2283 0.3619062562801221,
2284 -0.12887378261216414,
2285 0.0329836671980271,
2286 0.9292868468965546,
2287 0.03614466816999844,
2288 0.048177199566046255,
2289 0.26423952494422764,
2290 0.6335478258136937,
2291 ];
2292
2293 const LMS_TO_OKLAB: &[f32] = &[
2294 0.2104542553,
2295 0.7936177850,
2296 -0.0040720468,
2297 1.9779984951,
2298 -2.4285922050,
2299 0.4505937099,
2300 0.0259040371,
2301 0.7827717662,
2302 -0.8086757660,
2303 ];
2304
2305 let xyz = xyz.resolve_missing();
2306 let (a, b, c) = multiply_matrix(XYZ_TO_LMS, xyz.x, xyz.y, xyz.z);
2307 let (l, a, b) = multiply_matrix(LMS_TO_OKLAB, a.cbrt(), b.cbrt(), c.cbrt());
2308 OKLAB {
2309 l,
2310 a,
2311 b,
2312 alpha: xyz.alpha,
2313 }
2314 }
2315}
2316
2317impl From<XYZd50> for LAB {
2318 fn from(xyz: XYZd50) -> LAB {
2319 const E: f32 = 216.0 / 24389.0; const K: f32 = 24389.0 / 27.0; let xyz = xyz.resolve_missing();
2327 let x = xyz.x / D50[0];
2328 let y = xyz.y / D50[1];
2329 let z = xyz.z / D50[2];
2330
2331 let f0 = if x > E { x.cbrt() } else { (K * x + 16.0) / 116.0 };
2333
2334 let f1 = if y > E { y.cbrt() } else { (K * y + 16.0) / 116.0 };
2335
2336 let f2 = if z > E { z.cbrt() } else { (K * z + 16.0) / 116.0 };
2337
2338 let l = (116.0 * f1) - 16.0;
2339 let a = 500.0 * (f0 - f1);
2340 let b = 200.0 * (f1 - f2);
2341 LAB {
2342 l,
2343 a,
2344 b,
2345 alpha: xyz.alpha,
2346 }
2347 }
2348}
2349
2350impl From<SRGB> for SRGBLinear {
2351 fn from(rgb: SRGB) -> SRGBLinear {
2352 let rgb = rgb.resolve_missing();
2353 let (r, g, b) = lin_srgb(rgb.r, rgb.g, rgb.b);
2354 SRGBLinear {
2355 r,
2356 g,
2357 b,
2358 alpha: rgb.alpha,
2359 }
2360 }
2361}
2362
2363fn lin_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
2364 #[inline]
2373 fn lin_srgb_component(c: f32) -> f32 {
2374 let abs = c.abs();
2375 if abs < 0.04045 {
2376 return c / 12.92;
2377 }
2378
2379 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2380 sign * ((abs + 0.055) / 1.055).powf(2.4)
2381 }
2382
2383 let r = lin_srgb_component(r);
2384 let g = lin_srgb_component(g);
2385 let b = lin_srgb_component(b);
2386 (r, g, b)
2387}
2388
2389impl From<SRGBLinear> for XYZd65 {
2390 fn from(rgb: SRGBLinear) -> XYZd65 {
2391 const MATRIX: &[f32] = &[
2395 0.41239079926595934,
2396 0.357584339383878,
2397 0.1804807884018343,
2398 0.21263900587151027,
2399 0.715168678767756,
2400 0.07219231536073371,
2401 0.01933081871559182,
2402 0.11919477979462598,
2403 0.9505321522496607,
2404 ];
2405
2406 let rgb = rgb.resolve_missing();
2407 let (x, y, z) = multiply_matrix(MATRIX, rgb.r, rgb.g, rgb.b);
2408 XYZd65 {
2409 x,
2410 y,
2411 z,
2412 alpha: rgb.alpha,
2413 }
2414 }
2415}
2416
2417impl From<XYZd65> for P3 {
2418 fn from(xyz: XYZd65) -> P3 {
2419 const MATRIX: &[f32] = &[
2421 2.493496911941425,
2422 -0.9313836179191239,
2423 -0.40271078445071684,
2424 -0.8294889695615747,
2425 1.7626640603183463,
2426 0.023624685841943577,
2427 0.03584583024378447,
2428 -0.07617238926804182,
2429 0.9568845240076872,
2430 ];
2431
2432 let xyz = xyz.resolve_missing();
2433 let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2434 let (r, g, b) = gam_srgb(r, g, b); P3 {
2436 r,
2437 g,
2438 b,
2439 alpha: xyz.alpha,
2440 }
2441 }
2442}
2443
2444impl From<P3> for XYZd65 {
2445 fn from(p3: P3) -> XYZd65 {
2446 const MATRIX: &[f32] = &[
2451 0.4865709486482162,
2452 0.26566769316909306,
2453 0.1982172852343625,
2454 0.2289745640697488,
2455 0.6917385218365064,
2456 0.079286914093745,
2457 0.0000000000000000,
2458 0.04511338185890264,
2459 1.043944368900976,
2460 ];
2461
2462 let p3 = p3.resolve_missing();
2463 let (r, g, b) = lin_srgb(p3.r, p3.g, p3.b);
2464 let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
2465 XYZd65 {
2466 x,
2467 y,
2468 z,
2469 alpha: p3.alpha,
2470 }
2471 }
2472}
2473
2474impl From<A98> for XYZd65 {
2475 fn from(a98: A98) -> XYZd65 {
2476 #[inline]
2478 fn lin_a98rgb_component(c: f32) -> f32 {
2479 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2480 sign * c.abs().powf(563.0 / 256.0)
2481 }
2482
2483 let a98 = a98.resolve_missing();
2487 let r = lin_a98rgb_component(a98.r);
2488 let g = lin_a98rgb_component(a98.g);
2489 let b = lin_a98rgb_component(a98.b);
2490
2491 const MATRIX: &[f32] = &[
2499 0.5766690429101305,
2500 0.1855582379065463,
2501 0.1882286462349947,
2502 0.29734497525053605,
2503 0.6273635662554661,
2504 0.07529145849399788,
2505 0.02703136138641234,
2506 0.07068885253582723,
2507 0.9913375368376388,
2508 ];
2509
2510 let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
2511 XYZd65 {
2512 x,
2513 y,
2514 z,
2515 alpha: a98.alpha,
2516 }
2517 }
2518}
2519
2520impl From<XYZd65> for A98 {
2521 fn from(xyz: XYZd65) -> A98 {
2522 const MATRIX: &[f32] = &[
2525 2.0415879038107465,
2526 -0.5650069742788596,
2527 -0.34473135077832956,
2528 -0.9692436362808795,
2529 1.8759675015077202,
2530 0.04155505740717557,
2531 0.013444280632031142,
2532 -0.11836239223101838,
2533 1.0151749943912054,
2534 ];
2535
2536 #[inline]
2537 fn gam_a98_component(c: f32) -> f32 {
2538 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2543 sign * c.abs().powf(256.0 / 563.0)
2544 }
2545
2546 let xyz = xyz.resolve_missing();
2547 let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2548 let r = gam_a98_component(r);
2549 let g = gam_a98_component(g);
2550 let b = gam_a98_component(b);
2551 A98 {
2552 r,
2553 g,
2554 b,
2555 alpha: xyz.alpha,
2556 }
2557 }
2558}
2559
2560impl From<ProPhoto> for XYZd50 {
2561 fn from(prophoto: ProPhoto) -> XYZd50 {
2562 #[inline]
2570 fn lin_prophoto_component(c: f32) -> f32 {
2571 const ET2: f32 = 16.0 / 512.0;
2572 let abs = c.abs();
2573 if abs <= ET2 {
2574 return c / 16.0;
2575 }
2576
2577 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2578 sign * c.powf(1.8)
2579 }
2580
2581 let prophoto = prophoto.resolve_missing();
2582 let r = lin_prophoto_component(prophoto.r);
2583 let g = lin_prophoto_component(prophoto.g);
2584 let b = lin_prophoto_component(prophoto.b);
2585
2586 const MATRIX: &[f32] = &[
2591 0.7977604896723027,
2592 0.13518583717574031,
2593 0.0313493495815248,
2594 0.2880711282292934,
2595 0.7118432178101014,
2596 0.00008565396060525902,
2597 0.0,
2598 0.0,
2599 0.8251046025104601,
2600 ];
2601
2602 let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
2603 XYZd50 {
2604 x,
2605 y,
2606 z,
2607 alpha: prophoto.alpha,
2608 }
2609 }
2610}
2611
2612impl From<XYZd50> for ProPhoto {
2613 fn from(xyz: XYZd50) -> ProPhoto {
2614 const MATRIX: &[f32] = &[
2616 1.3457989731028281,
2617 -0.25558010007997534,
2618 -0.05110628506753401,
2619 -0.5446224939028347,
2620 1.5082327413132781,
2621 0.02053603239147973,
2622 0.0,
2623 0.0,
2624 1.2119675456389454,
2625 ];
2626
2627 #[inline]
2628 fn gam_prophoto_component(c: f32) -> f32 {
2629 const ET: f32 = 1.0 / 512.0;
2635 let abs = c.abs();
2636 if abs >= ET {
2637 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2638 return sign * abs.powf(1.0 / 1.8);
2639 }
2640
2641 16.0 * c
2642 }
2643
2644 let xyz = xyz.resolve_missing();
2645 let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2646 let r = gam_prophoto_component(r);
2647 let g = gam_prophoto_component(g);
2648 let b = gam_prophoto_component(b);
2649 ProPhoto {
2650 r,
2651 g,
2652 b,
2653 alpha: xyz.alpha,
2654 }
2655 }
2656}
2657
2658impl From<Rec2020> for XYZd65 {
2659 fn from(rec2020: Rec2020) -> XYZd65 {
2660 #[inline]
2666 fn lin_rec2020_component(c: f32) -> f32 {
2667 const A: f32 = 1.09929682680944;
2668 const B: f32 = 0.018053968510807;
2669
2670 let abs = c.abs();
2671 if abs < B * 4.5 {
2672 return c / 4.5;
2673 }
2674
2675 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2676 sign * ((abs + A - 1.0) / A).powf(1.0 / 0.45)
2677 }
2678
2679 let rec2020 = rec2020.resolve_missing();
2680 let r = lin_rec2020_component(rec2020.r);
2681 let g = lin_rec2020_component(rec2020.g);
2682 let b = lin_rec2020_component(rec2020.b);
2683
2684 const MATRIX: &[f32] = &[
2689 0.6369580483012914,
2690 0.14461690358620832,
2691 0.1688809751641721,
2692 0.2627002120112671,
2693 0.6779980715188708,
2694 0.05930171646986196,
2695 0.000000000000000,
2696 0.028072693049087428,
2697 1.060985057710791,
2698 ];
2699
2700 let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
2701 XYZd65 {
2702 x,
2703 y,
2704 z,
2705 alpha: rec2020.alpha,
2706 }
2707 }
2708}
2709
2710impl From<XYZd65> for Rec2020 {
2711 fn from(xyz: XYZd65) -> Rec2020 {
2712 const MATRIX: &[f32] = &[
2714 1.7166511879712674,
2715 -0.35567078377639233,
2716 -0.25336628137365974,
2717 -0.6666843518324892,
2718 1.6164812366349395,
2719 0.01576854581391113,
2720 0.017639857445310783,
2721 -0.042770613257808524,
2722 0.9421031212354738,
2723 ];
2724
2725 #[inline]
2726 fn gam_rec2020_component(c: f32) -> f32 {
2727 const A: f32 = 1.09929682680944;
2732 const B: f32 = 0.018053968510807;
2733
2734 let abs = c.abs();
2735 if abs > B {
2736 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2737 return sign * (A * abs.powf(0.45) - (A - 1.0));
2738 }
2739
2740 4.5 * c
2741 }
2742
2743 let xyz = xyz.resolve_missing();
2744 let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2745 let r = gam_rec2020_component(r);
2746 let g = gam_rec2020_component(g);
2747 let b = gam_rec2020_component(b);
2748 Rec2020 {
2749 r,
2750 g,
2751 b,
2752 alpha: xyz.alpha,
2753 }
2754 }
2755}
2756
2757impl From<SRGB> for HSL {
2758 fn from(rgb: SRGB) -> HSL {
2759 let rgb = rgb.resolve();
2761 let r = rgb.r;
2762 let g = rgb.g;
2763 let b = rgb.b;
2764 let max = r.max(g).max(b);
2765 let min = r.min(g).min(b);
2766 let mut h = f32::NAN;
2767 let mut s: f32 = 0.0;
2768 let l = (min + max) / 2.0;
2769 let d = max - min;
2770
2771 if d != 0.0 {
2772 s = if l == 0.0 || l == 1.0 {
2773 0.0
2774 } else {
2775 (max - l) / l.min(1.0 - l)
2776 };
2777
2778 if max == r {
2779 h = (g - b) / d + (if g < b { 6.0 } else { 0.0 });
2780 } else if max == g {
2781 h = (b - r) / d + 2.0;
2782 } else if max == b {
2783 h = (r - g) / d + 4.0;
2784 }
2785
2786 h = h * 60.0;
2787 }
2788
2789 HSL {
2790 h,
2791 s: s * 100.0,
2792 l: l * 100.0,
2793 alpha: rgb.alpha,
2794 }
2795 }
2796}
2797
2798impl From<HSL> for SRGB {
2799 fn from(hsl: HSL) -> SRGB {
2800 let hsl = hsl.resolve_missing();
2802 let h = (hsl.h - 360.0 * (hsl.h / 360.0).floor()) / 360.0;
2803 let h = if (0.0..=1.0).contains(&h) {
2805 h
2806 } else if hsl.h.is_finite() {
2807 hsl.h.rem_euclid(360.0) / 360.0
2808 } else {
2809 0.0
2810 };
2811 let (r, g, b) = hsl_to_rgb(h, hsl.s / 100.0, hsl.l / 100.0);
2812 SRGB {
2813 r,
2814 g,
2815 b,
2816 alpha: hsl.alpha,
2817 }
2818 }
2819}
2820
2821impl From<SRGB> for HWB {
2822 fn from(rgb: SRGB) -> HWB {
2823 let rgb = rgb.resolve();
2824 let hsl = HSL::from(rgb);
2825 let r = rgb.r;
2826 let g = rgb.g;
2827 let b = rgb.b;
2828 let w = r.min(g).min(b);
2829 let b = 1.0 - r.max(g).max(b);
2830 HWB {
2831 h: hsl.h,
2832 w: w * 100.0,
2833 b: b * 100.0,
2834 alpha: rgb.alpha,
2835 }
2836 }
2837}
2838
2839impl From<HWB> for SRGB {
2840 fn from(hwb: HWB) -> SRGB {
2841 let hwb = hwb.resolve_missing();
2843 let h = hwb.h;
2844 let w = hwb.w / 100.0;
2845 let b = hwb.b / 100.0;
2846
2847 if w + b >= 1.0 {
2848 let gray = w / (w + b);
2849 return SRGB {
2850 r: gray,
2851 g: gray,
2852 b: gray,
2853 alpha: hwb.alpha,
2854 };
2855 }
2856
2857 let mut rgba = SRGB::from(HSL {
2858 h,
2859 s: 100.0,
2860 l: 50.0,
2861 alpha: hwb.alpha,
2862 });
2863 let x = 1.0 - w - b;
2864 rgba.r = rgba.r * x + w;
2865 rgba.g = rgba.g * x + w;
2866 rgba.b = rgba.b * x + w;
2867 rgba
2868 }
2869}
2870
2871impl From<RGBA> for SRGB {
2872 fn from(rgb: RGBA) -> SRGB {
2873 SRGB {
2874 r: rgb.red_f32(),
2875 g: rgb.green_f32(),
2876 b: rgb.blue_f32(),
2877 alpha: rgb.alpha_f32(),
2878 }
2879 }
2880}
2881
2882impl From<SRGB> for RGBA {
2883 fn from(rgb: SRGB) -> RGBA {
2884 let rgb = rgb.resolve();
2885 RGBA::from_floats(rgb.r, rgb.g, rgb.b, rgb.alpha)
2886 }
2887}
2888
2889impl From<SRGB> for RGB {
2890 fn from(rgb: SRGB) -> Self {
2891 RGB {
2892 r: rgb.r * 255.0,
2893 g: rgb.g * 255.0,
2894 b: rgb.b * 255.0,
2895 alpha: rgb.alpha,
2896 }
2897 }
2898}
2899
2900impl From<RGB> for SRGB {
2901 fn from(rgb: RGB) -> Self {
2902 SRGB {
2903 r: rgb.r / 255.0,
2904 g: rgb.g / 255.0,
2905 b: rgb.b / 255.0,
2906 alpha: rgb.alpha,
2907 }
2908 }
2909}
2910
2911impl From<RGBA> for RGB {
2912 fn from(rgb: RGBA) -> Self {
2913 RGB::from(&rgb)
2914 }
2915}
2916
2917impl From<&RGBA> for RGB {
2918 fn from(rgb: &RGBA) -> Self {
2919 RGB {
2920 r: rgb.red as f32,
2921 g: rgb.green as f32,
2922 b: rgb.blue as f32,
2923 alpha: rgb.alpha_f32(),
2924 }
2925 }
2926}
2927
2928impl From<RGB> for RGBA {
2929 fn from(rgb: RGB) -> Self {
2930 let rgb = rgb.resolve();
2931 RGBA::new(
2932 clamp_floor_256_f32(rgb.r),
2933 clamp_floor_256_f32(rgb.g),
2934 clamp_floor_256_f32(rgb.b),
2935 rgb.alpha,
2936 )
2937 }
2938}
2939
2940via!(LAB -> XYZd50 -> XYZd65);
2942via!(ProPhoto -> XYZd50 -> XYZd65);
2943via!(OKLCH -> OKLAB -> XYZd65);
2944
2945via!(LAB -> XYZd65 -> OKLAB);
2946via!(LAB -> XYZd65 -> OKLCH);
2947via!(LAB -> XYZd65 -> SRGB);
2948via!(LAB -> XYZd65 -> SRGBLinear);
2949via!(LAB -> XYZd65 -> P3);
2950via!(LAB -> XYZd65 -> A98);
2951via!(LAB -> XYZd65 -> ProPhoto);
2952via!(LAB -> XYZd65 -> Rec2020);
2953via!(LAB -> XYZd65 -> HSL);
2954via!(LAB -> XYZd65 -> HWB);
2955
2956via!(LCH -> LAB -> XYZd65);
2957via!(LCH -> XYZd65 -> OKLAB);
2958via!(LCH -> XYZd65 -> OKLCH);
2959via!(LCH -> XYZd65 -> SRGB);
2960via!(LCH -> XYZd65 -> SRGBLinear);
2961via!(LCH -> XYZd65 -> P3);
2962via!(LCH -> XYZd65 -> A98);
2963via!(LCH -> XYZd65 -> ProPhoto);
2964via!(LCH -> XYZd65 -> Rec2020);
2965via!(LCH -> XYZd65 -> XYZd50);
2966via!(LCH -> XYZd65 -> HSL);
2967via!(LCH -> XYZd65 -> HWB);
2968
2969via!(SRGB -> SRGBLinear -> XYZd65);
2970via!(SRGB -> XYZd65 -> OKLAB);
2971via!(SRGB -> XYZd65 -> OKLCH);
2972via!(SRGB -> XYZd65 -> P3);
2973via!(SRGB -> XYZd65 -> A98);
2974via!(SRGB -> XYZd65 -> ProPhoto);
2975via!(SRGB -> XYZd65 -> Rec2020);
2976via!(SRGB -> XYZd65 -> XYZd50);
2977
2978via!(P3 -> XYZd65 -> SRGBLinear);
2979via!(P3 -> XYZd65 -> OKLAB);
2980via!(P3 -> XYZd65 -> OKLCH);
2981via!(P3 -> XYZd65 -> A98);
2982via!(P3 -> XYZd65 -> ProPhoto);
2983via!(P3 -> XYZd65 -> Rec2020);
2984via!(P3 -> XYZd65 -> XYZd50);
2985via!(P3 -> XYZd65 -> HSL);
2986via!(P3 -> XYZd65 -> HWB);
2987
2988via!(SRGBLinear -> XYZd65 -> OKLAB);
2989via!(SRGBLinear -> XYZd65 -> OKLCH);
2990via!(SRGBLinear -> XYZd65 -> A98);
2991via!(SRGBLinear -> XYZd65 -> ProPhoto);
2992via!(SRGBLinear -> XYZd65 -> Rec2020);
2993via!(SRGBLinear -> XYZd65 -> XYZd50);
2994via!(SRGBLinear -> XYZd65 -> HSL);
2995via!(SRGBLinear -> XYZd65 -> HWB);
2996
2997via!(A98 -> XYZd65 -> OKLAB);
2998via!(A98 -> XYZd65 -> OKLCH);
2999via!(A98 -> XYZd65 -> ProPhoto);
3000via!(A98 -> XYZd65 -> Rec2020);
3001via!(A98 -> XYZd65 -> XYZd50);
3002via!(A98 -> XYZd65 -> HSL);
3003via!(A98 -> XYZd65 -> HWB);
3004
3005via!(ProPhoto -> XYZd65 -> OKLAB);
3006via!(ProPhoto -> XYZd65 -> OKLCH);
3007via!(ProPhoto -> XYZd65 -> Rec2020);
3008via!(ProPhoto -> XYZd65 -> HSL);
3009via!(ProPhoto -> XYZd65 -> HWB);
3010
3011via!(XYZd50 -> XYZd65 -> OKLAB);
3012via!(XYZd50 -> XYZd65 -> OKLCH);
3013via!(XYZd50 -> XYZd65 -> Rec2020);
3014via!(XYZd50 -> XYZd65 -> HSL);
3015via!(XYZd50 -> XYZd65 -> HWB);
3016
3017via!(Rec2020 -> XYZd65 -> OKLAB);
3018via!(Rec2020 -> XYZd65 -> OKLCH);
3019via!(Rec2020 -> XYZd65 -> HSL);
3020via!(Rec2020 -> XYZd65 -> HWB);
3021
3022via!(HSL -> XYZd65 -> OKLAB);
3023via!(HSL -> XYZd65 -> OKLCH);
3024via!(HSL -> SRGB -> XYZd65);
3025via!(HSL -> SRGB -> HWB);
3026
3027via!(HWB -> SRGB -> XYZd65);
3028via!(HWB -> XYZd65 -> OKLAB);
3029via!(HWB -> XYZd65 -> OKLCH);
3030
3031via!(RGB -> SRGB -> LAB);
3032via!(RGB -> SRGB -> LCH);
3033via!(RGB -> SRGB -> OKLAB);
3034via!(RGB -> SRGB -> OKLCH);
3035via!(RGB -> SRGB -> P3);
3036via!(RGB -> SRGB -> SRGBLinear);
3037via!(RGB -> SRGB -> A98);
3038via!(RGB -> SRGB -> ProPhoto);
3039via!(RGB -> SRGB -> XYZd50);
3040via!(RGB -> SRGB -> XYZd65);
3041via!(RGB -> SRGB -> Rec2020);
3042via!(RGB -> SRGB -> HSL);
3043via!(RGB -> SRGB -> HWB);
3044
3045via!(RGBA -> SRGB -> LAB);
3048via!(RGBA -> SRGB -> LCH);
3049via!(RGBA -> SRGB -> OKLAB);
3050via!(RGBA -> SRGB -> OKLCH);
3051via!(RGBA -> SRGB -> P3);
3052via!(RGBA -> SRGB -> SRGBLinear);
3053via!(RGBA -> SRGB -> A98);
3054via!(RGBA -> SRGB -> ProPhoto);
3055via!(RGBA -> SRGB -> XYZd50);
3056via!(RGBA -> SRGB -> XYZd65);
3057via!(RGBA -> SRGB -> Rec2020);
3058via!(RGBA -> SRGB -> HSL);
3059via!(RGBA -> SRGB -> HWB);
3060
3061macro_rules! color_space {
3062 ($space: ty) => {
3063 impl From<LABColor> for $space {
3064 fn from(color: LABColor) -> $space {
3065 use LABColor::*;
3066
3067 match color {
3068 LAB(v) => v.into(),
3069 LCH(v) => v.into(),
3070 OKLAB(v) => v.into(),
3071 OKLCH(v) => v.into(),
3072 }
3073 }
3074 }
3075
3076 impl From<PredefinedColor> for $space {
3077 fn from(color: PredefinedColor) -> $space {
3078 use PredefinedColor::*;
3079
3080 match color {
3081 SRGB(v) => v.into(),
3082 SRGBLinear(v) => v.into(),
3083 DisplayP3(v) => v.into(),
3084 A98(v) => v.into(),
3085 ProPhoto(v) => v.into(),
3086 Rec2020(v) => v.into(),
3087 XYZd50(v) => v.into(),
3088 XYZd65(v) => v.into(),
3089 }
3090 }
3091 }
3092
3093 impl From<FloatColor> for $space {
3094 fn from(color: FloatColor) -> $space {
3095 use FloatColor::*;
3096
3097 match color {
3098 RGB(v) => v.into(),
3099 HSL(v) => v.into(),
3100 HWB(v) => v.into(),
3101 }
3102 }
3103 }
3104
3105 impl TryFrom<&CssColor> for $space {
3106 type Error = ();
3107 fn try_from(color: &CssColor) -> Result<$space, ()> {
3108 Ok(match color {
3109 CssColor::RGBA(rgba) => (*rgba).into(),
3110 CssColor::LAB(lab) => (**lab).into(),
3111 CssColor::Predefined(predefined) => (**predefined).into(),
3112 CssColor::Float(float) => (**float).into(),
3113 CssColor::CurrentColor => return Err(()),
3114 CssColor::LightDark(..) => return Err(()),
3115 CssColor::System(..) => return Err(()),
3116 })
3117 }
3118 }
3119
3120 impl TryFrom<CssColor> for $space {
3121 type Error = ();
3122 fn try_from(color: CssColor) -> Result<$space, ()> {
3123 Ok(match color {
3124 CssColor::RGBA(rgba) => rgba.into(),
3125 CssColor::LAB(lab) => (*lab).into(),
3126 CssColor::Predefined(predefined) => (*predefined).into(),
3127 CssColor::Float(float) => (*float).into(),
3128 CssColor::CurrentColor => return Err(()),
3129 CssColor::LightDark(..) => return Err(()),
3130 CssColor::System(..) => return Err(()),
3131 })
3132 }
3133 }
3134 };
3135}
3136
3137color_space!(LAB);
3138color_space!(LCH);
3139color_space!(OKLAB);
3140color_space!(OKLCH);
3141color_space!(SRGB);
3142color_space!(SRGBLinear);
3143color_space!(XYZd50);
3144color_space!(XYZd65);
3145color_space!(P3);
3146color_space!(A98);
3147color_space!(ProPhoto);
3148color_space!(Rec2020);
3149color_space!(HSL);
3150color_space!(HWB);
3151color_space!(RGB);
3152color_space!(RGBA);
3153
3154macro_rules! predefined {
3155 ($key: ident, $t: ty) => {
3156 impl From<$t> for PredefinedColor {
3157 fn from(color: $t) -> PredefinedColor {
3158 PredefinedColor::$key(color)
3159 }
3160 }
3161
3162 impl From<$t> for CssColor {
3163 fn from(color: $t) -> CssColor {
3164 CssColor::Predefined(Box::new(PredefinedColor::$key(color)))
3165 }
3166 }
3167 };
3168}
3169
3170predefined!(SRGBLinear, SRGBLinear);
3171predefined!(XYZd50, XYZd50);
3172predefined!(XYZd65, XYZd65);
3173predefined!(DisplayP3, P3);
3174predefined!(A98, A98);
3175predefined!(ProPhoto, ProPhoto);
3176predefined!(Rec2020, Rec2020);
3177
3178macro_rules! lab {
3179 ($key: ident, $t: ty) => {
3180 impl From<$t> for LABColor {
3181 fn from(color: $t) -> LABColor {
3182 LABColor::$key(color)
3183 }
3184 }
3185
3186 impl From<$t> for CssColor {
3187 fn from(color: $t) -> CssColor {
3188 CssColor::LAB(Box::new(LABColor::$key(color)))
3189 }
3190 }
3191 };
3192}
3193
3194lab!(LAB, LAB);
3195lab!(LCH, LCH);
3196lab!(OKLAB, OKLAB);
3197lab!(OKLCH, OKLCH);
3198
3199macro_rules! rgb {
3200 ($t: ty) => {
3201 impl From<$t> for CssColor {
3202 fn from(color: $t) -> CssColor {
3203 CssColor::RGBA(color.into())
3206 }
3207 }
3208 };
3209}
3210
3211rgb!(SRGB);
3212rgb!(HSL);
3213rgb!(HWB);
3214rgb!(RGB);
3215
3216impl From<RGBA> for CssColor {
3217 fn from(color: RGBA) -> CssColor {
3218 CssColor::RGBA(color)
3219 }
3220}
3221
3222pub trait ColorGamut {
3224 fn in_gamut(&self) -> bool;
3226 fn clip(&self) -> Self;
3228}
3229
3230macro_rules! bounded_color_gamut {
3231 ($t: ty, $a: ident, $b: ident, $c: ident) => {
3232 impl ColorGamut for $t {
3233 #[inline]
3234 fn in_gamut(&self) -> bool {
3235 self.$a >= 0.0 && self.$a <= 1.0 && self.$b >= 0.0 && self.$b <= 1.0 && self.$c >= 0.0 && self.$c <= 1.0
3236 }
3237
3238 #[inline]
3239 fn clip(&self) -> Self {
3240 Self {
3241 $a: self.$a.clamp(0.0, 1.0),
3242 $b: self.$b.clamp(0.0, 1.0),
3243 $c: self.$c.clamp(0.0, 1.0),
3244 alpha: self.alpha.clamp(0.0, 1.0),
3245 }
3246 }
3247 }
3248 };
3249}
3250
3251macro_rules! unbounded_color_gamut {
3252 ($t: ty, $a: ident, $b: ident, $c: ident) => {
3253 impl ColorGamut for $t {
3254 #[inline]
3255 fn in_gamut(&self) -> bool {
3256 true
3257 }
3258
3259 #[inline]
3260 fn clip(&self) -> Self {
3261 *self
3262 }
3263 }
3264 };
3265}
3266
3267macro_rules! hsl_hwb_color_gamut {
3268 ($t: ty, $a: ident, $b: ident) => {
3269 impl ColorGamut for $t {
3270 #[inline]
3271 fn in_gamut(&self) -> bool {
3272 self.$a >= 0.0 && self.$a <= 100.0 && self.$b >= 0.0 && self.$b <= 100.0
3273 }
3274
3275 #[inline]
3276 fn clip(&self) -> Self {
3277 Self {
3278 h: self.h % 360.0,
3279 $a: self.$a.clamp(0.0, 100.0),
3280 $b: self.$b.clamp(0.0, 100.0),
3281 alpha: self.alpha.clamp(0.0, 1.0),
3282 }
3283 }
3284 }
3285 };
3286}
3287
3288bounded_color_gamut!(SRGB, r, g, b);
3289bounded_color_gamut!(SRGBLinear, r, g, b);
3290bounded_color_gamut!(P3, r, g, b);
3291bounded_color_gamut!(A98, r, g, b);
3292bounded_color_gamut!(ProPhoto, r, g, b);
3293bounded_color_gamut!(Rec2020, r, g, b);
3294unbounded_color_gamut!(LAB, l, a, b);
3295unbounded_color_gamut!(OKLAB, l, a, b);
3296unbounded_color_gamut!(XYZd50, x, y, z);
3297unbounded_color_gamut!(XYZd65, x, y, z);
3298unbounded_color_gamut!(LCH, l, c, h);
3299unbounded_color_gamut!(OKLCH, l, c, h);
3300hsl_hwb_color_gamut!(HSL, s, l);
3301hsl_hwb_color_gamut!(HWB, w, b);
3302
3303impl ColorGamut for RGB {
3304 #[inline]
3305 fn in_gamut(&self) -> bool {
3306 self.r >= 0.0 && self.r <= 255.0 && self.g >= 0.0 && self.g <= 255.0 && self.b >= 0.0 && self.b <= 255.0
3307 }
3308
3309 #[inline]
3310 fn clip(&self) -> Self {
3311 Self {
3312 r: self.r.clamp(0.0, 255.0),
3313 g: self.g.clamp(0.0, 255.0),
3314 b: self.b.clamp(0.0, 255.0),
3315 alpha: self.alpha.clamp(0.0, 1.0),
3316 }
3317 }
3318}
3319
3320fn delta_eok<T: Into<OKLAB>>(a: T, b: OKLCH) -> f32 {
3321 let a: OKLAB = a.into();
3323 let b: OKLAB = b.into();
3324 let delta_l = a.l - b.l;
3325 let delta_a = a.a - b.a;
3326 let delta_b = a.b - b.b;
3327
3328 (delta_l.powi(2) + delta_a.powi(2) + delta_b.powi(2)).sqrt()
3329}
3330
3331fn map_gamut<T>(color: T) -> T
3332where
3333 T: Into<OKLCH> + ColorGamut + Into<OKLAB> + From<OKLCH> + Copy,
3334{
3335 const JND: f32 = 0.02;
3336 const EPSILON: f32 = 0.00001;
3337
3338 let mut current: OKLCH = color.into();
3340
3341 if (current.l - 1.0).abs() < EPSILON || current.l > 1.0 {
3343 return OKLCH {
3344 l: 1.0,
3345 c: 0.0,
3346 h: 0.0,
3347 alpha: current.alpha,
3348 }
3349 .into();
3350 }
3351
3352 if current.l < EPSILON {
3354 return OKLCH {
3355 l: 0.0,
3356 c: 0.0,
3357 h: 0.0,
3358 alpha: current.alpha,
3359 }
3360 .into();
3361 }
3362
3363 let mut min = 0.0;
3364 let mut max = current.c;
3365
3366 while (max - min) > EPSILON {
3367 let chroma = (min + max) / 2.0;
3368 current.c = chroma;
3369
3370 let converted = T::from(current);
3371 if converted.in_gamut() {
3372 min = chroma;
3373 continue;
3374 }
3375
3376 let clipped = converted.clip();
3377 let delta_e = delta_eok(clipped, current);
3378 if delta_e < JND {
3379 return clipped;
3380 }
3381
3382 max = chroma;
3383 }
3384
3385 current.into()
3386}
3387
3388fn parse_color_mix<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
3389 input.expect_ident_matching("in")?;
3390 let method = ColorSpaceName::parse(input)?;
3391
3392 let hue_method = if matches!(
3393 method,
3394 ColorSpaceName::Hsl | ColorSpaceName::Hwb | ColorSpaceName::LCH | ColorSpaceName::OKLCH
3395 ) {
3396 let hue_method = input.try_parse(HueInterpolationMethod::parse);
3397 if hue_method.is_ok() {
3398 input.expect_ident_matching("hue")?;
3399 }
3400 hue_method
3401 } else {
3402 Ok(HueInterpolationMethod::Shorter)
3403 };
3404
3405 let hue_method = hue_method.unwrap_or(HueInterpolationMethod::Shorter);
3406 input.expect_comma()?;
3407
3408 let first_percent = input.try_parse(|input| input.expect_percentage());
3409 let first_color = CssColor::parse(input)?;
3410 let first_percent = first_percent
3411 .or_else(|_| input.try_parse(|input| input.expect_percentage()))
3412 .ok();
3413 input.expect_comma()?;
3414
3415 let second_percent = input.try_parse(|input| input.expect_percentage());
3416 let second_color = CssColor::parse(input)?;
3417 let second_percent = second_percent
3418 .or_else(|_| input.try_parse(|input| input.expect_percentage()))
3419 .ok();
3420
3421 let (p1, p2) = if first_percent.is_none() && second_percent.is_none() {
3423 (0.5, 0.5)
3424 } else {
3425 let p2 = second_percent.unwrap_or_else(|| 1.0 - first_percent.unwrap());
3426 let p1 = first_percent.unwrap_or_else(|| 1.0 - second_percent.unwrap());
3427 (p1, p2)
3428 };
3429
3430 if (p1 + p2) == 0.0 {
3431 return Err(input.new_custom_error(ParserError::InvalidValue));
3432 }
3433
3434 match method {
3435 ColorSpaceName::SRGB => first_color.interpolate::<SRGB>(p1, &second_color, p2, hue_method),
3436 ColorSpaceName::SRGBLinear => first_color.interpolate::<SRGBLinear>(p1, &second_color, p2, hue_method),
3437 ColorSpaceName::Hsl => first_color.interpolate::<HSL>(p1, &second_color, p2, hue_method),
3438 ColorSpaceName::Hwb => first_color.interpolate::<HWB>(p1, &second_color, p2, hue_method),
3439 ColorSpaceName::LAB => first_color.interpolate::<LAB>(p1, &second_color, p2, hue_method),
3440 ColorSpaceName::LCH => first_color.interpolate::<LCH>(p1, &second_color, p2, hue_method),
3441 ColorSpaceName::OKLAB => first_color.interpolate::<OKLAB>(p1, &second_color, p2, hue_method),
3442 ColorSpaceName::OKLCH => first_color.interpolate::<OKLCH>(p1, &second_color, p2, hue_method),
3443 ColorSpaceName::XYZ | ColorSpaceName::XYZd65 => {
3444 first_color.interpolate::<XYZd65>(p1, &second_color, p2, hue_method)
3445 }
3446 ColorSpaceName::XYZd50 => first_color.interpolate::<XYZd50>(p1, &second_color, p2, hue_method),
3447 }
3448 .map_err(|_| input.new_custom_error(ParserError::InvalidValue))
3449}
3450
3451impl CssColor {
3452 fn get_type_id(&self) -> TypeId {
3453 match self {
3454 CssColor::RGBA(..) => TypeId::of::<SRGB>(),
3455 CssColor::LAB(lab) => match &**lab {
3456 LABColor::LAB(..) => TypeId::of::<LAB>(),
3457 LABColor::LCH(..) => TypeId::of::<LCH>(),
3458 LABColor::OKLAB(..) => TypeId::of::<OKLAB>(),
3459 LABColor::OKLCH(..) => TypeId::of::<OKLCH>(),
3460 },
3461 CssColor::Predefined(predefined) => match &**predefined {
3462 PredefinedColor::SRGB(..) => TypeId::of::<SRGB>(),
3463 PredefinedColor::SRGBLinear(..) => TypeId::of::<SRGBLinear>(),
3464 PredefinedColor::DisplayP3(..) => TypeId::of::<P3>(),
3465 PredefinedColor::A98(..) => TypeId::of::<A98>(),
3466 PredefinedColor::ProPhoto(..) => TypeId::of::<ProPhoto>(),
3467 PredefinedColor::Rec2020(..) => TypeId::of::<Rec2020>(),
3468 PredefinedColor::XYZd50(..) => TypeId::of::<XYZd50>(),
3469 PredefinedColor::XYZd65(..) => TypeId::of::<XYZd65>(),
3470 },
3471 CssColor::Float(float) => match &**float {
3472 FloatColor::RGB(..) => TypeId::of::<SRGB>(),
3473 FloatColor::HSL(..) => TypeId::of::<HSL>(),
3474 FloatColor::HWB(..) => TypeId::of::<HWB>(),
3475 },
3476 _ => unreachable!(),
3477 }
3478 }
3479
3480 fn to_light_dark(&self) -> CssColor {
3481 match self {
3482 CssColor::LightDark(..) => self.clone(),
3483 _ => CssColor::LightDark(Box::new(self.clone()), Box::new(self.clone())),
3484 }
3485 }
3486
3487 pub fn interpolate<T>(
3490 &self,
3491 mut p1: f32,
3492 other: &CssColor,
3493 mut p2: f32,
3494 method: HueInterpolationMethod,
3495 ) -> Result<CssColor, ()>
3496 where
3497 for<'a> T: 'static
3498 + TryFrom<&'a CssColor>
3499 + Interpolate
3500 + Into<CssColor>
3501 + Into<OKLCH>
3502 + ColorGamut
3503 + Into<OKLAB>
3504 + From<OKLCH>
3505 + Copy,
3506 {
3507 if matches!(self, CssColor::CurrentColor | CssColor::System(..))
3508 || matches!(other, CssColor::CurrentColor | CssColor::System(..))
3509 {
3510 return Err(());
3511 }
3512
3513 if matches!(self, CssColor::LightDark(..)) || matches!(other, CssColor::LightDark(..)) {
3514 if let (CssColor::LightDark(al, ad), CssColor::LightDark(bl, bd)) =
3515 (self.to_light_dark(), other.to_light_dark())
3516 {
3517 return Ok(CssColor::LightDark(
3518 Box::new(al.interpolate::<T>(p1, &bl, p2, method)?),
3519 Box::new(ad.interpolate::<T>(p1, &bd, p2, method)?),
3520 ));
3521 }
3522 }
3523
3524 let type_id = TypeId::of::<T>();
3525 let converted_first = self.get_type_id() != type_id;
3526 let converted_second = other.get_type_id() != type_id;
3527
3528 let mut first_color = T::try_from(self).map_err(|_| ())?;
3530 let mut second_color = T::try_from(other).map_err(|_| ())?;
3531
3532 if converted_first && !first_color.in_gamut() {
3533 first_color = map_gamut(first_color);
3534 }
3535
3536 if converted_second && !second_color.in_gamut() {
3537 second_color = map_gamut(second_color);
3538 }
3539
3540 if converted_first {
3542 first_color.adjust_powerless_components();
3543 }
3544
3545 if converted_second {
3546 second_color.adjust_powerless_components();
3547 }
3548
3549 first_color.fill_missing_components(&second_color);
3551 second_color.fill_missing_components(&first_color);
3552
3553 first_color.adjust_hue(&mut second_color, method);
3555
3556 first_color.premultiply();
3558 second_color.premultiply();
3559
3560 let mut alpha_multiplier = p1 + p2;
3562 if alpha_multiplier != 1.0 {
3563 p1 = p1 / alpha_multiplier;
3564 p2 = p2 / alpha_multiplier;
3565 if alpha_multiplier > 1.0 {
3566 alpha_multiplier = 1.0;
3567 }
3568 }
3569
3570 let mut result_color = first_color.interpolate(p1, &second_color, p2);
3571 result_color.unpremultiply(alpha_multiplier);
3572
3573 Ok(result_color.into())
3574 }
3575}
3576
3577pub trait Interpolate {
3579 fn adjust_powerless_components(&mut self) {}
3581 fn fill_missing_components(&mut self, other: &Self);
3583 fn adjust_hue(&mut self, _: &mut Self, _: HueInterpolationMethod) {}
3585 fn premultiply(&mut self);
3587 fn unpremultiply(&mut self, alpha_multiplier: f32);
3589 fn interpolate(&self, p1: f32, other: &Self, p2: f32) -> Self;
3591}
3592
3593macro_rules! interpolate {
3594 ($a: ident, $b: ident, $c: ident) => {
3595 fn fill_missing_components(&mut self, other: &Self) {
3596 if self.$a.is_nan() {
3597 self.$a = other.$a;
3598 }
3599
3600 if self.$b.is_nan() {
3601 self.$b = other.$b;
3602 }
3603
3604 if self.$c.is_nan() {
3605 self.$c = other.$c;
3606 }
3607
3608 if self.alpha.is_nan() {
3609 self.alpha = other.alpha;
3610 }
3611 }
3612
3613 fn interpolate(&self, p1: f32, other: &Self, p2: f32) -> Self {
3614 Self {
3615 $a: self.$a * p1 + other.$a * p2,
3616 $b: self.$b * p1 + other.$b * p2,
3617 $c: self.$c * p1 + other.$c * p2,
3618 alpha: self.alpha * p1 + other.alpha * p2,
3619 }
3620 }
3621 };
3622}
3623
3624macro_rules! rectangular_premultiply {
3625 ($a: ident, $b: ident, $c: ident) => {
3626 fn premultiply(&mut self) {
3627 if !self.alpha.is_nan() {
3628 self.$a *= self.alpha;
3629 self.$b *= self.alpha;
3630 self.$c *= self.alpha;
3631 }
3632 }
3633
3634 fn unpremultiply(&mut self, alpha_multiplier: f32) {
3635 if !self.alpha.is_nan() && self.alpha != 0.0 {
3636 self.$a /= self.alpha;
3637 self.$b /= self.alpha;
3638 self.$c /= self.alpha;
3639 self.alpha *= alpha_multiplier;
3640 }
3641 }
3642 };
3643}
3644
3645macro_rules! polar_premultiply {
3646 ($a: ident, $b: ident) => {
3647 fn premultiply(&mut self) {
3648 if !self.alpha.is_nan() {
3649 self.$a *= self.alpha;
3650 self.$b *= self.alpha;
3651 }
3652 }
3653
3654 fn unpremultiply(&mut self, alpha_multiplier: f32) {
3655 self.h %= 360.0;
3656 if !self.alpha.is_nan() {
3657 self.$a /= self.alpha;
3658 self.$b /= self.alpha;
3659 self.alpha *= alpha_multiplier;
3660 }
3661 }
3662 };
3663}
3664
3665macro_rules! adjust_powerless_lab {
3666 () => {
3667 fn adjust_powerless_components(&mut self) {
3668 if self.l.abs() < f32::EPSILON {
3670 self.a = f32::NAN;
3671 self.b = f32::NAN;
3672 }
3673 }
3674 };
3675}
3676
3677macro_rules! adjust_powerless_lch {
3678 () => {
3679 fn adjust_powerless_components(&mut self) {
3680 if self.c.abs() < f32::EPSILON {
3683 self.h = f32::NAN;
3684 }
3685
3686 if self.l.abs() < f32::EPSILON {
3687 self.c = f32::NAN;
3688 self.h = f32::NAN;
3689 }
3690 }
3691
3692 fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {
3693 method.interpolate(&mut self.h, &mut other.h);
3694 }
3695 };
3696}
3697
3698impl Interpolate for SRGB {
3699 rectangular_premultiply!(r, g, b);
3700 interpolate!(r, g, b);
3701}
3702
3703impl Interpolate for SRGBLinear {
3704 rectangular_premultiply!(r, g, b);
3705 interpolate!(r, g, b);
3706}
3707
3708impl Interpolate for XYZd50 {
3709 rectangular_premultiply!(x, y, z);
3710 interpolate!(x, y, z);
3711}
3712
3713impl Interpolate for XYZd65 {
3714 rectangular_premultiply!(x, y, z);
3715 interpolate!(x, y, z);
3716}
3717
3718impl Interpolate for LAB {
3719 adjust_powerless_lab!();
3720 rectangular_premultiply!(l, a, b);
3721 interpolate!(l, a, b);
3722}
3723
3724impl Interpolate for OKLAB {
3725 adjust_powerless_lab!();
3726 rectangular_premultiply!(l, a, b);
3727 interpolate!(l, a, b);
3728}
3729
3730impl Interpolate for LCH {
3731 adjust_powerless_lch!();
3732 polar_premultiply!(l, c);
3733 interpolate!(l, c, h);
3734}
3735
3736impl Interpolate for OKLCH {
3737 adjust_powerless_lch!();
3738 polar_premultiply!(l, c);
3739 interpolate!(l, c, h);
3740}
3741
3742impl Interpolate for HSL {
3743 polar_premultiply!(s, l);
3744
3745 fn adjust_powerless_components(&mut self) {
3746 if self.s.abs() < f32::EPSILON {
3749 self.h = f32::NAN;
3750 }
3751
3752 if self.l.abs() < f32::EPSILON || (self.l - 100.0).abs() < f32::EPSILON {
3753 self.h = f32::NAN;
3754 self.s = f32::NAN;
3755 }
3756 }
3757
3758 fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {
3759 method.interpolate(&mut self.h, &mut other.h);
3760 }
3761
3762 interpolate!(h, s, l);
3763}
3764
3765impl Interpolate for HWB {
3766 polar_premultiply!(w, b);
3767
3768 fn adjust_powerless_components(&mut self) {
3769 if (self.w + self.b - 100.0).abs() < f32::EPSILON {
3772 self.h = f32::NAN;
3773 }
3774 }
3775
3776 fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {
3777 method.interpolate(&mut self.h, &mut other.h);
3778 }
3779
3780 interpolate!(h, w, b);
3781}
3782
3783impl HueInterpolationMethod {
3784 fn interpolate(&self, a: &mut f32, b: &mut f32) {
3785 if *self != HueInterpolationMethod::Specified {
3787 *a = ((*a % 360.0) + 360.0) % 360.0;
3788 *b = ((*b % 360.0) + 360.0) % 360.0;
3789 }
3790
3791 match self {
3792 HueInterpolationMethod::Shorter => {
3793 let delta = *b - *a;
3795 if delta > 180.0 {
3796 *a += 360.0;
3797 } else if delta < -180.0 {
3798 *b += 360.0;
3799 }
3800 }
3801 HueInterpolationMethod::Longer => {
3802 let delta = *b - *a;
3804 if 0.0 < delta && delta < 180.0 {
3805 *a += 360.0;
3806 } else if -180.0 < delta && delta < 0.0 {
3807 *b += 360.0;
3808 }
3809 }
3810 HueInterpolationMethod::Increasing => {
3811 if *b < *a {
3813 *b += 360.0;
3814 }
3815 }
3816 HueInterpolationMethod::Decreasing => {
3817 if *a < *b {
3819 *a += 360.0;
3820 }
3821 }
3822 HueInterpolationMethod::Specified => {}
3823 }
3824 }
3825}
3826
3827#[cfg(feature = "visitor")]
3828#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
3829impl<'i, V: ?Sized + Visitor<'i, T>, T: Visit<'i, T, V>> Visit<'i, T, V> for RGBA {
3830 const CHILD_TYPES: VisitTypes = VisitTypes::empty();
3831 fn visit_children(&mut self, _: &mut V) -> Result<(), V::Error> {
3832 Ok(())
3833 }
3834}
3835
3836#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
3837#[css(case = lower)]
3838#[cfg_attr(feature = "visitor", derive(Visit))]
3839#[cfg_attr(
3840 feature = "serde",
3841 derive(serde::Serialize, serde::Deserialize),
3842 serde(rename_all = "lowercase")
3843)]
3844#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
3845#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
3846pub enum SystemColor {
3848 AccentColor,
3850 AccentColorText,
3852 ActiveText,
3854 ButtonBorder,
3856 ButtonFace,
3858 ButtonText,
3860 Canvas,
3862 CanvasText,
3864 Field,
3866 FieldText,
3868 GrayText,
3870 Highlight,
3872 HighlightText,
3874 LinkText,
3876 Mark,
3878 MarkText,
3880 SelectedItem,
3882 SelectedItemText,
3884 VisitedText,
3886
3887 ActiveBorder,
3890 ActiveCaption,
3892 AppWorkspace,
3894 Background,
3896 ButtonHighlight,
3898 ButtonShadow,
3900 CaptionText,
3902 InactiveBorder,
3904 InactiveCaption,
3906 InactiveCaptionText,
3908 InfoBackground,
3910 InfoText,
3912 Menu,
3914 MenuText,
3916 Scrollbar,
3918 ThreeDDarkShadow,
3920 ThreeDFace,
3922 ThreeDHighlight,
3924 ThreeDLightShadow,
3926 ThreeDShadow,
3928 Window,
3930 WindowFrame,
3932 WindowText,
3934}
3935
3936impl IsCompatible for SystemColor {
3937 fn is_compatible(&self, browsers: Browsers) -> bool {
3938 use SystemColor::*;
3939 match self {
3940 AccentColor | AccentColorText => Feature::AccentSystemColor.is_compatible(browsers),
3941 _ => true,
3942 }
3943 }
3944}