mirror of
https://github.com/servo/servo.git
synced 2025-10-04 02:29:12 +01:00
style: Use ColorMix for interpolated colors in the computed style rather than ComplexColorRatios
This among other things preserves the right color-space when interpolating currentColor. Differential Revision: https://phabricator.services.mozilla.com/D147512
This commit is contained in:
parent
0ac6aaa357
commit
b6e8088e8e
10 changed files with 460 additions and 537 deletions
|
@ -4,81 +4,268 @@
|
|||
|
||||
//! Generic types for color properties.
|
||||
|
||||
/// Ratios representing the contribution of color and currentcolor to
|
||||
/// the final color value.
|
||||
///
|
||||
/// NOTE(emilio): For animated colors, the sum of these two might be more than
|
||||
/// one (because the background color would've been scaled down already). So
|
||||
/// beware that it is not generally safe to assume that if bg is 1 then fg is 0,
|
||||
/// for example.
|
||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
|
||||
#[repr(C)]
|
||||
pub struct ComplexColorRatios {
|
||||
/// Numeric color contribution.
|
||||
pub bg: f32,
|
||||
/// currentcolor contribution.
|
||||
pub fg: f32,
|
||||
}
|
||||
|
||||
impl ComplexColorRatios {
|
||||
/// Ratios representing a `Numeric` color.
|
||||
pub const NUMERIC: ComplexColorRatios = ComplexColorRatios { bg: 1., fg: 0. };
|
||||
/// Ratios representing the `CurrentColor` color.
|
||||
pub const CURRENT_COLOR: ComplexColorRatios = ComplexColorRatios { bg: 0., fg: 1. };
|
||||
}
|
||||
use std::fmt::{self, Write};
|
||||
use style_traits::{CssWriter, ParseError, ToCss};
|
||||
use crate::values::{Parse, ParserContext, Parser};
|
||||
use crate::values::specified::percentage::ToPercentage;
|
||||
use crate::values::animated::ToAnimatedValue;
|
||||
use crate::values::animated::color::RGBA as AnimatedRGBA;
|
||||
|
||||
/// This struct represents a combined color from a numeric color and
|
||||
/// the current foreground color (currentcolor keyword).
|
||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
|
||||
#[repr(C)]
|
||||
pub struct GenericColor<RGBA> {
|
||||
pub enum GenericColor<RGBA, Percentage> {
|
||||
/// The actual numeric color.
|
||||
pub color: RGBA,
|
||||
/// The ratios of mixing between numeric and currentcolor.
|
||||
/// The formula is: `color * ratios.bg + currentcolor * ratios.fg`.
|
||||
pub ratios: ComplexColorRatios,
|
||||
Numeric(RGBA),
|
||||
/// The `CurrentColor` keyword.
|
||||
CurrentColor,
|
||||
/// The color-mix() function.
|
||||
ColorMix(Box<GenericColorMix<Self, Percentage>>),
|
||||
}
|
||||
|
||||
/// 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 ColorSpace {
|
||||
/// 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 ColorSpace {
|
||||
/// 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
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToAnimatedValue, ToComputedValue, ToCss, ToResolvedValue, ToShmem)]
|
||||
#[repr(u8)]
|
||||
pub enum HueInterpolationMethod {
|
||||
/// https://drafts.csswg.org/css-color-4/#shorter
|
||||
Shorter,
|
||||
/// https://drafts.csswg.org/css-color-4/#longer
|
||||
Longer,
|
||||
/// https://drafts.csswg.org/css-color-4/#increasing
|
||||
Increasing,
|
||||
/// https://drafts.csswg.org/css-color-4/#decreasing
|
||||
Decreasing,
|
||||
/// https://drafts.csswg.org/css-color-4/#specified
|
||||
Specified,
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/css-color-4/#color-interpolation-method
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem, ToAnimatedValue, ToComputedValue, ToResolvedValue)]
|
||||
#[repr(C)]
|
||||
pub struct ColorInterpolationMethod {
|
||||
/// The color-space the interpolation should be done in.
|
||||
pub space: ColorSpace,
|
||||
/// The hue interpolation method.
|
||||
pub hue: HueInterpolationMethod,
|
||||
}
|
||||
|
||||
impl ColorInterpolationMethod {
|
||||
/// Returns the srgb interpolation method.
|
||||
pub fn srgb() -> Self {
|
||||
Self {
|
||||
space: ColorSpace::Srgb,
|
||||
hue: HueInterpolationMethod::Shorter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for ColorInterpolationMethod {
|
||||
fn parse<'i, 't>(
|
||||
_: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
input.expect_ident_matching("in")?;
|
||||
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.
|
||||
let hue = if space.is_polar() {
|
||||
input.try_parse(|input| -> Result<_, ParseError<'i>> {
|
||||
let hue = HueInterpolationMethod::parse(input)?;
|
||||
input.expect_ident_matching("hue")?;
|
||||
Ok(hue)
|
||||
}).unwrap_or(HueInterpolationMethod::Shorter)
|
||||
} else {
|
||||
HueInterpolationMethod::Shorter
|
||||
};
|
||||
Ok(Self { space, hue })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCss for ColorInterpolationMethod {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
dest.write_str("in ")?;
|
||||
self.space.to_css(dest)?;
|
||||
if self.hue != HueInterpolationMethod::Shorter {
|
||||
dest.write_char(' ')?;
|
||||
self.hue.to_css(dest)?;
|
||||
dest.write_str(" hue")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A restricted version of the css `color-mix()` function, which only supports
|
||||
/// percentages.
|
||||
///
|
||||
/// https://drafts.csswg.org/css-color-5/#color-mix
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToComputedValue, ToResolvedValue, ToShmem)]
|
||||
#[allow(missing_docs)]
|
||||
#[repr(C)]
|
||||
pub struct GenericColorMix<Color, Percentage> {
|
||||
pub interpolation: ColorInterpolationMethod,
|
||||
pub left: Color,
|
||||
pub left_percentage: Percentage,
|
||||
pub right: Color,
|
||||
pub right_percentage: Percentage,
|
||||
pub normalize_weights: bool,
|
||||
}
|
||||
|
||||
pub use self::GenericColorMix as ColorMix;
|
||||
|
||||
impl<Color: ToCss, Percentage: ToCss + ToPercentage> ToCss for ColorMix<Color, Percentage> {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
fn can_omit<Percentage: ToPercentage>(percent: &Percentage, other: &Percentage, is_left: bool) -> bool {
|
||||
if percent.is_calc() {
|
||||
return false;
|
||||
}
|
||||
if percent.to_percentage() == 0.5 {
|
||||
return other.to_percentage() == 0.5;
|
||||
}
|
||||
if is_left {
|
||||
return false;
|
||||
}
|
||||
(1.0 - percent.to_percentage() - other.to_percentage()).abs() <= f32::EPSILON
|
||||
}
|
||||
|
||||
dest.write_str("color-mix(")?;
|
||||
self.interpolation.to_css(dest)?;
|
||||
dest.write_str(", ")?;
|
||||
self.left.to_css(dest)?;
|
||||
if !can_omit(&self.left_percentage, &self.right_percentage, true) {
|
||||
dest.write_str(" ")?;
|
||||
self.left_percentage.to_css(dest)?;
|
||||
}
|
||||
dest.write_str(", ")?;
|
||||
self.right.to_css(dest)?;
|
||||
if !can_omit(&self.right_percentage, &self.left_percentage, false) {
|
||||
dest.write_str(" ")?;
|
||||
self.right_percentage.to_css(dest)?;
|
||||
}
|
||||
dest.write_str(")")
|
||||
}
|
||||
}
|
||||
|
||||
impl<RGBA, Percentage> ColorMix<GenericColor<RGBA, Percentage>, Percentage> {
|
||||
fn to_rgba(&self) -> Option<RGBA>
|
||||
where
|
||||
RGBA: Clone + ToAnimatedValue<AnimatedValue = AnimatedRGBA>,
|
||||
Percentage: ToPercentage,
|
||||
{
|
||||
use crate::values::animated::color::Color as AnimatedColor;
|
||||
let left = self.left.as_numeric()?.clone().to_animated_value();
|
||||
let right = self.right.as_numeric()?.clone().to_animated_value();
|
||||
Some(ToAnimatedValue::from_animated_value(AnimatedColor::mix(
|
||||
&self.interpolation,
|
||||
&left,
|
||||
self.left_percentage.to_percentage(),
|
||||
&right,
|
||||
self.right_percentage.to_percentage(),
|
||||
self.normalize_weights,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub use self::GenericColor as Color;
|
||||
|
||||
impl Color<cssparser::RGBA> {
|
||||
/// Returns a color value representing currentcolor.
|
||||
pub fn currentcolor() -> Self {
|
||||
Color {
|
||||
color: cssparser::RGBA::transparent(),
|
||||
ratios: ComplexColorRatios::CURRENT_COLOR,
|
||||
impl<RGBA, Percentage> Color<RGBA, Percentage> {
|
||||
/// Returns the numeric rgba value if this color is numeric, or None
|
||||
/// otherwise.
|
||||
pub fn as_numeric(&self) -> Option<&RGBA> {
|
||||
match *self {
|
||||
Self::Numeric(ref rgba) => Some(rgba),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<RGBA> Color<RGBA> {
|
||||
/// Create a color based upon the specified ratios.
|
||||
pub fn new(color: RGBA, ratios: ComplexColorRatios) -> Self {
|
||||
Self { color, ratios }
|
||||
/// Simplifies the color-mix()es to the extent possible given a current
|
||||
/// color (or not).
|
||||
pub fn simplify(&mut self, current_color: Option<&RGBA>)
|
||||
where
|
||||
RGBA: Clone + ToAnimatedValue<AnimatedValue = AnimatedRGBA>,
|
||||
Percentage: ToPercentage,
|
||||
{
|
||||
match *self {
|
||||
Self::Numeric(..) => {},
|
||||
Self::CurrentColor => {
|
||||
if let Some(c) = current_color {
|
||||
*self = Self::Numeric(c.clone());
|
||||
}
|
||||
},
|
||||
Self::ColorMix(ref mut mix) => {
|
||||
mix.left.simplify(current_color);
|
||||
mix.right.simplify(current_color);
|
||||
|
||||
if let Some(mix) = mix.to_rgba() {
|
||||
*self = Self::Numeric(mix);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a color value representing currentcolor.
|
||||
pub fn currentcolor() -> Self {
|
||||
Self::CurrentColor
|
||||
}
|
||||
|
||||
/// Returns a numeric color representing the given RGBA value.
|
||||
pub fn rgba(color: RGBA) -> Self {
|
||||
Self {
|
||||
color,
|
||||
ratios: ComplexColorRatios::NUMERIC,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether it is a numeric color (no currentcolor component).
|
||||
pub fn is_numeric(&self) -> bool {
|
||||
self.ratios == ComplexColorRatios::NUMERIC
|
||||
Self::Numeric(color)
|
||||
}
|
||||
|
||||
/// Whether it is a currentcolor value (no numeric color component).
|
||||
pub fn is_currentcolor(&self) -> bool {
|
||||
self.ratios == ComplexColorRatios::CURRENT_COLOR
|
||||
matches!(*self, Self::CurrentColor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<RGBA> From<RGBA> for Color<RGBA> {
|
||||
fn from(color: RGBA) -> Self {
|
||||
Self::rgba(color)
|
||||
/// Whether it is a numeric color (no currentcolor component).
|
||||
pub fn is_numeric(&self) -> bool {
|
||||
matches!(*self, Self::Numeric(..))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue