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