mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
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:
parent
896aac5e4a
commit
922914aa38
7 changed files with 781 additions and 65 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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" }
|
||||
|
|
649
components/style/color/convert.rs
Normal file
649
components/style/color/convert.rs
Normal 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()
|
||||
}
|
||||
}
|
19
components/style/color/mod.rs
Normal file
19
components/style/color/mod.rs
Normal 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))
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue