style: Implement color() function from CSS specification

Colors can now be defined in different color spaces with the color()
function.

https://w3c.github.io/csswg-drafts/css-color-4/#predefined

Differential Revision: https://phabricator.services.mozilla.com/D164866
This commit is contained in:
Tiaan Louw 2023-01-27 12:44:18 +00:00 committed by Martin Robinson
parent 896aac5e4a
commit 922914aa38
7 changed files with 781 additions and 65 deletions

5
Cargo.lock generated
View file

@ -1171,12 +1171,11 @@ dependencies = [
[[package]]
name = "cssparser"
version = "0.30.0"
source = "git+https://github.com/servo/rust-cssparser?rev=722b30d2f1634714befab967ecae627813fa4cf0#722b30d2f1634714befab967ecae627813fa4cf0"
source = "git+https://github.com/servo/rust-cssparser?rev=d3670a89bae26ba3a8db4758eb7976616113987d#d3670a89bae26ba3a8db4758eb7976616113987d"
dependencies = [
"cssparser-macros",
"dtoa-short",
"itoa",
"matches",
"phf",
"proc-macro2",
"quote",
@ -1188,7 +1187,7 @@ dependencies = [
[[package]]
name = "cssparser-macros"
version = "0.6.0"
source = "git+https://github.com/servo/rust-cssparser?rev=722b30d2f1634714befab967ecae627813fa4cf0#722b30d2f1634714befab967ecae627813fa4cf0"
source = "git+https://github.com/servo/rust-cssparser?rev=d3670a89bae26ba3a8db4758eb7976616113987d#d3670a89bae26ba3a8db4758eb7976616113987d"
dependencies = [
"quote",
"syn 1.0.103",

View file

@ -27,7 +27,7 @@ compositing_traits = { path = "components/shared/compositing" }
content-security-policy = { version = "0.5", features = ["serde"] }
cookie = "0.12"
crossbeam-channel = "0.5"
cssparser = { version = "0.30", git = "https://github.com/servo/rust-cssparser", rev = "722b30d2f1634714befab967ecae627813fa4cf0" }
cssparser = { version = "0.30", git = "https://github.com/servo/rust-cssparser", rev = "d3670a89bae26ba3a8db4758eb7976616113987d" }
darling = { version = "0.14", default-features = false }
data-url = "0.1.0"
devtools_traits = { path = "components/shared/devtools" }

View file

@ -0,0 +1,649 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Color conversion algorithms.
//!
//! Algorithms, matrices and constants are from the [color-4] specification,
//! unless otherwise specified:
//!
//! https://drafts.csswg.org/csswg-drafts/css-color-4/#color-conversion-code
use crate::color::ColorComponents;
use std::f32::consts::PI;
type Transform = euclid::default::Transform3D<f32>;
type Vector = euclid::default::Vector3D<f32>;
const RAD_PER_DEG: f32 = PI / 180.0;
const DEG_PER_RAD: f32 = 180.0 / PI;
#[inline]
fn transform(from: &ColorComponents, mat: &Transform) -> ColorComponents {
let result = mat.transform_vector3d(Vector::new(from.0, from.1, from.2));
ColorComponents(result.x, result.y, result.z)
}
fn xyz_d65_to_xyz_d50(from: &ColorComponents) -> ColorComponents {
#[rustfmt::skip]
const MAT: Transform = Transform::new(
1.0479298208405488, 0.029627815688159344, -0.009243058152591178, 0.0,
0.022946793341019088, 0.990434484573249, 0.015055144896577895, 0.0,
-0.05019222954313557, -0.01707382502938514, 0.7518742899580008, 0.0,
0.0, 0.0, 0.0, 1.0,
);
transform(from, &MAT)
}
fn xyz_d50_to_xyz_d65(from: &ColorComponents) -> ColorComponents {
#[rustfmt::skip]
const MAT: Transform = Transform::new(
0.9554734527042182, -0.028369706963208136, 0.012314001688319899, 0.0,
-0.023098536874261423, 1.0099954580058226, -0.020507696433477912, 0.0,
0.0632593086610217, 0.021041398966943008, 1.3303659366080753, 0.0,
0.0, 0.0, 0.0, 1.0,
);
transform(from, &MAT)
}
/// A reference white that is used during color conversion.
pub enum WhitePoint {
/// D50 white reference.
D50,
/// D65 white reference.
D65,
}
fn convert_white_point(from: WhitePoint, to: WhitePoint, components: &mut ColorComponents) {
match (from, to) {
(WhitePoint::D50, WhitePoint::D65) => *components = xyz_d50_to_xyz_d65(components),
(WhitePoint::D65, WhitePoint::D50) => *components = xyz_d65_to_xyz_d50(components),
_ => {},
}
}
/// A trait that allows conversion of color spaces to and from XYZ coordinate
/// space with a specified white point.
///
/// Allows following the specified method of converting between color spaces:
/// - Convert to values to sRGB linear light.
/// - Convert to XYZ coordinate space.
/// - Adjust white point to target white point.
/// - Convert to sRGB linear light in target color space.
/// - Convert to sRGB gamma encoded in target color space.
///
/// https://drafts.csswg.org/csswg-drafts/css-color-4/#color-conversion
pub trait ColorSpaceConversion {
/// The white point that the implementer is represented in.
const WHITE_POINT: WhitePoint;
/// Convert the components from sRGB gamma encoded values to sRGB linear
/// light values.
fn to_linear_light(from: &ColorComponents) -> ColorComponents;
/// Convert the components from sRGB linear light values to XYZ coordinate
/// space.
fn to_xyz(from: &ColorComponents) -> ColorComponents;
/// Convert the components from XYZ coordinate space to sRGB linear light
/// values.
fn from_xyz(from: &ColorComponents) -> ColorComponents;
/// Convert the components from sRGB linear light values to sRGB gamma
/// encoded values.
fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents;
}
/// Convert the color components from the specified color space to XYZ and
/// return the components and the white point they are in.
pub fn to_xyz<From: ColorSpaceConversion>(from: &ColorComponents) -> (ColorComponents, WhitePoint) {
// Convert the color components where in-gamut values are in the range
// [0 - 1] to linear light (un-companded) form.
let result = From::to_linear_light(from);
// Convert the color components from the source color space to XYZ.
(From::to_xyz(&result), From::WHITE_POINT)
}
/// Convert the color components from XYZ at the given white point to the
/// specified color space.
pub fn from_xyz<To: ColorSpaceConversion>(
from: &ColorComponents,
white_point: WhitePoint,
) -> ColorComponents {
let mut xyz = from.clone();
// Convert the white point if needed.
convert_white_point(white_point, To::WHITE_POINT, &mut xyz);
// Convert the color from XYZ to the target color space.
let result = To::from_xyz(&xyz);
// Convert the color components of linear-light values in the range
// [0 - 1] to a gamma corrected form.
To::to_gamma_encoded(&result)
}
/// The sRGB color space.
/// https://drafts.csswg.org/csswg-drafts/css-color-4/#predefined-sRGB
pub struct Srgb;
impl Srgb {
#[rustfmt::skip]
const TO_XYZ: Transform = Transform::new(
0.4123907992659595, 0.21263900587151036, 0.01933081871559185, 0.0,
0.35758433938387796, 0.7151686787677559, 0.11919477979462599, 0.0,
0.1804807884018343, 0.07219231536073371, 0.9505321522496606, 0.0,
0.0, 0.0, 0.0, 1.0,
);
#[rustfmt::skip]
const FROM_XYZ: Transform = Transform::new(
3.2409699419045213, -0.9692436362808798, 0.05563007969699361, 0.0,
-1.5373831775700935, 1.8759675015077206, -0.20397695888897657, 0.0,
-0.4986107602930033, 0.04155505740717561, 1.0569715142428786, 0.0,
0.0, 0.0, 0.0, 1.0,
);
}
impl ColorSpaceConversion for Srgb {
const WHITE_POINT: WhitePoint = WhitePoint::D65;
fn to_linear_light(from: &ColorComponents) -> ColorComponents {
from.clone().map(|value| {
let abs = value.abs();
if abs < 0.04045 {
value / 12.92
} else {
value.signum() * ((abs + 0.055) / 1.055).powf(2.4)
}
})
}
fn to_xyz(from: &ColorComponents) -> ColorComponents {
transform(from, &Self::TO_XYZ)
}
fn from_xyz(from: &ColorComponents) -> ColorComponents {
transform(from, &Self::FROM_XYZ)
}
fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
from.clone().map(|value| {
let abs = value.abs();
if abs > 0.0031308 {
value.signum() * (1.055 * abs.powf(1.0 / 2.4) - 0.055)
} else {
12.92 * value
}
})
}
}
/// The same as sRGB color space, except the transfer function is linear light.
/// https://drafts.csswg.org/csswg-drafts/css-color-4/#predefined-sRGB-linear
pub struct SrgbLinear;
impl ColorSpaceConversion for SrgbLinear {
const WHITE_POINT: WhitePoint = Srgb::WHITE_POINT;
fn to_linear_light(from: &ColorComponents) -> ColorComponents {
// Already in linear light form.
from.clone()
}
fn to_xyz(from: &ColorComponents) -> ColorComponents {
Srgb::to_xyz(from)
}
fn from_xyz(from: &ColorComponents) -> ColorComponents {
Srgb::from_xyz(from)
}
fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
// Stay in linear light form.
from.clone()
}
}
/// The Display-P3 color space.
/// https://drafts.csswg.org/csswg-drafts/css-color-4/#predefined-display-p3
pub struct DisplayP3;
impl DisplayP3 {
#[rustfmt::skip]
const TO_XYZ: Transform = Transform::new(
0.48657094864821626, 0.22897456406974884, 0.0, 0.0,
0.26566769316909294, 0.6917385218365062, 0.045113381858902575, 0.0,
0.1982172852343625, 0.079286914093745, 1.0439443689009757, 0.0,
0.0, 0.0, 0.0, 1.0,
);
}
impl ColorSpaceConversion for DisplayP3 {
const WHITE_POINT: WhitePoint = WhitePoint::D65;
fn to_linear_light(from: &ColorComponents) -> ColorComponents {
Srgb::to_linear_light(from)
}
fn to_xyz(from: &ColorComponents) -> ColorComponents {
transform(from, &Self::TO_XYZ)
}
fn from_xyz(_from: &ColorComponents) -> ColorComponents {
todo!()
}
fn to_gamma_encoded(_from: &ColorComponents) -> ColorComponents {
todo!()
}
}
/// The a98-rgb color space.
/// https://drafts.csswg.org/csswg-drafts/css-color-4/#predefined-a98-rgb
pub struct A98Rgb;
impl A98Rgb {
#[rustfmt::skip]
const TO_XYZ: Transform = Transform::new(
0.5766690429101308, 0.29734497525053616, 0.027031361386412378, 0.0,
0.18555823790654627, 0.627363566255466, 0.07068885253582714, 0.0,
0.18822864623499472, 0.07529145849399789, 0.9913375368376389, 0.0,
0.0, 0.0, 0.0, 1.0,
);
}
impl ColorSpaceConversion for A98Rgb {
const WHITE_POINT: WhitePoint = WhitePoint::D65;
fn to_linear_light(from: &ColorComponents) -> ColorComponents {
#[inline]
fn map(value: f32) -> f32 {
value.signum() * value.abs().powf(2.19921875)
}
ColorComponents(map(from.0), map(from.1), map(from.2))
}
fn to_xyz(from: &ColorComponents) -> ColorComponents {
transform(from, &Self::TO_XYZ)
}
fn from_xyz(_from: &ColorComponents) -> ColorComponents {
todo!()
}
fn to_gamma_encoded(_from: &ColorComponents) -> ColorComponents {
todo!()
}
}
/// The ProPhoto RGB color space.
/// https://drafts.csswg.org/csswg-drafts/css-color-4/#predefined-prophoto-rgb
pub struct ProphotoRgb;
impl ProphotoRgb {
#[rustfmt::skip]
const TO_XYZ: Transform = Transform::new(
0.7977604896723027, 0.2880711282292934, 0.0, 0.0,
0.13518583717574031, 0.7118432178101014, 0.0, 0.0,
0.0313493495815248, 0.00008565396060525902, 0.8251046025104601, 0.0,
0.0, 0.0, 0.0, 1.0,
);
}
impl ColorSpaceConversion for ProphotoRgb {
const WHITE_POINT: WhitePoint = WhitePoint::D50;
fn to_linear_light(from: &ColorComponents) -> ColorComponents {
from.clone().map(|value| {
const ET2: f32 = 16.0 / 512.0;
let abs = value.abs();
if abs <= ET2 {
value / 16.0
} else {
value.signum() * abs.powf(1.8)
}
})
}
fn to_xyz(from: &ColorComponents) -> ColorComponents {
transform(from, &Self::TO_XYZ)
}
fn from_xyz(_from: &ColorComponents) -> ColorComponents {
todo!()
}
fn to_gamma_encoded(_from: &ColorComponents) -> ColorComponents {
todo!()
}
}
/// The Rec.2020 color space.
/// https://drafts.csswg.org/csswg-drafts/css-color-4/#predefined-rec2020
pub struct Rec2020;
impl Rec2020 {
#[rustfmt::skip]
const TO_XYZ: Transform = Transform::new(
0.6369580483012913, 0.26270021201126703, 0.0, 0.0,
0.14461690358620838, 0.677998071518871, 0.028072693049087508, 0.0,
0.16888097516417205, 0.059301716469861945, 1.0609850577107909, 0.0,
0.0, 0.0, 0.0, 1.0,
);
}
impl ColorSpaceConversion for Rec2020 {
const WHITE_POINT: WhitePoint = WhitePoint::D65;
fn to_linear_light(from: &ColorComponents) -> ColorComponents {
from.clone().map(|value| {
const ALPHA: f32 = 1.09929682680944;
const BETA: f32 = 0.018053968510807;
let abs = value.abs();
if abs < BETA * 4.5 {
value / 4.5
} else {
value.signum() * ((abs + ALPHA - 1.0) / ALPHA).powf(1.0 / 0.45)
}
})
}
fn to_xyz(from: &ColorComponents) -> ColorComponents {
transform(from, &Self::TO_XYZ)
}
fn from_xyz(_from: &ColorComponents) -> ColorComponents {
todo!()
}
fn to_gamma_encoded(_from: &ColorComponents) -> ColorComponents {
todo!()
}
}
/// A color in the XYZ coordinate space with a D50 white reference.
/// https://drafts.csswg.org/csswg-drafts/css-color-4/#predefined-xyz
pub struct XyzD50;
impl ColorSpaceConversion for XyzD50 {
const WHITE_POINT: WhitePoint = WhitePoint::D50;
fn to_linear_light(from: &ColorComponents) -> ColorComponents {
from.clone()
}
fn to_xyz(from: &ColorComponents) -> ColorComponents {
from.clone()
}
fn from_xyz(_from: &ColorComponents) -> ColorComponents {
todo!()
}
fn to_gamma_encoded(_from: &ColorComponents) -> ColorComponents {
todo!()
}
}
/// A color in the XYZ coordinate space with a D65 white reference.
/// https://drafts.csswg.org/csswg-drafts/css-color-4/#predefined-xyz
pub struct XyzD65;
impl ColorSpaceConversion for XyzD65 {
const WHITE_POINT: WhitePoint = WhitePoint::D65;
fn to_linear_light(from: &ColorComponents) -> ColorComponents {
from.clone()
}
fn to_xyz(from: &ColorComponents) -> ColorComponents {
from.clone()
}
fn from_xyz(_from: &ColorComponents) -> ColorComponents {
todo!()
}
fn to_gamma_encoded(_from: &ColorComponents) -> ColorComponents {
todo!()
}
}
/// The Lab color space.
/// https://drafts.csswg.org/csswg-drafts/css-color-4/#specifying-lab-lch
pub struct Lab;
impl Lab {
const KAPPA: f32 = 24389.0 / 27.0;
const EPSILON: f32 = 216.0 / 24389.0;
const WHITE: ColorComponents = ColorComponents(0.96422, 1.0, 0.82521);
}
impl ColorSpaceConversion for Lab {
const WHITE_POINT: WhitePoint = WhitePoint::D50;
fn to_linear_light(from: &ColorComponents) -> ColorComponents {
// No need for conversion.
from.clone()
}
/// Convert a CIELAB color to XYZ as specified in [1] and [2].
///
/// [1]: https://drafts.csswg.org/css-color/#lab-to-predefined
/// [2]: https://drafts.csswg.org/css-color/#color-conversion-code
fn to_xyz(from: &ColorComponents) -> ColorComponents {
let f1 = (from.0 + 16.0) / 116.0;
let f0 = (from.1 / 500.0) + f1;
let f2 = f1 - from.2 / 200.0;
let x = if f0.powf(3.0) > Self::EPSILON {
f0.powf(3.)
} else {
(116.0 * f0 - 16.0) / Self::KAPPA
};
let y = if from.0 > Self::KAPPA * Self::EPSILON {
((from.0 + 16.0) / 116.0).powf(3.0)
} else {
from.0 / Self::KAPPA
};
let z = if f2.powf(3.0) > Self::EPSILON {
f2.powf(3.0)
} else {
(116.0 * f2 - 16.0) / Self::KAPPA
};
ColorComponents(x * Self::WHITE.0, y * Self::WHITE.1, z * Self::WHITE.2)
}
/// Convert an XYZ colour to LAB as specified in [1] and [2].
///
/// [1]: https://drafts.csswg.org/css-color/#rgb-to-lab
/// [2]: https://drafts.csswg.org/css-color/#color-conversion-code
fn from_xyz(from: &ColorComponents) -> ColorComponents {
macro_rules! compute_f {
($value:expr) => {{
if $value > Self::EPSILON {
$value.cbrt()
} else {
(Self::KAPPA * $value + 16.0) / 116.0
}
}};
}
// 4. Convert D50-adapted XYZ to Lab.
let f = [
compute_f!(from.0 / Self::WHITE.0),
compute_f!(from.1 / Self::WHITE.1),
compute_f!(from.2 / Self::WHITE.2),
];
let lightness = 116.0 * f[1] - 16.0;
let a = 500.0 * (f[0] - f[1]);
let b = 200.0 * (f[1] - f[2]);
ColorComponents(lightness, a, b)
}
fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
// No need for conversion.
from.clone()
}
}
/// The Lch color space.
/// https://drafts.csswg.org/csswg-drafts/css-color-4/#specifying-lab-lch
pub struct Lch;
impl ColorSpaceConversion for Lch {
const WHITE_POINT: WhitePoint = Lab::WHITE_POINT;
fn to_linear_light(from: &ColorComponents) -> ColorComponents {
// No need for conversion.
from.clone()
}
fn to_xyz(from: &ColorComponents) -> ColorComponents {
// Convert LCH to Lab first.
let hue = from.2 * RAD_PER_DEG;
let a = from.1 * hue.cos();
let b = from.1 * hue.sin();
let lab = ColorComponents(from.0, a, b);
// Then convert the Lab to XYZ.
Lab::to_xyz(&lab)
}
fn from_xyz(from: &ColorComponents) -> ColorComponents {
// First convert the XYZ to LAB.
let ColorComponents(lightness, a, b) = Lab::from_xyz(&from);
// Then conver the Lab to LCH.
let hue = b.atan2(a) * DEG_PER_RAD;
let chroma = (a * a + b * b).sqrt();
ColorComponents(lightness, chroma, hue)
}
fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
// No need for conversion.
from.clone()
}
}
/// The Oklab color space.
/// https://drafts.csswg.org/csswg-drafts/css-color-4/#specifying-oklab-oklch
pub struct Oklab;
impl Oklab {
#[rustfmt::skip]
const XYZ_TO_LMS: Transform = Transform::new(
0.8190224432164319, 0.0329836671980271, 0.048177199566046255, 0.0,
0.3619062562801221, 0.9292868468965546, 0.26423952494422764, 0.0,
-0.12887378261216414, 0.03614466816999844, 0.6335478258136937, 0.0,
0.0, 0.0, 0.0, 1.0,
);
#[rustfmt::skip]
const LMS_TO_OKLAB: Transform = Transform::new(
0.2104542553, 1.9779984951, 0.0259040371, 0.0,
0.7936177850, -2.4285922050, 0.7827717662, 0.0,
-0.0040720468, 0.4505937099, -0.8086757660, 0.0,
0.0, 0.0, 0.0, 1.0,
);
#[rustfmt::skip]
const LMS_TO_XYZ: Transform = Transform::new(
1.2268798733741557, -0.04057576262431372, -0.07637294974672142, 0.0,
-0.5578149965554813, 1.1122868293970594, -0.4214933239627914, 0.0,
0.28139105017721583, -0.07171106666151701, 1.5869240244272418, 0.0,
0.0, 0.0, 0.0, 1.0,
);
#[rustfmt::skip]
const OKLAB_TO_LMS: Transform = Transform::new(
0.99999999845051981432, 1.0000000088817607767, 1.0000000546724109177, 0.0,
0.39633779217376785678, -0.1055613423236563494, -0.089484182094965759684, 0.0,
0.21580375806075880339, -0.063854174771705903402, -1.2914855378640917399, 0.0,
0.0, 0.0, 0.0, 1.0,
);
}
impl ColorSpaceConversion for Oklab {
const WHITE_POINT: WhitePoint = WhitePoint::D65;
fn to_linear_light(from: &ColorComponents) -> ColorComponents {
// No need for conversion.
from.clone()
}
fn to_xyz(from: &ColorComponents) -> ColorComponents {
let lms = transform(&from, &Self::OKLAB_TO_LMS);
let lms = lms.map(|v| v.powf(3.0));
transform(&lms, &Self::LMS_TO_XYZ)
}
fn from_xyz(from: &ColorComponents) -> ColorComponents {
let lms = transform(&from, &Self::XYZ_TO_LMS);
let lms = lms.map(|v| v.cbrt());
transform(&lms, &Self::LMS_TO_OKLAB)
}
fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
// No need for conversion.
from.clone()
}
}
/// The Oklch color space.
/// https://drafts.csswg.org/csswg-drafts/css-color-4/#specifying-oklab-oklch
pub struct Oklch;
impl ColorSpaceConversion for Oklch {
const WHITE_POINT: WhitePoint = Oklab::WHITE_POINT;
fn to_linear_light(from: &ColorComponents) -> ColorComponents {
// No need for conversion.
from.clone()
}
fn to_xyz(from: &ColorComponents) -> ColorComponents {
// First convert OkLCH to Oklab.
let hue = from.2 * RAD_PER_DEG;
let a = from.1 * hue.cos();
let b = from.1 * hue.sin();
let oklab = ColorComponents(from.0, a, b);
// Then convert Oklab to XYZ.
Oklab::to_xyz(&oklab)
}
fn from_xyz(from: &ColorComponents) -> ColorComponents {
// First convert XYZ to Oklab.
let ColorComponents(lightness, a, b) = Oklab::from_xyz(&from);
// Then convert Oklab to OkLCH.
let hue = b.atan2(a) * DEG_PER_RAD;
let chroma = (a * a + b * b).sqrt();
ColorComponents(lightness, chroma, hue)
}
fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
// No need for conversion.
from.clone()
}
}

