mirror of
https://github.com/servo/servo.git
synced 2025-08-05 05:30:08 +01:00
style: Part 2 - Use new color space for color mixing
Mixing is now using the new color space for specifying the interpolation color space. For that reason hsl and hwb is added to the color space and also converting to/from them. Differential Revision: https://phabricator.services.mozilla.com/D169929
This commit is contained in:
parent
e963abe2e9
commit
954c38cccb
3 changed files with 220 additions and 65 deletions
|
@ -21,6 +21,123 @@ type Vector = euclid::default::Vector3D<f32>;
|
|||
const RAD_PER_DEG: f32 = PI / 180.0;
|
||||
const DEG_PER_RAD: f32 = 180.0 / PI;
|
||||
|
||||
/// Normalize hue into [0, 360).
|
||||
#[inline]
|
||||
fn normalize_hue(hue: f32) -> f32 {
|
||||
hue - 360. * (hue / 360.).floor()
|
||||
}
|
||||
|
||||
/// Calculate the hue from RGB components and return it along with the min and
|
||||
/// max RGB values.
|
||||
#[inline]
|
||||
fn rgb_to_hue_min_max(red: f32, green: f32, blue: f32) -> (f32, f32, f32) {
|
||||
let max = red.max(green).max(blue);
|
||||
let min = red.min(green).min(blue);
|
||||
|
||||
let delta = max - min;
|
||||
|
||||
let hue = if delta != 0.0 {
|
||||
60.0 * if max == red {
|
||||
(green - blue) / delta + if green < blue { 6.0 } else { 0.0 }
|
||||
} else if max == green {
|
||||
(blue - red) / delta + 2.0
|
||||
} else {
|
||||
(red - green) / delta + 4.0
|
||||
}
|
||||
} else {
|
||||
std::f32::NAN
|
||||
};
|
||||
|
||||
(hue, min, max)
|
||||
}
|
||||
|
||||
/// Convert a hue value into red, green, blue components.
|
||||
#[inline]
|
||||
fn hue_to_rgb(t1: f32, t2: f32, hue: f32) -> f32 {
|
||||
let hue = normalize_hue(hue);
|
||||
|
||||
if hue * 6.0 < 360.0 {
|
||||
t1 + (t2 - t1) * hue / 60.0
|
||||
} else if hue * 2.0 < 360.0 {
|
||||
t2
|
||||
} else if hue * 3.0 < 720.0 {
|
||||
t1 + (t2 - t1) * (240.0 - hue) / 60.0
|
||||
} else {
|
||||
t1
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert from HSL notation to RGB notation.
|
||||
#[inline]
|
||||
pub fn hsl_to_rgb(from: &ColorComponents) -> ColorComponents {
|
||||
let ColorComponents(hue, saturation, lightness) = *from;
|
||||
|
||||
let t2 = if lightness <= 0.5 {
|
||||
lightness * (saturation + 1.0)
|
||||
} else {
|
||||
lightness + saturation - lightness * saturation
|
||||
};
|
||||
let t1 = lightness * 2.0 - t2;
|
||||
|
||||
ColorComponents(
|
||||
hue_to_rgb(t1, t2, hue + 120.0),
|
||||
hue_to_rgb(t1, t2, hue),
|
||||
hue_to_rgb(t1, t2, hue - 120.0),
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert from RGB notation to HSL notation.
|
||||
/// https://drafts.csswg.org/css-color/#rgb-to-hsl
|
||||
pub fn rgb_to_hsl(from: &ColorComponents) -> ColorComponents {
|
||||
let ColorComponents(red, green, blue) = *from;
|
||||
|
||||
let (hue, min, max) = rgb_to_hue_min_max(red, green, blue);
|
||||
|
||||
let light = (min + max) / 2.0;
|
||||
let delta = max - min;
|
||||
|
||||
let sat = if delta != 0.0 {
|
||||
if light == 0.0 || light == 1.0 {
|
||||
0.0
|
||||
} else {
|
||||
(max - light) / light.min(1.0 - light)
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
ColorComponents(hue, sat, light)
|
||||
}
|
||||
|
||||
/// Convert from HWB notation to RGB notation.
|
||||
/// https://drafts.csswg.org/css-color-4/#hwb-to-rgb
|
||||
#[inline]
|
||||
pub fn hwb_to_rgb(from: &ColorComponents) -> ColorComponents {
|
||||
let ColorComponents(hue, whiteness, blackness) = *from;
|
||||
|
||||
if whiteness + blackness > 1.0 {
|
||||
let gray = whiteness / (whiteness + blackness);
|
||||
return ColorComponents(gray, gray, gray);
|
||||
}
|
||||
|
||||
let x = 1.0 - whiteness - blackness;
|
||||
hsl_to_rgb(&ColorComponents(hue, 1.0, 0.5)).map(|v| v * x + whiteness)
|
||||
}
|
||||
|
||||
/// Convert from RGB notation to HWB notation.
|
||||
/// https://drafts.csswg.org/css-color-4/#rgb-to-hwb
|
||||
#[inline]
|
||||
pub fn rgb_to_hwb(from: &ColorComponents) -> ColorComponents {
|
||||
let ColorComponents(red, green, blue) = *from;
|
||||
|
||||
let (hue, min, max) = rgb_to_hue_min_max(red, green, blue);
|
||||
|
||||
let whiteness = min;
|
||||
let blackness = 1.0 - max;
|
||||
|
||||
ColorComponents(hue, whiteness, blackness)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn transform(from: &ColorComponents, mat: &Transform) -> ColorComponents {
|
||||
let result = mat.transform_vector3d(Vector::new(from.0, from.1, from.2));
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
//! Color mixing/interpolation.
|
||||
|
||||
use super::ColorSpace;
|
||||
use crate::parser::{Parse, ParserContext};
|
||||
use crate::values::animated::color::AnimatedRGBA as RGBA;
|
||||
use cssparser::Parser;
|
||||
|
@ -15,55 +16,6 @@ use style_traits::{CssWriter, ParseError, ToCss};
|
|||
const RAD_PER_DEG: f32 = PI / 180.0;
|
||||
const DEG_PER_RAD: f32 = 180.0 / PI;
|
||||
|
||||
/// A color space as defined in [1].
|
||||
///
|
||||
/// [1]: https://drafts.csswg.org/css-color-4/#typedef-color-space
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Eq,
|
||||
MallocSizeOf,
|
||||
Parse,
|
||||
PartialEq,
|
||||
ToAnimatedValue,
|
||||
ToComputedValue,
|
||||
ToCss,
|
||||
ToResolvedValue,
|
||||
ToShmem,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum InterpolationColorSpace {
|
||||
/// The sRGB color space.
|
||||
Srgb,
|
||||
/// The linear-sRGB color space.
|
||||
LinearSrgb,
|
||||
/// The CIEXYZ color space.
|
||||
#[parse(aliases = "xyz-d65")]
|
||||
Xyz,
|
||||
/// https://drafts.csswg.org/css-color-4/#valdef-color-xyz
|
||||
XyzD50,
|
||||
/// The CIELAB color space.
|
||||
Lab,
|
||||
/// https://drafts.csswg.org/css-color-4/#valdef-hsl-hsl
|
||||
Hsl,
|
||||
/// https://drafts.csswg.org/css-color-4/#valdef-hwb-hwb
|
||||
Hwb,
|
||||
/// The CIELAB color space, expressed in cylindrical coordinates.
|
||||
Lch,
|
||||
// TODO: Oklab, Lch
|
||||
}
|
||||
|
||||
impl InterpolationColorSpace {
|
||||
/// Returns whether this is a `<polar-color-space>`.
|
||||
pub fn is_polar(self) -> bool {
|
||||
match self {
|
||||
Self::Srgb | Self::LinearSrgb | Self::Xyz | Self::XyzD50 | Self::Lab => false,
|
||||
Self::Hsl | Self::Hwb | Self::Lch => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A hue-interpolation-method as defined in [1].
|
||||
///
|
||||
/// [1]: https://drafts.csswg.org/css-color-4/#typedef-hue-interpolation-method
|
||||
|
@ -111,7 +63,7 @@ pub enum HueInterpolationMethod {
|
|||
#[repr(C)]
|
||||
pub struct ColorInterpolationMethod {
|
||||
/// The color-space the interpolation should be done in.
|
||||
pub space: InterpolationColorSpace,
|
||||
pub space: ColorSpace,
|
||||
/// The hue interpolation method.
|
||||
pub hue: HueInterpolationMethod,
|
||||
}
|
||||
|
@ -120,7 +72,7 @@ impl ColorInterpolationMethod {
|
|||
/// Returns the srgb interpolation method.
|
||||
pub fn srgb() -> Self {
|
||||
Self {
|
||||
space: InterpolationColorSpace::Srgb,
|
||||
space: ColorSpace::Srgb,
|
||||
hue: HueInterpolationMethod::Shorter,
|
||||
}
|
||||
}
|
||||
|
@ -132,7 +84,7 @@ impl Parse for ColorInterpolationMethod {
|
|||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
input.expect_ident_matching("in")?;
|
||||
let space = InterpolationColorSpace::parse(input)?;
|
||||
let space = ColorSpace::parse(input)?;
|
||||
// https://drafts.csswg.org/css-color-4/#hue-interpolation
|
||||
// Unless otherwise specified, if no specific hue interpolation
|
||||
// algorithm is selected by the host syntax, the default is shorter.
|
||||
|
@ -209,14 +161,23 @@ pub fn mix(
|
|||
}
|
||||
|
||||
let mix_function = match interpolation.space {
|
||||
InterpolationColorSpace::Srgb => mix_in::<RGBA>,
|
||||
InterpolationColorSpace::LinearSrgb => mix_in::<LinearRGBA>,
|
||||
InterpolationColorSpace::Xyz => mix_in::<XYZD65A>,
|
||||
InterpolationColorSpace::XyzD50 => mix_in::<XYZD50A>,
|
||||
InterpolationColorSpace::Lab => mix_in::<LABA>,
|
||||
InterpolationColorSpace::Hwb => mix_in::<HWBA>,
|
||||
InterpolationColorSpace::Hsl => mix_in::<HSLA>,
|
||||
InterpolationColorSpace::Lch => mix_in::<LCHA>,
|
||||
ColorSpace::Srgb => mix_in::<RGBA>,
|
||||
ColorSpace::SrgbLinear => mix_in::<LinearRGBA>,
|
||||
ColorSpace::XyzD65 => mix_in::<XYZD65A>,
|
||||
ColorSpace::XyzD50 => mix_in::<XYZD50A>,
|
||||
ColorSpace::Lab => mix_in::<LABA>,
|
||||
ColorSpace::Hwb => mix_in::<HWBA>,
|
||||
ColorSpace::Hsl => mix_in::<HSLA>,
|
||||
ColorSpace::Lch => mix_in::<LCHA>,
|
||||
|
||||
ColorSpace::Oklab |
|
||||
ColorSpace::Oklch |
|
||||
ColorSpace::DisplayP3 |
|
||||
ColorSpace::A98Rgb |
|
||||
ColorSpace::ProphotoRgb |
|
||||
ColorSpace::Rec2020 => {
|
||||
todo!()
|
||||
},
|
||||
};
|
||||
mix_function(
|
||||
left_color,
|
||||
|
|
|
@ -24,10 +24,36 @@ impl ColorComponents {
|
|||
|
||||
/// A color space representation in the CSS specification.
|
||||
///
|
||||
/// https://w3c.github.io/csswg-drafts/css-color-4/#color-type
|
||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||
/// https://drafts.csswg.org/css-color-4/#typedef-color-space
|
||||
///
|
||||
/// NOTE: Right now HSL and HWB colors can not be constructed by the user. They
|
||||
/// are converted to RGB in the parser. The parser should return the
|
||||
/// HSL/HWB values as is to avoid unnescessary conversions to/from RGB.
|
||||
/// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1817035
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Eq,
|
||||
MallocSizeOf,
|
||||
Parse,
|
||||
PartialEq,
|
||||
ToAnimatedValue,
|
||||
ToComputedValue,
|
||||
ToCss,
|
||||
ToResolvedValue,
|
||||
ToShmem,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum ColorSpace {
|
||||
/// A color specified in the Hsl notation in the sRGB color space, e.g.
|
||||
/// "hsl(289.18 93.136% 65.531%)"
|
||||
/// https://drafts.csswg.org/css-color-4/#the-hsl-notation
|
||||
Hsl,
|
||||
/// A color specified in the Hwb notation in the sRGB color space, e.g.
|
||||
/// "hwb(740deg 20% 30%)"
|
||||
/// https://drafts.csswg.org/css-color-4/#the-hwb-notation
|
||||
Hwb,
|
||||
/// A color specified in the Lab color format, e.g.
|
||||
/// "lab(29.2345% 39.3825 20.0664)".
|
||||
/// https://w3c.github.io/csswg-drafts/css-color-4/#lab-colors
|
||||
|
@ -67,9 +93,24 @@ pub enum ColorSpace {
|
|||
XyzD50,
|
||||
/// A color specified with the color(..) function and the "xyz-d65" or "xyz"
|
||||
/// color space, e.g. "color(xyz-d65 0.21661 0.14602 0.59452)".
|
||||
#[parse(aliases = "xyz")]
|
||||
XyzD65,
|
||||
}
|
||||
|
||||
impl ColorSpace {
|
||||
/// Returns whether this is a `<rectangular-color-space>`.
|
||||
#[inline]
|
||||
pub fn is_rectangular(&self) -> bool {
|
||||
!self.is_polar()
|
||||
}
|
||||
|
||||
/// Returns whether this is a `<polar-color-space>`.
|
||||
#[inline]
|
||||
pub fn is_polar(&self) -> bool {
|
||||
matches!(self, Self::Hsl | Self::Hwb | Self::Lch | Self::Oklch)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Flags used when serializing colors.
|
||||
#[derive(Default, MallocSizeOf, ToShmem)]
|
||||
|
@ -96,6 +137,14 @@ pub struct AbsoluteColor {
|
|||
pub flags: SerializationFlags,
|
||||
}
|
||||
|
||||
/// Given an [`AbsoluteColor`], return the 4 float components as the type given,
|
||||
/// e.g.:
|
||||
///
|
||||
/// ```rust
|
||||
/// let srgb = AbsoluteColor::new(ColorSpace::Srgb, 1.0, 0.0, 0.0, 0.0);
|
||||
/// let floats = color_components_as!(&srgb, [f32; 4]); // [1.0, 0.0, 0.0, 0.0]
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! color_components_as {
|
||||
($c:expr, $t:ty) => {{
|
||||
// This macro is not an inline function, because we can't use the
|
||||
|
@ -139,6 +188,14 @@ impl AbsoluteColor {
|
|||
}
|
||||
|
||||
let (xyz, white_point) = match self.color_space {
|
||||
Hsl => {
|
||||
let rgb = convert::hsl_to_rgb(&self.components);
|
||||
convert::to_xyz::<convert::Srgb>(&rgb)
|
||||
},
|
||||
Hwb => {
|
||||
let rgb = convert::hwb_to_rgb(&self.components);
|
||||
convert::to_xyz::<convert::Srgb>(&rgb)
|
||||
},
|
||||
Lab => convert::to_xyz::<convert::Lab>(&self.components),
|
||||
Lch => convert::to_xyz::<convert::Lch>(&self.components),
|
||||
Oklab => convert::to_xyz::<convert::Oklab>(&self.components),
|
||||
|
@ -154,6 +211,14 @@ impl AbsoluteColor {
|
|||
};
|
||||
|
||||
let result = match color_space {
|
||||
Hsl => {
|
||||
let rgb = convert::from_xyz::<convert::Srgb>(&xyz, white_point);
|
||||
convert::rgb_to_hsl(&rgb)
|
||||
},
|
||||
Hwb => {
|
||||
let rgb = convert::from_xyz::<convert::Srgb>(&xyz, white_point);
|
||||
convert::rgb_to_hwb(&rgb)
|
||||
},
|
||||
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),
|
||||
|
@ -239,6 +304,17 @@ impl ToCss for AbsoluteColor {
|
|||
W: Write,
|
||||
{
|
||||
match self.color_space {
|
||||
ColorSpace::Hsl => {
|
||||
let rgb = convert::hsl_to_rgb(&self.components);
|
||||
Self::new(ColorSpace::Srgb, rgb, self.alpha).to_css(dest)
|
||||
},
|
||||
|
||||
ColorSpace::Hwb => {
|
||||
let rgb = convert::hwb_to_rgb(&self.components);
|
||||
|
||||
Self::new(ColorSpace::Srgb, rgb, self.alpha).to_css(dest)
|
||||
},
|
||||
|
||||
ColorSpace::Srgb if !self.flags.contains(SerializationFlags::AS_COLOR_FUNCTION) => {
|
||||
cssparser::ToCss::to_css(
|
||||
&cssparser::RGBA::from_floats(
|
||||
|
@ -268,9 +344,6 @@ impl ToCss for AbsoluteColor {
|
|||
),
|
||||
_ => {
|
||||
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),
|
||||
|
@ -286,6 +359,10 @@ impl ToCss for AbsoluteColor {
|
|||
ColorSpace::Rec2020 => cssparser::PredefinedColorSpace::Rec2020,
|
||||
ColorSpace::XyzD50 => cssparser::PredefinedColorSpace::XyzD50,
|
||||
ColorSpace::XyzD65 => cssparser::PredefinedColorSpace::XyzD65,
|
||||
|
||||
_ => {
|
||||
unreachable!("other color spaces do not support color() syntax")
|
||||
},
|
||||
};
|
||||
|
||||
let color_function = cssparser::ColorFunction {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue