1pub mod convert;
9
10mod color_function;
11pub mod component;
12pub mod mix;
13pub mod parsing;
14mod to_css;
15
16use self::parsing::ChannelKeyword;
17pub use color_function::*;
18use component::ColorComponent;
19use cssparser::color::PredefinedColorSpace;
20
21#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
23#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
24#[repr(C)]
25pub struct ColorComponents(pub f32, pub f32, pub f32);
26
27impl ColorComponents {
28 #[must_use]
30 pub fn map(self, f: impl Fn(f32) -> f32) -> Self {
31 Self(f(self.0), f(self.1), f(self.2))
32 }
33}
34
35impl std::ops::Mul for ColorComponents {
36 type Output = Self;
37
38 fn mul(self, rhs: Self) -> Self::Output {
39 Self(self.0 * rhs.0, self.1 * rhs.1, self.2 * rhs.2)
40 }
41}
42
43impl std::ops::Div for ColorComponents {
44 type Output = Self;
45
46 fn div(self, rhs: Self) -> Self::Output {
47 Self(self.0 / rhs.0, self.1 / rhs.1, self.2 / rhs.2)
48 }
49}
50
51#[derive(
55 Clone,
56 Copy,
57 Debug,
58 Eq,
59 MallocSizeOf,
60 Parse,
61 PartialEq,
62 ToAnimatedValue,
63 ToComputedValue,
64 ToCss,
65 ToResolvedValue,
66 ToShmem,
67)]
68#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
69#[repr(u8)]
70pub enum ColorSpace {
71 Srgb = 0,
76 Hsl,
80 Hwb,
84 Lab,
88 Lch,
92 Oklab,
96 Oklch,
100 SrgbLinear,
103 DisplayP3,
106 DisplayP3Linear,
109 A98Rgb,
112 ProphotoRgb,
115 Rec2020,
118 XyzD50,
121 #[parse(aliases = "xyz")]
126 XyzD65,
127}
128
129impl ColorSpace {
130 #[inline]
132 pub fn is_rectangular(&self) -> bool {
133 !self.is_polar()
134 }
135
136 #[inline]
138 pub fn is_polar(&self) -> bool {
139 matches!(self, Self::Hsl | Self::Hwb | Self::Lch | Self::Oklch)
140 }
141
142 #[inline]
144 pub fn is_rgb_or_xyz_like(&self) -> bool {
145 match self {
146 Self::Srgb
147 | Self::SrgbLinear
148 | Self::DisplayP3
149 | Self::DisplayP3Linear
150 | Self::A98Rgb
151 | Self::ProphotoRgb
152 | Self::Rec2020
153 | Self::XyzD50
154 | Self::XyzD65 => true,
155 _ => false,
156 }
157 }
158
159 #[inline]
162 pub fn hue_index(&self) -> Option<usize> {
163 match self {
164 Self::Hsl | Self::Hwb => Some(0),
165 Self::Lch | Self::Oklch => Some(2),
166
167 _ => {
168 debug_assert!(!self.is_polar());
169 None
170 },
171 }
172 }
173}
174
175#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
177#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
178#[repr(C)]
179pub struct ColorFlags(u8);
180bitflags! {
181 impl ColorFlags : u8 {
182 const C0_IS_NONE = 1 << 0;
184 const C1_IS_NONE = 1 << 1;
186 const C2_IS_NONE = 1 << 2;
188 const ALPHA_IS_NONE = 1 << 3;
190 const IS_LEGACY_SRGB = 1 << 4;
193 }
194}
195
196#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, ToShmem, ToTyped)]
199#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
200#[repr(C)]
201pub struct AbsoluteColor {
202 pub components: ColorComponents,
204 pub alpha: f32,
206 pub color_space: ColorSpace,
208 pub flags: ColorFlags,
210}
211
212macro_rules! color_components_as {
220 ($c:expr, $t:ty) => {{
221 const_assert_eq!(std::mem::size_of::<$t>(), std::mem::size_of::<[f32; 4]>());
225 const_assert_eq!(std::mem::align_of::<$t>(), std::mem::align_of::<[f32; 4]>());
226 const_assert!(std::mem::size_of::<AbsoluteColor>() >= std::mem::size_of::<$t>());
227 const_assert_eq!(
228 std::mem::align_of::<AbsoluteColor>(),
229 std::mem::align_of::<$t>()
230 );
231
232 std::mem::transmute::<&ColorComponents, &$t>(&$c.components)
233 }};
234}
235
236pub struct ComponentDetails {
238 value: f32,
239 is_none: bool,
240}
241
242impl From<f32> for ComponentDetails {
243 fn from(value: f32) -> Self {
244 Self {
245 value,
246 is_none: false,
247 }
248 }
249}
250
251impl From<u8> for ComponentDetails {
252 fn from(value: u8) -> Self {
253 Self {
254 value: value as f32 / 255.0,
255 is_none: false,
256 }
257 }
258}
259
260impl From<Option<f32>> for ComponentDetails {
261 fn from(value: Option<f32>) -> Self {
262 if let Some(value) = value {
263 Self {
264 value,
265 is_none: false,
266 }
267 } else {
268 Self {
269 value: 0.0,
270 is_none: true,
271 }
272 }
273 }
274}
275
276impl From<ColorComponent<f32>> for ComponentDetails {
277 fn from(value: ColorComponent<f32>) -> Self {
278 if let ColorComponent::Value(value) = value {
279 Self {
280 value,
281 is_none: false,
282 }
283 } else {
284 Self {
285 value: 0.0,
286 is_none: true,
287 }
288 }
289 }
290}
291
292impl AbsoluteColor {
293 pub const TRANSPARENT_BLACK: Self = Self {
295 components: ColorComponents(0.0, 0.0, 0.0),
296 alpha: 0.0,
297 color_space: ColorSpace::Srgb,
298 flags: ColorFlags::IS_LEGACY_SRGB,
299 };
300
301 pub const BLACK: Self = Self {
303 components: ColorComponents(0.0, 0.0, 0.0),
304 alpha: 1.0,
305 color_space: ColorSpace::Srgb,
306 flags: ColorFlags::IS_LEGACY_SRGB,
307 };
308
309 pub const WHITE: Self = Self {
311 components: ColorComponents(1.0, 1.0, 1.0),
312 alpha: 1.0,
313 color_space: ColorSpace::Srgb,
314 flags: ColorFlags::IS_LEGACY_SRGB,
315 };
316
317 pub fn new(
320 color_space: ColorSpace,
321 c1: impl Into<ComponentDetails>,
322 c2: impl Into<ComponentDetails>,
323 c3: impl Into<ComponentDetails>,
324 alpha: impl Into<ComponentDetails>,
325 ) -> Self {
326 let mut flags = ColorFlags::empty();
327
328 macro_rules! cd {
329 ($c:expr,$flag:expr) => {{
330 let component_details = $c.into();
331 if component_details.is_none {
332 flags |= $flag;
333 }
334 component_details.value
335 }};
336 }
337
338 let mut components = ColorComponents(
339 cd!(c1, ColorFlags::C0_IS_NONE),
340 cd!(c2, ColorFlags::C1_IS_NONE),
341 cd!(c3, ColorFlags::C2_IS_NONE),
342 );
343
344 let alpha = cd!(alpha, ColorFlags::ALPHA_IS_NONE);
345
346 if matches!(color_space, ColorSpace::Lab | ColorSpace::Lch) {
348 components.0 = components.0.clamp(0.0, 100.0);
349 }
350
351 if matches!(color_space, ColorSpace::Oklab | ColorSpace::Oklch) {
353 components.0 = components.0.clamp(0.0, 1.0);
354 }
355
356 if matches!(color_space, ColorSpace::Lch | ColorSpace::Oklch) {
358 components.1 = components.1.max(0.0);
359 }
360
361 let alpha = alpha.clamp(0.0, 1.0);
363
364 Self {
365 components,
366 alpha,
367 color_space,
368 flags,
369 }
370 }
371
372 #[inline]
375 #[must_use]
376 pub fn into_srgb_legacy(self) -> Self {
377 let mut result = if !matches!(self.color_space, ColorSpace::Srgb) {
378 self.to_color_space(ColorSpace::Srgb)
379 } else {
380 self
381 };
382
383 result.flags = ColorFlags::IS_LEGACY_SRGB;
386
387 result
388 }
389
390 pub fn srgb_legacy(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
392 let mut result = Self::new(ColorSpace::Srgb, red, green, blue, alpha);
393 result.flags = ColorFlags::IS_LEGACY_SRGB;
394 result
395 }
396
397 #[inline]
399 pub fn raw_components(&self) -> &[f32; 4] {
400 unsafe { color_components_as!(self, [f32; 4]) }
401 }
402
403 #[inline]
405 pub fn is_legacy_syntax(&self) -> bool {
406 match self.color_space {
408 ColorSpace::Srgb => self.flags.contains(ColorFlags::IS_LEGACY_SRGB),
409 ColorSpace::Hsl | ColorSpace::Hwb => true,
410 _ => false,
411 }
412 }
413
414 #[inline]
416 pub fn is_transparent(&self) -> bool {
417 self.flags.contains(ColorFlags::ALPHA_IS_NONE) || self.alpha == 0.0
418 }
419
420 #[inline]
422 pub fn c0(&self) -> Option<f32> {
423 if self.flags.contains(ColorFlags::C0_IS_NONE) {
424 None
425 } else {
426 Some(self.components.0)
427 }
428 }
429
430 #[inline]
432 pub fn c1(&self) -> Option<f32> {
433 if self.flags.contains(ColorFlags::C1_IS_NONE) {
434 None
435 } else {
436 Some(self.components.1)
437 }
438 }
439
440 #[inline]
442 pub fn c2(&self) -> Option<f32> {
443 if self.flags.contains(ColorFlags::C2_IS_NONE) {
444 None
445 } else {
446 Some(self.components.2)
447 }
448 }
449
450 #[inline]
452 pub fn alpha(&self) -> Option<f32> {
453 if self.flags.contains(ColorFlags::ALPHA_IS_NONE) {
454 None
455 } else {
456 Some(self.alpha)
457 }
458 }
459
460 pub fn get_component_by_channel_keyword(
462 &self,
463 channel_keyword: ChannelKeyword,
464 ) -> Result<Option<f32>, ()> {
465 if channel_keyword == ChannelKeyword::Alpha {
466 return Ok(self.alpha());
467 }
468
469 Ok(match self.color_space {
470 ColorSpace::Srgb => {
471 if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) {
472 match channel_keyword {
473 ChannelKeyword::R => self.c0().map(|v| v * 255.0),
474 ChannelKeyword::G => self.c1().map(|v| v * 255.0),
475 ChannelKeyword::B => self.c2().map(|v| v * 255.0),
476 _ => return Err(()),
477 }
478 } else {
479 match channel_keyword {
480 ChannelKeyword::R => self.c0(),
481 ChannelKeyword::G => self.c1(),
482 ChannelKeyword::B => self.c2(),
483 _ => return Err(()),
484 }
485 }
486 },
487 ColorSpace::Hsl => match channel_keyword {
488 ChannelKeyword::H => self.c0(),
489 ChannelKeyword::S => self.c1(),
490 ChannelKeyword::L => self.c2(),
491 _ => return Err(()),
492 },
493 ColorSpace::Hwb => match channel_keyword {
494 ChannelKeyword::H => self.c0(),
495 ChannelKeyword::W => self.c1(),
496 ChannelKeyword::B => self.c2(),
497 _ => return Err(()),
498 },
499 ColorSpace::Lab | ColorSpace::Oklab => match channel_keyword {
500 ChannelKeyword::L => self.c0(),
501 ChannelKeyword::A => self.c1(),
502 ChannelKeyword::B => self.c2(),
503 _ => return Err(()),
504 },
505 ColorSpace::Lch | ColorSpace::Oklch => match channel_keyword {
506 ChannelKeyword::L => self.c0(),
507 ChannelKeyword::C => self.c1(),
508 ChannelKeyword::H => self.c2(),
509 _ => return Err(()),
510 },
511 ColorSpace::SrgbLinear
512 | ColorSpace::DisplayP3
513 | ColorSpace::DisplayP3Linear
514 | ColorSpace::A98Rgb
515 | ColorSpace::ProphotoRgb
516 | ColorSpace::Rec2020 => match channel_keyword {
517 ChannelKeyword::R => self.c0(),
518 ChannelKeyword::G => self.c1(),
519 ChannelKeyword::B => self.c2(),
520 _ => return Err(()),
521 },
522 ColorSpace::XyzD50 | ColorSpace::XyzD65 => match channel_keyword {
523 ChannelKeyword::X => self.c0(),
524 ChannelKeyword::Y => self.c1(),
525 ChannelKeyword::Z => self.c2(),
526 _ => return Err(()),
527 },
528 })
529 }
530
531 pub fn to_color_space(&self, color_space: ColorSpace) -> Self {
533 use ColorSpace::*;
534
535 if self.color_space == color_space {
536 return self.clone();
537 }
538
539 macro_rules! missing_to_nan {
543 ($c:expr) => {{
544 if let Some(v) = $c {
545 crate::values::normalize(v)
546 } else {
547 f32::NAN
548 }
549 }};
550 }
551
552 let components = ColorComponents(
553 missing_to_nan!(self.c0()),
554 missing_to_nan!(self.c1()),
555 missing_to_nan!(self.c2()),
556 );
557
558 let result = match (self.color_space, color_space) {
559 (Srgb, Hsl) => convert::rgb_to_hsl(&components),
563 (Srgb, Hwb) => convert::rgb_to_hwb(&components),
564 (Hsl, Srgb) => convert::hsl_to_rgb(&components),
565 (Hwb, Srgb) => convert::hwb_to_rgb(&components),
566 (Lab, Lch) | (Oklab, Oklch) => convert::orthogonal_to_polar(
567 &components,
568 convert::epsilon_for_range(0.0, if color_space == Lch { 100.0 } else { 1.0 }),
569 ),
570 (Lch, Lab) | (Oklch, Oklab) => convert::polar_to_orthogonal(&components),
571
572 _ => {
574 let (xyz, white_point) = match self.color_space {
575 Lab => convert::to_xyz::<convert::Lab>(&components),
576 Lch => convert::to_xyz::<convert::Lch>(&components),
577 Oklab => convert::to_xyz::<convert::Oklab>(&components),
578 Oklch => convert::to_xyz::<convert::Oklch>(&components),
579 Srgb => convert::to_xyz::<convert::Srgb>(&components),
580 Hsl => convert::to_xyz::<convert::Hsl>(&components),
581 Hwb => convert::to_xyz::<convert::Hwb>(&components),
582 SrgbLinear => convert::to_xyz::<convert::SrgbLinear>(&components),
583 DisplayP3 => convert::to_xyz::<convert::DisplayP3>(&components),
584 DisplayP3Linear => convert::to_xyz::<convert::DisplayP3Linear>(&components),
585 A98Rgb => convert::to_xyz::<convert::A98Rgb>(&components),
586 ProphotoRgb => convert::to_xyz::<convert::ProphotoRgb>(&components),
587 Rec2020 => convert::to_xyz::<convert::Rec2020>(&components),
588 XyzD50 => convert::to_xyz::<convert::XyzD50>(&components),
589 XyzD65 => convert::to_xyz::<convert::XyzD65>(&components),
590 };
591
592 match color_space {
593 Lab => convert::from_xyz::<convert::Lab>(&xyz, white_point),
594 Lch => convert::from_xyz::<convert::Lch>(&xyz, white_point),
595 Oklab => convert::from_xyz::<convert::Oklab>(&xyz, white_point),
596 Oklch => convert::from_xyz::<convert::Oklch>(&xyz, white_point),
597 Srgb => convert::from_xyz::<convert::Srgb>(&xyz, white_point),
598 Hsl => convert::from_xyz::<convert::Hsl>(&xyz, white_point),
599 Hwb => convert::from_xyz::<convert::Hwb>(&xyz, white_point),
600 SrgbLinear => convert::from_xyz::<convert::SrgbLinear>(&xyz, white_point),
601 DisplayP3 => convert::from_xyz::<convert::DisplayP3>(&xyz, white_point),
602 DisplayP3Linear => {
603 convert::from_xyz::<convert::DisplayP3Linear>(&xyz, white_point)
604 },
605 A98Rgb => convert::from_xyz::<convert::A98Rgb>(&xyz, white_point),
606 ProphotoRgb => convert::from_xyz::<convert::ProphotoRgb>(&xyz, white_point),
607 Rec2020 => convert::from_xyz::<convert::Rec2020>(&xyz, white_point),
608 XyzD50 => convert::from_xyz::<convert::XyzD50>(&xyz, white_point),
609 XyzD65 => convert::from_xyz::<convert::XyzD65>(&xyz, white_point),
610 }
611 },
612 };
613
614 macro_rules! nan_to_missing {
617 ($v:expr) => {{
618 if $v.is_nan() {
619 None
620 } else {
621 Some($v)
622 }
623 }};
624 }
625
626 Self::new(
627 color_space,
628 nan_to_missing!(result.0),
629 nan_to_missing!(result.1),
630 nan_to_missing!(result.2),
631 self.alpha(),
632 )
633 }
634}
635
636impl From<PredefinedColorSpace> for ColorSpace {
637 fn from(value: PredefinedColorSpace) -> Self {
638 match value {
639 PredefinedColorSpace::Srgb => ColorSpace::Srgb,
640 PredefinedColorSpace::SrgbLinear => ColorSpace::SrgbLinear,
641 PredefinedColorSpace::DisplayP3 => ColorSpace::DisplayP3,
642 PredefinedColorSpace::DisplayP3Linear => ColorSpace::DisplayP3Linear,
643 PredefinedColorSpace::A98Rgb => ColorSpace::A98Rgb,
644 PredefinedColorSpace::ProphotoRgb => ColorSpace::ProphotoRgb,
645 PredefinedColorSpace::Rec2020 => ColorSpace::Rec2020,
646 PredefinedColorSpace::XyzD50 => ColorSpace::XyzD50,
647 PredefinedColorSpace::XyzD65 => ColorSpace::XyzD65,
648 }
649 }
650}