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, PartialEq, 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
219macro_rules! color_components_as {
227 ($c:expr, $t:ty) => {{
228 const_assert_eq!(std::mem::size_of::<$t>(), std::mem::size_of::<[f32; 4]>());
232 const_assert_eq!(std::mem::align_of::<$t>(), std::mem::align_of::<[f32; 4]>());
233 const_assert!(std::mem::size_of::<AbsoluteColor>() >= std::mem::size_of::<$t>());
234 const_assert_eq!(
235 std::mem::align_of::<AbsoluteColor>(),
236 std::mem::align_of::<$t>()
237 );
238
239 std::mem::transmute::<&ColorComponents, &$t>(&$c.components)
240 }};
241}
242
243pub struct ComponentDetails {
245 value: f32,
246 is_none: bool,
247}
248
249impl From<f32> for ComponentDetails {
250 fn from(value: f32) -> Self {
251 Self {
252 value,
253 is_none: false,
254 }
255 }
256}
257
258impl From<u8> for ComponentDetails {
259 fn from(value: u8) -> Self {
260 Self {
261 value: value as f32 / 255.0,
262 is_none: false,
263 }
264 }
265}
266
267impl From<Option<f32>> for ComponentDetails {
268 fn from(value: Option<f32>) -> Self {
269 if let Some(value) = value {
270 Self {
271 value,
272 is_none: false,
273 }
274 } else {
275 Self {
276 value: 0.0,
277 is_none: true,
278 }
279 }
280 }
281}
282
283impl From<ColorComponent<f32>> for ComponentDetails {
284 fn from(value: ColorComponent<f32>) -> Self {
285 if let ColorComponent::Value(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 AbsoluteColor {
300 pub const TRANSPARENT_BLACK: Self = Self {
302 components: ColorComponents(0.0, 0.0, 0.0),
303 alpha: 0.0,
304 color_space: ColorSpace::Srgb,
305 flags: ColorFlags::IS_LEGACY_SRGB,
306 };
307
308 pub const BLACK: Self = Self {
310 components: ColorComponents(0.0, 0.0, 0.0),
311 alpha: 1.0,
312 color_space: ColorSpace::Srgb,
313 flags: ColorFlags::IS_LEGACY_SRGB,
314 };
315
316 pub const WHITE: Self = Self {
318 components: ColorComponents(1.0, 1.0, 1.0),
319 alpha: 1.0,
320 color_space: ColorSpace::Srgb,
321 flags: ColorFlags::IS_LEGACY_SRGB,
322 };
323
324 pub fn new(
327 color_space: ColorSpace,
328 c1: impl Into<ComponentDetails>,
329 c2: impl Into<ComponentDetails>,
330 c3: impl Into<ComponentDetails>,
331 alpha: impl Into<ComponentDetails>,
332 ) -> Self {
333 let mut flags = ColorFlags::empty();
334
335 macro_rules! cd {
336 ($c:expr,$flag:expr) => {{
337 let component_details = $c.into();
338 if component_details.is_none {
339 flags |= $flag;
340 }
341 component_details.value
342 }};
343 }
344
345 let mut components = ColorComponents(
346 cd!(c1, ColorFlags::C0_IS_NONE),
347 cd!(c2, ColorFlags::C1_IS_NONE),
348 cd!(c3, ColorFlags::C2_IS_NONE),
349 );
350
351 let alpha = cd!(alpha, ColorFlags::ALPHA_IS_NONE);
352
353 if matches!(color_space, ColorSpace::Lab | ColorSpace::Lch) {
355 components.0 = components.0.clamp(0.0, 100.0);
356 }
357
358 if matches!(color_space, ColorSpace::Oklab | ColorSpace::Oklch) {
360 components.0 = components.0.clamp(0.0, 1.0);
361 }
362
363 if matches!(color_space, ColorSpace::Lch | ColorSpace::Oklch) {
365 components.1 = components.1.max(0.0);
366 }
367
368 let alpha = alpha.clamp(0.0, 1.0);
370
371 Self {
372 components,
373 alpha,
374 color_space,
375 flags,
376 }
377 }
378
379 #[inline]
382 #[must_use]
383 pub fn into_srgb_legacy(self) -> Self {
384 let mut result = if !matches!(self.color_space, ColorSpace::Srgb) {
385 self.to_color_space(ColorSpace::Srgb)
386 } else {
387 self
388 };
389
390 result.flags = ColorFlags::IS_LEGACY_SRGB;
393
394 result
395 }
396
397 pub fn srgb_legacy(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
399 let mut result = Self::new(ColorSpace::Srgb, red, green, blue, alpha);
400 result.flags = ColorFlags::IS_LEGACY_SRGB;
401 result
402 }
403
404 #[inline]
406 pub fn raw_components(&self) -> &[f32; 4] {
407 unsafe { color_components_as!(self, [f32; 4]) }
408 }
409
410 #[inline]
412 pub fn is_legacy_syntax(&self) -> bool {
413 match self.color_space {
415 ColorSpace::Srgb => self.flags.contains(ColorFlags::IS_LEGACY_SRGB),
416 ColorSpace::Hsl | ColorSpace::Hwb => true,
417 _ => false,
418 }
419 }
420
421 #[inline]
423 pub fn is_transparent(&self) -> bool {
424 self.flags.contains(ColorFlags::ALPHA_IS_NONE) || self.alpha == 0.0
425 }
426
427 #[inline]
429 pub fn c0(&self) -> Option<f32> {
430 if self.flags.contains(ColorFlags::C0_IS_NONE) {
431 None
432 } else {
433 Some(self.components.0)
434 }
435 }
436
437 #[inline]
439 pub fn c1(&self) -> Option<f32> {
440 if self.flags.contains(ColorFlags::C1_IS_NONE) {
441 None
442 } else {
443 Some(self.components.1)
444 }
445 }
446
447 #[inline]
449 pub fn c2(&self) -> Option<f32> {
450 if self.flags.contains(ColorFlags::C2_IS_NONE) {
451 None
452 } else {
453 Some(self.components.2)
454 }
455 }
456
457 #[inline]
459 pub fn alpha(&self) -> Option<f32> {
460 if self.flags.contains(ColorFlags::ALPHA_IS_NONE) {
461 None
462 } else {
463 Some(self.alpha)
464 }
465 }
466
467 pub fn get_component_by_channel_keyword(
469 &self,
470 channel_keyword: ChannelKeyword,
471 ) -> Result<Option<f32>, ()> {
472 if channel_keyword == ChannelKeyword::Alpha {
473 return Ok(self.alpha());
474 }
475
476 Ok(match self.color_space {
477 ColorSpace::Srgb => {
478 if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) {
479 match channel_keyword {
480 ChannelKeyword::R => self.c0().map(|v| v * 255.0),
481 ChannelKeyword::G => self.c1().map(|v| v * 255.0),
482 ChannelKeyword::B => self.c2().map(|v| v * 255.0),
483 _ => return Err(()),
484 }
485 } else {
486 match channel_keyword {
487 ChannelKeyword::R => self.c0(),
488 ChannelKeyword::G => self.c1(),
489 ChannelKeyword::B => self.c2(),
490 _ => return Err(()),
491 }
492 }
493 },
494 ColorSpace::Hsl => match channel_keyword {
495 ChannelKeyword::H => self.c0(),
496 ChannelKeyword::S => self.c1(),
497 ChannelKeyword::L => self.c2(),
498 _ => return Err(()),
499 },
500 ColorSpace::Hwb => match channel_keyword {
501 ChannelKeyword::H => self.c0(),
502 ChannelKeyword::W => self.c1(),
503 ChannelKeyword::B => self.c2(),
504 _ => return Err(()),
505 },
506 ColorSpace::Lab | ColorSpace::Oklab => match channel_keyword {
507 ChannelKeyword::L => self.c0(),
508 ChannelKeyword::A => self.c1(),
509 ChannelKeyword::B => self.c2(),
510 _ => return Err(()),
511 },
512 ColorSpace::Lch | ColorSpace::Oklch => match channel_keyword {
513 ChannelKeyword::L => self.c0(),
514 ChannelKeyword::C => self.c1(),
515 ChannelKeyword::H => self.c2(),
516 _ => return Err(()),
517 },
518 ColorSpace::SrgbLinear
519 | ColorSpace::DisplayP3
520 | ColorSpace::DisplayP3Linear
521 | ColorSpace::A98Rgb
522 | ColorSpace::ProphotoRgb
523 | ColorSpace::Rec2020 => match channel_keyword {
524 ChannelKeyword::R => self.c0(),
525 ChannelKeyword::G => self.c1(),
526 ChannelKeyword::B => self.c2(),
527 _ => return Err(()),
528 },
529 ColorSpace::XyzD50 | ColorSpace::XyzD65 => match channel_keyword {
530 ChannelKeyword::X => self.c0(),
531 ChannelKeyword::Y => self.c1(),
532 ChannelKeyword::Z => self.c2(),
533 _ => return Err(()),
534 },
535 })
536 }
537
538 pub fn to_color_space(&self, color_space: ColorSpace) -> Self {
540 use ColorSpace::*;
541
542 if self.color_space == color_space {
543 return self.clone();
544 }
545
546 macro_rules! missing_to_nan {
550 ($c:expr) => {{
551 if let Some(v) = $c {
552 crate::values::normalize(v)
553 } else {
554 f32::NAN
555 }
556 }};
557 }
558
559 let components = ColorComponents(
560 missing_to_nan!(self.c0()),
561 missing_to_nan!(self.c1()),
562 missing_to_nan!(self.c2()),
563 );
564
565 let result = match (self.color_space, color_space) {
566 (Srgb, Hsl) => convert::rgb_to_hsl(&components),
570 (Srgb, Hwb) => convert::rgb_to_hwb(&components),
571 (Hsl, Srgb) => convert::hsl_to_rgb(&components),
572 (Hwb, Srgb) => convert::hwb_to_rgb(&components),
573 (Lab, Lch) | (Oklab, Oklch) => convert::orthogonal_to_polar(
574 &components,
575 convert::epsilon_for_range(0.0, if color_space == Lch { 100.0 } else { 1.0 }),
576 ),
577 (Lch, Lab) | (Oklch, Oklab) => convert::polar_to_orthogonal(&components),
578
579 _ => {
581 let (xyz, white_point) = match self.color_space {
582 Lab => convert::to_xyz::<convert::Lab>(&components),
583 Lch => convert::to_xyz::<convert::Lch>(&components),
584 Oklab => convert::to_xyz::<convert::Oklab>(&components),
585 Oklch => convert::to_xyz::<convert::Oklch>(&components),
586 Srgb => convert::to_xyz::<convert::Srgb>(&components),
587 Hsl => convert::to_xyz::<convert::Hsl>(&components),
588 Hwb => convert::to_xyz::<convert::Hwb>(&components),
589 SrgbLinear => convert::to_xyz::<convert::SrgbLinear>(&components),
590 DisplayP3 => convert::to_xyz::<convert::DisplayP3>(&components),
591 DisplayP3Linear => convert::to_xyz::<convert::DisplayP3Linear>(&components),
592 A98Rgb => convert::to_xyz::<convert::A98Rgb>(&components),
593 ProphotoRgb => convert::to_xyz::<convert::ProphotoRgb>(&components),
594 Rec2020 => convert::to_xyz::<convert::Rec2020>(&components),
595 XyzD50 => convert::to_xyz::<convert::XyzD50>(&components),
596 XyzD65 => convert::to_xyz::<convert::XyzD65>(&components),
597 };
598
599 match color_space {
600 Lab => convert::from_xyz::<convert::Lab>(&xyz, white_point),
601 Lch => convert::from_xyz::<convert::Lch>(&xyz, white_point),
602 Oklab => convert::from_xyz::<convert::Oklab>(&xyz, white_point),
603 Oklch => convert::from_xyz::<convert::Oklch>(&xyz, white_point),
604 Srgb => convert::from_xyz::<convert::Srgb>(&xyz, white_point),
605 Hsl => convert::from_xyz::<convert::Hsl>(&xyz, white_point),
606 Hwb => convert::from_xyz::<convert::Hwb>(&xyz, white_point),
607 SrgbLinear => convert::from_xyz::<convert::SrgbLinear>(&xyz, white_point),
608 DisplayP3 => convert::from_xyz::<convert::DisplayP3>(&xyz, white_point),
609 DisplayP3Linear => {
610 convert::from_xyz::<convert::DisplayP3Linear>(&xyz, white_point)
611 },
612 A98Rgb => convert::from_xyz::<convert::A98Rgb>(&xyz, white_point),
613 ProphotoRgb => convert::from_xyz::<convert::ProphotoRgb>(&xyz, white_point),
614 Rec2020 => convert::from_xyz::<convert::Rec2020>(&xyz, white_point),
615 XyzD50 => convert::from_xyz::<convert::XyzD50>(&xyz, white_point),
616 XyzD65 => convert::from_xyz::<convert::XyzD65>(&xyz, white_point),
617 }
618 },
619 };
620
621 macro_rules! nan_to_missing {
624 ($v:expr) => {{
625 if $v.is_nan() {
626 None
627 } else {
628 Some($v)
629 }
630 }};
631 }
632
633 Self::new(
634 color_space,
635 nan_to_missing!(result.0),
636 nan_to_missing!(result.1),
637 nan_to_missing!(result.2),
638 self.alpha(),
639 )
640 }
641}
642
643impl From<PredefinedColorSpace> for ColorSpace {
644 fn from(value: PredefinedColorSpace) -> Self {
645 match value {
646 PredefinedColorSpace::Srgb => ColorSpace::Srgb,
647 PredefinedColorSpace::SrgbLinear => ColorSpace::SrgbLinear,
648 PredefinedColorSpace::DisplayP3 => ColorSpace::DisplayP3,
649 PredefinedColorSpace::DisplayP3Linear => ColorSpace::DisplayP3Linear,
650 PredefinedColorSpace::A98Rgb => ColorSpace::A98Rgb,
651 PredefinedColorSpace::ProphotoRgb => ColorSpace::ProphotoRgb,
652 PredefinedColorSpace::Rec2020 => ColorSpace::Rec2020,
653 PredefinedColorSpace::XyzD50 => ColorSpace::XyzD50,
654 PredefinedColorSpace::XyzD65 => ColorSpace::XyzD65,
655 }
656 }
657}