style: Support color-mix() in non-sRGB color spaces

Out of gamut colours are currently clipped into sRGB.

Differential Revision: https://phabricator.services.mozilla.com/D120561
This commit is contained in:
Barret Rennie 2023-05-22 14:23:02 +02:00 committed by Oriol Brufau
parent d564f200aa
commit c241182d09
2 changed files with 716 additions and 36 deletions

View file

@ -7,6 +7,8 @@
use crate::values::animated::{Animate, Procedure, ToAnimatedZero};
use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
use crate::values::generics::color::{Color as GenericColor, ComplexColorRatios};
use crate::values::specified::color::{ColorSpaceKind, HueAdjuster};
use euclid::default::{Transform3D, Vector3D};
/// An animated RGBA color.
///
@ -41,6 +43,26 @@ impl RGBA {
alpha,
}
}
/// Returns whether or not the colour is in gamut for sRGB.
pub fn in_gamut(&self) -> bool {
0. <= self.red &&
self.red <= 1. &&
0. <= self.green &&
self.green <= 1. &&
0. <= self.blue &&
self.blue <= 1.
}
/// Returns the colour with coordinates clamped to the sRGB range.
pub fn clamp(&self) -> Self {
Self {
red: self.red.max(0.).min(1.),
green: self.green.max(0.).min(1.),
blue: self.blue.max(0.).min(1.),
alpha: self.alpha,
}
}
}
impl Animate for RGBA {
@ -106,43 +128,69 @@ impl Color {
/// Mix two colors into one.
pub fn mix(
color_space: ColorSpaceKind,
left_color: &Color,
left_weight: f32,
right_color: &Color,
right_weight: f32,
hue_adjuster: HueAdjuster,
) -> Self {
let left_bg = left_color.scaled_rgba();
let right_bg = right_color.scaled_rgba();
let alpha = (left_bg.alpha * left_weight +
right_bg.alpha * right_weight)
.min(1.);
let mut fg = 0.;
let mut red = 0.;
let mut green = 0.;
let mut blue = 0.;
let colors = [
(left_color, &left_bg, left_weight),
(right_color, &right_bg, right_weight),
];
for &(color, bg, weight) in &colors {
fg += color.ratios.fg * weight;
red += bg.red * bg.alpha * weight;
green += bg.green * bg.alpha * weight;
blue += bg.blue * bg.alpha * weight;
match color_space {
ColorSpaceKind::Srgb => Self::mix_in::<RGBA>(
left_color,
left_weight,
right_color,
right_weight,
hue_adjuster,
),
ColorSpaceKind::Xyz => Self::mix_in::<XYZA>(
left_color,
left_weight,
right_color,
right_weight,
hue_adjuster,
),
ColorSpaceKind::Lab => Self::mix_in::<LABA>(
left_color,
left_weight,
right_color,
right_weight,
hue_adjuster,
),
ColorSpaceKind::Lch => Self::mix_in::<LCHA>(
left_color,
left_weight,
right_color,
right_weight,
hue_adjuster,
),
}
}
let color = if alpha <= 0. {
RGBA::transparent()
fn mix_in<S>(
left_color: &Color,
left_weight: f32,
right_color: &Color,
right_weight: f32,
hue_adjuster: HueAdjuster,
) -> Self
where
S: ModelledColor,
{
let left_bg = S::from(left_color.scaled_rgba());
let right_bg = S::from(right_color.scaled_rgba());
let color = S::lerp(left_bg, left_weight, right_bg, right_weight, hue_adjuster);
let rgba: RGBA = color.into();
let rgba = if !rgba.in_gamut() {
// TODO: Better gamut mapping.
rgba.clamp()
} else {
let inv = 1. / alpha;
RGBA::new(red * inv, green * inv, blue * inv, alpha)
rgba
};
Self::new(color, ComplexColorRatios { bg: 1., fg })
let fg = left_color.ratios.fg * left_weight + right_color.ratios.fg * right_weight;
Self::new(rgba, ComplexColorRatios { bg: 1., fg })
}
fn scaled_rgba(&self) -> RGBA {
@ -309,3 +357,573 @@ impl ToAnimatedZero for Color {
Ok(RGBA::transparent().into())
}
}
/// A color modelled in a specific color space (such as sRGB or CIE XYZ).
///
/// For now, colors modelled in other spaces need to be convertible to and from
/// `RGBA` because we use sRGB for displaying colors.
trait ModelledColor: Clone + Copy + From<RGBA> + Into<RGBA> {
/// Linearly interpolate between the left and right colors.
///
/// The HueAdjuster parameter is only for color spaces where the hue is
/// represented as an angle (e.g., CIE LCH).
fn lerp(
left_bg: Self,
left_weight: f32,
right_bg: Self,
right_weight: f32,
hue_adjuster: HueAdjuster,
) -> Self;
}
impl ModelledColor for RGBA {
fn lerp(
left_bg: Self,
left_weight: f32,
right_bg: Self,
right_weight: f32,
_: HueAdjuster,
) -> Self {
// Interpolation with alpha, as per
// https://drafts.csswg.org/css-color/#interpolation-alpha.
let mut red = 0.;
let mut green = 0.;
let mut blue = 0.;
// sRGB is a rectangular othogonal color space, so all component values
// are multiplied by the alpha value.
for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] {
red += bg.red * bg.alpha * weight;
green += bg.green * bg.alpha * weight;
blue += bg.blue * bg.alpha * weight;
}
let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.);
if alpha <= 0. {
RGBA::transparent()
} else {
let inv = 1. / alpha;
RGBA::new(red * inv, green * inv, blue * inv, alpha)
}
}
}
/// An animated XYZA colour.
#[derive(Clone, Copy, Debug)]
pub struct XYZA {
/// The x component.
pub x: f32,
/// The y component.
pub y: f32,
/// The z component.
pub z: f32,
/// The alpha component.
pub alpha: f32,
}
impl XYZA {
/// Returns a transparent color.
#[inline]
pub fn transparent() -> Self {
Self {
x: 0.,
y: 0.,
z: 0.,
alpha: 0.,
}
}
}
impl ModelledColor for XYZA {
fn lerp(
left_bg: Self,
left_weight: f32,
right_bg: Self,
right_weight: f32,
_: HueAdjuster,
) -> Self {
// Interpolation with alpha, as per
// https://drafts.csswg.org/css-color/#interpolation-alpha.
let mut x = 0.;
let mut y = 0.;
let mut z = 0.;
// CIE XYZ is a rectangular othogonal color space, so all component
// values are multiplied by the alpha value.
for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] {
x += bg.x * bg.alpha * weight;
y += bg.y * bg.alpha * weight;
z += bg.z * bg.alpha * weight;
}
let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.);
if alpha <= 0. {
Self::transparent()
} else {
let inv = 1. / alpha;
Self {
x: x * inv,
y: y * inv,
z: z * inv,
alpha,
}
}
}
}
/// An animated LABA colour.
#[derive(Clone, Copy, Debug)]
pub struct LABA {
/// The lightness component.
pub lightness: f32,
/// The a component.
pub a: f32,
/// The b component.
pub b: f32,
/// The alpha component.
pub alpha: f32,
}
impl LABA {
/// Returns a transparent color.
#[inline]
pub fn transparent() -> Self {
Self {
lightness: 0.,
a: 0.,
b: 0.,
alpha: 0.,
}
}
}
impl ModelledColor for LABA {
fn lerp(
left_bg: Self,
left_weight: f32,
right_bg: Self,
right_weight: f32,
_: HueAdjuster,
) -> Self {
// Interpolation with alpha, as per
// https://drafts.csswg.org/css-color/#interpolation-alpha.
let mut lightness = 0.;
let mut a = 0.;
let mut b = 0.;
// CIE LAB is a rectangular othogonal color space, so all component
// values are multiplied by the alpha value.
for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] {
lightness += bg.lightness * bg.alpha * weight;
a += bg.a * bg.alpha * weight;
b += bg.b * bg.alpha * weight;
}
let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.);
if alpha <= 0. {
Self::transparent()
} else {
let inv = 1. / alpha;
Self {
lightness: lightness * inv,
a: a * inv,
b: b * inv,
alpha,
}
}
}
}
/// An animated LCHA colour.
#[derive(Clone, Copy, Debug)]
pub struct LCHA {
/// The lightness component.
pub lightness: f32,
/// The chroma component.
pub chroma: f32,
/// The hua component.
pub hue: f32,
/// The alpha component.
pub alpha: f32,
}
impl LCHA {
/// Returns a transparent color.
#[inline]
pub fn transparent() -> Self {
Self {
lightness: 0.,
chroma: 0.,
hue: 0.,
alpha: 0.,
}
}
}
impl LCHA {
fn adjust(left_bg: Self, right_bg: Self, hue_adjuster: HueAdjuster) -> (Self, Self) {
use std::f32::consts::{PI, TAU};
let mut left_bg = left_bg;
let mut right_bg = right_bg;
// Adjust the hue angle as per
// https://drafts.csswg.org/css-color/#hue-interpolation.
//
// If both hue angles are NAN, they should be set to 0. Otherwise, if a
// single hue angle is NAN, it should use the other hue angle.
if left_bg.hue.is_nan() || right_bg.hue.is_nan() {
if left_bg.hue.is_nan() && right_bg.hue.is_nan() {
left_bg.hue = 0.;
right_bg.hue = 0.;
} else if left_bg.hue.is_nan() {
left_bg.hue = right_bg.hue;
} else if right_bg.hue.is_nan() {
right_bg.hue = left_bg.hue;
}
}
if hue_adjuster != HueAdjuster::Specified {
// Normalize hue into [0, 2 * PI)
while left_bg.hue < 0. {
left_bg.hue += TAU;
}
while left_bg.hue > TAU {
left_bg.hue -= TAU;
}
while right_bg.hue < 0. {
right_bg.hue += TAU;
}
while right_bg.hue >= TAU {
right_bg.hue -= TAU;
}
}
match hue_adjuster {
HueAdjuster::Shorter => {
let delta = right_bg.hue - left_bg.hue;
if delta > PI {
left_bg.hue += PI;
} else if delta < -1. * PI {
right_bg.hue += PI;
}
},
HueAdjuster::Longer => {
let delta = right_bg.hue - left_bg.hue;
if 0. < delta && delta < PI {
left_bg.hue += TAU;
} else if -1. * PI < delta && delta < 0. {
right_bg.hue += TAU;
}
},
HueAdjuster::Increasing => {
if right_bg.hue < left_bg.hue {
right_bg.hue += TAU;
}
},
HueAdjuster::Decreasing => {
if left_bg.hue < right_bg.hue {
left_bg.hue += TAU;
}
},
//Angles are not adjusted. They are interpolated like any other
//component.
HueAdjuster::Specified => {},
}
(left_bg, right_bg)
}
}
impl ModelledColor for LCHA {
fn lerp(
left_bg: Self,
left_weight: f32,
right_bg: Self,
right_weight: f32,
hue_adjuster: HueAdjuster,
) -> Self {
// Interpolation with alpha, as per
// https://drafts.csswg.org/css-color/#interpolation-alpha.
let (left_bg, right_bg) = Self::adjust(left_bg, right_bg, hue_adjuster);
let mut lightness = 0.;
let mut chroma = 0.;
let mut hue = 0.;
// CIE LCH is a cylindical polar color space, so all component values
// are multiplied by the alpha value.
for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] {
lightness += bg.lightness * bg.alpha * weight;
chroma += bg.chroma * bg.alpha * weight;
// LCHA is a cylindrical color space so the hue coordinate is not
// pre-multipled by the alpha component when interpolating.
hue += bg.hue * weight;
}
let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.);
if alpha <= 0. {
Self::transparent()
} else {
let inv = 1. / alpha;
Self {
lightness: lightness * inv,
chroma: chroma * inv,
hue,
alpha,
}
}
}
}
impl From<RGBA> for XYZA {
/// Convert an RGBA colour to XYZ as specified in [1].
///
/// [1]: https://drafts.csswg.org/css-color/#rgb-to-lab
fn from(rgba: RGBA) -> Self {
fn linearize(value: f32) -> f32 {
let sign = if value < 0. { -1. } else { 1. };
let abs = value.abs();
if abs < 0.04045 {
return value / 12.92;
}
sign * ((abs + 0.055) / 1.055).powf(2.4)
}
#[cfg_attr(rustfmt, rustfmt_skip)]
const SRGB_TO_XYZ: Transform3D<f32> = Transform3D::new(
0.41239079926595934, 0.21263900587151027, 0.01933081871559182, 0.,
0.357584339383878, 0.715168678767756, 0.11919477979462598, 0.,
0.1804807884018343, 0.07219231536073371, 0.9505321522496607, 0.,
0., 0., 0., 1.,
);
#[cfg_attr(rustfmt, rustfmt_skip)]
const BRADFORD: Transform3D<f32> = Transform3D::new(
1.0479298208405488, 0.029627815688159344, -0.009243058152591178, 0.,
0.022946793341019088, 0.990434484573249, 0.015055144896577895, 0.,
-0.05019222954313557, -0.01707382502938514, 0.7518742899580008, 0.,
0., 0., 0., 1.,
);
// 1. Convert from sRGB to linear-light sRGB (undo gamma encoding).
let rgb = Vector3D::new(
linearize(rgba.red),
linearize(rgba.green),
linearize(rgba.blue),
);
// 2. Convert from linear sRGB to CIE XYZ.
// 3. Convert from a D65 whitepoint (used by sRGB) to the D50 whitepoint used in XYZ
// with the Bradford transform.
let xyz = SRGB_TO_XYZ.then(&BRADFORD).transform_vector3d(rgb);
XYZA {
x: xyz.x,
y: xyz.y,
z: xyz.z,
alpha: rgba.alpha,
}
}
}
impl From<XYZA> for LABA {
/// 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(xyza: XYZA) -> Self {
const WHITE: [f32; 3] = [0.96422, 1., 0.82521];
fn compute_f(value: f32) -> f32 {
const EPSILON: f32 = 216. / 24389.;
const KAPPA: f32 = 24389. / 27.;
if value > EPSILON {
value.cbrt()
} else {
(KAPPA * value + 16.) / 116.
}
}
// 4. Convert D50-adapted XYZ to Lab.
let f = [
compute_f(xyza.x / WHITE[0]),
compute_f(xyza.y / WHITE[1]),
compute_f(xyza.z / WHITE[2]),
];
let lightness = 116. * f[1] - 16.;
let a = 500. * (f[0] - f[1]);
let b = 200. * (f[1] - f[2]);
LABA {
lightness,
a,
b,
alpha: xyza.alpha,
}
}
}
impl From<LABA> for LCHA {
/// Convert a LAB color to LCH as specified in [1].
///
/// [1]: https://drafts.csswg.org/css-color/#color-conversion-code
fn from(laba: LABA) -> Self {
let hue = laba.b.atan2(laba.a);
let chroma = (laba.a * laba.a + laba.b * laba.b).sqrt();
LCHA {
lightness: laba.lightness,
chroma,
hue,
alpha: laba.alpha,
}
}
}
impl From<LCHA> for LABA {
/// Convert a LCH color to LAB as specified in [1].
///
/// [1]: https://drafts.csswg.org/css-color/#color-conversion-code
fn from(lcha: LCHA) -> Self {
let a = lcha.chroma * lcha.hue.cos();
let b = lcha.chroma * lcha.hue.sin();
LABA {
lightness: lcha.lightness,
a,
b,
alpha: lcha.alpha,
}
}
}
impl From<LABA> for XYZA {
/// 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 from(laba: LABA) -> Self {
// 1. Convert LAB to (D50-adapated) XYZ.
const KAPPA: f32 = 24389. / 27.;
const EPSILON: f32 = 216. / 24389.;
const WHITE: [f32; 3] = [0.96422, 1., 0.82521];
let f1 = (laba.lightness + 16f32) / 116f32;
let f0 = (laba.a / 500.) + f1;
let f2 = f1 - laba.b / 200.;
let x = if f0.powf(3.) > EPSILON {
f0.powf(3.)
} else {
(116. * f0 - 16.) / KAPPA
};
let y = if laba.lightness > KAPPA * EPSILON {
((laba.lightness + 16.) / 116.).powf(3.)
} else {
laba.lightness / KAPPA
};
let z = if f2.powf(3.) > EPSILON {
f2.powf(3.)
} else {
(116. * f2 - 16.) / KAPPA
};
XYZA {
x: x * WHITE[0],
y: y * WHITE[1],
z: z * WHITE[2],
alpha: laba.alpha,
}
}
}
impl From<XYZA> for RGBA {
/// Convert an XYZ color to sRGB as specified in [1] and [2].
///
/// [1]: https://www.w3.org/TR/css-color-4/#lab-to-predefined
/// [2]: https://www.w3.org/TR/css-color-4/#color-conversion-code
fn from(xyza: XYZA) -> Self {
#[cfg_attr(rustfmt, rustfmt_skip)]
const BRADFORD_INVERSE: Transform3D<f32> = Transform3D::new(
0.9554734527042182, -0.028369706963208136, 0.012314001688319899, 0.,
-0.023098536874261423, 1.0099954580058226, -0.020507696433477912, 0.,
0.0632593086610217, 0.021041398966943008, 1.3303659366080753, 0.,
0., 0., 0., 1.,
);
#[cfg_attr(rustfmt, rustfmt_skip)]
const XYZ_TO_SRGB: Transform3D<f32> = Transform3D::new(
3.2409699419045226, -0.9692436362808796, 0.05563007969699366, 0.,
-1.537383177570094, 1.8759675015077202, -0.20397695888897652, 0.,
-0.4986107602930034, 0.04155505740717559, 1.0569715142428786, 0.,
0., 0., 0., 1.,
);
// 2. Convert from a D50 whitepoint (used by Lab) to the D65 whitepoint
// used in sRGB, with the Bradford transform.
// 3. Convert from (D65-adapted) CIE XYZ to linear-light srgb
let xyz = Vector3D::new(xyza.x, xyza.y, xyza.z);
let linear_rgb = BRADFORD_INVERSE.then(&XYZ_TO_SRGB).transform_vector3d(xyz);
// 4. Convert from linear-light srgb to srgb (do gamma encoding).
fn delinearize(value: f32) -> f32 {
let sign = if value < 0. { -1. } else { 1. };
let abs = value.abs();
if abs > 0.0031308 {
sign * (1.055 * abs.powf(1. / 2.4) - 0.055)
} else {
12.92 * value
}
}
let red = delinearize(linear_rgb.x);
let green = delinearize(linear_rgb.y);
let blue = delinearize(linear_rgb.z);
RGBA {
red,
green,
blue,
alpha: xyza.alpha,
}
}
}
impl From<RGBA> for LABA {
fn from(rgba: RGBA) -> Self {
let xyza: XYZA = rgba.into();
xyza.into()
}
}
impl From<LABA> for RGBA {
fn from(laba: LABA) -> Self {
let xyza: XYZA = laba.into();
xyza.into()
}
}
impl From<RGBA> for LCHA {
fn from(rgba: RGBA) -> Self {
let xyza: XYZA = rgba.into();
let laba: LABA = xyza.into();
laba.into()
}
}
impl From<LCHA> for RGBA {
fn from(lcha: LCHA) -> Self {
let laba: LABA = lcha.into();
let xyza: XYZA = laba.into();
xyza.into()
}
}

View file

@ -9,7 +9,7 @@ use super::AllowQuirks;
use crate::gecko_bindings::structs::nscolor;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
use crate::values::generics::color::{GenericColorOrAuto, GenericCaretColor};
use crate::values::generics::color::{GenericCaretColor, GenericColorOrAuto};
use crate::values::specified::calc::CalcNode;
use crate::values::specified::Percentage;
use crate::values::CustomIdent;
@ -21,17 +21,51 @@ use std::io::Write as IoWrite;
use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind};
use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind};
/// A color space as defined in [1].
///
/// [1]: https://drafts.csswg.org/css-color-5/#typedef-colorspace
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
pub enum ColorSpaceKind {
/// The sRGB color space.
Srgb,
/// The CIEXYZ color space.
Xyz,
/// The CIELAB color space.
Lab,
/// The CIELAB color space, expressed in cylindrical coordinates.
Lch,
}
/// A hue adjuster as defined in [1].
///
/// [1]: https://drafts.csswg.org/css-color-5/#typedef-hue-adjuster
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
pub enum HueAdjuster {
/// The "shorter" angle adjustment.
Shorter,
/// The "longer" angle adjustment.
Longer,
/// The "increasing" angle adjustment.
Increasing,
/// The "decreasing" angle adjustment.
Decreasing,
/// The "specified" angle adjustment.
Specified,
}
/// A restricted version of the css `color-mix()` function, which only supports
/// percentages and sRGB color-space interpolation.
/// percentages.
///
/// https://drafts.csswg.org/css-color-5/#color-mix
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
#[allow(missing_docs)]
pub struct ColorMix {
pub color_space: ColorSpaceKind,
pub left: Color,
pub left_percentage: Percentage,
pub right: Color,
pub right_percentage: Percentage,
pub hue_adjuster: HueAdjuster,
}
#[cfg(feature = "gecko")]
@ -62,6 +96,9 @@ impl Parse for ColorMix {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
let color_spaces_enabled = context.chrome_rules_enabled() ||
static_prefs::pref!("layout.css.color-mix.color-spaces.enabled");
input.expect_function_matching("color-mix")?;
// NOTE(emilio): This implements the syntax described here for now,
@ -70,12 +107,18 @@ impl Parse for ColorMix {
// https://github.com/w3c/csswg-drafts/issues/6066#issuecomment-789836765
input.parse_nested_block(|input| {
input.expect_ident_matching("in")?;
// TODO: support multiple interpolation spaces.
input.expect_ident_matching("srgb")?;
let color_space = if color_spaces_enabled {
ColorSpaceKind::parse(input)?
} else {
input.expect_ident_matching("srgb")?;
ColorSpaceKind::Srgb
};
input.expect_comma()?;
let left = Color::parse(context, input)?;
let left_percentage = input.try_parse(|input| Percentage::parse(context, input)).ok();
let left_percentage = input
.try_parse(|input| Percentage::parse(context, input))
.ok();
input.expect_comma()?;
@ -88,11 +131,18 @@ impl Parse for ColorMix {
let left_percentage =
left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get()));
let hue_adjuster = input
.try_parse(|input| HueAdjuster::parse(input))
.unwrap_or(HueAdjuster::Shorter);
Ok(ColorMix {
color_space,
left,
left_percentage,
right,
right_percentage,
hue_adjuster,
})
})
}
@ -116,7 +166,9 @@ impl ToCss for ColorMix {
(1.0 - percent.get() - other.get()).abs() <= f32::EPSILON
}
dest.write_str("color-mix(in srgb, ")?;
dest.write_str("color-mix(in ")?;
self.color_space.to_css(dest)?;
dest.write_str(", ")?;
self.left.to_css(dest)?;
if !can_omit(&self.left_percentage, &self.right_percentage, true) {
dest.write_str(" ")?;
@ -128,6 +180,12 @@ impl ToCss for ColorMix {
dest.write_str(" ")?;
self.right_percentage.to_css(dest)?;
}
if self.hue_adjuster != HueAdjuster::Shorter {
dest.write_str(" ")?;
self.hue_adjuster.to_css(dest)?;
}
dest.write_str(")")
}
}
@ -433,7 +491,7 @@ impl SystemColor {
return ComputedColor::currentcolor();
}
color
}
},
})
}
}
@ -552,7 +610,9 @@ impl Parse for Color {
}
if context.chrome_rules_enabled() {
if let Ok((color, scheme)) = input.try_parse(|i| parse_moz_system_color(context, i)) {
if let Ok((color, scheme)) =
input.try_parse(|i| parse_moz_system_color(context, i))
{
return Ok(Color::System(color, scheme));
}
}
@ -603,7 +663,7 @@ impl ToCss for Color {
scheme.to_css(dest)?;
dest.write_char(')')
}
}
},
#[cfg(feature = "gecko")]
Color::InheritFromBodyQuirk => Ok(()),
}
@ -762,10 +822,12 @@ impl Color {
let left = mix.left.to_computed_color(context)?.to_animated_value();
let right = mix.right.to_computed_color(context)?.to_animated_value();
ToAnimatedValue::from_animated_value(AnimatedColor::mix(
mix.color_space,
&left,
mix.left_percentage.get(),
&right,
mix.right_percentage.get(),
mix.hue_adjuster,
))
},
#[cfg(feature = "gecko")]