View file

@ -0,0 +1,19 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Color support functions.
/// cbindgen:ignore
pub mod convert;
/// The 3 components that make up a color. (Does not include the alpha component)
#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub struct ColorComponents(pub f32, pub f32, pub f32);
impl ColorComponents {
/// Apply a function to each of the 3 components of the color.
pub fn map(self, f: impl Fn(f32) -> f32) -> Self {
Self(f(self.0), f(self.1), f(self.2))
}
}

View file

@ -80,6 +80,7 @@ pub mod attr;
pub mod author_styles;
pub mod bezier;
pub mod bloom;
pub mod color;
#[path = "properties/computed_value_flags.rs"]
pub mod computed_value_flags;
pub mod context;

View file

@ -13,6 +13,10 @@ use crate::values::generics::color::{
use euclid::default::{Transform3D, Vector3D};
use std::f32::consts::PI;
// TODO(tlouw): The code does not use the new color conversions that were added
// for new color spaces and needs to be refectored, see:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1812545
/// An animated RGBA color.
///
/// Unlike in computed values, each component value may exceed the

View file

@ -5,6 +5,7 @@
//! Specified color values.
use super::AllowQuirks;
use crate::color::ColorComponents;
use crate::media_queries::Device;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
@ -162,10 +163,6 @@ bitflags! {
}
}
/// The 3 components that make up a color. (Does not include the alpha component)
#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
struct ColorComponents(f32, f32, f32);
/// An absolutely specified color, using either rgb(), rgba(), lab(), lch(),
/// oklab(), oklch() or color().
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
@ -196,9 +193,9 @@ macro_rules! color_components_as {
impl AbsoluteColor {
/// Create a new [AbsoluteColor] with the given [ColorSpace] and components.
pub fn new(color_space: ColorSpace, c1: f32, c2: f32, c3: f32, alpha: f32) -> Self {
pub fn new(color_space: ColorSpace, components: ColorComponents, alpha: f32) -> Self {
Self {
components: ColorComponents(c1, c2, c3),
components,
alpha,
color_space,
flags: SerializationFlags::empty(),
@ -211,7 +208,11 @@ impl AbsoluteColor {
let green = rgba.green as f32 / 255.0;
let blue = rgba.blue as f32 / 255.0;
Self::new(ColorSpace::Srgb, red, green, blue, rgba.alpha)
Self::new(
ColorSpace::Srgb,
ColorComponents(red, green, blue),
rgba.alpha,
)
}
/// Return the alpha component.
@ -233,47 +234,58 @@ impl AbsoluteColor {
/// Convert this color to the specified color space.
pub fn to_color_space(&self, color_space: ColorSpace) -> Self {
use crate::values::animated::color::{AnimatedRGBA, LABA, LCHA, OKLABA, OKLCHA};
use crate::color::convert;
use ColorSpace::*;
if self.color_space == color_space {
return self.clone();
}
match (self.color_space, color_space) {
(Lab, Srgb) => {
let laba = unsafe { color_components_as!(self, LABA) };
let rgba = AnimatedRGBA::from(*laba);
Self::new(Srgb, rgba.red, rgba.green, rgba.blue, rgba.alpha)
},
let (xyz, white_point) = match self.color_space {
Lab => convert::to_xyz::<convert::Lab>(&self.components),
Lch => convert::to_xyz::<convert::Lch>(&self.components),
Oklab => convert::to_xyz::<convert::Oklab>(&self.components),
Oklch => convert::to_xyz::<convert::Oklch>(&self.components),
Srgb => convert::to_xyz::<convert::Srgb>(&self.components),
SrgbLinear => convert::to_xyz::<convert::SrgbLinear>(&self.components),
DisplayP3 => convert::to_xyz::<convert::DisplayP3>(&self.components),
A98Rgb => convert::to_xyz::<convert::A98Rgb>(&self.components),
ProphotoRgb => convert::to_xyz::<convert::ProphotoRgb>(&self.components),
Rec2020 => convert::to_xyz::<convert::Rec2020>(&self.components),
XyzD50 => convert::to_xyz::<convert::XyzD50>(&self.components),
XyzD65 => convert::to_xyz::<convert::XyzD65>(&self.components),
};
(Oklab, Srgb) => {
let oklaba: &OKLABA = unsafe { color_components_as!(self, OKLABA) };
let rgba = AnimatedRGBA::from(*oklaba);
Self::new(Srgb, rgba.red, rgba.green, rgba.blue, rgba.alpha)
},
let result = match color_space {
Lab => convert::from_xyz::<convert::Lab>(&xyz, white_point),
Lch => convert::from_xyz::<convert::Lch>(&xyz, white_point),
Oklab => convert::from_xyz::<convert::Oklab>(&xyz, white_point),
Oklch => convert::from_xyz::<convert::Oklch>(&xyz, white_point),
Srgb => convert::from_xyz::<convert::Srgb>(&xyz, white_point),
SrgbLinear => convert::from_xyz::<convert::SrgbLinear>(&xyz, white_point),
DisplayP3 => convert::from_xyz::<convert::DisplayP3>(&xyz, white_point),
A98Rgb => convert::from_xyz::<convert::A98Rgb>(&xyz, white_point),
ProphotoRgb => convert::from_xyz::<convert::ProphotoRgb>(&xyz, white_point),
Rec2020 => convert::from_xyz::<convert::Rec2020>(&xyz, white_point),
XyzD50 => convert::from_xyz::<convert::XyzD50>(&xyz, white_point),
XyzD65 => convert::from_xyz::<convert::XyzD65>(&xyz, white_point),
};
(Lch, Srgb) => {
let lcha: &LCHA = unsafe { color_components_as!(self, LCHA) };
let rgba = AnimatedRGBA::from(*lcha);
Self::new(Srgb, rgba.red, rgba.green, rgba.blue, rgba.alpha)
},
Self::new(color_space, result, self.alpha)
}
}
(Oklch, Srgb) => {
let oklcha: &OKLCHA = unsafe { color_components_as!(self, OKLCHA) };
let rgba = AnimatedRGBA::from(*oklcha);
Self::new(Srgb, rgba.red, rgba.green, rgba.blue, rgba.alpha)
},
_ => {
// Conversion to other color spaces is not implemented yet.
log::warn!(
"Can not convert from {:?} to {:?}!",
self.color_space,
color_space
);
Self::from_rgba(RGBA::new(0, 0, 0, 1.0))
},
impl From<cssparser::PredefinedColorSpace> for ColorSpace {
fn from(value: cssparser::PredefinedColorSpace) -> Self {
match value {
cssparser::PredefinedColorSpace::Srgb => ColorSpace::Srgb,
cssparser::PredefinedColorSpace::SrgbLinear => ColorSpace::SrgbLinear,
cssparser::PredefinedColorSpace::DisplayP3 => ColorSpace::DisplayP3,
cssparser::PredefinedColorSpace::A98Rgb => ColorSpace::A98Rgb,
cssparser::PredefinedColorSpace::ProphotoRgb => ColorSpace::ProphotoRgb,
cssparser::PredefinedColorSpace::Rec2020 => ColorSpace::Rec2020,
cssparser::PredefinedColorSpace::XyzD50 => ColorSpace::XyzD50,
cssparser::PredefinedColorSpace::XyzD65 => ColorSpace::XyzD65,
}
}
}
@ -283,33 +295,43 @@ impl From<cssparser::AbsoluteColor> for AbsoluteColor {
match f {
cssparser::AbsoluteColor::Rgba(rgba) => Self::from_rgba(rgba),
cssparser::AbsoluteColor::Lab(lab) => {
Self::new(ColorSpace::Lab, lab.lightness, lab.a, lab.b, lab.alpha)
},
cssparser::AbsoluteColor::Lab(lab) => Self::new(
ColorSpace::Lab,
ColorComponents(lab.lightness, lab.a, lab.b),
lab.alpha,
),
cssparser::AbsoluteColor::Lch(lch) => Self::new(
ColorSpace::Lch,
lch.lightness,
lch.chroma,
lch.hue,
ColorComponents(lch.lightness, lch.chroma, lch.hue),
lch.alpha,
),
cssparser::AbsoluteColor::Oklab(oklab) => Self::new(
ColorSpace::Oklab,
oklab.lightness,
oklab.a,
oklab.b,
ColorComponents(oklab.lightness, oklab.a, oklab.b),
oklab.alpha,
),
cssparser::AbsoluteColor::Oklch(oklch) => Self::new(
ColorSpace::Oklch,
oklch.lightness,
oklch.chroma,
oklch.hue,
ColorComponents(oklch.lightness, oklch.chroma, oklch.hue),
oklch.alpha,
),
cssparser::AbsoluteColor::ColorFunction(c) => {
let mut result = AbsoluteColor::new(
c.color_space.into(),
ColorComponents(c.c1, c.c2, c.c3),
c.alpha,
);
if matches!(c.color_space, cssparser::PredefinedColorSpace::Srgb) {
result.flags |= SerializationFlags::AS_COLOR_FUNCTION;
}
result
},
}
}
}
@ -347,16 +369,38 @@ impl ToCss for AbsoluteColor {
unsafe { color_components_as!(self, cssparser::Oklch) },
dest,
),
// Other color spaces are not implemented yet. See:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1128204
ColorSpace::Srgb |
ColorSpace::SrgbLinear |
ColorSpace::DisplayP3 |
ColorSpace::A98Rgb |
ColorSpace::ProphotoRgb |
ColorSpace::Rec2020 |
ColorSpace::XyzD50 |
ColorSpace::XyzD65 => todo!(),
_ => {
let color_space = match self.color_space {
ColorSpace::Lab | ColorSpace::Lch | ColorSpace::Oklab | ColorSpace::Oklch => {
unreachable!("Handle these in the wrapping match case!!")
},
ColorSpace::Srgb => {
debug_assert!(
self.flags.contains(SerializationFlags::AS_COLOR_FUNCTION),
"The case without this flag should be handled in the wrapping match case!!"
);
cssparser::PredefinedColorSpace::Srgb
},
ColorSpace::SrgbLinear => cssparser::PredefinedColorSpace::SrgbLinear,
ColorSpace::DisplayP3 => cssparser::PredefinedColorSpace::DisplayP3,
ColorSpace::A98Rgb => cssparser::PredefinedColorSpace::A98Rgb,
ColorSpace::ProphotoRgb => cssparser::PredefinedColorSpace::ProphotoRgb,
ColorSpace::Rec2020 => cssparser::PredefinedColorSpace::Rec2020,
ColorSpace::XyzD50 => cssparser::PredefinedColorSpace::XyzD50,
ColorSpace::XyzD65 => cssparser::PredefinedColorSpace::XyzD65,
};
let color_function = cssparser::ColorFunction {
color_space,
c1: self.components.0,
c2: self.components.1,
c3: self.components.2,
alpha: self.alpha,
};
let color = cssparser::AbsoluteColor::ColorFunction(color_function);
cssparser::ToCss::to_css(&color, dest)
},
}
}
}