mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
style: Color interpolation takes none keyword into account
Now that the none keyword is available, we can take it into account when interpolating colors following the rules from the spec here: https://drafts.csswg.org/css-color-4/#interpolation-missing Differential Revision: https://phabricator.services.mozilla.com/D172666
This commit is contained in:
parent
11414d0c94
commit
f3402e6144
3 changed files with 175 additions and 70 deletions
|
@ -4,7 +4,7 @@
|
|||
|
||||
//! Color mixing/interpolation.
|
||||
|
||||
use super::{AbsoluteColor, ColorComponents, ColorSpace};
|
||||
use super::{AbsoluteColor, ColorComponents, ColorFlags, ColorSpace};
|
||||
use crate::parser::{Parse, ParserContext};
|
||||
use cssparser::Parser;
|
||||
use std::fmt::{self, Write};
|
||||
|
@ -135,24 +135,6 @@ impl ToCss for ColorInterpolationMethod {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// Linearly interpolate between the left and right colors.
|
||||
///
|
||||
/// The HueInterpolationMethod 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_interpolation: HueInterpolationMethod,
|
||||
) -> Self;
|
||||
}
|
||||
|
||||
/// Mix two colors into one.
|
||||
pub fn mix(
|
||||
interpolation: ColorInterpolationMethod,
|
||||
|
@ -187,6 +169,38 @@ pub fn mix(
|
|||
)
|
||||
}
|
||||
|
||||
/// What the outcome of each component should be in a mix result.
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
enum ComponentMixOutcome {
|
||||
/// Mix the left and right sides to give the result.
|
||||
Mix,
|
||||
/// Carry the left side forward to the result.
|
||||
UseLeft,
|
||||
/// Carry the right side forward to the result.
|
||||
UseRight,
|
||||
/// The resulting component should also be none.
|
||||
None,
|
||||
}
|
||||
|
||||
impl ComponentMixOutcome {
|
||||
fn from_colors(
|
||||
left: &AbsoluteColor,
|
||||
right: &AbsoluteColor,
|
||||
flags_to_check: ColorFlags,
|
||||
) -> Self {
|
||||
match (
|
||||
left.flags.contains(flags_to_check),
|
||||
right.flags.contains(flags_to_check),
|
||||
) {
|
||||
(true, true) => Self::None,
|
||||
(true, false) => Self::UseRight,
|
||||
(false, true) => Self::UseLeft,
|
||||
(false, false) => Self::Mix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mix_in(
|
||||
color_space: ColorSpace,
|
||||
left_color: &AbsoluteColor,
|
||||
|
@ -196,6 +210,13 @@ fn mix_in(
|
|||
hue_interpolation: HueInterpolationMethod,
|
||||
alpha_multiplier: f32,
|
||||
) -> AbsoluteColor {
|
||||
let outcomes = [
|
||||
ComponentMixOutcome::from_colors(left_color, right_color, ColorFlags::C1_IS_NONE),
|
||||
ComponentMixOutcome::from_colors(left_color, right_color, ColorFlags::C2_IS_NONE),
|
||||
ComponentMixOutcome::from_colors(left_color, right_color, ColorFlags::C3_IS_NONE),
|
||||
ComponentMixOutcome::from_colors(left_color, right_color, ColorFlags::ALPHA_IS_NONE),
|
||||
];
|
||||
|
||||
// Convert both colors into the interpolation color space.
|
||||
let left = left_color.to_color_space(color_space);
|
||||
let left = left.raw_components();
|
||||
|
@ -203,13 +224,14 @@ fn mix_in(
|
|||
let right = right_color.to_color_space(color_space);
|
||||
let right = right.raw_components();
|
||||
|
||||
let result = interpolate_premultiplied(
|
||||
let (result, result_flags) = interpolate_premultiplied(
|
||||
&left,
|
||||
left_weight,
|
||||
&right,
|
||||
right_weight,
|
||||
color_space.hue_index(),
|
||||
hue_interpolation,
|
||||
&outcomes,
|
||||
);
|
||||
|
||||
let alpha = if alpha_multiplier != 1.0 {
|
||||
|
@ -225,11 +247,19 @@ fn mix_in(
|
|||
// then divide after calculations?
|
||||
let alpha = (alpha * 1000.0).round() / 1000.0;
|
||||
|
||||
AbsoluteColor::new(
|
||||
let mut result = AbsoluteColor::new(
|
||||
color_space,
|
||||
ColorComponents(result[0], result[1], result[2]),
|
||||
alpha,
|
||||
)
|
||||
);
|
||||
|
||||
result.flags = result_flags;
|
||||
// If both sides are legacy RGB, then the result stays in legacy RGB.
|
||||
if !left_color.is_legacy_color() || !right_color.is_legacy_color() {
|
||||
result.flags.insert(ColorFlags::AS_COLOR_FUNCTION);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn interpolate_premultiplied_component(
|
||||
|
@ -239,9 +269,8 @@ fn interpolate_premultiplied_component(
|
|||
right: f32,
|
||||
right_weight: f32,
|
||||
right_alpha: f32,
|
||||
inverse_of_result_alpha: f32,
|
||||
) -> f32 {
|
||||
(left * left_weight * left_alpha + right * right_weight * right_alpha) * inverse_of_result_alpha
|
||||
left * left_weight * left_alpha + right * right_weight * right_alpha
|
||||
}
|
||||
|
||||
// Normalize hue into [0, 360)
|
||||
|
@ -323,6 +352,63 @@ fn interpolate_hue(
|
|||
left * left_weight + right * right_weight
|
||||
}
|
||||
|
||||
struct InterpolatedAlpha {
|
||||
/// The adjusted left alpha value.
|
||||
left: f32,
|
||||
/// The adjusted right alpha value.
|
||||
right: f32,
|
||||
/// The interpolated alpha value.
|
||||
interpolated: f32,
|
||||
/// Whether the alpha component should be `none`.
|
||||
is_none: bool,
|
||||
}
|
||||
|
||||
fn interpolate_alpha(
|
||||
left: f32,
|
||||
left_weight: f32,
|
||||
right: f32,
|
||||
right_weight: f32,
|
||||
outcome: ComponentMixOutcome,
|
||||
) -> InterpolatedAlpha {
|
||||
// <https://drafts.csswg.org/css-color-4/#interpolation-missing>
|
||||
let mut result = match outcome {
|
||||
ComponentMixOutcome::Mix => {
|
||||
let interpolated = left * left_weight + right * right_weight;
|
||||
InterpolatedAlpha {
|
||||
left,
|
||||
right,
|
||||
interpolated,
|
||||
is_none: false,
|
||||
}
|
||||
},
|
||||
ComponentMixOutcome::UseLeft => InterpolatedAlpha {
|
||||
left,
|
||||
right: left,
|
||||
interpolated: left,
|
||||
is_none: false,
|
||||
},
|
||||
ComponentMixOutcome::UseRight => InterpolatedAlpha {
|
||||
left: right,
|
||||
right,
|
||||
interpolated: right,
|
||||
is_none: false,
|
||||
},
|
||||
ComponentMixOutcome::None => InterpolatedAlpha {
|
||||
left: 1.0,
|
||||
right: 1.0,
|
||||
interpolated: 0.0,
|
||||
is_none: true,
|
||||
},
|
||||
};
|
||||
|
||||
// Clip all alpha values to [0.0..1.0].
|
||||
result.left = result.left.clamp(0.0, 1.0);
|
||||
result.right = result.right.clamp(0.0, 1.0);
|
||||
result.interpolated = result.interpolated.clamp(0.0, 1.0);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn interpolate_premultiplied(
|
||||
left: &[f32; 4],
|
||||
left_weight: f32,
|
||||
|
@ -330,39 +416,60 @@ fn interpolate_premultiplied(
|
|||
right_weight: f32,
|
||||
hue_index: Option<usize>,
|
||||
hue_interpolation: HueInterpolationMethod,
|
||||
) -> [f32; 4] {
|
||||
let left_alpha = left[3];
|
||||
let right_alpha = right[3];
|
||||
let result_alpha = (left_alpha * left_weight + right_alpha * right_weight).min(1.);
|
||||
outcomes: &[ComponentMixOutcome; 4],
|
||||
) -> ([f32; 4], ColorFlags) {
|
||||
let alpha = interpolate_alpha(left[3], left_weight, right[3], right_weight, outcomes[3]);
|
||||
let mut flags = if alpha.is_none {
|
||||
ColorFlags::ALPHA_IS_NONE
|
||||
} else {
|
||||
ColorFlags::empty()
|
||||
};
|
||||
|
||||
let mut result = [0.; 4];
|
||||
if result_alpha <= 0. {
|
||||
return result;
|
||||
}
|
||||
|
||||
let inverse_of_result_alpha = 1. / result_alpha;
|
||||
for i in 0..3 {
|
||||
let is_hue = hue_index == Some(i);
|
||||
result[i] = if is_hue {
|
||||
interpolate_hue(
|
||||
left[i],
|
||||
left_weight,
|
||||
right[i],
|
||||
right_weight,
|
||||
hue_interpolation,
|
||||
)
|
||||
} else {
|
||||
interpolate_premultiplied_component(
|
||||
left[i],
|
||||
left_weight,
|
||||
left_alpha,
|
||||
right[i],
|
||||
right_weight,
|
||||
right_alpha,
|
||||
inverse_of_result_alpha,
|
||||
)
|
||||
};
|
||||
}
|
||||
result[3] = result_alpha;
|
||||
match outcomes[i] {
|
||||
ComponentMixOutcome::Mix => {
|
||||
let is_hue = hue_index == Some(i);
|
||||
result[i] = if is_hue {
|
||||
normalize_hue(interpolate_hue(
|
||||
left[i],
|
||||
left_weight,
|
||||
right[i],
|
||||
right_weight,
|
||||
hue_interpolation,
|
||||
))
|
||||
} else {
|
||||
let interpolated = interpolate_premultiplied_component(
|
||||
left[i],
|
||||
left_weight,
|
||||
alpha.left,
|
||||
right[i],
|
||||
right_weight,
|
||||
alpha.right,
|
||||
);
|
||||
|
||||
result
|
||||
if alpha.interpolated == 0.0 {
|
||||
interpolated
|
||||
} else {
|
||||
interpolated / alpha.interpolated
|
||||
}
|
||||
};
|
||||
},
|
||||
ComponentMixOutcome::UseLeft => result[i] = left[i],
|
||||
ComponentMixOutcome::UseRight => result[i] = right[i],
|
||||
ComponentMixOutcome::None => {
|
||||
result[i] = 0.0;
|
||||
match i {
|
||||
0 => flags.insert(ColorFlags::C1_IS_NONE),
|
||||
1 => flags.insert(ColorFlags::C2_IS_NONE),
|
||||
2 => flags.insert(ColorFlags::C3_IS_NONE),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
result[3] = alpha.interpolated;
|
||||
|
||||
(result, flags)
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ bitflags! {
|
|||
/// Flags used when serializing colors.
|
||||
#[derive(Default, MallocSizeOf, ToShmem)]
|
||||
#[repr(C)]
|
||||
pub struct SerializationFlags : u8 {
|
||||
pub struct ColorFlags : u8 {
|
||||
/// If set, serializes sRGB colors into `color(srgb ...)` instead of
|
||||
/// `rgba(...)`.
|
||||
const AS_COLOR_FUNCTION = 1 << 0;
|
||||
|
@ -157,7 +157,7 @@ pub struct AbsoluteColor {
|
|||
/// The current color space that the components represent.
|
||||
pub color_space: ColorSpace,
|
||||
/// Extra flags used durring serialization of this color.
|
||||
pub flags: SerializationFlags,
|
||||
pub flags: ColorFlags,
|
||||
}
|
||||
|
||||
/// Given an [`AbsoluteColor`], return the 4 float components as the type given,
|
||||
|
@ -207,7 +207,7 @@ impl AbsoluteColor {
|
|||
components,
|
||||
alpha: alpha.clamp(0.0, 1.0),
|
||||
color_space,
|
||||
flags: SerializationFlags::empty(),
|
||||
flags: ColorFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,7 +242,7 @@ impl AbsoluteColor {
|
|||
pub fn is_legacy_color(&self) -> bool {
|
||||
// rgb(), rgba(), hsl(), hsla(), hwb(), hwba()
|
||||
match self.color_space {
|
||||
ColorSpace::Srgb => !self.flags.contains(SerializationFlags::AS_COLOR_FUNCTION),
|
||||
ColorSpace::Srgb => !self.flags.contains(ColorFlags::AS_COLOR_FUNCTION),
|
||||
ColorSpace::Hsl | ColorSpace::Hwb => true,
|
||||
_ => false,
|
||||
}
|
||||
|
@ -377,7 +377,7 @@ impl ToCss for AbsoluteColor {
|
|||
{
|
||||
macro_rules! value_or_none {
|
||||
($v:expr,$flag:tt) => {{
|
||||
if self.flags.contains(SerializationFlags::$flag) {
|
||||
if self.flags.contains(ColorFlags::$flag) {
|
||||
None
|
||||
} else {
|
||||
Some($v)
|
||||
|
@ -402,7 +402,7 @@ impl ToCss for AbsoluteColor {
|
|||
Self::new(ColorSpace::Srgb, rgb, self.alpha).to_css(dest)
|
||||
},
|
||||
|
||||
ColorSpace::Srgb if !self.flags.contains(SerializationFlags::AS_COLOR_FUNCTION) => {
|
||||
ColorSpace::Srgb if !self.flags.contains(ColorFlags::AS_COLOR_FUNCTION) => {
|
||||
// Althought we are passing Option<_> in here, the to_css fn
|
||||
// knows that the "none" keyword is not supported in the
|
||||
// rgb/rgba legacy syntax.
|
||||
|
@ -431,7 +431,7 @@ impl ToCss for AbsoluteColor {
|
|||
let color_space = match self.color_space {
|
||||
ColorSpace::Srgb => {
|
||||
debug_assert!(
|
||||
self.flags.contains(SerializationFlags::AS_COLOR_FUNCTION),
|
||||
self.flags.contains(ColorFlags::AS_COLOR_FUNCTION),
|
||||
"The case without this flag should be handled in the wrapping match case!!"
|
||||
);
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
use super::AllowQuirks;
|
||||
use crate::color::mix::ColorInterpolationMethod;
|
||||
use crate::color::{AbsoluteColor, ColorComponents, ColorSpace, SerializationFlags};
|
||||
use crate::color::{AbsoluteColor, ColorComponents, ColorFlags, ColorSpace};
|
||||
use crate::media_queries::Device;
|
||||
use crate::parser::{Parse, ParserContext};
|
||||
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
|
||||
|
@ -416,14 +416,14 @@ fn new_absolute(
|
|||
c3: Option<f32>,
|
||||
alpha: Option<f32>,
|
||||
) -> Color {
|
||||
let mut flags = SerializationFlags::empty();
|
||||
let mut flags = ColorFlags::empty();
|
||||
|
||||
macro_rules! c {
|
||||
($v:expr,$flag:tt) => {{
|
||||
if let Some(value) = $v {
|
||||
value
|
||||
} else {
|
||||
flags |= SerializationFlags::$flag;
|
||||
flags |= ColorFlags::$flag;
|
||||
0.0
|
||||
}
|
||||
}};
|
||||
|
@ -521,7 +521,7 @@ impl cssparser::FromParsedColor for Color {
|
|||
let mut result = new_absolute(color_space.into(), c1, c2, c3, alpha);
|
||||
if let Color::Absolute(ref mut absolute) = result {
|
||||
if matches!(absolute.color.color_space, ColorSpace::Srgb) {
|
||||
absolute.color.flags |= SerializationFlags::AS_COLOR_FUNCTION;
|
||||
absolute.color.flags |= ColorFlags::AS_COLOR_FUNCTION;
|
||||
}
|
||||
}
|
||||
result
|
||||
|
@ -637,10 +637,8 @@ impl Color {
|
|||
absolute.color.color_space,
|
||||
ColorSpace::Srgb | ColorSpace::Hsl
|
||||
);
|
||||
let is_color_function = absolute
|
||||
.color
|
||||
.flags
|
||||
.contains(SerializationFlags::AS_COLOR_FUNCTION);
|
||||
let is_color_function =
|
||||
absolute.color.flags.contains(ColorFlags::AS_COLOR_FUNCTION);
|
||||
let pref_enabled = allow_more_color_4();
|
||||
|
||||
(is_legacy_color && !is_color_function) || pref_enabled
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue