1pub mod convert;
9
10mod color_function;
11pub mod component;
12pub mod mix;
13pub mod parsing;
14mod to_css;
15
16use self::parsing::ChannelKeyword;
17use crate::derives::*;
18pub use color_function::*;
19use component::ColorComponent;
20use cssparser::color::PredefinedColorSpace;
21
22pub const PRE_ALLOCATED_COLOR_MIX_ITEMS: usize = 3;
24
25pub type ColorMixItemList<T> = smallvec::SmallVec<[T; PRE_ALLOCATED_COLOR_MIX_ITEMS]>;
27
28#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
30#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
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
42impl std::ops::Mul for ColorComponents {
43 type Output = Self;
44
45 fn mul(self, rhs: Self) -> Self::Output {
46 Self(self.0 * rhs.0, self.1 * rhs.1, self.2 * rhs.2)
47 }
48}
49
50impl std::ops::Div for ColorComponents {
51 type Output = Self;
52
53 fn div(self, rhs: Self) -> Self::Output {
54 Self(self.0 / rhs.0, self.1 / rhs.1, self.2 / rhs.2)
55 }
56}
57
58#[derive(
62 Clone,
63 Copy,
64 Debug,
65 Eq,
66 MallocSizeOf,
67 Parse,
68 PartialEq,
69 ToAnimatedValue,
70 ToComputedValue,
71 ToCss,
72 ToResolvedValue,
73 ToShmem,
74)]
75#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
76#[repr(u8)]
77pub enum ColorSpace {
78 Srgb = 0,
83 Hsl,
87 Hwb,
91 Lab,
95 Lch,
99 Oklab,
103 Oklch,
107 SrgbLinear,
110 DisplayP3,
113 DisplayP3Linear,
116 A98Rgb,
119 ProphotoRgb,
122 Rec2020,
125 XyzD50,
128 #[parse(aliases = "xyz")]
133 XyzD65,
134}
135
136impl ColorSpace {
137 #[inline]
139 pub fn is_rectangular(&self) -> bool {
140 !self.is_polar()
141 }
142
143 #[inline]
145 pub fn is_polar(&self) -> bool {
146 matches!(self, Self::Hsl | Self::Hwb | Self::Lch | Self::Oklch)
147 }
148
149 #[inline]
151 pub fn is_rgb_or_xyz_like(&self) -> bool {
152 match self {
153 Self::Srgb
154 | Self::SrgbLinear
155 | Self::DisplayP3
156 | Self::DisplayP3Linear
157 | Self::A98Rgb
158 | Self::ProphotoRgb
159 | Self::Rec2020
160 | Self::XyzD50
161 | Self::XyzD65 => true,
162 _ => false,
163 }
164 }
165
166 #[inline]
169 pub fn hue_index(&self) -> Option<usize> {
170 match self {
171 Self::Hsl | Self::Hwb => Some(0),
172 Self::Lch | Self::Oklch => Some(2),
173
174 _ => {
175 debug_assert!(!self.is_polar());
176 None
177 },
178 }
179 }
180}
181
182#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
184#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
185#[repr(C)]
186pub struct ColorFlags(u8);
187bitflags! {
188 impl ColorFlags : u8 {
189 const C0_IS_NONE = 1 << 0;
191 const C1_IS_NONE = 1 << 1;
193 const C2_IS_NONE = 1 << 2;
195 const ALPHA_IS_NONE = 1 << 3;
197 const IS_LEGACY_SRGB = 1 << 4;
200 }
201}
202
203#[derive(Copy, Clone, Debug, MallocSizeOf, ToShmem, ToTyped)]
206#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
207#[repr(C)]
208#[typed(todo_derive_fields)]
209pub struct AbsoluteColor {
210 pub components: ColorComponents,
212 pub alpha: f32,
214 pub color_space: ColorSpace,
216 pub flags: ColorFlags,
218}
219
220impl PartialEq for AbsoluteColor {
221 fn eq(&self, other: &Self) -> bool {
223 let none_flags = ColorFlags::C0_IS_NONE
224 | ColorFlags::C1_IS_NONE
225 | ColorFlags::C2_IS_NONE
226 | ColorFlags::ALPHA_IS_NONE;
227 if self.color_space == other.color_space {
230 return self.components == other.components
231 && self.alpha == other.alpha
232 && (self.flags & none_flags) == (other.flags & none_flags);
233 }
234 if self.flags.union(other.flags).intersects(none_flags) {
236 return false;
237 }
238 const EPSILON: f32 = 0.0001;
242 let a = self.to_color_space(ColorSpace::Oklab);
243 let b = other.to_color_space(ColorSpace::Oklab);
244 (a.components.0 - b.components.0).abs() <= EPSILON
245 && (a.components.1 - b.components.1).abs() <= EPSILON
246 && (a.components.2 - b.components.2).abs() <= EPSILON
247 && (a.alpha - b.alpha).abs() <= EPSILON
248 }
249}
250
251macro_rules! color_components_as {
259 ($c:expr, $t:ty) => {{
260 const_assert_eq!(std::mem::size_of::<$t>(), std::mem::size_of::<[f32; 4]>());
264 const_assert_eq!(std::mem::align_of::<$t>(), std::mem::align_of::<[f32; 4]>());
265 const_assert!(std::mem::size_of::<AbsoluteColor>() >= std::mem::size_of::<$t>());
266 const_assert_eq!(
267 std::mem::align_of::<AbsoluteColor>(),
268 std::mem::align_of::<$t>()
269 );
270
271 std::mem::transmute::<&ColorComponents, &$t>(&$c.components)
272 }};
273}
274
275pub struct ComponentDetails {
277 value: f32,
278 is_none: bool,
279}
280
281impl From<f32> for ComponentDetails {
282 fn from(value: f32) -> Self {
283 Self {
284 value,
285 is_none: false,
286 }
287 }
288}
289
290impl From<u8> for ComponentDetails {
291 fn from(value: u8) -> Self {
292 Self {
293 value: value as f32 / 255.0,
294 is_none: false,
295 }
296 }
297}
298
299impl From<Option<f32>> for ComponentDetails {
300 fn from(value: Option<f32>) -> Self {
301 if let Some(value) = value {
302 Self {
303 value,
304 is_none: false,
305 }
306 } else {
307 Self {
308 value: 0.0,
309 is_none: true,
310 }
311 }
312 }
313}
314
315impl From<ColorComponent<f32>> for ComponentDetails {
316 fn from(value: ColorComponent<f32>) -> Self {
317 if let ColorComponent::Value(value) = value {
318 Self {
319 value,
320 is_none: false,
321 }
322 } else {
323 Self {
324 value: 0.0,
325 is_none: true,
326 }
327 }
328 }
329}
330
331impl AbsoluteColor {
332 pub const TRANSPARENT_BLACK: Self = Self {
334 components: ColorComponents(0.0, 0.0, 0.0),
335 alpha: 0.0,
336 color_space: ColorSpace::Srgb,
337 flags: ColorFlags::IS_LEGACY_SRGB,
338 };
339
340 pub const BLACK: Self = Self {
342 components: ColorComponents(0.0, 0.0, 0.0),
343 alpha: 1.0,
344 color_space: ColorSpace::Srgb,
345 flags: ColorFlags::IS_LEGACY_SRGB,
346 };
347
348 pub const WHITE: Self = Self {
350 components: ColorComponents(1.0, 1.0, 1.0),
351 alpha: 1.0,
352 color_space: ColorSpace::Srgb,
353 flags: ColorFlags::IS_LEGACY_SRGB,
354 };
355
356 pub fn new(
359 color_space: ColorSpace,
360 c1: impl Into<ComponentDetails>,
361 c2: impl Into<ComponentDetails>,
362 c3: impl Into<ComponentDetails>,
363 alpha: impl Into<ComponentDetails>,
364 ) -> Self {
365 let mut flags = ColorFlags::empty();
366
367 macro_rules! cd {
368 ($c:expr,$flag:expr) => {{
369 let component_details = $c.into();
370 if component_details.is_none {
371 flags |= $flag;
372 }
373 component_details.value
374 }};
375 }
376
377 let mut components = ColorComponents(
378 cd!(c1, ColorFlags::C0_IS_NONE),
379 cd!(c2, ColorFlags::C1_IS_NONE),
380 cd!(c3, ColorFlags::C2_IS_NONE),
381 );
382
383 let alpha = cd!(alpha, ColorFlags::ALPHA_IS_NONE);
384
385 if matches!(color_space, ColorSpace::Lab | ColorSpace::Lch) {
387 components.0 = components.0.clamp(0.0, 100.0);
388 }
389
390 if matches!(color_space, ColorSpace::Oklab | ColorSpace::Oklch) {
392 components.0 = components.0.clamp(0.0, 1.0);
393 }
394
395 if matches!(color_space, ColorSpace::Lch | ColorSpace::Oklch) {
397 components.1 = components.1.max(0.0);
398 }
399
400 let alpha = alpha.clamp(0.0, 1.0);
402
403 Self {
404 components,
405 alpha,
406 color_space,
407 flags,
408 }
409 }
410
411 #[inline]
414 #[must_use]
415 pub fn into_srgb_legacy(self) -> Self {
416 let mut result = if !matches!(self.color_space, ColorSpace::Srgb) {
417 self.to_color_space(ColorSpace::Srgb)
418 } else {
419 self
420 };
421
422 result.flags = ColorFlags::IS_LEGACY_SRGB;
425
426 result
427 }
428
429 pub fn srgb_legacy(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
431 let mut result = Self::new(ColorSpace::Srgb, red, green, blue, alpha);
432 result.flags = ColorFlags::IS_LEGACY_SRGB;
433 result
434 }
435
436 #[inline]
438 pub fn raw_components(&self) -> &[f32; 4] {
439 unsafe { color_components_as!(self, [f32; 4]) }
440 }
441
442 #[inline]
444 pub fn is_legacy_syntax(&self) -> bool {
445 match self.color_space {
447 ColorSpace::Srgb => self.flags.contains(ColorFlags::IS_LEGACY_SRGB),
448 ColorSpace::Hsl | ColorSpace::Hwb => true,
449 _ => false,
450 }
451 }
452
453 #[inline]
455 pub fn is_transparent(&self) -> bool {
456 self.flags.contains(ColorFlags::ALPHA_IS_NONE) || self.alpha == 0.0
457 }
458
459 #[inline]
461 pub fn c0(&self) -> Option<f32> {
462 if self.flags.contains(ColorFlags::C0_IS_NONE) {
463 None
464 } else {
465 Some(self.components.0)
466 }
467 }
468
469 #[inline]
471 pub fn c1(&self) -> Option<f32> {
472 if self.flags.contains(ColorFlags::C1_IS_NONE) {
473 None
474 } else {
475 Some(self.components.1)
476 }
477 }
478
479 #[inline]
481 pub fn c2(&self) -> Option<f32> {
482 if self.flags.contains(ColorFlags::C2_IS_NONE) {
483 None
484 } else {
485 Some(self.components.2)
486 }
487 }
488
489 #[inline]
491 pub fn alpha(&self) -> Option<f32> {
492 if self.flags.contains(ColorFlags::ALPHA_IS_NONE) {
493 None
494 } else {
495 Some(self.alpha)
496 }
497 }
498
499 pub fn get_component_by_channel_keyword(
501 &self,
502 channel_keyword: ChannelKeyword,
503 ) -> Result<Option<f32>, ()> {
504 if channel_keyword == ChannelKeyword::Alpha {
505 return Ok(self.alpha());
506 }
507
508 Ok(match self.color_space {
509 ColorSpace::Srgb => {
510 if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) {
511 match channel_keyword {
512 ChannelKeyword::R => self.c0().map(|v| v * 255.0),
513 ChannelKeyword::G => self.c1().map(|v| v * 255.0),
514 ChannelKeyword::B => self.c2().map(|v| v * 255.0),
515 _ => return Err(()),
516 }
517 } else {
518 match channel_keyword {
519 ChannelKeyword::R => self.c0(),
520 ChannelKeyword::G => self.c1(),
521 ChannelKeyword::B => self.c2(),
522 _ => return Err(()),
523 }
524 }
525 },
526 ColorSpace::Hsl => match channel_keyword {
527 ChannelKeyword::H => self.c0(),
528 ChannelKeyword::S => self.c1(),
529 ChannelKeyword::L => self.c2(),
530 _ => return Err(()),
531 },
532 ColorSpace::Hwb => match channel_keyword {
533 ChannelKeyword::H => self.c0(),
534 ChannelKeyword::W => self.c1(),
535 ChannelKeyword::B => self.c2(),
536 _ => return Err(()),
537 },
538 ColorSpace::Lab | ColorSpace::Oklab => match channel_keyword {
539 ChannelKeyword::L => self.c0(),
540 ChannelKeyword::A => self.c1(),
541 ChannelKeyword::B => self.c2(),
542 _ => return Err(()),
543 },
544 ColorSpace::Lch | ColorSpace::Oklch => match channel_keyword {
545 ChannelKeyword::L => self.c0(),
546 ChannelKeyword::C => self.c1(),
547 ChannelKeyword::H => self.c2(),
548 _ => return Err(()),
549 },
550 ColorSpace::SrgbLinear
551 | ColorSpace::DisplayP3
552 | ColorSpace::DisplayP3Linear
553 | ColorSpace::A98Rgb
554 | ColorSpace::ProphotoRgb
555 | ColorSpace::Rec2020 => match channel_keyword {
556 ChannelKeyword::R => self.c0(),
557 ChannelKeyword::G => self.c1(),
558 ChannelKeyword::B => self.c2(),
559 _ => return Err(()),
560 },
561 ColorSpace::XyzD50 | ColorSpace::XyzD65 => match channel_keyword {
562 ChannelKeyword::X => self.c0(),
563 ChannelKeyword::Y => self.c1(),
564 ChannelKeyword::Z => self.c2(),
565 _ => return Err(()),
566 },
567 })
568 }
569
570 pub fn to_color_space(&self, color_space: ColorSpace) -> Self {
572 use ColorSpace::*;
573
574 if self.color_space == color_space {
575 return self.clone();
576 }
577
578 macro_rules! missing_to_nan {
582 ($c:expr) => {{
583 if let Some(v) = $c {
584 crate::values::normalize(v)
585 } else {
586 f32::NAN
587 }
588 }};
589 }
590
591 let components = ColorComponents(
592 missing_to_nan!(self.c0()),
593 missing_to_nan!(self.c1()),
594 missing_to_nan!(self.c2()),
595 );
596
597 let result = match (self.color_space, color_space) {
598 (Srgb, Hsl) => convert::rgb_to_hsl(&components),
602 (Srgb, Hwb) => convert::rgb_to_hwb(&components),
603 (Hsl, Srgb) => convert::hsl_to_rgb(&components),
604 (Hwb, Srgb) => convert::hwb_to_rgb(&components),
605 (Lab, Lch) | (Oklab, Oklch) => convert::orthogonal_to_polar(
606 &components,
607 convert::epsilon_for_range(0.0, if color_space == Lch { 100.0 } else { 1.0 }),
608 ),
609 (Lch, Lab) | (Oklch, Oklab) => convert::polar_to_orthogonal(&components),
610
611 _ => {
613 let (xyz, white_point) = match self.color_space {
614 Lab => convert::to_xyz::<convert::Lab>(&components),
615 Lch => convert::to_xyz::<convert::Lch>(&components),
616 Oklab => convert::to_xyz::<convert::Oklab>(&components),
617 Oklch => convert::to_xyz::<convert::Oklch>(&components),
618 Srgb => convert::to_xyz::<convert::Srgb>(&components),
619 Hsl => convert::to_xyz::<convert::Hsl>(&components),
620 Hwb => convert::to_xyz::<convert::Hwb>(&components),
621 SrgbLinear => convert::to_xyz::<convert::SrgbLinear>(&components),
622 DisplayP3 => convert::to_xyz::<convert::DisplayP3>(&components),
623 DisplayP3Linear => convert::to_xyz::<convert::DisplayP3Linear>(&components),
624 A98Rgb => convert::to_xyz::<convert::A98Rgb>(&components),
625 ProphotoRgb => convert::to_xyz::<convert::ProphotoRgb>(&components),
626 Rec2020 => convert::to_xyz::<convert::Rec2020>(&components),
627 XyzD50 => convert::to_xyz::<convert::XyzD50>(&components),
628 XyzD65 => convert::to_xyz::<convert::XyzD65>(&components),
629 };
630
631 match color_space {
632 Lab => convert::from_xyz::<convert::Lab>(&xyz, white_point),
633 Lch => convert::from_xyz::<convert::Lch>(&xyz, white_point),
634 Oklab => convert::from_xyz::<convert::Oklab>(&xyz, white_point),
635 Oklch => convert::from_xyz::<convert::Oklch>(&xyz, white_point),
636 Srgb => convert::from_xyz::<convert::Srgb>(&xyz, white_point),
637 Hsl => convert::from_xyz::<convert::Hsl>(&xyz, white_point),
638 Hwb => convert::from_xyz::<convert::Hwb>(&xyz, white_point),
639 SrgbLinear => convert::from_xyz::<convert::SrgbLinear>(&xyz, white_point),
640 DisplayP3 => convert::from_xyz::<convert::DisplayP3>(&xyz, white_point),
641 DisplayP3Linear => {
642 convert::from_xyz::<convert::DisplayP3Linear>(&xyz, white_point)
643 },
644 A98Rgb => convert::from_xyz::<convert::A98Rgb>(&xyz, white_point),
645 ProphotoRgb => convert::from_xyz::<convert::ProphotoRgb>(&xyz, white_point),
646 Rec2020 => convert::from_xyz::<convert::Rec2020>(&xyz, white_point),
647 XyzD50 => convert::from_xyz::<convert::XyzD50>(&xyz, white_point),
648 XyzD65 => convert::from_xyz::<convert::XyzD65>(&xyz, white_point),
649 }
650 },
651 };
652
653 macro_rules! nan_to_missing {
656 ($v:expr) => {{
657 if $v.is_nan() {
658 None
659 } else {
660 Some($v)
661 }
662 }};
663 }
664
665 Self::new(
666 color_space,
667 nan_to_missing!(result.0),
668 nan_to_missing!(result.1),
669 nan_to_missing!(result.2),
670 self.alpha(),
671 )
672 }
673
674 pub fn to_nscolor(&self) -> u32 {
676 let srgb = self.to_color_space(ColorSpace::Srgb);
677 u32::from_le_bytes([
678 (srgb.components.0 * 255.0).round() as u8,
679 (srgb.components.1 * 255.0).round() as u8,
680 (srgb.components.2 * 255.0).round() as u8,
681 (srgb.alpha * 255.0).round() as u8,
682 ])
683 }
684
685 pub fn from_nscolor(color: u32) -> Self {
687 let [r, g, b, a] = color.to_le_bytes();
688 Self::srgb_legacy(r, g, b, a as f32 / 255.0)
689 }
690}
691
692#[test]
693fn from_nscolor_should_be_in_legacy_syntax() {
694 let result = AbsoluteColor::from_nscolor(0x336699CC);
695 assert!(result.flags.contains(ColorFlags::IS_LEGACY_SRGB));
696 assert!(result.is_legacy_syntax());
697}
698
699impl From<PredefinedColorSpace> for ColorSpace {
700 fn from(value: PredefinedColorSpace) -> Self {
701 match value {
702 PredefinedColorSpace::Srgb => ColorSpace::Srgb,
703 PredefinedColorSpace::SrgbLinear => ColorSpace::SrgbLinear,
704 PredefinedColorSpace::DisplayP3 => ColorSpace::DisplayP3,
705 PredefinedColorSpace::DisplayP3Linear => ColorSpace::DisplayP3Linear,
706 PredefinedColorSpace::A98Rgb => ColorSpace::A98Rgb,
707 PredefinedColorSpace::ProphotoRgb => ColorSpace::ProphotoRgb,
708 PredefinedColorSpace::Rec2020 => ColorSpace::Rec2020,
709 PredefinedColorSpace::XyzD50 => ColorSpace::XyzD50,
710 PredefinedColorSpace::XyzD65 => ColorSpace::XyzD65,
711 }
712 }
713}