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)]
208pub struct AbsoluteColor {
209 pub components: ColorComponents,
211 pub alpha: f32,
213 pub color_space: ColorSpace,
215 pub flags: ColorFlags,
217}
218
219impl PartialEq for AbsoluteColor {
220 fn eq(&self, other: &Self) -> bool {
225 const EPSILON: f32 = 0.0001;
226 let a = self.to_color_space(ColorSpace::Oklab);
227 let b = other.to_color_space(ColorSpace::Oklab);
228 (a.components.0 - b.components.0).abs() <= EPSILON
229 && (a.components.1 - b.components.1).abs() <= EPSILON
230 && (a.components.2 - b.components.2).abs() <= EPSILON
231 && (a.alpha - b.alpha).abs() <= EPSILON
232 }
233}
234
235macro_rules! color_components_as {
243 ($c:expr, $t:ty) => {{
244 const_assert_eq!(std::mem::size_of::<$t>(), std::mem::size_of::<[f32; 4]>());
248 const_assert_eq!(std::mem::align_of::<$t>(), std::mem::align_of::<[f32; 4]>());
249 const_assert!(std::mem::size_of::<AbsoluteColor>() >= std::mem::size_of::<$t>());
250 const_assert_eq!(
251 std::mem::align_of::<AbsoluteColor>(),
252 std::mem::align_of::<$t>()
253 );
254
255 std::mem::transmute::<&ColorComponents, &$t>(&$c.components)
256 }};
257}
258
259pub struct ComponentDetails {
261 value: f32,
262 is_none: bool,
263}
264
265impl From<f32> for ComponentDetails {
266 fn from(value: f32) -> Self {
267 Self {
268 value,
269 is_none: false,
270 }
271 }
272}
273
274impl From<u8> for ComponentDetails {
275 fn from(value: u8) -> Self {
276 Self {
277 value: value as f32 / 255.0,
278 is_none: false,
279 }
280 }
281}
282
283impl From<Option<f32>> for ComponentDetails {
284 fn from(value: Option<f32>) -> Self {
285 if let Some(value) = value {
286 Self {
287 value,
288 is_none: false,
289 }
290 } else {
291 Self {
292 value: 0.0,
293 is_none: true,
294 }
295 }
296 }
297}
298
299impl From<ColorComponent<f32>> for ComponentDetails {
300 fn from(value: ColorComponent<f32>) -> Self {
301 if let ColorComponent::Value(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 AbsoluteColor {
316 pub const TRANSPARENT_BLACK: Self = Self {
318 components: ColorComponents(0.0, 0.0, 0.0),
319 alpha: 0.0,
320 color_space: ColorSpace::Srgb,
321 flags: ColorFlags::IS_LEGACY_SRGB,
322 };
323
324 pub const BLACK: Self = Self {
326 components: ColorComponents(0.0, 0.0, 0.0),
327 alpha: 1.0,
328 color_space: ColorSpace::Srgb,
329 flags: ColorFlags::IS_LEGACY_SRGB,
330 };
331
332 pub const WHITE: Self = Self {
334 components: ColorComponents(1.0, 1.0, 1.0),
335 alpha: 1.0,
336 color_space: ColorSpace::Srgb,
337 flags: ColorFlags::IS_LEGACY_SRGB,
338 };
339
340 pub fn new(
343 color_space: ColorSpace,
344 c1: impl Into<ComponentDetails>,
345 c2: impl Into<ComponentDetails>,
346 c3: impl Into<ComponentDetails>,
347 alpha: impl Into<ComponentDetails>,
348 ) -> Self {
349 let mut flags = ColorFlags::empty();
350
351 macro_rules! cd {
352 ($c:expr,$flag:expr) => {{
353 let component_details = $c.into();
354 if component_details.is_none {
355 flags |= $flag;
356 }
357 component_details.value
358 }};
359 }
360
361 let mut components = ColorComponents(
362 cd!(c1, ColorFlags::C0_IS_NONE),
363 cd!(c2, ColorFlags::C1_IS_NONE),
364 cd!(c3, ColorFlags::C2_IS_NONE),
365 );
366
367 let alpha = cd!(alpha, ColorFlags::ALPHA_IS_NONE);
368
369 if matches!(color_space, ColorSpace::Lab | ColorSpace::Lch) {
371 components.0 = components.0.clamp(0.0, 100.0);
372 }
373
374 if matches!(color_space, ColorSpace::Oklab | ColorSpace::Oklch) {
376 components.0 = components.0.clamp(0.0, 1.0);
377 }
378
379 if matches!(color_space, ColorSpace::Lch | ColorSpace::Oklch) {
381 components.1 = components.1.max(0.0);
382 }
383
384 let alpha = alpha.clamp(0.0, 1.0);
386
387 Self {
388 components,
389 alpha,
390 color_space,
391 flags,
392 }
393 }
394
395 #[inline]
398 #[must_use]
399 pub fn into_srgb_legacy(self) -> Self {
400 let mut result = if !matches!(self.color_space, ColorSpace::Srgb) {
401 self.to_color_space(ColorSpace::Srgb)
402 } else {
403 self
404 };
405
406 result.flags = ColorFlags::IS_LEGACY_SRGB;
409
410 result
411 }
412
413 pub fn srgb_legacy(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
415 let mut result = Self::new(ColorSpace::Srgb, red, green, blue, alpha);
416 result.flags = ColorFlags::IS_LEGACY_SRGB;
417 result
418 }
419
420 #[inline]
422 pub fn raw_components(&self) -> &[f32; 4] {
423 unsafe { color_components_as!(self, [f32; 4]) }
424 }
425
426 #[inline]
428 pub fn is_legacy_syntax(&self) -> bool {
429 match self.color_space {
431 ColorSpace::Srgb => self.flags.contains(ColorFlags::IS_LEGACY_SRGB),
432 ColorSpace::Hsl | ColorSpace::Hwb => true,
433 _ => false,
434 }
435 }
436
437 #[inline]
439 pub fn is_transparent(&self) -> bool {
440 self.flags.contains(ColorFlags::ALPHA_IS_NONE) || self.alpha == 0.0
441 }
442
443 #[inline]
445 pub fn c0(&self) -> Option<f32> {
446 if self.flags.contains(ColorFlags::C0_IS_NONE) {
447 None
448 } else {
449 Some(self.components.0)
450 }
451 }
452
453 #[inline]
455 pub fn c1(&self) -> Option<f32> {
456 if self.flags.contains(ColorFlags::C1_IS_NONE) {
457 None
458 } else {
459 Some(self.components.1)
460 }
461 }
462
463 #[inline]
465 pub fn c2(&self) -> Option<f32> {
466 if self.flags.contains(ColorFlags::C2_IS_NONE) {
467 None
468 } else {
469 Some(self.components.2)
470 }
471 }
472
473 #[inline]
475 pub fn alpha(&self) -> Option<f32> {
476 if self.flags.contains(ColorFlags::ALPHA_IS_NONE) {
477 None
478 } else {
479 Some(self.alpha)
480 }
481 }
482
483 pub fn get_component_by_channel_keyword(
485 &self,
486 channel_keyword: ChannelKeyword,
487 ) -> Result<Option<f32>, ()> {
488 if channel_keyword == ChannelKeyword::Alpha {
489 return Ok(self.alpha());
490 }
491
492 Ok(match self.color_space {
493 ColorSpace::Srgb => {
494 if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) {
495 match channel_keyword {
496 ChannelKeyword::R => self.c0().map(|v| v * 255.0),
497 ChannelKeyword::G => self.c1().map(|v| v * 255.0),
498 ChannelKeyword::B => self.c2().map(|v| v * 255.0),
499 _ => return Err(()),
500 }
501 } else {
502 match channel_keyword {
503 ChannelKeyword::R => self.c0(),
504 ChannelKeyword::G => self.c1(),
505 ChannelKeyword::B => self.c2(),
506 _ => return Err(()),
507 }
508 }
509 },
510 ColorSpace::Hsl => match channel_keyword {
511 ChannelKeyword::H => self.c0(),
512 ChannelKeyword::S => self.c1(),
513 ChannelKeyword::L => self.c2(),
514 _ => return Err(()),
515 },
516 ColorSpace::Hwb => match channel_keyword {
517 ChannelKeyword::H => self.c0(),
518 ChannelKeyword::W => self.c1(),
519 ChannelKeyword::B => self.c2(),
520 _ => return Err(()),
521 },
522 ColorSpace::Lab | ColorSpace::Oklab => match channel_keyword {
523 ChannelKeyword::L => self.c0(),
524 ChannelKeyword::A => self.c1(),
525 ChannelKeyword::B => self.c2(),
526 _ => return Err(()),
527 },
528 ColorSpace::Lch | ColorSpace::Oklch => match channel_keyword {
529 ChannelKeyword::L => self.c0(),
530 ChannelKeyword::C => self.c1(),
531 ChannelKeyword::H => self.c2(),
532 _ => return Err(()),
533 },
534 ColorSpace::SrgbLinear
535 | ColorSpace::DisplayP3
536 | ColorSpace::DisplayP3Linear
537 | ColorSpace::A98Rgb
538 | ColorSpace::ProphotoRgb
539 | ColorSpace::Rec2020 => match channel_keyword {
540 ChannelKeyword::R => self.c0(),
541 ChannelKeyword::G => self.c1(),
542 ChannelKeyword::B => self.c2(),
543 _ => return Err(()),
544 },
545 ColorSpace::XyzD50 | ColorSpace::XyzD65 => match channel_keyword {
546 ChannelKeyword::X => self.c0(),
547 ChannelKeyword::Y => self.c1(),
548 ChannelKeyword::Z => self.c2(),
549 _ => return Err(()),
550 },
551 })
552 }
553
554 pub fn to_color_space(&self, color_space: ColorSpace) -> Self {
556 use ColorSpace::*;
557
558 if self.color_space == color_space {
559 return self.clone();
560 }
561
562 macro_rules! missing_to_nan {
566 ($c:expr) => {{
567 if let Some(v) = $c {
568 crate::values::normalize(v)
569 } else {
570 f32::NAN
571 }
572 }};
573 }
574
575 let components = ColorComponents(
576 missing_to_nan!(self.c0()),
577 missing_to_nan!(self.c1()),
578 missing_to_nan!(self.c2()),
579 );
580
581 let result = match (self.color_space, color_space) {
582 (Srgb, Hsl) => convert::rgb_to_hsl(&components),
586 (Srgb, Hwb) => convert::rgb_to_hwb(&components),
587 (Hsl, Srgb) => convert::hsl_to_rgb(&components),
588 (Hwb, Srgb) => convert::hwb_to_rgb(&components),
589 (Lab, Lch) | (Oklab, Oklch) => convert::orthogonal_to_polar(
590 &components,
591 convert::epsilon_for_range(0.0, if color_space == Lch { 100.0 } else { 1.0 }),
592 ),
593 (Lch, Lab) | (Oklch, Oklab) => convert::polar_to_orthogonal(&components),
594
595 _ => {
597 let (xyz, white_point) = match self.color_space {
598 Lab => convert::to_xyz::<convert::Lab>(&components),
599 Lch => convert::to_xyz::<convert::Lch>(&components),
600 Oklab => convert::to_xyz::<convert::Oklab>(&components),
601 Oklch => convert::to_xyz::<convert::Oklch>(&components),
602 Srgb => convert::to_xyz::<convert::Srgb>(&components),
603 Hsl => convert::to_xyz::<convert::Hsl>(&components),
604 Hwb => convert::to_xyz::<convert::Hwb>(&components),
605 SrgbLinear => convert::to_xyz::<convert::SrgbLinear>(&components),
606 DisplayP3 => convert::to_xyz::<convert::DisplayP3>(&components),
607 DisplayP3Linear => convert::to_xyz::<convert::DisplayP3Linear>(&components),
608 A98Rgb => convert::to_xyz::<convert::A98Rgb>(&components),
609 ProphotoRgb => convert::to_xyz::<convert::ProphotoRgb>(&components),
610 Rec2020 => convert::to_xyz::<convert::Rec2020>(&components),
611 XyzD50 => convert::to_xyz::<convert::XyzD50>(&components),
612 XyzD65 => convert::to_xyz::<convert::XyzD65>(&components),
613 };
614
615 match color_space {
616 Lab => convert::from_xyz::<convert::Lab>(&xyz, white_point),
617 Lch => convert::from_xyz::<convert::Lch>(&xyz, white_point),
618 Oklab => convert::from_xyz::<convert::Oklab>(&xyz, white_point),
619 Oklch => convert::from_xyz::<convert::Oklch>(&xyz, white_point),
620 Srgb => convert::from_xyz::<convert::Srgb>(&xyz, white_point),
621 Hsl => convert::from_xyz::<convert::Hsl>(&xyz, white_point),
622 Hwb => convert::from_xyz::<convert::Hwb>(&xyz, white_point),
623 SrgbLinear => convert::from_xyz::<convert::SrgbLinear>(&xyz, white_point),
624 DisplayP3 => convert::from_xyz::<convert::DisplayP3>(&xyz, white_point),
625 DisplayP3Linear => {
626 convert::from_xyz::<convert::DisplayP3Linear>(&xyz, white_point)
627 },
628 A98Rgb => convert::from_xyz::<convert::A98Rgb>(&xyz, white_point),
629 ProphotoRgb => convert::from_xyz::<convert::ProphotoRgb>(&xyz, white_point),
630 Rec2020 => convert::from_xyz::<convert::Rec2020>(&xyz, white_point),
631 XyzD50 => convert::from_xyz::<convert::XyzD50>(&xyz, white_point),
632 XyzD65 => convert::from_xyz::<convert::XyzD65>(&xyz, white_point),
633 }
634 },
635 };
636
637 macro_rules! nan_to_missing {
640 ($v:expr) => {{
641 if $v.is_nan() {
642 None
643 } else {
644 Some($v)
645 }
646 }};
647 }
648
649 Self::new(
650 color_space,
651 nan_to_missing!(result.0),
652 nan_to_missing!(result.1),
653 nan_to_missing!(result.2),
654 self.alpha(),
655 )
656 }
657
658 pub fn to_nscolor(&self) -> u32 {
660 let srgb = self.to_color_space(ColorSpace::Srgb);
661 u32::from_le_bytes([
662 (srgb.components.0 * 255.0).round() as u8,
663 (srgb.components.1 * 255.0).round() as u8,
664 (srgb.components.2 * 255.0).round() as u8,
665 (srgb.alpha * 255.0).round() as u8,
666 ])
667 }
668
669 pub fn from_nscolor(color: u32) -> Self {
671 let [r, g, b, a] = color.to_le_bytes();
672 Self::srgb_legacy(r, g, b, a as f32 / 255.0)
673 }
674}
675
676#[test]
677fn from_nscolor_should_be_in_legacy_syntax() {
678 let result = AbsoluteColor::from_nscolor(0x336699CC);
679 assert!(result.flags.contains(ColorFlags::IS_LEGACY_SRGB));
680 assert!(result.is_legacy_syntax());
681}
682
683impl From<PredefinedColorSpace> for ColorSpace {
684 fn from(value: PredefinedColorSpace) -> Self {
685 match value {
686 PredefinedColorSpace::Srgb => ColorSpace::Srgb,
687 PredefinedColorSpace::SrgbLinear => ColorSpace::SrgbLinear,
688 PredefinedColorSpace::DisplayP3 => ColorSpace::DisplayP3,
689 PredefinedColorSpace::DisplayP3Linear => ColorSpace::DisplayP3Linear,
690 PredefinedColorSpace::A98Rgb => ColorSpace::A98Rgb,
691 PredefinedColorSpace::ProphotoRgb => ColorSpace::ProphotoRgb,
692 PredefinedColorSpace::Rec2020 => ColorSpace::Rec2020,
693 PredefinedColorSpace::XyzD50 => ColorSpace::XyzD50,
694 PredefinedColorSpace::XyzD65 => ColorSpace::XyzD65,
695 }
696 }
697}