1pub mod convert;
9
10mod color_function;
11pub mod component;
12pub mod gamut;
13pub mod mix;
14pub mod parsing;
15mod to_css;
16
17use self::parsing::ChannelKeyword;
18use crate::derives::*;
19pub use color_function::*;
20use component::ColorComponent;
21use cssparser::color::PredefinedColorSpace;
22
23pub const PRE_ALLOCATED_COLOR_MIX_ITEMS: usize = 3;
25
26pub type ColorMixItemList<T> = smallvec::SmallVec<[T; PRE_ALLOCATED_COLOR_MIX_ITEMS]>;
28
29#[derive(Copy, Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, ToShmem)]
31#[repr(C)]
32pub struct ColorComponents(pub f32, pub f32, pub f32);
33
34impl ColorComponents {
35 #[must_use]
37 pub fn map(self, f: impl Fn(f32) -> f32) -> Self {
38 Self(f(self.0), f(self.1), f(self.2))
39 }
40
41 #[inline]
43 pub fn to_array(&self) -> [f32; 3] {
44 [self.0, self.1, self.2]
45 }
46}
47
48impl std::ops::Add for ColorComponents {
49 type Output = Self;
50
51 fn add(self, rhs: Self) -> Self::Output {
52 Self(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2)
53 }
54}
55
56impl std::ops::Sub for ColorComponents {
57 type Output = Self;
58
59 fn sub(self, rhs: Self) -> Self::Output {
60 Self(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2)
61 }
62}
63
64impl std::ops::Mul for ColorComponents {
65 type Output = Self;
66
67 fn mul(self, rhs: Self) -> Self::Output {
68 Self(self.0 * rhs.0, self.1 * rhs.1, self.2 * rhs.2)
69 }
70}
71
72impl std::ops::Div for ColorComponents {
73 type Output = Self;
74
75 fn div(self, rhs: Self) -> Self::Output {
76 Self(self.0 / rhs.0, self.1 / rhs.1, self.2 / rhs.2)
77 }
78}
79
80#[derive(
84 Clone,
85 Copy,
86 Debug,
87 Deserialize,
88 Eq,
89 MallocSizeOf,
90 Parse,
91 PartialEq,
92 Serialize,
93 ToAnimatedValue,
94 ToComputedValue,
95 ToCss,
96 ToResolvedValue,
97 ToShmem,
98)]
99#[repr(u8)]
100pub enum ColorSpace {
101 Srgb = 0,
106 Hsl,
110 Hwb,
114 Lab,
118 Lch,
122 Oklab,
126 Oklch,
130 SrgbLinear,
133 DisplayP3,
136 DisplayP3Linear,
139 A98Rgb,
142 ProphotoRgb,
145 Rec2020,
148 XyzD50,
151 #[parse(aliases = "xyz")]
156 XyzD65,
157}
158
159impl ColorSpace {
160 #[inline]
162 pub fn is_rectangular(&self) -> bool {
163 !self.is_polar()
164 }
165
166 #[inline]
168 pub fn is_polar(&self) -> bool {
169 matches!(self, Self::Hsl | Self::Hwb | Self::Lch | Self::Oklch)
170 }
171
172 #[inline]
174 pub fn is_rgb_or_xyz_like(&self) -> bool {
175 match self {
176 Self::Srgb
177 | Self::SrgbLinear
178 | Self::DisplayP3
179 | Self::DisplayP3Linear
180 | Self::A98Rgb
181 | Self::ProphotoRgb
182 | Self::Rec2020
183 | Self::XyzD50
184 | Self::XyzD65 => true,
185 _ => false,
186 }
187 }
188
189 #[inline]
192 pub fn hue_index(&self) -> Option<usize> {
193 match self {
194 Self::Hsl | Self::Hwb => Some(0),
195 Self::Lch | Self::Oklch => Some(2),
196
197 _ => {
198 debug_assert!(!self.is_polar());
199 None
200 },
201 }
202 }
203
204 #[inline]
209 pub fn get_linear_color_space(self) -> Option<Self> {
210 match self {
211 Self::Srgb | Self::Hsl | Self::Hwb => Some(Self::SrgbLinear),
212 Self::DisplayP3 => Some(Self::DisplayP3Linear),
213 Self::SrgbLinear | Self::DisplayP3Linear | Self::XyzD50 | Self::XyzD65 => Some(self),
214 Self::A98Rgb | Self::ProphotoRgb | Self::Rec2020 => None,
218 Self::Lab | Self::Lch | Self::Oklab | Self::Oklch => None,
219 }
220 }
221}
222
223#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
225#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
226#[repr(C)]
227pub struct ColorFlags(u8);
228bitflags! {
229 impl ColorFlags : u8 {
230 const C0_IS_NONE = 1 << 0;
232 const C1_IS_NONE = 1 << 1;
234 const C2_IS_NONE = 1 << 2;
236 const ALPHA_IS_NONE = 1 << 3;
238 const NONE_FLAGS = Self::C0_IS_NONE.0 | Self::C1_IS_NONE.0 | Self::C2_IS_NONE.0 | Self::ALPHA_IS_NONE.0;
240 const IS_LEGACY_SRGB = 1 << 4;
243 }
244}
245
246#[derive(Copy, Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToShmem, ToTyped)]
249#[repr(C)]
250#[typed(todo_derive_fields)]
251pub struct AbsoluteColor {
252 pub components: ColorComponents,
254 pub alpha: f32,
256 pub color_space: ColorSpace,
258 pub flags: ColorFlags,
260}
261
262impl PartialEq for AbsoluteColor {
263 fn eq(&self, other: &Self) -> bool {
265 if self.color_space == other.color_space {
268 return self.components == other.components
269 && self.alpha == other.alpha
270 && (self.flags & ColorFlags::NONE_FLAGS) == (other.flags & ColorFlags::NONE_FLAGS);
271 }
272 if self
274 .flags
275 .union(other.flags)
276 .intersects(ColorFlags::NONE_FLAGS)
277 {
278 return false;
279 }
280 const EPSILON: f32 = 0.0001;
284 let a = self.to_color_space(ColorSpace::Oklab);
285 let b = other.to_color_space(ColorSpace::Oklab);
286 (a.components.0 - b.components.0).abs() <= EPSILON
287 && (a.components.1 - b.components.1).abs() <= EPSILON
288 && (a.components.2 - b.components.2).abs() <= EPSILON
289 && (a.alpha - b.alpha).abs() <= EPSILON
290 }
291}
292
293macro_rules! color_components_as {
301 ($c:expr, $t:ty) => {{
302 const_assert_eq!(std::mem::size_of::<$t>(), std::mem::size_of::<[f32; 4]>());
306 const_assert_eq!(std::mem::align_of::<$t>(), std::mem::align_of::<[f32; 4]>());
307 const_assert!(std::mem::size_of::<AbsoluteColor>() >= std::mem::size_of::<$t>());
308 const_assert_eq!(
309 std::mem::align_of::<AbsoluteColor>(),
310 std::mem::align_of::<$t>()
311 );
312
313 std::mem::transmute::<&ColorComponents, &$t>(&$c.components)
314 }};
315}
316
317pub struct ComponentDetails {
319 value: f32,
320 is_none: bool,
321}
322
323impl From<f32> for ComponentDetails {
324 fn from(value: f32) -> Self {
325 Self {
326 value,
327 is_none: false,
328 }
329 }
330}
331
332impl From<u8> for ComponentDetails {
333 fn from(value: u8) -> Self {
334 Self {
335 value: value as f32 / 255.0,
336 is_none: false,
337 }
338 }
339}
340
341impl From<Option<f32>> for ComponentDetails {
342 fn from(value: Option<f32>) -> Self {
343 if let Some(value) = value {
344 Self {
345 value,
346 is_none: false,
347 }
348 } else {
349 Self {
350 value: 0.0,
351 is_none: true,
352 }
353 }
354 }
355}
356
357impl From<ColorComponent<f32>> for ComponentDetails {
358 fn from(value: ColorComponent<f32>) -> Self {
359 if let ColorComponent::Value(value) = value {
360 Self {
361 value,
362 is_none: false,
363 }
364 } else {
365 Self {
366 value: 0.0,
367 is_none: true,
368 }
369 }
370 }
371}
372
373impl AbsoluteColor {
374 pub const TRANSPARENT_BLACK: Self = Self {
376 components: ColorComponents(0.0, 0.0, 0.0),
377 alpha: 0.0,
378 color_space: ColorSpace::Srgb,
379 flags: ColorFlags::IS_LEGACY_SRGB,
380 };
381
382 pub const BLACK: Self = Self {
384 components: ColorComponents(0.0, 0.0, 0.0),
385 alpha: 1.0,
386 color_space: ColorSpace::Srgb,
387 flags: ColorFlags::IS_LEGACY_SRGB,
388 };
389
390 pub const WHITE: Self = Self {
392 components: ColorComponents(1.0, 1.0, 1.0),
393 alpha: 1.0,
394 color_space: ColorSpace::Srgb,
395 flags: ColorFlags::IS_LEGACY_SRGB,
396 };
397
398 pub fn new(
401 color_space: ColorSpace,
402 c1: impl Into<ComponentDetails>,
403 c2: impl Into<ComponentDetails>,
404 c3: impl Into<ComponentDetails>,
405 alpha: impl Into<ComponentDetails>,
406 ) -> Self {
407 let mut flags = ColorFlags::empty();
408
409 macro_rules! cd {
410 ($c:expr,$flag:expr) => {{
411 let component_details = $c.into();
412 if component_details.is_none {
413 flags |= $flag;
414 }
415 component_details.value
416 }};
417 }
418
419 let mut components = ColorComponents(
420 cd!(c1, ColorFlags::C0_IS_NONE),
421 cd!(c2, ColorFlags::C1_IS_NONE),
422 cd!(c3, ColorFlags::C2_IS_NONE),
423 );
424
425 let alpha = cd!(alpha, ColorFlags::ALPHA_IS_NONE);
426
427 if matches!(color_space, ColorSpace::Lab | ColorSpace::Lch) {
429 components.0 = components.0.clamp(0.0, 100.0);
430 }
431
432 if matches!(color_space, ColorSpace::Oklab | ColorSpace::Oklch) {
434 components.0 = components.0.clamp(0.0, 1.0);
435 }
436
437 if matches!(color_space, ColorSpace::Lch | ColorSpace::Oklch) {
439 components.1 = components.1.max(0.0);
440 }
441
442 let alpha = alpha.clamp(0.0, 1.0);
444
445 Self {
446 components,
447 alpha,
448 color_space,
449 flags,
450 }
451 }
452
453 pub fn with_alpha(&self, alpha: impl Into<ComponentDetails>) -> Self {
455 let mut result = *self;
456 let alpha_details = alpha.into();
457 result.alpha = alpha_details.value;
458 result
459 .flags
460 .set(ColorFlags::ALPHA_IS_NONE, alpha_details.is_none);
461 result.flags.remove(ColorFlags::IS_LEGACY_SRGB);
462 result
463 }
464
465 #[inline]
468 #[must_use]
469 pub fn into_srgb_legacy(self) -> Self {
470 let mut result = if !matches!(self.color_space, ColorSpace::Srgb) {
471 self.to_color_space(ColorSpace::Srgb)
472 } else {
473 self
474 };
475
476 result.flags = ColorFlags::IS_LEGACY_SRGB;
479
480 result
481 }
482
483 pub fn srgb_legacy(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
485 let mut result = Self::new(ColorSpace::Srgb, red, green, blue, alpha);
486 result.flags = ColorFlags::IS_LEGACY_SRGB;
487 result
488 }
489
490 #[inline]
492 pub fn raw_components(&self) -> &[f32; 4] {
493 unsafe { color_components_as!(self, [f32; 4]) }
494 }
495
496 #[inline]
498 pub fn is_legacy_syntax(&self) -> bool {
499 match self.color_space {
501 ColorSpace::Srgb => self.flags.contains(ColorFlags::IS_LEGACY_SRGB),
502 ColorSpace::Hsl | ColorSpace::Hwb => true,
503 _ => false,
504 }
505 }
506
507 #[inline]
509 pub fn is_transparent(&self) -> bool {
510 self.flags.contains(ColorFlags::ALPHA_IS_NONE) || self.alpha == 0.0
511 }
512
513 #[inline]
515 pub fn c0(&self) -> Option<f32> {
516 if self.flags.contains(ColorFlags::C0_IS_NONE) {
517 None
518 } else {
519 Some(self.components.0)
520 }
521 }
522
523 #[inline]
525 pub fn c1(&self) -> Option<f32> {
526 if self.flags.contains(ColorFlags::C1_IS_NONE) {
527 None
528 } else {
529 Some(self.components.1)
530 }
531 }
532
533 #[inline]
535 pub fn c2(&self) -> Option<f32> {
536 if self.flags.contains(ColorFlags::C2_IS_NONE) {
537 None
538 } else {
539 Some(self.components.2)
540 }
541 }
542
543 #[inline]
545 pub fn alpha(&self) -> Option<f32> {
546 if self.flags.contains(ColorFlags::ALPHA_IS_NONE) {
547 None
548 } else {
549 Some(self.alpha)
550 }
551 }
552
553 pub fn get_component_by_channel_keyword(
555 &self,
556 channel_keyword: ChannelKeyword,
557 ) -> Result<Option<f32>, ()> {
558 if channel_keyword == ChannelKeyword::ALPHA {
559 return Ok(self.alpha());
560 }
561
562 Ok(match self.color_space {
563 ColorSpace::Srgb => {
564 if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) {
565 match channel_keyword {
566 ChannelKeyword::R => self.c0().map(|v| v * 255.0),
567 ChannelKeyword::G => self.c1().map(|v| v * 255.0),
568 ChannelKeyword::B => self.c2().map(|v| v * 255.0),
569 _ => return Err(()),
570 }
571 } else {
572 match channel_keyword {
573 ChannelKeyword::R => self.c0(),
574 ChannelKeyword::G => self.c1(),
575 ChannelKeyword::B => self.c2(),
576 _ => return Err(()),
577 }
578 }
579 },
580 ColorSpace::Hsl => match channel_keyword {
581 ChannelKeyword::H => self.c0(),
582 ChannelKeyword::S => self.c1(),
583 ChannelKeyword::L => self.c2(),
584 _ => return Err(()),
585 },
586 ColorSpace::Hwb => match channel_keyword {
587 ChannelKeyword::H => self.c0(),
588 ChannelKeyword::W => self.c1(),
589 ChannelKeyword::B => self.c2(),
590 _ => return Err(()),
591 },
592 ColorSpace::Lab | ColorSpace::Oklab => match channel_keyword {
593 ChannelKeyword::L => self.c0(),
594 ChannelKeyword::A => self.c1(),
595 ChannelKeyword::B => self.c2(),
596 _ => return Err(()),
597 },
598 ColorSpace::Lch | ColorSpace::Oklch => match channel_keyword {
599 ChannelKeyword::L => self.c0(),
600 ChannelKeyword::C => self.c1(),
601 ChannelKeyword::H => self.c2(),
602 _ => return Err(()),
603 },
604 ColorSpace::SrgbLinear
605 | ColorSpace::DisplayP3
606 | ColorSpace::DisplayP3Linear
607 | ColorSpace::A98Rgb
608 | ColorSpace::ProphotoRgb
609 | ColorSpace::Rec2020 => match channel_keyword {
610 ChannelKeyword::R => self.c0(),
611 ChannelKeyword::G => self.c1(),
612 ChannelKeyword::B => self.c2(),
613 _ => return Err(()),
614 },
615 ColorSpace::XyzD50 | ColorSpace::XyzD65 => match channel_keyword {
616 ChannelKeyword::X => self.c0(),
617 ChannelKeyword::Y => self.c1(),
618 ChannelKeyword::Z => self.c2(),
619 _ => return Err(()),
620 },
621 })
622 }
623
624 pub fn to_color_space(&self, color_space: ColorSpace) -> Self {
626 use ColorSpace::*;
627
628 if self.color_space == color_space {
629 return *self;
630 }
631
632 macro_rules! missing_to_zero {
636 ($c:expr) => {{ crate::values::normalize($c.unwrap_or(0.0)) }};
637 }
638
639 let components = ColorComponents(
640 missing_to_zero!(self.c0()),
641 missing_to_zero!(self.c1()),
642 missing_to_zero!(self.c2()),
643 );
644
645 let result = match (self.color_space, color_space) {
646 (Srgb, Hsl) => convert::rgb_to_hsl(&components),
650 (Srgb, Hwb) => convert::rgb_to_hwb(&components),
651 (Hsl, Srgb) => convert::hsl_to_rgb(&components),
652 (Hwb, Srgb) => convert::hwb_to_rgb(&components),
653 (Lab, Lch) | (Oklab, Oklch) => convert::orthogonal_to_polar(
654 &components,
655 convert::epsilon_for_range(0.0, if color_space == Lch { 100.0 } else { 1.0 }),
656 ),
657 (Lch, Lab) | (Oklch, Oklab) => convert::polar_to_orthogonal(&components),
658
659 _ => {
661 let (xyz, white_point) = match self.color_space {
662 Lab => convert::to_xyz::<convert::Lab>(&components),
663 Lch => convert::to_xyz::<convert::Lch>(&components),
664 Oklab => convert::to_xyz::<convert::Oklab>(&components),
665 Oklch => convert::to_xyz::<convert::Oklch>(&components),
666 Srgb => convert::to_xyz::<convert::Srgb>(&components),
667 Hsl => convert::to_xyz::<convert::Hsl>(&components),
668 Hwb => convert::to_xyz::<convert::Hwb>(&components),
669 SrgbLinear => convert::to_xyz::<convert::SrgbLinear>(&components),
670 DisplayP3 => convert::to_xyz::<convert::DisplayP3>(&components),
671 DisplayP3Linear => convert::to_xyz::<convert::DisplayP3Linear>(&components),
672 A98Rgb => convert::to_xyz::<convert::A98Rgb>(&components),
673 ProphotoRgb => convert::to_xyz::<convert::ProphotoRgb>(&components),
674 Rec2020 => convert::to_xyz::<convert::Rec2020>(&components),
675 XyzD50 => convert::to_xyz::<convert::XyzD50>(&components),
676 XyzD65 => convert::to_xyz::<convert::XyzD65>(&components),
677 };
678
679 match color_space {
680 Lab => convert::from_xyz::<convert::Lab>(&xyz, white_point),
681 Lch => convert::from_xyz::<convert::Lch>(&xyz, white_point),
682 Oklab => convert::from_xyz::<convert::Oklab>(&xyz, white_point),
683 Oklch => convert::from_xyz::<convert::Oklch>(&xyz, white_point),
684 Srgb => convert::from_xyz::<convert::Srgb>(&xyz, white_point),
685 Hsl => convert::from_xyz::<convert::Hsl>(&xyz, white_point),
686 Hwb => convert::from_xyz::<convert::Hwb>(&xyz, white_point),
687 SrgbLinear => convert::from_xyz::<convert::SrgbLinear>(&xyz, white_point),
688 DisplayP3 => convert::from_xyz::<convert::DisplayP3>(&xyz, white_point),
689 DisplayP3Linear => {
690 convert::from_xyz::<convert::DisplayP3Linear>(&xyz, white_point)
691 },
692 A98Rgb => convert::from_xyz::<convert::A98Rgb>(&xyz, white_point),
693 ProphotoRgb => convert::from_xyz::<convert::ProphotoRgb>(&xyz, white_point),
694 Rec2020 => convert::from_xyz::<convert::Rec2020>(&xyz, white_point),
695 XyzD50 => convert::from_xyz::<convert::XyzD50>(&xyz, white_point),
696 XyzD65 => convert::from_xyz::<convert::XyzD65>(&xyz, white_point),
697 }
698 },
699 };
700
701 macro_rules! nan_to_missing {
704 ($v:expr) => {{ if $v.is_nan() { None } else { Some($v) } }};
705 }
706
707 Self::new(
708 color_space,
709 nan_to_missing!(result.0),
710 nan_to_missing!(result.1),
711 nan_to_missing!(result.2),
712 self.alpha(),
713 )
714 }
715
716 pub fn from_nscolor(color: u32) -> Self {
718 let [r, g, b, a] = color.to_le_bytes();
719 Self::srgb_legacy(r, g, b, a as f32 / 255.0)
720 }
721}
722
723#[test]
724fn from_nscolor_should_be_in_legacy_syntax() {
725 let result = AbsoluteColor::from_nscolor(0x336699CC);
726 assert!(result.flags.contains(ColorFlags::IS_LEGACY_SRGB));
727 assert!(result.is_legacy_syntax());
728}
729
730impl From<PredefinedColorSpace> for ColorSpace {
731 fn from(value: PredefinedColorSpace) -> Self {
732 match value {
733 PredefinedColorSpace::Srgb => ColorSpace::Srgb,
734 PredefinedColorSpace::SrgbLinear => ColorSpace::SrgbLinear,
735 PredefinedColorSpace::DisplayP3 => ColorSpace::DisplayP3,
736 PredefinedColorSpace::DisplayP3Linear => ColorSpace::DisplayP3Linear,
737 PredefinedColorSpace::A98Rgb => ColorSpace::A98Rgb,
738 PredefinedColorSpace::ProphotoRgb => ColorSpace::ProphotoRgb,
739 PredefinedColorSpace::Rec2020 => ColorSpace::Rec2020,
740 PredefinedColorSpace::XyzD50 => ColorSpace::XyzD50,
741 PredefinedColorSpace::XyzD65 => ColorSpace::XyzD65,
742 }
743 }
744}