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 (r, g, b) = hsl_to_rgb(h, hsl.s / 100.0, hsl.l / 100.0);
2804 SRGB {
2805 r,
2806 g,
2807 b,
2808 alpha: hsl.alpha,
2809 }
2810 }
2811}
2812
2813impl From<SRGB> for HWB {
2814 fn from(rgb: SRGB) -> HWB {
2815 let rgb = rgb.resolve();
2816 let hsl = HSL::from(rgb);
2817 let r = rgb.r;
2818 let g = rgb.g;
2819 let b = rgb.b;
2820 let w = r.min(g).min(b);
2821 let b = 1.0 - r.max(g).max(b);
2822 HWB {
2823 h: hsl.h,
2824 w: w * 100.0,
2825 b: b * 100.0,
2826 alpha: rgb.alpha,
2827 }
2828 }
2829}
2830
2831impl From<HWB> for SRGB {
2832 fn from(hwb: HWB) -> SRGB {
2833 let hwb = hwb.resolve_missing();
2835 let h = hwb.h;
2836 let w = hwb.w / 100.0;
2837 let b = hwb.b / 100.0;
2838
2839 if w + b >= 1.0 {
2840 let gray = w / (w + b);
2841 return SRGB {
2842 r: gray,
2843 g: gray,
2844 b: gray,
2845 alpha: hwb.alpha,
2846 };
2847 }
2848
2849 let mut rgba = SRGB::from(HSL {
2850 h,
2851 s: 100.0,
2852 l: 50.0,
2853 alpha: hwb.alpha,
2854 });
2855 let x = 1.0 - w - b;
2856 rgba.r = rgba.r * x + w;
2857 rgba.g = rgba.g * x + w;
2858 rgba.b = rgba.b * x + w;
2859 rgba
2860 }
2861}
2862
2863impl From<RGBA> for SRGB {
2864 fn from(rgb: RGBA) -> SRGB {
2865 SRGB {
2866 r: rgb.red_f32(),
2867 g: rgb.green_f32(),
2868 b: rgb.blue_f32(),
2869 alpha: rgb.alpha_f32(),
2870 }
2871 }
2872}
2873
2874impl From<SRGB> for RGBA {
2875 fn from(rgb: SRGB) -> RGBA {
2876 let rgb = rgb.resolve();
2877 RGBA::from_floats(rgb.r, rgb.g, rgb.b, rgb.alpha)
2878 }
2879}
2880
2881impl From<SRGB> for RGB {
2882 fn from(rgb: SRGB) -> Self {
2883 RGB {
2884 r: rgb.r * 255.0,
2885 g: rgb.g * 255.0,
2886 b: rgb.b * 255.0,
2887 alpha: rgb.alpha,
2888 }
2889 }
2890}
2891
2892impl From<RGB> for SRGB {
2893 fn from(rgb: RGB) -> Self {
2894 SRGB {
2895 r: rgb.r / 255.0,
2896 g: rgb.g / 255.0,
2897 b: rgb.b / 255.0,
2898 alpha: rgb.alpha,
2899 }
2900 }
2901}
2902
2903impl From<RGBA> for RGB {
2904 fn from(rgb: RGBA) -> Self {
2905 RGB::from(&rgb)
2906 }
2907}
2908
2909impl From<&RGBA> for RGB {
2910 fn from(rgb: &RGBA) -> Self {
2911 RGB {
2912 r: rgb.red as f32,
2913 g: rgb.green as f32,
2914 b: rgb.blue as f32,
2915 alpha: rgb.alpha_f32(),
2916 }
2917 }
2918}
2919
2920impl From<RGB> for RGBA {
2921 fn from(rgb: RGB) -> Self {
2922 let rgb = rgb.resolve();
2923 RGBA::new(
2924 clamp_floor_256_f32(rgb.r),
2925 clamp_floor_256_f32(rgb.g),
2926 clamp_floor_256_f32(rgb.b),
2927 rgb.alpha,
2928 )
2929 }
2930}
2931
2932via!(LAB -> XYZd50 -> XYZd65);
2934via!(ProPhoto -> XYZd50 -> XYZd65);
2935via!(OKLCH -> OKLAB -> XYZd65);
2936
2937via!(LAB -> XYZd65 -> OKLAB);
2938via!(LAB -> XYZd65 -> OKLCH);
2939via!(LAB -> XYZd65 -> SRGB);
2940via!(LAB -> XYZd65 -> SRGBLinear);
2941via!(LAB -> XYZd65 -> P3);
2942via!(LAB -> XYZd65 -> A98);
2943via!(LAB -> XYZd65 -> ProPhoto);
2944via!(LAB -> XYZd65 -> Rec2020);
2945via!(LAB -> XYZd65 -> HSL);
2946via!(LAB -> XYZd65 -> HWB);
2947
2948via!(LCH -> LAB -> XYZd65);
2949via!(LCH -> XYZd65 -> OKLAB);
2950via!(LCH -> XYZd65 -> OKLCH);
2951via!(LCH -> XYZd65 -> SRGB);
2952via!(LCH -> XYZd65 -> SRGBLinear);
2953via!(LCH -> XYZd65 -> P3);
2954via!(LCH -> XYZd65 -> A98);
2955via!(LCH -> XYZd65 -> ProPhoto);
2956via!(LCH -> XYZd65 -> Rec2020);
2957via!(LCH -> XYZd65 -> XYZd50);
2958via!(LCH -> XYZd65 -> HSL);
2959via!(LCH -> XYZd65 -> HWB);
2960
2961via!(SRGB -> SRGBLinear -> XYZd65);
2962via!(SRGB -> XYZd65 -> OKLAB);
2963via!(SRGB -> XYZd65 -> OKLCH);
2964via!(SRGB -> XYZd65 -> P3);
2965via!(SRGB -> XYZd65 -> A98);
2966via!(SRGB -> XYZd65 -> ProPhoto);
2967via!(SRGB -> XYZd65 -> Rec2020);
2968via!(SRGB -> XYZd65 -> XYZd50);
2969
2970via!(P3 -> XYZd65 -> SRGBLinear);
2971via!(P3 -> XYZd65 -> OKLAB);
2972via!(P3 -> XYZd65 -> OKLCH);
2973via!(P3 -> XYZd65 -> A98);
2974via!(P3 -> XYZd65 -> ProPhoto);
2975via!(P3 -> XYZd65 -> Rec2020);
2976via!(P3 -> XYZd65 -> XYZd50);
2977via!(P3 -> XYZd65 -> HSL);
2978via!(P3 -> XYZd65 -> HWB);
2979
2980via!(SRGBLinear -> XYZd65 -> OKLAB);
2981via!(SRGBLinear -> XYZd65 -> OKLCH);
2982via!(SRGBLinear -> XYZd65 -> A98);
2983via!(SRGBLinear -> XYZd65 -> ProPhoto);
2984via!(SRGBLinear -> XYZd65 -> Rec2020);
2985via!(SRGBLinear -> XYZd65 -> XYZd50);
2986via!(SRGBLinear -> XYZd65 -> HSL);
2987via!(SRGBLinear -> XYZd65 -> HWB);
2988
2989via!(A98 -> XYZd65 -> OKLAB);
2990via!(A98 -> XYZd65 -> OKLCH);
2991via!(A98 -> XYZd65 -> ProPhoto);
2992via!(A98 -> XYZd65 -> Rec2020);
2993via!(A98 -> XYZd65 -> XYZd50);
2994via!(A98 -> XYZd65 -> HSL);
2995via!(A98 -> XYZd65 -> HWB);
2996
2997via!(ProPhoto -> XYZd65 -> OKLAB);
2998via!(ProPhoto -> XYZd65 -> OKLCH);
2999via!(ProPhoto -> XYZd65 -> Rec2020);
3000via!(ProPhoto -> XYZd65 -> HSL);
3001via!(ProPhoto -> XYZd65 -> HWB);
3002
3003via!(XYZd50 -> XYZd65 -> OKLAB);
3004via!(XYZd50 -> XYZd65 -> OKLCH);
3005via!(XYZd50 -> XYZd65 -> Rec2020);
3006via!(XYZd50 -> XYZd65 -> HSL);
3007via!(XYZd50 -> XYZd65 -> HWB);
3008
3009via!(Rec2020 -> XYZd65 -> OKLAB);
3010via!(Rec2020 -> XYZd65 -> OKLCH);
3011via!(Rec2020 -> XYZd65 -> HSL);
3012via!(Rec2020 -> XYZd65 -> HWB);
3013
3014via!(HSL -> XYZd65 -> OKLAB);
3015via!(HSL -> XYZd65 -> OKLCH);
3016via!(HSL -> SRGB -> XYZd65);
3017via!(HSL -> SRGB -> HWB);
3018
3019via!(HWB -> SRGB -> XYZd65);
3020via!(HWB -> XYZd65 -> OKLAB);
3021via!(HWB -> XYZd65 -> OKLCH);
3022
3023via!(RGB -> SRGB -> LAB);
3024via!(RGB -> SRGB -> LCH);
3025via!(RGB -> SRGB -> OKLAB);
3026via!(RGB -> SRGB -> OKLCH);
3027via!(RGB -> SRGB -> P3);
3028via!(RGB -> SRGB -> SRGBLinear);
3029via!(RGB -> SRGB -> A98);
3030via!(RGB -> SRGB -> ProPhoto);
3031via!(RGB -> SRGB -> XYZd50);
3032via!(RGB -> SRGB -> XYZd65);
3033via!(RGB -> SRGB -> Rec2020);
3034via!(RGB -> SRGB -> HSL);
3035via!(RGB -> SRGB -> HWB);
3036
3037via!(RGBA -> SRGB -> LAB);
3040via!(RGBA -> SRGB -> LCH);
3041via!(RGBA -> SRGB -> OKLAB);
3042via!(RGBA -> SRGB -> OKLCH);
3043via!(RGBA -> SRGB -> P3);
3044via!(RGBA -> SRGB -> SRGBLinear);
3045via!(RGBA -> SRGB -> A98);
3046via!(RGBA -> SRGB -> ProPhoto);
3047via!(RGBA -> SRGB -> XYZd50);
3048via!(RGBA -> SRGB -> XYZd65);
3049via!(RGBA -> SRGB -> Rec2020);
3050via!(RGBA -> SRGB -> HSL);
3051via!(RGBA -> SRGB -> HWB);
3052
3053macro_rules! color_space {
3054 ($space: ty) => {
3055 impl From<LABColor> for $space {
3056 fn from(color: LABColor) -> $space {
3057 use LABColor::*;
3058
3059 match color {
3060 LAB(v) => v.into(),
3061 LCH(v) => v.into(),
3062 OKLAB(v) => v.into(),
3063 OKLCH(v) => v.into(),
3064 }
3065 }
3066 }
3067
3068 impl From<PredefinedColor> for $space {
3069 fn from(color: PredefinedColor) -> $space {
3070 use PredefinedColor::*;
3071
3072 match color {
3073 SRGB(v) => v.into(),
3074 SRGBLinear(v) => v.into(),
3075 DisplayP3(v) => v.into(),
3076 A98(v) => v.into(),
3077 ProPhoto(v) => v.into(),
3078 Rec2020(v) => v.into(),
3079 XYZd50(v) => v.into(),
3080 XYZd65(v) => v.into(),
3081 }
3082 }
3083 }
3084
3085 impl From<FloatColor> for $space {
3086 fn from(color: FloatColor) -> $space {
3087 use FloatColor::*;
3088
3089 match color {
3090 RGB(v) => v.into(),
3091 HSL(v) => v.into(),
3092 HWB(v) => v.into(),
3093 }
3094 }
3095 }
3096
3097 impl TryFrom<&CssColor> for $space {
3098 type Error = ();
3099 fn try_from(color: &CssColor) -> Result<$space, ()> {
3100 Ok(match color {
3101 CssColor::RGBA(rgba) => (*rgba).into(),
3102 CssColor::LAB(lab) => (**lab).into(),
3103 CssColor::Predefined(predefined) => (**predefined).into(),
3104 CssColor::Float(float) => (**float).into(),
3105 CssColor::CurrentColor => return Err(()),
3106 CssColor::LightDark(..) => return Err(()),
3107 CssColor::System(..) => return Err(()),
3108 })
3109 }
3110 }
3111
3112 impl TryFrom<CssColor> for $space {
3113 type Error = ();
3114 fn try_from(color: CssColor) -> Result<$space, ()> {
3115 Ok(match color {
3116 CssColor::RGBA(rgba) => rgba.into(),
3117 CssColor::LAB(lab) => (*lab).into(),
3118 CssColor::Predefined(predefined) => (*predefined).into(),
3119 CssColor::Float(float) => (*float).into(),
3120 CssColor::CurrentColor => return Err(()),
3121 CssColor::LightDark(..) => return Err(()),
3122 CssColor::System(..) => return Err(()),
3123 })
3124 }
3125 }
3126 };
3127}
3128
3129color_space!(LAB);
3130color_space!(LCH);
3131color_space!(OKLAB);
3132color_space!(OKLCH);
3133color_space!(SRGB);
3134color_space!(SRGBLinear);
3135color_space!(XYZd50);
3136color_space!(XYZd65);
3137color_space!(P3);
3138color_space!(A98);
3139color_space!(ProPhoto);
3140color_space!(Rec2020);
3141color_space!(HSL);
3142color_space!(HWB);
3143color_space!(RGB);
3144color_space!(RGBA);
3145
3146macro_rules! predefined {
3147 ($key: ident, $t: ty) => {
3148 impl From<$t> for PredefinedColor {
3149 fn from(color: $t) -> PredefinedColor {
3150 PredefinedColor::$key(color)
3151 }
3152 }
3153
3154 impl From<$t> for CssColor {
3155 fn from(color: $t) -> CssColor {
3156 CssColor::Predefined(Box::new(PredefinedColor::$key(color)))
3157 }
3158 }
3159 };
3160}
3161
3162predefined!(SRGBLinear, SRGBLinear);
3163predefined!(XYZd50, XYZd50);
3164predefined!(XYZd65, XYZd65);
3165predefined!(DisplayP3, P3);
3166predefined!(A98, A98);
3167predefined!(ProPhoto, ProPhoto);
3168predefined!(Rec2020, Rec2020);
3169
3170macro_rules! lab {
3171 ($key: ident, $t: ty) => {
3172 impl From<$t> for LABColor {
3173 fn from(color: $t) -> LABColor {
3174 LABColor::$key(color)
3175 }
3176 }
3177
3178 impl From<$t> for CssColor {
3179 fn from(color: $t) -> CssColor {
3180 CssColor::LAB(Box::new(LABColor::$key(color)))
3181 }
3182 }
3183 };
3184}
3185
3186lab!(LAB, LAB);
3187lab!(LCH, LCH);
3188lab!(OKLAB, OKLAB);
3189lab!(OKLCH, OKLCH);
3190
3191macro_rules! rgb {
3192 ($t: ty) => {
3193 impl From<$t> for CssColor {
3194 fn from(color: $t) -> CssColor {
3195 CssColor::RGBA(color.into())
3198 }
3199 }
3200 };
3201}
3202
3203rgb!(SRGB);
3204rgb!(HSL);
3205rgb!(HWB);
3206rgb!(RGB);
3207
3208impl From<RGBA> for CssColor {
3209 fn from(color: RGBA) -> CssColor {
3210 CssColor::RGBA(color)
3211 }
3212}
3213
3214pub trait ColorGamut {
3216 fn in_gamut(&self) -> bool;
3218 fn clip(&self) -> Self;
3220}
3221
3222macro_rules! bounded_color_gamut {
3223 ($t: ty, $a: ident, $b: ident, $c: ident) => {
3224 impl ColorGamut for $t {
3225 #[inline]
3226 fn in_gamut(&self) -> bool {
3227 self.$a >= 0.0 && self.$a <= 1.0 && self.$b >= 0.0 && self.$b <= 1.0 && self.$c >= 0.0 && self.$c <= 1.0
3228 }
3229
3230 #[inline]
3231 fn clip(&self) -> Self {
3232 Self {
3233 $a: self.$a.clamp(0.0, 1.0),
3234 $b: self.$b.clamp(0.0, 1.0),
3235 $c: self.$c.clamp(0.0, 1.0),
3236 alpha: self.alpha.clamp(0.0, 1.0),
3237 }
3238 }
3239 }
3240 };
3241}
3242
3243macro_rules! unbounded_color_gamut {
3244 ($t: ty, $a: ident, $b: ident, $c: ident) => {
3245 impl ColorGamut for $t {
3246 #[inline]
3247 fn in_gamut(&self) -> bool {
3248 true
3249 }
3250
3251 #[inline]
3252 fn clip(&self) -> Self {
3253 *self
3254 }
3255 }
3256 };
3257}
3258
3259macro_rules! hsl_hwb_color_gamut {
3260 ($t: ty, $a: ident, $b: ident) => {
3261 impl ColorGamut for $t {
3262 #[inline]
3263 fn in_gamut(&self) -> bool {
3264 self.$a >= 0.0 && self.$a <= 100.0 && self.$b >= 0.0 && self.$b <= 100.0
3265 }
3266
3267 #[inline]
3268 fn clip(&self) -> Self {
3269 Self {
3270 h: self.h % 360.0,
3271 $a: self.$a.clamp(0.0, 100.0),
3272 $b: self.$b.clamp(0.0, 100.0),
3273 alpha: self.alpha.clamp(0.0, 1.0),
3274 }
3275 }
3276 }
3277 };
3278}
3279
3280bounded_color_gamut!(SRGB, r, g, b);
3281bounded_color_gamut!(SRGBLinear, r, g, b);
3282bounded_color_gamut!(P3, r, g, b);
3283bounded_color_gamut!(A98, r, g, b);
3284bounded_color_gamut!(ProPhoto, r, g, b);
3285bounded_color_gamut!(Rec2020, r, g, b);
3286unbounded_color_gamut!(LAB, l, a, b);
3287unbounded_color_gamut!(OKLAB, l, a, b);
3288unbounded_color_gamut!(XYZd50, x, y, z);
3289unbounded_color_gamut!(XYZd65, x, y, z);
3290unbounded_color_gamut!(LCH, l, c, h);
3291unbounded_color_gamut!(OKLCH, l, c, h);
3292hsl_hwb_color_gamut!(HSL, s, l);
3293hsl_hwb_color_gamut!(HWB, w, b);
3294
3295impl ColorGamut for RGB {
3296 #[inline]
3297 fn in_gamut(&self) -> bool {
3298 self.r >= 0.0 && self.r <= 255.0 && self.g >= 0.0 && self.g <= 255.0 && self.b >= 0.0 && self.b <= 255.0
3299 }
3300
3301 #[inline]
3302 fn clip(&self) -> Self {
3303 Self {
3304 r: self.r.clamp(0.0, 255.0),
3305 g: self.g.clamp(0.0, 255.0),
3306 b: self.b.clamp(0.0, 255.0),
3307 alpha: self.alpha.clamp(0.0, 1.0),
3308 }
3309 }
3310}
3311
3312fn delta_eok<T: Into<OKLAB>>(a: T, b: OKLCH) -> f32 {
3313 let a: OKLAB = a.into();
3315 let b: OKLAB = b.into();
3316 let delta_l = a.l - b.l;
3317 let delta_a = a.a - b.a;
3318 let delta_b = a.b - b.b;
3319
3320 (delta_l.powi(2) + delta_a.powi(2) + delta_b.powi(2)).sqrt()
3321}
3322
3323fn map_gamut<T>(color: T) -> T
3324where
3325 T: Into<OKLCH> + ColorGamut + Into<OKLAB> + From<OKLCH> + Copy,
3326{
3327 const JND: f32 = 0.02;
3328 const EPSILON: f32 = 0.00001;
3329
3330 let mut current: OKLCH = color.into();
3332
3333 if (current.l - 1.0).abs() < EPSILON || current.l > 1.0 {
3335 return OKLCH {
3336 l: 1.0,
3337 c: 0.0,
3338 h: 0.0,
3339 alpha: current.alpha,
3340 }
3341 .into();
3342 }
3343
3344 if current.l < EPSILON {
3346 return OKLCH {
3347 l: 0.0,
3348 c: 0.0,
3349 h: 0.0,
3350 alpha: current.alpha,
3351 }
3352 .into();
3353 }
3354
3355 let mut min = 0.0;
3356 let mut max = current.c;
3357
3358 while (max - min) > EPSILON {
3359 let chroma = (min + max) / 2.0;
3360 current.c = chroma;
3361
3362 let converted = T::from(current);
3363 if converted.in_gamut() {
3364 min = chroma;
3365 continue;
3366 }
3367
3368 let clipped = converted.clip();
3369 let delta_e = delta_eok(clipped, current);
3370 if delta_e < JND {
3371 return clipped;
3372 }
3373
3374 max = chroma;
3375 }
3376
3377 current.into()
3378}
3379
3380fn parse_color_mix<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
3381 input.expect_ident_matching("in")?;
3382 let method = ColorSpaceName::parse(input)?;
3383
3384 let hue_method = if matches!(
3385 method,
3386 ColorSpaceName::Hsl | ColorSpaceName::Hwb | ColorSpaceName::LCH | ColorSpaceName::OKLCH
3387 ) {
3388 let hue_method = input.try_parse(HueInterpolationMethod::parse);
3389 if hue_method.is_ok() {
3390 input.expect_ident_matching("hue")?;
3391 }
3392 hue_method
3393 } else {
3394 Ok(HueInterpolationMethod::Shorter)
3395 };
3396
3397 let hue_method = hue_method.unwrap_or(HueInterpolationMethod::Shorter);
3398 input.expect_comma()?;
3399
3400 let first_percent = input.try_parse(|input| input.expect_percentage());
3401 let first_color = CssColor::parse(input)?;
3402 let first_percent = first_percent
3403 .or_else(|_| input.try_parse(|input| input.expect_percentage()))
3404 .ok();
3405 input.expect_comma()?;
3406
3407 let second_percent = input.try_parse(|input| input.expect_percentage());
3408 let second_color = CssColor::parse(input)?;
3409 let second_percent = second_percent
3410 .or_else(|_| input.try_parse(|input| input.expect_percentage()))
3411 .ok();
3412
3413 let (p1, p2) = if first_percent.is_none() && second_percent.is_none() {
3415 (0.5, 0.5)
3416 } else {
3417 let p2 = second_percent.unwrap_or_else(|| 1.0 - first_percent.unwrap());
3418 let p1 = first_percent.unwrap_or_else(|| 1.0 - second_percent.unwrap());
3419 (p1, p2)
3420 };
3421
3422 if (p1 + p2) == 0.0 {
3423 return Err(input.new_custom_error(ParserError::InvalidValue));
3424 }
3425
3426 match method {
3427 ColorSpaceName::SRGB => first_color.interpolate::<SRGB>(p1, &second_color, p2, hue_method),
3428 ColorSpaceName::SRGBLinear => first_color.interpolate::<SRGBLinear>(p1, &second_color, p2, hue_method),
3429 ColorSpaceName::Hsl => first_color.interpolate::<HSL>(p1, &second_color, p2, hue_method),
3430 ColorSpaceName::Hwb => first_color.interpolate::<HWB>(p1, &second_color, p2, hue_method),
3431 ColorSpaceName::LAB => first_color.interpolate::<LAB>(p1, &second_color, p2, hue_method),
3432 ColorSpaceName::LCH => first_color.interpolate::<LCH>(p1, &second_color, p2, hue_method),
3433 ColorSpaceName::OKLAB => first_color.interpolate::<OKLAB>(p1, &second_color, p2, hue_method),
3434 ColorSpaceName::OKLCH => first_color.interpolate::<OKLCH>(p1, &second_color, p2, hue_method),
3435 ColorSpaceName::XYZ | ColorSpaceName::XYZd65 => {
3436 first_color.interpolate::<XYZd65>(p1, &second_color, p2, hue_method)
3437 }
3438 ColorSpaceName::XYZd50 => first_color.interpolate::<XYZd50>(p1, &second_color, p2, hue_method),
3439 }
3440 .map_err(|_| input.new_custom_error(ParserError::InvalidValue))
3441}
3442
3443impl CssColor {
3444 fn get_type_id(&self) -> TypeId {
3445 match self {
3446 CssColor::RGBA(..) => TypeId::of::<SRGB>(),
3447 CssColor::LAB(lab) => match &**lab {
3448 LABColor::LAB(..) => TypeId::of::<LAB>(),
3449 LABColor::LCH(..) => TypeId::of::<LCH>(),
3450 LABColor::OKLAB(..) => TypeId::of::<OKLAB>(),
3451 LABColor::OKLCH(..) => TypeId::of::<OKLCH>(),
3452 },
3453 CssColor::Predefined(predefined) => match &**predefined {
3454 PredefinedColor::SRGB(..) => TypeId::of::<SRGB>(),
3455 PredefinedColor::SRGBLinear(..) => TypeId::of::<SRGBLinear>(),
3456 PredefinedColor::DisplayP3(..) => TypeId::of::<P3>(),
3457 PredefinedColor::A98(..) => TypeId::of::<A98>(),
3458 PredefinedColor::ProPhoto(..) => TypeId::of::<ProPhoto>(),
3459 PredefinedColor::Rec2020(..) => TypeId::of::<Rec2020>(),
3460 PredefinedColor::XYZd50(..) => TypeId::of::<XYZd50>(),
3461 PredefinedColor::XYZd65(..) => TypeId::of::<XYZd65>(),
3462 },
3463 CssColor::Float(float) => match &**float {
3464 FloatColor::RGB(..) => TypeId::of::<SRGB>(),
3465 FloatColor::HSL(..) => TypeId::of::<HSL>(),
3466 FloatColor::HWB(..) => TypeId::of::<HWB>(),
3467 },
3468 _ => unreachable!(),
3469 }
3470 }
3471
3472 fn to_light_dark(&self) -> CssColor {
3473 match self {
3474 CssColor::LightDark(..) => self.clone(),
3475 _ => CssColor::LightDark(Box::new(self.clone()), Box::new(self.clone())),
3476 }
3477 }
3478
3479 pub fn interpolate<T>(
3482 &self,
3483 mut p1: f32,
3484 other: &CssColor,
3485 mut p2: f32,
3486 method: HueInterpolationMethod,
3487 ) -> Result<CssColor, ()>
3488 where
3489 for<'a> T: 'static
3490 + TryFrom<&'a CssColor>
3491 + Interpolate
3492 + Into<CssColor>
3493 + Into<OKLCH>
3494 + ColorGamut
3495 + Into<OKLAB>
3496 + From<OKLCH>
3497 + Copy,
3498 {
3499 if matches!(self, CssColor::CurrentColor | CssColor::System(..))
3500 || matches!(other, CssColor::CurrentColor | CssColor::System(..))
3501 {
3502 return Err(());
3503 }
3504
3505 if matches!(self, CssColor::LightDark(..)) || matches!(other, CssColor::LightDark(..)) {
3506 if let (CssColor::LightDark(al, ad), CssColor::LightDark(bl, bd)) =
3507 (self.to_light_dark(), other.to_light_dark())
3508 {
3509 return Ok(CssColor::LightDark(
3510 Box::new(al.interpolate::<T>(p1, &bl, p2, method)?),
3511 Box::new(ad.interpolate::<T>(p1, &bd, p2, method)?),
3512 ));
3513 }
3514 }
3515
3516 let type_id = TypeId::of::<T>();
3517 let converted_first = self.get_type_id() != type_id;
3518 let converted_second = other.get_type_id() != type_id;
3519
3520 let mut first_color = T::try_from(self).map_err(|_| ())?;
3522 let mut second_color = T::try_from(other).map_err(|_| ())?;
3523
3524 if converted_first && !first_color.in_gamut() {
3525 first_color = map_gamut(first_color);
3526 }
3527
3528 if converted_second && !second_color.in_gamut() {
3529 second_color = map_gamut(second_color);
3530 }
3531
3532 if converted_first {
3534 first_color.adjust_powerless_components();
3535 }
3536
3537 if converted_second {
3538 second_color.adjust_powerless_components();
3539 }
3540
3541 first_color.fill_missing_components(&second_color);
3543 second_color.fill_missing_components(&first_color);
3544
3545 first_color.adjust_hue(&mut second_color, method);
3547
3548 first_color.premultiply();
3550 second_color.premultiply();
3551
3552 let mut alpha_multiplier = p1 + p2;
3554 if alpha_multiplier != 1.0 {
3555 p1 = p1 / alpha_multiplier;
3556 p2 = p2 / alpha_multiplier;
3557 if alpha_multiplier > 1.0 {
3558 alpha_multiplier = 1.0;
3559 }
3560 }
3561
3562 let mut result_color = first_color.interpolate(p1, &second_color, p2);
3563 result_color.unpremultiply(alpha_multiplier);
3564
3565 Ok(result_color.into())
3566 }
3567}
3568
3569pub trait Interpolate {
3571 fn adjust_powerless_components(&mut self) {}
3573 fn fill_missing_components(&mut self, other: &Self);
3575 fn adjust_hue(&mut self, _: &mut Self, _: HueInterpolationMethod) {}
3577 fn premultiply(&mut self);
3579 fn unpremultiply(&mut self, alpha_multiplier: f32);
3581 fn interpolate(&self, p1: f32, other: &Self, p2: f32) -> Self;
3583}
3584
3585macro_rules! interpolate {
3586 ($a: ident, $b: ident, $c: ident) => {
3587 fn fill_missing_components(&mut self, other: &Self) {
3588 if self.$a.is_nan() {
3589 self.$a = other.$a;
3590 }
3591
3592 if self.$b.is_nan() {
3593 self.$b = other.$b;
3594 }
3595
3596 if self.$c.is_nan() {
3597 self.$c = other.$c;
3598 }
3599
3600 if self.alpha.is_nan() {
3601 self.alpha = other.alpha;
3602 }
3603 }
3604
3605 fn interpolate(&self, p1: f32, other: &Self, p2: f32) -> Self {
3606 Self {
3607 $a: self.$a * p1 + other.$a * p2,
3608 $b: self.$b * p1 + other.$b * p2,
3609 $c: self.$c * p1 + other.$c * p2,
3610 alpha: self.alpha * p1 + other.alpha * p2,
3611 }
3612 }
3613 };
3614}
3615
3616macro_rules! rectangular_premultiply {
3617 ($a: ident, $b: ident, $c: ident) => {
3618 fn premultiply(&mut self) {
3619 if !self.alpha.is_nan() {
3620 self.$a *= self.alpha;
3621 self.$b *= self.alpha;
3622 self.$c *= self.alpha;
3623 }
3624 }
3625
3626 fn unpremultiply(&mut self, alpha_multiplier: f32) {
3627 if !self.alpha.is_nan() && self.alpha != 0.0 {
3628 self.$a /= self.alpha;
3629 self.$b /= self.alpha;
3630 self.$c /= self.alpha;
3631 self.alpha *= alpha_multiplier;
3632 }
3633 }
3634 };
3635}
3636
3637macro_rules! polar_premultiply {
3638 ($a: ident, $b: ident) => {
3639 fn premultiply(&mut self) {
3640 if !self.alpha.is_nan() {
3641 self.$a *= self.alpha;
3642 self.$b *= self.alpha;
3643 }
3644 }
3645
3646 fn unpremultiply(&mut self, alpha_multiplier: f32) {
3647 self.h %= 360.0;
3648 if !self.alpha.is_nan() {
3649 self.$a /= self.alpha;
3650 self.$b /= self.alpha;
3651 self.alpha *= alpha_multiplier;
3652 }
3653 }
3654 };
3655}
3656
3657macro_rules! adjust_powerless_lab {
3658 () => {
3659 fn adjust_powerless_components(&mut self) {
3660 if self.l.abs() < f32::EPSILON {
3662 self.a = f32::NAN;
3663 self.b = f32::NAN;
3664 }
3665 }
3666 };
3667}
3668
3669macro_rules! adjust_powerless_lch {
3670 () => {
3671 fn adjust_powerless_components(&mut self) {
3672 if self.c.abs() < f32::EPSILON {
3675 self.h = f32::NAN;
3676 }
3677
3678 if self.l.abs() < f32::EPSILON {
3679 self.c = f32::NAN;
3680 self.h = f32::NAN;
3681 }
3682 }
3683
3684 fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {
3685 method.interpolate(&mut self.h, &mut other.h);
3686 }
3687 };
3688}
3689
3690impl Interpolate for SRGB {
3691 rectangular_premultiply!(r, g, b);
3692 interpolate!(r, g, b);
3693}
3694
3695impl Interpolate for SRGBLinear {
3696 rectangular_premultiply!(r, g, b);
3697 interpolate!(r, g, b);
3698}
3699
3700impl Interpolate for XYZd50 {
3701 rectangular_premultiply!(x, y, z);
3702 interpolate!(x, y, z);
3703}
3704
3705impl Interpolate for XYZd65 {
3706 rectangular_premultiply!(x, y, z);
3707 interpolate!(x, y, z);
3708}
3709
3710impl Interpolate for LAB {
3711 adjust_powerless_lab!();
3712 rectangular_premultiply!(l, a, b);
3713 interpolate!(l, a, b);
3714}
3715
3716impl Interpolate for OKLAB {
3717 adjust_powerless_lab!();
3718 rectangular_premultiply!(l, a, b);
3719 interpolate!(l, a, b);
3720}
3721
3722impl Interpolate for LCH {
3723 adjust_powerless_lch!();
3724 polar_premultiply!(l, c);
3725 interpolate!(l, c, h);
3726}
3727
3728impl Interpolate for OKLCH {
3729 adjust_powerless_lch!();
3730 polar_premultiply!(l, c);
3731 interpolate!(l, c, h);
3732}
3733
3734impl Interpolate for HSL {
3735 polar_premultiply!(s, l);
3736
3737 fn adjust_powerless_components(&mut self) {
3738 if self.s.abs() < f32::EPSILON {
3741 self.h = f32::NAN;
3742 }
3743
3744 if self.l.abs() < f32::EPSILON || (self.l - 100.0).abs() < f32::EPSILON {
3745 self.h = f32::NAN;
3746 self.s = f32::NAN;
3747 }
3748 }
3749
3750 fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {
3751 method.interpolate(&mut self.h, &mut other.h);
3752 }
3753
3754 interpolate!(h, s, l);
3755}
3756
3757impl Interpolate for HWB {
3758 polar_premultiply!(w, b);
3759
3760 fn adjust_powerless_components(&mut self) {
3761 if (self.w + self.b - 100.0).abs() < f32::EPSILON {
3764 self.h = f32::NAN;
3765 }
3766 }
3767
3768 fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {
3769 method.interpolate(&mut self.h, &mut other.h);
3770 }
3771
3772 interpolate!(h, w, b);
3773}
3774
3775impl HueInterpolationMethod {
3776 fn interpolate(&self, a: &mut f32, b: &mut f32) {
3777 if *self != HueInterpolationMethod::Specified {
3779 *a = ((*a % 360.0) + 360.0) % 360.0;
3780 *b = ((*b % 360.0) + 360.0) % 360.0;
3781 }
3782
3783 match self {
3784 HueInterpolationMethod::Shorter => {
3785 let delta = *b - *a;
3787 if delta > 180.0 {
3788 *a += 360.0;
3789 } else if delta < -180.0 {
3790 *b += 360.0;
3791 }
3792 }
3793 HueInterpolationMethod::Longer => {
3794 let delta = *b - *a;
3796 if 0.0 < delta && delta < 180.0 {
3797 *a += 360.0;
3798 } else if -180.0 < delta && delta < 0.0 {
3799 *b += 360.0;
3800 }
3801 }
3802 HueInterpolationMethod::Increasing => {
3803 if *b < *a {
3805 *b += 360.0;
3806 }
3807 }
3808 HueInterpolationMethod::Decreasing => {
3809 if *a < *b {
3811 *a += 360.0;
3812 }
3813 }
3814 HueInterpolationMethod::Specified => {}
3815 }
3816 }
3817}
3818
3819#[cfg(feature = "visitor")]
3820#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
3821impl<'i, V: ?Sized + Visitor<'i, T>, T: Visit<'i, T, V>> Visit<'i, T, V> for RGBA {
3822 const CHILD_TYPES: VisitTypes = VisitTypes::empty();
3823 fn visit_children(&mut self, _: &mut V) -> Result<(), V::Error> {
3824 Ok(())
3825 }
3826}
3827
3828#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
3829#[css(case = lower)]
3830#[cfg_attr(feature = "visitor", derive(Visit))]
3831#[cfg_attr(
3832 feature = "serde",
3833 derive(serde::Serialize, serde::Deserialize),
3834 serde(rename_all = "lowercase")
3835)]
3836#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
3837#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
3838pub enum SystemColor {
3840 AccentColor,
3842 AccentColorText,
3844 ActiveText,
3846 ButtonBorder,
3848 ButtonFace,
3850 ButtonText,
3852 Canvas,
3854 CanvasText,
3856 Field,
3858 FieldText,
3860 GrayText,
3862 Highlight,
3864 HighlightText,
3866 LinkText,
3868 Mark,
3870 MarkText,
3872 SelectedItem,
3874 SelectedItemText,
3876 VisitedText,
3878
3879 ActiveBorder,
3882 ActiveCaption,
3884 AppWorkspace,
3886 Background,
3888 ButtonHighlight,
3890 ButtonShadow,
3892 CaptionText,
3894 InactiveBorder,
3896 InactiveCaption,
3898 InactiveCaptionText,
3900 InfoBackground,
3902 InfoText,
3904 Menu,
3906 MenuText,
3908 Scrollbar,
3910 ThreeDDarkShadow,
3912 ThreeDFace,
3914 ThreeDHighlight,
3916 ThreeDLightShadow,
3918 ThreeDShadow,
3920 Window,
3922 WindowFrame,
3924 WindowText,
3926}
3927
3928impl IsCompatible for SystemColor {
3929 fn is_compatible(&self, browsers: Browsers) -> bool {
3930 use SystemColor::*;
3931 match self {
3932 AccentColor | AccentColorText => Feature::AccentSystemColor.is_compatible(browsers),
3933 _ => true,
3934 }
3935 }
3936}