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 IS_LEGACY_SRGB = 1 << 4;
241 }
242}
243
244#[derive(Copy, Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToShmem, ToTyped)]
247#[repr(C)]
248#[typed(todo_derive_fields)]
249pub struct AbsoluteColor {
250 pub components: ColorComponents,
252 pub alpha: f32,
254 pub color_space: ColorSpace,
256 pub flags: ColorFlags,
258}
259
260impl PartialEq for AbsoluteColor {
261 fn eq(&self, other: &Self) -> bool {
263 let none_flags = ColorFlags::C0_IS_NONE
264 | ColorFlags::C1_IS_NONE
265 | ColorFlags::C2_IS_NONE
266 | ColorFlags::ALPHA_IS_NONE;
267 if self.color_space == other.color_space {
270 return self.components == other.components
271 && self.alpha == other.alpha
272 && (self.flags & none_flags) == (other.flags & none_flags);
273 }
274 if self.flags.union(other.flags).intersects(none_flags) {
276 return false;
277 }
278 const EPSILON: f32 = 0.0001;
282 let a = self.to_color_space(ColorSpace::Oklab);
283 let b = other.to_color_space(ColorSpace::Oklab);
284 (a.components.0 - b.components.0).abs() <= EPSILON
285 && (a.components.1 - b.components.1).abs() <= EPSILON
286 && (a.components.2 - b.components.2).abs() <= EPSILON
287 && (a.alpha - b.alpha).abs() <= EPSILON
288 }
289}
290
291macro_rules! color_components_as {
299 ($c:expr, $t:ty) => {{
300 const_assert_eq!(std::mem::size_of::<$t>(), std::mem::size_of::<[f32; 4]>());
304 const_assert_eq!(std::mem::align_of::<$t>(), std::mem::align_of::<[f32; 4]>());
305 const_assert!(std::mem::size_of::<AbsoluteColor>() >= std::mem::size_of::<$t>());
306 const_assert_eq!(
307 std::mem::align_of::<AbsoluteColor>(),
308 std::mem::align_of::<$t>()
309 );
310
311 std::mem::transmute::<&ColorComponents, &$t>(&$c.components)
312 }};
313}
314
315pub struct ComponentDetails {
317 value: f32,
318 is_none: bool,
319}
320
321impl From<f32> for ComponentDetails {
322 fn from(value: f32) -> Self {
323 Self {
324 value,
325 is_none: false,
326 }
327 }
328}
329
330impl From<u8> for ComponentDetails {
331 fn from(value: u8) -> Self {
332 Self {
333 value: value as f32 / 255.0,
334 is_none: false,
335 }
336 }
337}
338
339impl From<Option<f32>> for ComponentDetails {
340 fn from(value: Option<f32>) -> Self {
341 if let Some(value) = value {
342 Self {
343 value,
344 is_none: false,
345 }
346 } else {
347 Self {
348 value: 0.0,
349 is_none: true,
350 }
351 }
352 }
353}
354
355impl From<ColorComponent<f32>> for ComponentDetails {
356 fn from(value: ColorComponent<f32>) -> Self {
357 if let ColorComponent::Value(value) = value {
358 Self {
359 value,
360 is_none: false,
361 }
362 } else {
363 Self {
364 value: 0.0,
365 is_none: true,
366 }
367 }
368 }
369}
370
371impl AbsoluteColor {
372 pub const TRANSPARENT_BLACK: Self = Self {
374 components: ColorComponents(0.0, 0.0, 0.0),
375 alpha: 0.0,
376 color_space: ColorSpace::Srgb,
377 flags: ColorFlags::IS_LEGACY_SRGB,
378 };
379
380 pub const BLACK: Self = Self {
382 components: ColorComponents(0.0, 0.0, 0.0),
383 alpha: 1.0,
384 color_space: ColorSpace::Srgb,
385 flags: ColorFlags::IS_LEGACY_SRGB,
386 };
387
388 pub const WHITE: Self = Self {
390 components: ColorComponents(1.0, 1.0, 1.0),
391 alpha: 1.0,
392 color_space: ColorSpace::Srgb,
393 flags: ColorFlags::IS_LEGACY_SRGB,
394 };
395
396 pub fn new(
399 color_space: ColorSpace,
400 c1: impl Into<ComponentDetails>,
401 c2: impl Into<ComponentDetails>,
402 c3: impl Into<ComponentDetails>,
403 alpha: impl Into<ComponentDetails>,
404 ) -> Self {
405 let mut flags = ColorFlags::empty();
406
407 macro_rules! cd {
408 ($c:expr,$flag:expr) => {{
409 let component_details = $c.into();
410 if component_details.is_none {
411 flags |= $flag;
412 }
413 component_details.value
414 }};
415 }
416
417 let mut components = ColorComponents(
418 cd!(c1, ColorFlags::C0_IS_NONE),
419 cd!(c2, ColorFlags::C1_IS_NONE),
420 cd!(c3, ColorFlags::C2_IS_NONE),
421 );
422
423 let alpha = cd!(alpha, ColorFlags::ALPHA_IS_NONE);
424
425 if matches!(color_space, ColorSpace::Lab | ColorSpace::Lch) {
427 components.0 = components.0.clamp(0.0, 100.0);
428 }
429
430 if matches!(color_space, ColorSpace::Oklab | ColorSpace::Oklch) {
432 components.0 = components.0.clamp(0.0, 1.0);
433 }
434
435 if matches!(color_space, ColorSpace::Lch | ColorSpace::Oklch) {
437 components.1 = components.1.max(0.0);
438 }
439
440 let alpha = alpha.clamp(0.0, 1.0);
442
443 Self {
444 components,
445 alpha,
446 color_space,
447 flags,
448 }
449 }
450
451 pub fn with_alpha(&self, alpha: impl Into<ComponentDetails>) -> Self {
453 let mut result = *self;
454 let alpha_details = alpha.into();
455 result.alpha = alpha_details.value;
456 result
457 .flags
458 .set(ColorFlags::ALPHA_IS_NONE, alpha_details.is_none);
459 result.flags.remove(ColorFlags::IS_LEGACY_SRGB);
460 result
461 }
462
463 #[inline]
466 #[must_use]
467 pub fn into_srgb_legacy(self) -> Self {
468 let mut result = if !matches!(self.color_space, ColorSpace::Srgb) {
469 self.to_color_space(ColorSpace::Srgb)
470 } else {
471 self
472 };
473
474 result.flags = ColorFlags::IS_LEGACY_SRGB;
477
478 result
479 }
480
481 pub fn srgb_legacy(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
483 let mut result = Self::new(ColorSpace::Srgb, red, green, blue, alpha);
484 result.flags = ColorFlags::IS_LEGACY_SRGB;
485 result
486 }
487
488 #[inline]
490 pub fn raw_components(&self) -> &[f32; 4] {
491 unsafe { color_components_as!(self, [f32; 4]) }
492 }
493
494 #[inline]
496 pub fn is_legacy_syntax(&self) -> bool {
497 match self.color_space {
499 ColorSpace::Srgb => self.flags.contains(ColorFlags::IS_LEGACY_SRGB),
500 ColorSpace::Hsl | ColorSpace::Hwb => true,
501 _ => false,
502 }
503 }
504
505 #[inline]
507 pub fn is_transparent(&self) -> bool {
508 self.flags.contains(ColorFlags::ALPHA_IS_NONE) || self.alpha == 0.0
509 }
510
511 #[inline]
513 pub fn c0(&self) -> Option<f32> {
514 if self.flags.contains(ColorFlags::C0_IS_NONE) {
515 None
516 } else {
517 Some(self.components.0)
518 }
519 }
520
521 #[inline]
523 pub fn c1(&self) -> Option<f32> {
524 if self.flags.contains(ColorFlags::C1_IS_NONE) {
525 None
526 } else {
527 Some(self.components.1)
528 }
529 }
530
531 #[inline]
533 pub fn c2(&self) -> Option<f32> {
534 if self.flags.contains(ColorFlags::C2_IS_NONE) {
535 None
536 } else {
537 Some(self.components.2)
538 }
539 }
540
541 #[inline]
543 pub fn alpha(&self) -> Option<f32> {
544 if self.flags.contains(ColorFlags::ALPHA_IS_NONE) {
545 None
546 } else {
547 Some(self.alpha)
548 }
549 }
550
551 pub fn get_component_by_channel_keyword(
553 &self,
554 channel_keyword: ChannelKeyword,
555 ) -> Result<Option<f32>, ()> {
556 if channel_keyword == ChannelKeyword::ALPHA {
557 return Ok(self.alpha());
558 }
559
560 Ok(match self.color_space {
561 ColorSpace::Srgb => {
562 if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) {
563 match channel_keyword {
564 ChannelKeyword::R => self.c0().map(|v| v * 255.0),
565 ChannelKeyword::G => self.c1().map(|v| v * 255.0),
566 ChannelKeyword::B => self.c2().map(|v| v * 255.0),
567 _ => return Err(()),
568 }
569 } else {
570 match channel_keyword {
571 ChannelKeyword::R => self.c0(),
572 ChannelKeyword::G => self.c1(),
573 ChannelKeyword::B => self.c2(),
574 _ => return Err(()),
575 }
576 }
577 },
578 ColorSpace::Hsl => match channel_keyword {
579 ChannelKeyword::H => self.c0(),
580 ChannelKeyword::S => self.c1(),
581 ChannelKeyword::L => self.c2(),
582 _ => return Err(()),
583 },
584 ColorSpace::Hwb => match channel_keyword {
585 ChannelKeyword::H => self.c0(),
586 ChannelKeyword::W => self.c1(),
587 ChannelKeyword::B => self.c2(),
588 _ => return Err(()),
589 },
590 ColorSpace::Lab | ColorSpace::Oklab => match channel_keyword {
591 ChannelKeyword::L => self.c0(),
592 ChannelKeyword::A => self.c1(),
593 ChannelKeyword::B => self.c2(),
594 _ => return Err(()),
595 },
596 ColorSpace::Lch | ColorSpace::Oklch => match channel_keyword {
597 ChannelKeyword::L => self.c0(),
598 ChannelKeyword::C => self.c1(),
599 ChannelKeyword::H => self.c2(),
600 _ => return Err(()),
601 },
602 ColorSpace::SrgbLinear
603 | ColorSpace::DisplayP3
604 | ColorSpace::DisplayP3Linear
605 | ColorSpace::A98Rgb
606 | ColorSpace::ProphotoRgb
607 | ColorSpace::Rec2020 => match channel_keyword {
608 ChannelKeyword::R => self.c0(),
609 ChannelKeyword::G => self.c1(),
610 ChannelKeyword::B => self.c2(),
611 _ => return Err(()),
612 },
613 ColorSpace::XyzD50 | ColorSpace::XyzD65 => match channel_keyword {
614 ChannelKeyword::X => self.c0(),
615 ChannelKeyword::Y => self.c1(),
616 ChannelKeyword::Z => self.c2(),
617 _ => return Err(()),
618 },
619 })
620 }
621
622 pub fn to_color_space(&self, color_space: ColorSpace) -> Self {
624 use ColorSpace::*;
625
626 if self.color_space == color_space {
627 return self.clone();
628 }
629
630 macro_rules! missing_to_zero {
634 ($c:expr) => {{
635 crate::values::normalize($c.unwrap_or(0.0))
636 }};
637 }
638
639 let components = ColorComponents(
640 missing_to_zero!(self.c0()),
641 missing_to_zero!(self.c1()),
642 missing_to_zero!(self.c2()),
643 );
644
645 let result = match (self.color_space, color_space) {
646 (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) => {{
705 if $v.is_nan() {
706 None
707 } else {
708 Some($v)
709 }
710 }};
711 }
712
713 Self::new(
714 color_space,
715 nan_to_missing!(result.0),
716 nan_to_missing!(result.1),
717 nan_to_missing!(result.2),
718 self.alpha(),
719 )
720 }
721
722 pub fn to_nscolor(&self) -> u32 {
724 let srgb = self.to_color_space(ColorSpace::Srgb);
725 u32::from_le_bytes([
726 (srgb.components.0 * 255.0).round() as u8,
727 (srgb.components.1 * 255.0).round() as u8,
728 (srgb.components.2 * 255.0).round() as u8,
729 (srgb.alpha * 255.0).round() as u8,
730 ])
731 }
732
733 pub fn from_nscolor(color: u32) -> Self {
735 let [r, g, b, a] = color.to_le_bytes();
736 Self::srgb_legacy(r, g, b, a as f32 / 255.0)
737 }
738}
739
740#[test]
741fn from_nscolor_should_be_in_legacy_syntax() {
742 let result = AbsoluteColor::from_nscolor(0x336699CC);
743 assert!(result.flags.contains(ColorFlags::IS_LEGACY_SRGB));
744 assert!(result.is_legacy_syntax());
745}
746
747impl From<PredefinedColorSpace> for ColorSpace {
748 fn from(value: PredefinedColorSpace) -> Self {
749 match value {
750 PredefinedColorSpace::Srgb => ColorSpace::Srgb,
751 PredefinedColorSpace::SrgbLinear => ColorSpace::SrgbLinear,
752 PredefinedColorSpace::DisplayP3 => ColorSpace::DisplayP3,
753 PredefinedColorSpace::DisplayP3Linear => ColorSpace::DisplayP3Linear,
754 PredefinedColorSpace::A98Rgb => ColorSpace::A98Rgb,
755 PredefinedColorSpace::ProphotoRgb => ColorSpace::ProphotoRgb,
756 PredefinedColorSpace::Rec2020 => ColorSpace::Rec2020,
757 PredefinedColorSpace::XyzD50 => ColorSpace::XyzD50,
758 PredefinedColorSpace::XyzD65 => ColorSpace::XyzD65,
759 }
760 }
761}