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 A98Rgb,
109 ProphotoRgb,
112 Rec2020,
115 XyzD50,
118 #[parse(aliases = "xyz")]
123 XyzD65,
124}
125
126impl ColorSpace {
127 #[inline]
129 pub fn is_rectangular(&self) -> bool {
130 !self.is_polar()
131 }
132
133 #[inline]
135 pub fn is_polar(&self) -> bool {
136 matches!(self, Self::Hsl | Self::Hwb | Self::Lch | Self::Oklch)
137 }
138
139 #[inline]
141 pub fn is_rgb_or_xyz_like(&self) -> bool {
142 match self {
143 Self::Srgb
144 | Self::SrgbLinear
145 | Self::DisplayP3
146 | Self::A98Rgb
147 | Self::ProphotoRgb
148 | Self::Rec2020
149 | Self::XyzD50
150 | Self::XyzD65 => true,
151 _ => false,
152 }
153 }
154
155 #[inline]
158 pub fn hue_index(&self) -> Option<usize> {
159 match self {
160 Self::Hsl | Self::Hwb => Some(0),
161 Self::Lch | Self::Oklch => Some(2),
162
163 _ => {
164 debug_assert!(!self.is_polar());
165 None
166 },
167 }
168 }
169}
170
171#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
173#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
174#[repr(C)]
175pub struct ColorFlags(u8);
176bitflags! {
177 impl ColorFlags : u8 {
178 const C0_IS_NONE = 1 << 0;
180 const C1_IS_NONE = 1 << 1;
182 const C2_IS_NONE = 1 << 2;
184 const ALPHA_IS_NONE = 1 << 3;
186 const IS_LEGACY_SRGB = 1 << 4;
189 }
190}
191
192#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
195#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
196#[repr(C)]
197pub struct AbsoluteColor {
198 pub components: ColorComponents,
200 pub alpha: f32,
202 pub color_space: ColorSpace,
204 pub flags: ColorFlags,
206}
207
208macro_rules! color_components_as {
216 ($c:expr, $t:ty) => {{
217 const_assert_eq!(std::mem::size_of::<$t>(), std::mem::size_of::<[f32; 4]>());
221 const_assert_eq!(std::mem::align_of::<$t>(), std::mem::align_of::<[f32; 4]>());
222 const_assert!(std::mem::size_of::<AbsoluteColor>() >= std::mem::size_of::<$t>());
223 const_assert_eq!(
224 std::mem::align_of::<AbsoluteColor>(),
225 std::mem::align_of::<$t>()
226 );
227
228 std::mem::transmute::<&ColorComponents, &$t>(&$c.components)
229 }};
230}
231
232pub struct ComponentDetails {
234 value: f32,
235 is_none: bool,
236}
237
238impl From<f32> for ComponentDetails {
239 fn from(value: f32) -> Self {
240 Self {
241 value,
242 is_none: false,
243 }
244 }
245}
246
247impl From<u8> for ComponentDetails {
248 fn from(value: u8) -> Self {
249 Self {
250 value: value as f32 / 255.0,
251 is_none: false,
252 }
253 }
254}
255
256impl From<Option<f32>> for ComponentDetails {
257 fn from(value: Option<f32>) -> Self {
258 if let Some(value) = value {
259 Self {
260 value,
261 is_none: false,
262 }
263 } else {
264 Self {
265 value: 0.0,
266 is_none: true,
267 }
268 }
269 }
270}
271
272impl From<ColorComponent<f32>> for ComponentDetails {
273 fn from(value: ColorComponent<f32>) -> Self {
274 if let ColorComponent::Value(value) = value {
275 Self {
276 value,
277 is_none: false,
278 }
279 } else {
280 Self {
281 value: 0.0,
282 is_none: true,
283 }
284 }
285 }
286}
287
288impl AbsoluteColor {
289 pub const TRANSPARENT_BLACK: Self = Self {
291 components: ColorComponents(0.0, 0.0, 0.0),
292 alpha: 0.0,
293 color_space: ColorSpace::Srgb,
294 flags: ColorFlags::IS_LEGACY_SRGB,
295 };
296
297 pub const BLACK: Self = Self {
299 components: ColorComponents(0.0, 0.0, 0.0),
300 alpha: 1.0,
301 color_space: ColorSpace::Srgb,
302 flags: ColorFlags::IS_LEGACY_SRGB,
303 };
304
305 pub const WHITE: Self = Self {
307 components: ColorComponents(1.0, 1.0, 1.0),
308 alpha: 1.0,
309 color_space: ColorSpace::Srgb,
310 flags: ColorFlags::IS_LEGACY_SRGB,
311 };
312
313 pub fn new(
316 color_space: ColorSpace,
317 c1: impl Into<ComponentDetails>,
318 c2: impl Into<ComponentDetails>,
319 c3: impl Into<ComponentDetails>,
320 alpha: impl Into<ComponentDetails>,
321 ) -> Self {
322 let mut flags = ColorFlags::empty();
323
324 macro_rules! cd {
325 ($c:expr,$flag:expr) => {{
326 let component_details = $c.into();
327 if component_details.is_none {
328 flags |= $flag;
329 }
330 component_details.value
331 }};
332 }
333
334 let mut components = ColorComponents(
335 cd!(c1, ColorFlags::C0_IS_NONE),
336 cd!(c2, ColorFlags::C1_IS_NONE),
337 cd!(c3, ColorFlags::C2_IS_NONE),
338 );
339
340 let alpha = cd!(alpha, ColorFlags::ALPHA_IS_NONE);
341
342 if matches!(color_space, ColorSpace::Lab | ColorSpace::Lch) {
344 components.0 = components.0.clamp(0.0, 100.0);
345 }
346
347 if matches!(color_space, ColorSpace::Oklab | ColorSpace::Oklch) {
349 components.0 = components.0.clamp(0.0, 1.0);
350 }
351
352 if matches!(color_space, ColorSpace::Lch | ColorSpace::Oklch) {
354 components.1 = components.1.max(0.0);
355 }
356
357 let alpha = alpha.clamp(0.0, 1.0);
359
360 Self {
361 components,
362 alpha,
363 color_space,
364 flags,
365 }
366 }
367
368 #[inline]
371 #[must_use]
372 pub fn into_srgb_legacy(self) -> Self {
373 let mut result = if !matches!(self.color_space, ColorSpace::Srgb) {
374 self.to_color_space(ColorSpace::Srgb)
375 } else {
376 self
377 };
378
379 result.flags = ColorFlags::IS_LEGACY_SRGB;
382
383 result
384 }
385
386 pub fn srgb_legacy(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
388 let mut result = Self::new(ColorSpace::Srgb, red, green, blue, alpha);
389 result.flags = ColorFlags::IS_LEGACY_SRGB;
390 result
391 }
392
393 #[inline]
395 pub fn raw_components(&self) -> &[f32; 4] {
396 unsafe { color_components_as!(self, [f32; 4]) }
397 }
398
399 #[inline]
401 pub fn is_legacy_syntax(&self) -> bool {
402 match self.color_space {
404 ColorSpace::Srgb => self.flags.contains(ColorFlags::IS_LEGACY_SRGB),
405 ColorSpace::Hsl | ColorSpace::Hwb => true,
406 _ => false,
407 }
408 }
409
410 #[inline]
412 pub fn is_transparent(&self) -> bool {
413 self.flags.contains(ColorFlags::ALPHA_IS_NONE) || self.alpha == 0.0
414 }
415
416 #[inline]
418 pub fn c0(&self) -> Option<f32> {
419 if self.flags.contains(ColorFlags::C0_IS_NONE) {
420 None
421 } else {
422 Some(self.components.0)
423 }
424 }
425
426 #[inline]
428 pub fn c1(&self) -> Option<f32> {
429 if self.flags.contains(ColorFlags::C1_IS_NONE) {
430 None
431 } else {
432 Some(self.components.1)
433 }
434 }
435
436 #[inline]
438 pub fn c2(&self) -> Option<f32> {
439 if self.flags.contains(ColorFlags::C2_IS_NONE) {
440 None
441 } else {
442 Some(self.components.2)
443 }
444 }
445
446 #[inline]
448 pub fn alpha(&self) -> Option<f32> {
449 if self.flags.contains(ColorFlags::ALPHA_IS_NONE) {
450 None
451 } else {
452 Some(self.alpha)
453 }
454 }
455
456 pub fn get_component_by_channel_keyword(
458 &self,
459 channel_keyword: ChannelKeyword,
460 ) -> Result<Option<f32>, ()> {
461 if channel_keyword == ChannelKeyword::Alpha {
462 return Ok(self.alpha());
463 }
464
465 Ok(match self.color_space {
466 ColorSpace::Srgb => {
467 if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) {
468 match channel_keyword {
469 ChannelKeyword::R => self.c0().map(|v| v * 255.0),
470 ChannelKeyword::G => self.c1().map(|v| v * 255.0),
471 ChannelKeyword::B => self.c2().map(|v| v * 255.0),
472 _ => return Err(()),
473 }
474 } else {
475 match channel_keyword {
476 ChannelKeyword::R => self.c0(),
477 ChannelKeyword::G => self.c1(),
478 ChannelKeyword::B => self.c2(),
479 _ => return Err(()),
480 }
481 }
482 },
483 ColorSpace::Hsl => match channel_keyword {
484 ChannelKeyword::H => self.c0(),
485 ChannelKeyword::S => self.c1(),
486 ChannelKeyword::L => self.c2(),
487 _ => return Err(()),
488 },
489 ColorSpace::Hwb => match channel_keyword {
490 ChannelKeyword::H => self.c0(),
491 ChannelKeyword::W => self.c1(),
492 ChannelKeyword::B => self.c2(),
493 _ => return Err(()),
494 },
495 ColorSpace::Lab | ColorSpace::Oklab => match channel_keyword {
496 ChannelKeyword::L => self.c0(),
497 ChannelKeyword::A => self.c1(),
498 ChannelKeyword::B => self.c2(),
499 _ => return Err(()),
500 },
501 ColorSpace::Lch | ColorSpace::Oklch => match channel_keyword {
502 ChannelKeyword::L => self.c0(),
503 ChannelKeyword::C => self.c1(),
504 ChannelKeyword::H => self.c2(),
505 _ => return Err(()),
506 },
507 ColorSpace::SrgbLinear
508 | ColorSpace::DisplayP3
509 | ColorSpace::A98Rgb
510 | ColorSpace::ProphotoRgb
511 | ColorSpace::Rec2020 => match channel_keyword {
512 ChannelKeyword::R => self.c0(),
513 ChannelKeyword::G => self.c1(),
514 ChannelKeyword::B => self.c2(),
515 _ => return Err(()),
516 },
517 ColorSpace::XyzD50 | ColorSpace::XyzD65 => match channel_keyword {
518 ChannelKeyword::X => self.c0(),
519 ChannelKeyword::Y => self.c1(),
520 ChannelKeyword::Z => self.c2(),
521 _ => return Err(()),
522 },
523 })
524 }
525
526 pub fn to_color_space(&self, color_space: ColorSpace) -> Self {
528 use ColorSpace::*;
529
530 if self.color_space == color_space {
531 return self.clone();
532 }
533
534 macro_rules! missing_to_nan {
538 ($c:expr) => {{
539 if let Some(v) = $c {
540 crate::values::normalize(v)
541 } else {
542 f32::NAN
543 }
544 }};
545 }
546
547 let components = ColorComponents(
548 missing_to_nan!(self.c0()),
549 missing_to_nan!(self.c1()),
550 missing_to_nan!(self.c2()),
551 );
552
553 let result = match (self.color_space, color_space) {
554 (Srgb, Hsl) => convert::rgb_to_hsl(&components),
558 (Srgb, Hwb) => convert::rgb_to_hwb(&components),
559 (Hsl, Srgb) => convert::hsl_to_rgb(&components),
560 (Hwb, Srgb) => convert::hwb_to_rgb(&components),
561 (Lab, Lch) | (Oklab, Oklch) => convert::orthogonal_to_polar(
562 &components,
563 convert::epsilon_for_range(0.0, if color_space == Lch { 100.0 } else { 1.0 }),
564 ),
565 (Lch, Lab) | (Oklch, Oklab) => convert::polar_to_orthogonal(&components),
566
567 _ => {
569 let (xyz, white_point) = match self.color_space {
570 Lab => convert::to_xyz::<convert::Lab>(&components),
571 Lch => convert::to_xyz::<convert::Lch>(&components),
572 Oklab => convert::to_xyz::<convert::Oklab>(&components),
573 Oklch => convert::to_xyz::<convert::Oklch>(&components),
574 Srgb => convert::to_xyz::<convert::Srgb>(&components),
575 Hsl => convert::to_xyz::<convert::Hsl>(&components),
576 Hwb => convert::to_xyz::<convert::Hwb>(&components),
577 SrgbLinear => convert::to_xyz::<convert::SrgbLinear>(&components),
578 DisplayP3 => convert::to_xyz::<convert::DisplayP3>(&components),
579 A98Rgb => convert::to_xyz::<convert::A98Rgb>(&components),
580 ProphotoRgb => convert::to_xyz::<convert::ProphotoRgb>(&components),
581 Rec2020 => convert::to_xyz::<convert::Rec2020>(&components),
582 XyzD50 => convert::to_xyz::<convert::XyzD50>(&components),
583 XyzD65 => convert::to_xyz::<convert::XyzD65>(&components),
584 };
585
586 match color_space {
587 Lab => convert::from_xyz::<convert::Lab>(&xyz, white_point),
588 Lch => convert::from_xyz::<convert::Lch>(&xyz, white_point),
589 Oklab => convert::from_xyz::<convert::Oklab>(&xyz, white_point),
590 Oklch => convert::from_xyz::<convert::Oklch>(&xyz, white_point),
591 Srgb => convert::from_xyz::<convert::Srgb>(&xyz, white_point),
592 Hsl => convert::from_xyz::<convert::Hsl>(&xyz, white_point),
593 Hwb => convert::from_xyz::<convert::Hwb>(&xyz, white_point),
594 SrgbLinear => convert::from_xyz::<convert::SrgbLinear>(&xyz, white_point),
595 DisplayP3 => convert::from_xyz::<convert::DisplayP3>(&xyz, white_point),
596 A98Rgb => convert::from_xyz::<convert::A98Rgb>(&xyz, white_point),
597 ProphotoRgb => convert::from_xyz::<convert::ProphotoRgb>(&xyz, white_point),
598 Rec2020 => convert::from_xyz::<convert::Rec2020>(&xyz, white_point),
599 XyzD50 => convert::from_xyz::<convert::XyzD50>(&xyz, white_point),
600 XyzD65 => convert::from_xyz::<convert::XyzD65>(&xyz, white_point),
601 }
602 },
603 };
604
605 macro_rules! nan_to_missing {
608 ($v:expr) => {{
609 if $v.is_nan() {
610 None
611 } else {
612 Some($v)
613 }
614 }};
615 }
616
617 Self::new(
618 color_space,
619 nan_to_missing!(result.0),
620 nan_to_missing!(result.1),
621 nan_to_missing!(result.2),
622 self.alpha(),
623 )
624 }
625}
626
627impl From<PredefinedColorSpace> for ColorSpace {
628 fn from(value: PredefinedColorSpace) -> Self {
629 match value {
630 PredefinedColorSpace::Srgb => ColorSpace::Srgb,
631 PredefinedColorSpace::SrgbLinear => ColorSpace::SrgbLinear,
632 PredefinedColorSpace::DisplayP3 => ColorSpace::DisplayP3,
633 PredefinedColorSpace::A98Rgb => ColorSpace::A98Rgb,
634 PredefinedColorSpace::ProphotoRgb => ColorSpace::ProphotoRgb,
635 PredefinedColorSpace::Rec2020 => ColorSpace::Rec2020,
636 PredefinedColorSpace::XyzD50 => ColorSpace::XyzD50,
637 PredefinedColorSpace::XyzD65 => ColorSpace::XyzD65,
638 }
639 }
640}