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, MallocSizeOf, PartialEq, ToShmem)]
31#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
32#[repr(C)]
33pub struct ColorComponents(pub f32, pub f32, pub f32);
34
35impl ColorComponents {
36 #[must_use]
38 pub fn map(self, f: impl Fn(f32) -> f32) -> Self {
39 Self(f(self.0), f(self.1), f(self.2))
40 }
41
42 #[inline]
44 pub fn to_array(&self) -> [f32; 3] {
45 [self.0, self.1, self.2]
46 }
47}
48
49impl std::ops::Add for ColorComponents {
50 type Output = Self;
51
52 fn add(self, rhs: Self) -> Self::Output {
53 Self(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2)
54 }
55}
56
57impl std::ops::Sub for ColorComponents {
58 type Output = Self;
59
60 fn sub(self, rhs: Self) -> Self::Output {
61 Self(self.0 - rhs.0, self.1 - rhs.1, self.2 - rhs.2)
62 }
63}
64
65impl std::ops::Mul for ColorComponents {
66 type Output = Self;
67
68 fn mul(self, rhs: Self) -> Self::Output {
69 Self(self.0 * rhs.0, self.1 * rhs.1, self.2 * rhs.2)
70 }
71}
72
73impl std::ops::Div for ColorComponents {
74 type Output = Self;
75
76 fn div(self, rhs: Self) -> Self::Output {
77 Self(self.0 / rhs.0, self.1 / rhs.1, self.2 / rhs.2)
78 }
79}
80
81#[derive(
85 Clone,
86 Copy,
87 Debug,
88 Eq,
89 MallocSizeOf,
90 Parse,
91 PartialEq,
92 ToAnimatedValue,
93 ToComputedValue,
94 ToCss,
95 ToResolvedValue,
96 ToShmem,
97)]
98#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
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, MallocSizeOf, ToShmem, ToTyped)]
247#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
248#[repr(C)]
249#[typed(todo_derive_fields)]
250pub struct AbsoluteColor {
251 pub components: ColorComponents,
253 pub alpha: f32,
255 pub color_space: ColorSpace,
257 pub flags: ColorFlags,
259}
260
261impl PartialEq for AbsoluteColor {
262 fn eq(&self, other: &Self) -> bool {
264 let none_flags = ColorFlags::C0_IS_NONE
265 | ColorFlags::C1_IS_NONE
266 | ColorFlags::C2_IS_NONE
267 | ColorFlags::ALPHA_IS_NONE;
268 if self.color_space == other.color_space {
271 return self.components == other.components
272 && self.alpha == other.alpha
273 && (self.flags & none_flags) == (other.flags & none_flags);
274 }
275 if self.flags.union(other.flags).intersects(none_flags) {
277 return false;
278 }
279 const EPSILON: f32 = 0.0001;
283 let a = self.to_color_space(ColorSpace::Oklab);
284 let b = other.to_color_space(ColorSpace::Oklab);
285 (a.components.0 - b.components.0).abs() <= EPSILON
286 && (a.components.1 - b.components.1).abs() <= EPSILON
287 && (a.components.2 - b.components.2).abs() <= EPSILON
288 && (a.alpha - b.alpha).abs() <= EPSILON
289 }
290}
291
292macro_rules! color_components_as {
300 ($c:expr, $t:ty) => {{
301 const_assert_eq!(std::mem::size_of::<$t>(), std::mem::size_of::<[f32; 4]>());
305 const_assert_eq!(std::mem::align_of::<$t>(), std::mem::align_of::<[f32; 4]>());
306 const_assert!(std::mem::size_of::<AbsoluteColor>() >= std::mem::size_of::<$t>());
307 const_assert_eq!(
308 std::mem::align_of::<AbsoluteColor>(),
309 std::mem::align_of::<$t>()
310 );
311
312 std::mem::transmute::<&ColorComponents, &$t>(&$c.components)
313 }};
314}
315
316pub struct ComponentDetails {
318 value: f32,
319 is_none: bool,
320}
321
322impl From<f32> for ComponentDetails {
323 fn from(value: f32) -> Self {
324 Self {
325 value,
326 is_none: false,
327 }
328 }
329}
330
331impl From<u8> for ComponentDetails {
332 fn from(value: u8) -> Self {
333 Self {
334 value: value as f32 / 255.0,
335 is_none: false,
336 }
337 }
338}
339
340impl From<Option<f32>> for ComponentDetails {
341 fn from(value: Option<f32>) -> Self {
342 if let Some(value) = value {
343 Self {
344 value,
345 is_none: false,
346 }
347 } else {
348 Self {
349 value: 0.0,
350 is_none: true,
351 }
352 }
353 }
354}
355
356impl From<ColorComponent<f32>> for ComponentDetails {
357 fn from(value: ColorComponent<f32>) -> Self {
358 if let ColorComponent::Value(value) = value {
359 Self {
360 value,
361 is_none: false,
362 }
363 } else {
364 Self {
365 value: 0.0,
366 is_none: true,
367 }
368 }
369 }
370}
371
372impl AbsoluteColor {
373 pub const TRANSPARENT_BLACK: Self = Self {
375 components: ColorComponents(0.0, 0.0, 0.0),
376 alpha: 0.0,
377 color_space: ColorSpace::Srgb,
378 flags: ColorFlags::IS_LEGACY_SRGB,
379 };
380
381 pub const BLACK: Self = Self {
383 components: ColorComponents(0.0, 0.0, 0.0),
384 alpha: 1.0,
385 color_space: ColorSpace::Srgb,
386 flags: ColorFlags::IS_LEGACY_SRGB,
387 };
388
389 pub const WHITE: Self = Self {
391 components: ColorComponents(1.0, 1.0, 1.0),
392 alpha: 1.0,
393 color_space: ColorSpace::Srgb,
394 flags: ColorFlags::IS_LEGACY_SRGB,
395 };
396
397 pub fn new(
400 color_space: ColorSpace,
401 c1: impl Into<ComponentDetails>,
402 c2: impl Into<ComponentDetails>,
403 c3: impl Into<ComponentDetails>,
404 alpha: impl Into<ComponentDetails>,
405 ) -> Self {
406 let mut flags = ColorFlags::empty();
407
408 macro_rules! cd {
409 ($c:expr,$flag:expr) => {{
410 let component_details = $c.into();
411 if component_details.is_none {
412 flags |= $flag;
413 }
414 component_details.value
415 }};
416 }
417
418 let mut components = ColorComponents(
419 cd!(c1, ColorFlags::C0_IS_NONE),
420 cd!(c2, ColorFlags::C1_IS_NONE),
421 cd!(c3, ColorFlags::C2_IS_NONE),
422 );
423
424 let alpha = cd!(alpha, ColorFlags::ALPHA_IS_NONE);
425
426 if matches!(color_space, ColorSpace::Lab | ColorSpace::Lch) {
428 components.0 = components.0.clamp(0.0, 100.0);
429 }
430
431 if matches!(color_space, ColorSpace::Oklab | ColorSpace::Oklch) {
433 components.0 = components.0.clamp(0.0, 1.0);
434 }
435
436 if matches!(color_space, ColorSpace::Lch | ColorSpace::Oklch) {
438 components.1 = components.1.max(0.0);
439 }
440
441 let alpha = alpha.clamp(0.0, 1.0);
443
444 Self {
445 components,
446 alpha,
447 color_space,
448 flags,
449 }
450 }
451
452 #[inline]
455 #[must_use]
456 pub fn into_srgb_legacy(self) -> Self {
457 let mut result = if !matches!(self.color_space, ColorSpace::Srgb) {
458 self.to_color_space(ColorSpace::Srgb)
459 } else {
460 self
461 };
462
463 result.flags = ColorFlags::IS_LEGACY_SRGB;
466
467 result
468 }
469
470 pub fn srgb_legacy(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
472 let mut result = Self::new(ColorSpace::Srgb, red, green, blue, alpha);
473 result.flags = ColorFlags::IS_LEGACY_SRGB;
474 result
475 }
476
477 #[inline]
479 pub fn raw_components(&self) -> &[f32; 4] {
480 unsafe { color_components_as!(self, [f32; 4]) }
481 }
482
483 #[inline]
485 pub fn is_legacy_syntax(&self) -> bool {
486 match self.color_space {
488 ColorSpace::Srgb => self.flags.contains(ColorFlags::IS_LEGACY_SRGB),
489 ColorSpace::Hsl | ColorSpace::Hwb => true,
490 _ => false,
491 }
492 }
493
494 #[inline]
496 pub fn is_transparent(&self) -> bool {
497 self.flags.contains(ColorFlags::ALPHA_IS_NONE) || self.alpha == 0.0
498 }
499
500 #[inline]
502 pub fn c0(&self) -> Option<f32> {
503 if self.flags.contains(ColorFlags::C0_IS_NONE) {
504 None
505 } else {
506 Some(self.components.0)
507 }
508 }
509
510 #[inline]
512 pub fn c1(&self) -> Option<f32> {
513 if self.flags.contains(ColorFlags::C1_IS_NONE) {
514 None
515 } else {
516 Some(self.components.1)
517 }
518 }
519
520 #[inline]
522 pub fn c2(&self) -> Option<f32> {
523 if self.flags.contains(ColorFlags::C2_IS_NONE) {
524 None
525 } else {
526 Some(self.components.2)
527 }
528 }
529
530 #[inline]
532 pub fn alpha(&self) -> Option<f32> {
533 if self.flags.contains(ColorFlags::ALPHA_IS_NONE) {
534 None
535 } else {
536 Some(self.alpha)
537 }
538 }
539
540 pub fn get_component_by_channel_keyword(
542 &self,
543 channel_keyword: ChannelKeyword,
544 ) -> Result<Option<f32>, ()> {
545 if channel_keyword == ChannelKeyword::Alpha {
546 return Ok(self.alpha());
547 }
548
549 Ok(match self.color_space {
550 ColorSpace::Srgb => {
551 if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) {
552 match channel_keyword {
553 ChannelKeyword::R => self.c0().map(|v| v * 255.0),
554 ChannelKeyword::G => self.c1().map(|v| v * 255.0),
555 ChannelKeyword::B => self.c2().map(|v| v * 255.0),
556 _ => return Err(()),
557 }
558 } else {
559 match channel_keyword {
560 ChannelKeyword::R => self.c0(),
561 ChannelKeyword::G => self.c1(),
562 ChannelKeyword::B => self.c2(),
563 _ => return Err(()),
564 }
565 }
566 },
567 ColorSpace::Hsl => match channel_keyword {
568 ChannelKeyword::H => self.c0(),
569 ChannelKeyword::S => self.c1(),
570 ChannelKeyword::L => self.c2(),
571 _ => return Err(()),
572 },
573 ColorSpace::Hwb => match channel_keyword {
574 ChannelKeyword::H => self.c0(),
575 ChannelKeyword::W => self.c1(),
576 ChannelKeyword::B => self.c2(),
577 _ => return Err(()),
578 },
579 ColorSpace::Lab | ColorSpace::Oklab => match channel_keyword {
580 ChannelKeyword::L => self.c0(),
581 ChannelKeyword::A => self.c1(),
582 ChannelKeyword::B => self.c2(),
583 _ => return Err(()),
584 },
585 ColorSpace::Lch | ColorSpace::Oklch => match channel_keyword {
586 ChannelKeyword::L => self.c0(),
587 ChannelKeyword::C => self.c1(),
588 ChannelKeyword::H => self.c2(),
589 _ => return Err(()),
590 },
591 ColorSpace::SrgbLinear
592 | ColorSpace::DisplayP3
593 | ColorSpace::DisplayP3Linear
594 | ColorSpace::A98Rgb
595 | ColorSpace::ProphotoRgb
596 | ColorSpace::Rec2020 => match channel_keyword {
597 ChannelKeyword::R => self.c0(),
598 ChannelKeyword::G => self.c1(),
599 ChannelKeyword::B => self.c2(),
600 _ => return Err(()),
601 },
602 ColorSpace::XyzD50 | ColorSpace::XyzD65 => match channel_keyword {
603 ChannelKeyword::X => self.c0(),
604 ChannelKeyword::Y => self.c1(),
605 ChannelKeyword::Z => self.c2(),
606 _ => return Err(()),
607 },
608 })
609 }
610
611 pub fn to_color_space(&self, color_space: ColorSpace) -> Self {
613 use ColorSpace::*;
614
615 if self.color_space == color_space {
616 return self.clone();
617 }
618
619 macro_rules! missing_to_nan {
623 ($c:expr) => {{
624 if let Some(v) = $c {
625 crate::values::normalize(v)
626 } else {
627 f32::NAN
628 }
629 }};
630 }
631
632 let components = ColorComponents(
633 missing_to_nan!(self.c0()),
634 missing_to_nan!(self.c1()),
635 missing_to_nan!(self.c2()),
636 );
637
638 let result = match (self.color_space, color_space) {
639 (Srgb, Hsl) => convert::rgb_to_hsl(&components),
643 (Srgb, Hwb) => convert::rgb_to_hwb(&components),
644 (Hsl, Srgb) => convert::hsl_to_rgb(&components),
645 (Hwb, Srgb) => convert::hwb_to_rgb(&components),
646 (Lab, Lch) | (Oklab, Oklch) => convert::orthogonal_to_polar(
647 &components,
648 convert::epsilon_for_range(0.0, if color_space == Lch { 100.0 } else { 1.0 }),
649 ),
650 (Lch, Lab) | (Oklch, Oklab) => convert::polar_to_orthogonal(&components),
651
652 _ => {
654 let (xyz, white_point) = match self.color_space {
655 Lab => convert::to_xyz::<convert::Lab>(&components),
656 Lch => convert::to_xyz::<convert::Lch>(&components),
657 Oklab => convert::to_xyz::<convert::Oklab>(&components),
658 Oklch => convert::to_xyz::<convert::Oklch>(&components),
659 Srgb => convert::to_xyz::<convert::Srgb>(&components),
660 Hsl => convert::to_xyz::<convert::Hsl>(&components),
661 Hwb => convert::to_xyz::<convert::Hwb>(&components),
662 SrgbLinear => convert::to_xyz::<convert::SrgbLinear>(&components),
663 DisplayP3 => convert::to_xyz::<convert::DisplayP3>(&components),
664 DisplayP3Linear => convert::to_xyz::<convert::DisplayP3Linear>(&components),
665 A98Rgb => convert::to_xyz::<convert::A98Rgb>(&components),
666 ProphotoRgb => convert::to_xyz::<convert::ProphotoRgb>(&components),
667 Rec2020 => convert::to_xyz::<convert::Rec2020>(&components),
668 XyzD50 => convert::to_xyz::<convert::XyzD50>(&components),
669 XyzD65 => convert::to_xyz::<convert::XyzD65>(&components),
670 };
671
672 match color_space {
673 Lab => convert::from_xyz::<convert::Lab>(&xyz, white_point),
674 Lch => convert::from_xyz::<convert::Lch>(&xyz, white_point),
675 Oklab => convert::from_xyz::<convert::Oklab>(&xyz, white_point),
676 Oklch => convert::from_xyz::<convert::Oklch>(&xyz, white_point),
677 Srgb => convert::from_xyz::<convert::Srgb>(&xyz, white_point),
678 Hsl => convert::from_xyz::<convert::Hsl>(&xyz, white_point),
679 Hwb => convert::from_xyz::<convert::Hwb>(&xyz, white_point),
680 SrgbLinear => convert::from_xyz::<convert::SrgbLinear>(&xyz, white_point),
681 DisplayP3 => convert::from_xyz::<convert::DisplayP3>(&xyz, white_point),
682 DisplayP3Linear => {
683 convert::from_xyz::<convert::DisplayP3Linear>(&xyz, white_point)
684 },
685 A98Rgb => convert::from_xyz::<convert::A98Rgb>(&xyz, white_point),
686 ProphotoRgb => convert::from_xyz::<convert::ProphotoRgb>(&xyz, white_point),
687 Rec2020 => convert::from_xyz::<convert::Rec2020>(&xyz, white_point),
688 XyzD50 => convert::from_xyz::<convert::XyzD50>(&xyz, white_point),
689 XyzD65 => convert::from_xyz::<convert::XyzD65>(&xyz, white_point),
690 }
691 },
692 };
693
694 macro_rules! nan_to_missing {
697 ($v:expr) => {{
698 if $v.is_nan() {
699 None
700 } else {
701 Some($v)
702 }
703 }};
704 }
705
706 Self::new(
707 color_space,
708 nan_to_missing!(result.0),
709 nan_to_missing!(result.1),
710 nan_to_missing!(result.2),
711 self.alpha(),
712 )
713 }
714
715 pub fn to_nscolor(&self) -> u32 {
717 let srgb = self.to_color_space(ColorSpace::Srgb);
718 u32::from_le_bytes([
719 (srgb.components.0 * 255.0).round() as u8,
720 (srgb.components.1 * 255.0).round() as u8,
721 (srgb.components.2 * 255.0).round() as u8,
722 (srgb.alpha * 255.0).round() as u8,
723 ])
724 }
725
726 pub fn from_nscolor(color: u32) -> Self {
728 let [r, g, b, a] = color.to_le_bytes();
729 Self::srgb_legacy(r, g, b, a as f32 / 255.0)
730 }
731}
732
733#[test]
734fn from_nscolor_should_be_in_legacy_syntax() {
735 let result = AbsoluteColor::from_nscolor(0x336699CC);
736 assert!(result.flags.contains(ColorFlags::IS_LEGACY_SRGB));
737 assert!(result.is_legacy_syntax());
738}
739
740impl From<PredefinedColorSpace> for ColorSpace {
741 fn from(value: PredefinedColorSpace) -> Self {
742 match value {
743 PredefinedColorSpace::Srgb => ColorSpace::Srgb,
744 PredefinedColorSpace::SrgbLinear => ColorSpace::SrgbLinear,
745 PredefinedColorSpace::DisplayP3 => ColorSpace::DisplayP3,
746 PredefinedColorSpace::DisplayP3Linear => ColorSpace::DisplayP3Linear,
747 PredefinedColorSpace::A98Rgb => ColorSpace::A98Rgb,
748 PredefinedColorSpace::ProphotoRgb => ColorSpace::ProphotoRgb,
749 PredefinedColorSpace::Rec2020 => ColorSpace::Rec2020,
750 PredefinedColorSpace::XyzD50 => ColorSpace::XyzD50,
751 PredefinedColorSpace::XyzD65 => ColorSpace::XyzD65,
752 }
753 }
754}