diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 4b423751d6bf..5a235b45c19c 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -7,7 +7,7 @@ <% from data import to_idl_name, SYSTEM_FONT_LONGHANDS %> use app_units::Au; -use cssparser::{Parser, RGBA}; +use cssparser::Parser; use euclid::{Point2D, Size2D}; #[cfg(feature = "gecko")] use gecko_bindings::bindings::RawServoAnimationValueMap; #[cfg(feature = "gecko")] use gecko_bindings::structs::RawGeckoGfxMatrix4x4; @@ -38,13 +38,14 @@ use super::ComputedValues; use values::Auto; use values::{CSSFloat, CustomIdent, Either}; use values::animated::{ToAnimatedValue, ToAnimatedZero}; +use values::animated::color::{Color as AnimatedColor, RGBA as AnimatedRGBA}; use values::animated::effects::BoxShadowList as AnimatedBoxShadowList; use values::animated::effects::Filter as AnimatedFilter; use values::animated::effects::FilterList as AnimatedFilterList; use values::animated::effects::TextShadowList as AnimatedTextShadowList; use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone}; use values::computed::{BorderCornerRadius, ClipRect}; -use values::computed::{CalcLengthOrPercentage, Color, Context, ComputedValueAsSpecified, ComputedUrl}; +use values::computed::{CalcLengthOrPercentage, Context, ComputedValueAsSpecified, ComputedUrl}; use values::computed::{LengthOrPercentage, MaxLength, MozLength, Percentage, ToComputedValue}; use values::computed::{NonNegativeAu, NonNegativeNumber, PositiveIntegerOrAuto}; use values::computed::length::{NonNegativeLengthOrAuto, NonNegativeLengthOrNormal}; @@ -2449,253 +2450,11 @@ where } } -#[derive(Copy, Clone, Debug, PartialEq)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -/// Unlike RGBA, each component value may exceed the range [0.0, 1.0]. -pub struct IntermediateRGBA { - /// The red component. - pub red: f32, - /// The green component. - pub green: f32, - /// The blue component. - pub blue: f32, - /// The alpha component. - pub alpha: f32, -} - -impl IntermediateRGBA { - /// Returns a transparent color. - #[inline] - pub fn transparent() -> Self { - Self::new(0., 0., 0., 0.) - } - - /// Returns a new color. - #[inline] - pub fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Self { - IntermediateRGBA { red: red, green: green, blue: blue, alpha: alpha } - } -} - -impl ToAnimatedValue for RGBA { - type AnimatedValue = IntermediateRGBA; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - IntermediateRGBA::new( - self.red_f32(), - self.green_f32(), - self.blue_f32(), - self.alpha_f32(), - ) - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - // RGBA::from_floats clamps each component values. - RGBA::from_floats( - animated.red, - animated.green, - animated.blue, - animated.alpha, - ) - } -} - -/// Unlike Animatable for RGBA we don't clamp any component values. -impl Animatable for IntermediateRGBA { - #[inline] - fn add_weighted(&self, other: &IntermediateRGBA, self_portion: f64, other_portion: f64) - -> Result { - let mut alpha = self.alpha.add_weighted(&other.alpha, self_portion, other_portion)?; - if alpha <= 0. { - // Ideally we should return color value that only alpha component is - // 0, but this is what current gecko does. - Ok(IntermediateRGBA::transparent()) - } else { - alpha = alpha.min(1.); - let red = (self.red * self.alpha).add_weighted( - &(other.red * other.alpha), self_portion, other_portion - )? * 1. / alpha; - let green = (self.green * self.alpha).add_weighted( - &(other.green * other.alpha), self_portion, other_portion - )? * 1. / alpha; - let blue = (self.blue * self.alpha).add_weighted( - &(other.blue * other.alpha), self_portion, other_portion - )? * 1. / alpha; - Ok(IntermediateRGBA::new(red, green, blue, alpha)) - } - } -} - -impl ComputeSquaredDistance for IntermediateRGBA { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - let start = [ self.alpha, self.red * self.alpha, self.green * self.alpha, self.blue * self.alpha ]; - let end = [ other.alpha, other.red * other.alpha, other.green * other.alpha, other.blue * other.alpha ]; - start.iter().zip(&end).map(|(this, other)| this.compute_squared_distance(other)).sum() - } -} - -impl ToAnimatedZero for IntermediateRGBA { - #[inline] - fn to_animated_zero(&self) -> Result { - Ok(IntermediateRGBA::transparent()) - } -} - -#[derive(Copy, Clone, Debug, PartialEq)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct IntermediateColor { - color: IntermediateRGBA, - foreground_ratio: f32, -} - -impl IntermediateColor { - fn currentcolor() -> Self { - IntermediateColor { - color: IntermediateRGBA::transparent(), - foreground_ratio: 1., - } - } - - /// Returns a transparent intermediate color. - pub fn transparent() -> Self { - IntermediateColor { - color: IntermediateRGBA::transparent(), - foreground_ratio: 0., - } - } - - fn is_currentcolor(&self) -> bool { - self.foreground_ratio >= 1. - } - - fn is_numeric(&self) -> bool { - self.foreground_ratio <= 0. - } - - fn effective_intermediate_rgba(&self) -> IntermediateRGBA { - IntermediateRGBA { - alpha: self.color.alpha * (1. - self.foreground_ratio), - .. self.color - } - } -} - -impl ToAnimatedValue for Color { - type AnimatedValue = IntermediateColor; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - IntermediateColor { - color: self.color.to_animated_value(), - foreground_ratio: self.foreground_ratio as f32 * (1. / 255.), - } - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - Color { - color: RGBA::from_animated_value(animated.color), - foreground_ratio: (animated.foreground_ratio * 255.).round() as u8, - } - } -} - -impl Animatable for IntermediateColor { - #[inline] - fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result { - // Common cases are interpolating between two numeric colors, - // two currentcolors, and a numeric color and a currentcolor. - // - // Note: this algorithm assumes self_portion + other_portion - // equals to one, so it may be broken for additive operation. - // To properly support additive color interpolation, we would - // need two ratio fields in computed color types. - if self.foreground_ratio == other.foreground_ratio { - if self.is_currentcolor() { - Ok(IntermediateColor::currentcolor()) - } else { - Ok(IntermediateColor { - color: self.color.add_weighted(&other.color, self_portion, other_portion)?, - foreground_ratio: self.foreground_ratio, - }) - } - } else if self.is_currentcolor() && other.is_numeric() { - Ok(IntermediateColor { - color: other.color, - foreground_ratio: self_portion as f32, - }) - } else if self.is_numeric() && other.is_currentcolor() { - Ok(IntermediateColor { - color: self.color, - foreground_ratio: other_portion as f32, - }) - } else { - // For interpolating between two complex colors, we need to - // generate colors with effective alpha value. - let self_color = self.effective_intermediate_rgba(); - let other_color = other.effective_intermediate_rgba(); - let color = self_color.add_weighted(&other_color, self_portion, other_portion)?; - // Then we compute the final foreground ratio, and derive - // the final alpha value from the effective alpha value. - let foreground_ratio = self.foreground_ratio - .add_weighted(&other.foreground_ratio, self_portion, other_portion)?; - let alpha = color.alpha / (1. - foreground_ratio); - Ok(IntermediateColor { - color: IntermediateRGBA { - alpha: alpha, - .. color - }, - foreground_ratio: foreground_ratio, - }) - } - } -} - -impl ComputeSquaredDistance for IntermediateColor { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - // All comments in add_weighted also applies here. - if self.foreground_ratio == other.foreground_ratio { - if self.is_currentcolor() { - Ok(SquaredDistance::Value(0.)) - } else { - self.color.compute_squared_distance(&other.color) - } - } else if self.is_currentcolor() && other.is_numeric() { - Ok( - IntermediateRGBA::transparent().compute_squared_distance(&other.color)? + - SquaredDistance::Value(1.), - ) - } else if self.is_numeric() && other.is_currentcolor() { - Ok( - self.color.compute_squared_distance(&IntermediateRGBA::transparent())? + - SquaredDistance::Value(1.), - ) - } else { - let self_color = self.effective_intermediate_rgba(); - let other_color = other.effective_intermediate_rgba(); - Ok( - self_color.compute_squared_distance(&other_color)? + - self.foreground_ratio.compute_squared_distance(&other.foreground_ratio)?, - ) - } - } -} - -impl ToAnimatedZero for IntermediateColor { - #[inline] - fn to_animated_zero(&self) -> Result { Err(()) } -} - /// Animatable SVGPaint -pub type IntermediateSVGPaint = SVGPaint; +pub type IntermediateSVGPaint = SVGPaint; /// Animatable SVGPaintKind -pub type IntermediateSVGPaintKind = SVGPaintKind; +pub type IntermediateSVGPaintKind = SVGPaintKind; impl Animatable for IntermediateSVGPaint { #[inline] diff --git a/components/style/properties/longhand/background.mako.rs b/components/style/properties/longhand/background.mako.rs index b2de4e38fc6f..b54e0648e333 100644 --- a/components/style/properties/longhand/background.mako.rs +++ b/components/style/properties/longhand/background.mako.rs @@ -6,14 +6,17 @@ <% data.new_style_struct("Background", inherited=False) %> -${helpers.predefined_type("background-color", "Color", +${helpers.predefined_type( + "background-color", + "Color", "computed_value::T::transparent()", initial_specified_value="SpecifiedValue::transparent()", spec="https://drafts.csswg.org/css-backgrounds/#background-color", - animation_value_type="IntermediateColor", + animation_value_type="AnimatedColor", ignored_when_colors_disabled=True, allow_quirks=True, - flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER")} + flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER", +)} ${helpers.predefined_type("background-image", "ImageLayer", initial_value="Either::First(None_)", diff --git a/components/style/properties/longhand/border.mako.rs b/components/style/properties/longhand/border.mako.rs index db5fd47b2bd8..6b8891ffc2cb 100644 --- a/components/style/properties/longhand/border.mako.rs +++ b/components/style/properties/longhand/border.mako.rs @@ -20,15 +20,17 @@ side_name = side[0] is_logical = side[1] %> - ${helpers.predefined_type("border-%s-color" % side_name, "Color", - "computed_value::T::currentcolor()", - alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-color"), - spec=maybe_logical_spec(side, "color"), - animation_value_type="IntermediateColor", - logical=is_logical, - allow_quirks=not is_logical, - flags="APPLIES_TO_FIRST_LETTER", - ignored_when_colors_disabled=True)} + ${helpers.predefined_type( + "border-%s-color" % side_name, "Color", + "computed_value::T::currentcolor()", + alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-color"), + spec=maybe_logical_spec(side, "color"), + animation_value_type="AnimatedColor", + logical=is_logical, + allow_quirks=not is_logical, + flags="APPLIES_TO_FIRST_LETTER", + ignored_when_colors_disabled=True, + )} ${helpers.predefined_type("border-%s-style" % side_name, "BorderStyle", "specified::BorderStyle::none", diff --git a/components/style/properties/longhand/color.mako.rs b/components/style/properties/longhand/color.mako.rs index 9ac3bbfcfbc6..9ee704f14247 100644 --- a/components/style/properties/longhand/color.mako.rs +++ b/components/style/properties/longhand/color.mako.rs @@ -9,7 +9,7 @@ <% from data import to_rust_ident %> <%helpers:longhand name="color" need_clone="True" - animation_value_type="IntermediateRGBA" + animation_value_type="AnimatedRGBA" ignored_when_colors_disabled="True" flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER" spec="https://drafts.csswg.org/css-color/#color"> diff --git a/components/style/properties/longhand/column.mako.rs b/components/style/properties/longhand/column.mako.rs index dda11ea696c1..d2f7dd6d0b12 100644 --- a/components/style/properties/longhand/column.mako.rs +++ b/components/style/properties/longhand/column.mako.rs @@ -48,12 +48,18 @@ ${helpers.predefined_type("column-rule-width", extra_prefixes="moz")} // https://drafts.csswg.org/css-multicol-1/#crc -${helpers.predefined_type("column-rule-color", "Color", - "computed_value::T::currentcolor()", - initial_specified_value="specified::Color::currentcolor()", - products="gecko", animation_value_type="IntermediateColor", extra_prefixes="moz", - need_clone=True, ignored_when_colors_disabled=True, - spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-color")} +${helpers.predefined_type( + "column-rule-color", + "Color", + "computed_value::T::currentcolor()", + initial_specified_value="specified::Color::currentcolor()", + products="gecko", + animation_value_type="AnimatedColor", + extra_prefixes="moz", + need_clone=True, + ignored_when_colors_disabled=True, + spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-color", +)} ${helpers.single_keyword("column-span", "none all", products="gecko", animation_value_type="discrete", diff --git a/components/style/properties/longhand/inherited_text.mako.rs b/components/style/properties/longhand/inherited_text.mako.rs index ecb0a9364a84..7f8e032f0211 100644 --- a/components/style/properties/longhand/inherited_text.mako.rs +++ b/components/style/properties/longhand/inherited_text.mako.rs @@ -705,13 +705,17 @@ ${helpers.predefined_type( % endif -${helpers.predefined_type("text-emphasis-color", "Color", - "computed_value::T::currentcolor()", - initial_specified_value="specified::Color::currentcolor()", - products="gecko", animation_value_type="IntermediateColor", - need_clone=True, ignored_when_colors_disabled=True, - spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-color")} - +${helpers.predefined_type( + "text-emphasis-color", + "Color", + "computed_value::T::currentcolor()", + initial_specified_value="specified::Color::currentcolor()", + products="gecko", + animation_value_type="AnimatedColor", + need_clone=True, + ignored_when_colors_disabled=True, + spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-color", +)} ${helpers.predefined_type( "-moz-tab-size", "length::NonNegativeLengthOrNumber", @@ -723,21 +727,28 @@ ${helpers.predefined_type( // CSS Compatibility // https://compat.spec.whatwg.org ${helpers.predefined_type( - "-webkit-text-fill-color", "Color", + "-webkit-text-fill-color", + "Color", "computed_value::T::currentcolor()", - products="gecko", animation_value_type="IntermediateColor", - need_clone=True, ignored_when_colors_disabled=True, + products="gecko", + animation_value_type="AnimatedColor", + need_clone=True, + ignored_when_colors_disabled=True, flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER", - spec="https://compat.spec.whatwg.org/#the-webkit-text-fill-color")} + spec="https://compat.spec.whatwg.org/#the-webkit-text-fill-color", +)} ${helpers.predefined_type( - "-webkit-text-stroke-color", "Color", + "-webkit-text-stroke-color", + "Color", "computed_value::T::currentcolor()", initial_specified_value="specified::Color::currentcolor()", - products="gecko", animation_value_type="IntermediateColor", + products="gecko", + animation_value_type="AnimatedColor", need_clone=True, ignored_when_colors_disabled=True, flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER", - spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-color")} + spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-color", +)} ${helpers.predefined_type("-webkit-text-stroke-width", "BorderSideWidth", diff --git a/components/style/properties/longhand/outline.mako.rs b/components/style/properties/longhand/outline.mako.rs index f08537f65459..6adb743da1f0 100644 --- a/components/style/properties/longhand/outline.mako.rs +++ b/components/style/properties/longhand/outline.mako.rs @@ -10,11 +10,16 @@ additional_methods=[Method("outline_has_nonzero_width", "bool")]) %> // TODO(pcwalton): `invert` -${helpers.predefined_type("outline-color", "Color", "computed_value::T::currentcolor()", - initial_specified_value="specified::Color::currentcolor()", - animation_value_type="IntermediateColor", need_clone=True, - ignored_when_colors_disabled=True, - spec="https://drafts.csswg.org/css-ui/#propdef-outline-color")} +${helpers.predefined_type( + "outline-color", + "Color", + "computed_value::T::currentcolor()", + initial_specified_value="specified::Color::currentcolor()", + animation_value_type="AnimatedColor", + need_clone=True, + ignored_when_colors_disabled=True, + spec="https://drafts.csswg.org/css-ui/#propdef-outline-color", +)} <%helpers:longhand name="outline-style" animation_value_type="discrete" spec="https://drafts.csswg.org/css-ui/#propdef-outline-style"> diff --git a/components/style/properties/longhand/pointing.mako.rs b/components/style/properties/longhand/pointing.mako.rs index 9e7923889b17..026275d3b174 100644 --- a/components/style/properties/longhand/pointing.mako.rs +++ b/components/style/properties/longhand/pointing.mako.rs @@ -178,11 +178,13 @@ ${helpers.single_keyword("-moz-user-focus", animation_value_type="discrete", spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-user-focus)")} -${helpers.predefined_type("caret-color", - "ColorOrAuto", - "Either::Second(Auto)", - spec="https://drafts.csswg.org/css-ui/#caret-color", - animation_value_type="Either", - boxed=True, - ignored_when_colors_disabled=True, - products="gecko")} +${helpers.predefined_type( + "caret-color", + "ColorOrAuto", + "Either::Second(Auto)", + spec="https://drafts.csswg.org/css-ui/#caret-color", + animation_value_type="Either", + boxed=True, + ignored_when_colors_disabled=True, + products="gecko", +)} diff --git a/components/style/properties/longhand/svg.mako.rs b/components/style/properties/longhand/svg.mako.rs index 43db3322ca30..9bbd5e224634 100644 --- a/components/style/properties/longhand/svg.mako.rs +++ b/components/style/properties/longhand/svg.mako.rs @@ -20,11 +20,13 @@ ${helpers.single_keyword("vector-effect", "none non-scaling-stroke", // Section 13 - Gradients and Patterns ${helpers.predefined_type( - "stop-color", "RGBAColor", + "stop-color", + "RGBAColor", "RGBA::new(0, 0, 0, 255)", products="gecko", - animation_value_type="IntermediateRGBA", - spec="https://www.w3.org/TR/SVGTiny12/painting.html#StopColorProperty")} + animation_value_type="AnimatedRGBA", + spec="https://www.w3.org/TR/SVGTiny12/painting.html#StopColorProperty", +)} ${helpers.predefined_type("stop-opacity", "Opacity", "1.0", products="gecko", @@ -34,22 +36,26 @@ ${helpers.predefined_type("stop-opacity", "Opacity", "1.0", // Section 15 - Filter Effects ${helpers.predefined_type( - "flood-color", "RGBAColor", + "flood-color", + "RGBAColor", "RGBA::new(0, 0, 0, 255)", products="gecko", - animation_value_type="IntermediateRGBA", - spec="https://www.w3.org/TR/SVG/filters.html#FloodColorProperty")} + animation_value_type="AnimatedRGBA", + spec="https://www.w3.org/TR/SVG/filters.html#FloodColorProperty", +)} ${helpers.predefined_type("flood-opacity", "Opacity", "1.0", products="gecko", animation_value_type="ComputedValue", spec="https://www.w3.org/TR/SVG/filters.html#FloodOpacityProperty")} ${helpers.predefined_type( - "lighting-color", "RGBAColor", + "lighting-color", + "RGBAColor", "RGBA::new(255, 255, 255, 255)", products="gecko", - animation_value_type="IntermediateRGBA", - spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty")} + animation_value_type="AnimatedRGBA", + spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty", +)} // CSS Masking Module Level 1 // https://drafts.fxtf.org/css-masking diff --git a/components/style/properties/longhand/text.mako.rs b/components/style/properties/longhand/text.mako.rs index 9c502b5addcf..7f7a22fc38ad 100644 --- a/components/style/properties/longhand/text.mako.rs +++ b/components/style/properties/longhand/text.mako.rs @@ -268,14 +268,16 @@ ${helpers.single_keyword("text-decoration-style", spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-style")} ${helpers.predefined_type( - "text-decoration-color", "Color", + "text-decoration-color", + "Color", "computed_value::T::currentcolor()", initial_specified_value="specified::Color::currentcolor()", products="gecko", - animation_value_type="IntermediateColor", + animation_value_type="AnimatedColor", ignored_when_colors_disabled=True, flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER", - spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-color")} + spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-color", +)} ${helpers.predefined_type( "initial-letter", diff --git a/components/style/values/animated/color.rs b/components/style/values/animated/color.rs new file mode 100644 index 000000000000..534e72fb9321 --- /dev/null +++ b/components/style/values/animated/color.rs @@ -0,0 +1,214 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! Animated types for CSS colors. + +use properties::animated_properties::Animatable; +use values::animated::ToAnimatedZero; +use values::distance::{ComputeSquaredDistance, SquaredDistance}; + +/// An animated RGBA color. +/// +/// Unlike in computed values, each component value may exceed the +/// range `[0.0, 1.0]`. +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct RGBA { + /// The red component. + pub red: f32, + /// The green component. + pub green: f32, + /// The blue component. + pub blue: f32, + /// The alpha component. + pub alpha: f32, +} + +impl RGBA { + /// Returns a transparent color. + #[inline] + pub fn transparent() -> Self { + Self::new(0., 0., 0., 0.) + } + + /// Returns a new color. + #[inline] + pub fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Self { + RGBA { red: red, green: green, blue: blue, alpha: alpha } + } +} + +/// Unlike Animatable for computed colors, we don't clamp any component values. +/// +/// FIXME(nox): Why do computed colors even implement Animatable? +impl Animatable for RGBA { + #[inline] + fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result { + let mut alpha = self.alpha.add_weighted(&other.alpha, self_portion, other_portion)?; + if alpha <= 0. { + // Ideally we should return color value that only alpha component is + // 0, but this is what current gecko does. + return Ok(RGBA::transparent()); + } + + alpha = alpha.min(1.); + let red = (self.red * self.alpha).add_weighted( + &(other.red * other.alpha), self_portion, other_portion + )? * 1. / alpha; + let green = (self.green * self.alpha).add_weighted( + &(other.green * other.alpha), self_portion, other_portion + )? * 1. / alpha; + let blue = (self.blue * self.alpha).add_weighted( + &(other.blue * other.alpha), self_portion, other_portion + )? * 1. / alpha; + + Ok(RGBA::new(red, green, blue, alpha)) + } +} + +impl ComputeSquaredDistance for RGBA { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + let start = [ self.alpha, self.red * self.alpha, self.green * self.alpha, self.blue * self.alpha ]; + let end = [ other.alpha, other.red * other.alpha, other.green * other.alpha, other.blue * other.alpha ]; + start.iter().zip(&end).map(|(this, other)| this.compute_squared_distance(other)).sum() + } +} + +impl ToAnimatedZero for RGBA { + #[inline] + fn to_animated_zero(&self) -> Result { + Ok(RGBA::transparent()) + } +} + +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Color { + pub color: RGBA, + pub foreground_ratio: f32, +} + +impl Color { + fn currentcolor() -> Self { + Color { + color: RGBA::transparent(), + foreground_ratio: 1., + } + } + + /// Returns a transparent intermediate color. + pub fn transparent() -> Self { + Color { + color: RGBA::transparent(), + foreground_ratio: 0., + } + } + + fn is_currentcolor(&self) -> bool { + self.foreground_ratio >= 1. + } + + fn is_numeric(&self) -> bool { + self.foreground_ratio <= 0. + } + + fn effective_intermediate_rgba(&self) -> RGBA { + RGBA { + alpha: self.color.alpha * (1. - self.foreground_ratio), + .. self.color + } + } +} + +impl Animatable for Color { + #[inline] + fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result { + // Common cases are interpolating between two numeric colors, + // two currentcolors, and a numeric color and a currentcolor. + // + // Note: this algorithm assumes self_portion + other_portion + // equals to one, so it may be broken for additive operation. + // To properly support additive color interpolation, we would + // need two ratio fields in computed color types. + if self.foreground_ratio == other.foreground_ratio { + if self.is_currentcolor() { + Ok(Color::currentcolor()) + } else { + Ok(Color { + color: self.color.add_weighted(&other.color, self_portion, other_portion)?, + foreground_ratio: self.foreground_ratio, + }) + } + } else if self.is_currentcolor() && other.is_numeric() { + Ok(Color { + color: other.color, + foreground_ratio: self_portion as f32, + }) + } else if self.is_numeric() && other.is_currentcolor() { + Ok(Color { + color: self.color, + foreground_ratio: other_portion as f32, + }) + } else { + // For interpolating between two complex colors, we need to + // generate colors with effective alpha value. + let self_color = self.effective_intermediate_rgba(); + let other_color = other.effective_intermediate_rgba(); + let color = self_color.add_weighted(&other_color, self_portion, other_portion)?; + // Then we compute the final foreground ratio, and derive + // the final alpha value from the effective alpha value. + let foreground_ratio = self.foreground_ratio + .add_weighted(&other.foreground_ratio, self_portion, other_portion)?; + let alpha = color.alpha / (1. - foreground_ratio); + Ok(Color { + color: RGBA { + alpha: alpha, + .. color + }, + foreground_ratio: foreground_ratio, + }) + } + } +} + +impl ComputeSquaredDistance for Color { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + // All comments in add_weighted also applies here. + if self.foreground_ratio == other.foreground_ratio { + if self.is_currentcolor() { + Ok(SquaredDistance::Value(0.)) + } else { + self.color.compute_squared_distance(&other.color) + } + } else if self.is_currentcolor() && other.is_numeric() { + Ok( + RGBA::transparent().compute_squared_distance(&other.color)? + + SquaredDistance::Value(1.), + ) + } else if self.is_numeric() && other.is_currentcolor() { + Ok( + self.color.compute_squared_distance(&RGBA::transparent())? + + SquaredDistance::Value(1.), + ) + } else { + let self_color = self.effective_intermediate_rgba(); + let other_color = other.effective_intermediate_rgba(); + Ok( + self_color.compute_squared_distance(&other_color)? + + self.foreground_ratio.compute_squared_distance(&other.foreground_ratio)?, + ) + } + } +} + +impl ToAnimatedZero for Color { + #[inline] + fn to_animated_zero(&self) -> Result { + /// FIXME(nox): This does not look correct to me. + Err(()) + } +} diff --git a/components/style/values/animated/effects.rs b/components/style/values/animated/effects.rs index 37cf926a1d7f..8db9f43689b7 100644 --- a/components/style/values/animated/effects.rs +++ b/components/style/values/animated/effects.rs @@ -4,7 +4,7 @@ //! Animated types for CSS values related to effects. -use properties::animated_properties::{Animatable, IntermediateColor}; +use properties::animated_properties::Animatable; use properties::longhands::box_shadow::computed_value::T as ComputedBoxShadowList; use properties::longhands::filter::computed_value::T as ComputedFilterList; use properties::longhands::text_shadow::computed_value::T as ComputedTextShadowList; @@ -12,6 +12,7 @@ use std::cmp; #[cfg(not(feature = "gecko"))] use values::Impossible; use values::animated::{ToAnimatedValue, ToAnimatedZero}; +use values::animated::color::Color; use values::computed::{Angle, NonNegativeNumber}; use values::computed::length::{Length, NonNegativeLength}; use values::distance::{ComputeSquaredDistance, SquaredDistance}; @@ -33,7 +34,7 @@ pub type TextShadowList = ShadowList; pub struct ShadowList(Vec); /// An animated value for a single `box-shadow`. -pub type BoxShadow = GenericBoxShadow; +pub type BoxShadow = GenericBoxShadow; /// An animated value for the `filter` property. #[cfg_attr(feature = "servo", derive(HeapSizeOf))] @@ -49,7 +50,7 @@ pub type Filter = GenericFilter; /// An animated value for the `drop-shadow()` filter. -pub type SimpleShadow = GenericSimpleShadow; +pub type SimpleShadow = GenericSimpleShadow; impl ToAnimatedValue for ComputedBoxShadowList { type AnimatedValue = BoxShadowList; @@ -231,7 +232,7 @@ impl ToAnimatedZero for SimpleShadow { #[inline] fn to_animated_zero(&self) -> Result { Ok(SimpleShadow { - color: IntermediateColor::transparent(), + color: Color::transparent(), horizontal: self.horizontal.to_animated_zero()?, vertical: self.vertical.to_animated_zero()?, blur: self.blur.to_animated_zero()?, diff --git a/components/style/values/animated/mod.rs b/components/style/values/animated/mod.rs index 464dbf860405..8f505211ab6a 100644 --- a/components/style/values/animated/mod.rs +++ b/components/style/values/animated/mod.rs @@ -24,6 +24,7 @@ use values::computed::NonNegativeNumber as ComputedNonNegativeNumber; use values::computed::PositiveInteger as ComputedPositiveInteger; use values::specified::url::SpecifiedUrl; +pub mod color; pub mod effects; /// Conversion between computed values and intermediate values for animations. diff --git a/components/style/values/computed/color.rs b/components/style/values/computed/color.rs index 8c9a863b2c52..7294ce871905 100644 --- a/components/style/values/computed/color.rs +++ b/components/style/values/computed/color.rs @@ -7,6 +7,8 @@ use cssparser::{Color as CSSParserColor, RGBA}; use std::fmt; use style_traits::ToCss; +use values::animated::ToAnimatedValue; +use values::animated::color::{Color as AnimatedColor, RGBA as AnimatedRGBA}; /// This struct represents a combined color from a numeric color and /// the current foreground color (currentcolor keyword). @@ -21,13 +23,8 @@ pub struct Color { pub foreground_ratio: u8, } -fn blend_color_component(bg: u8, fg: u8, fg_alpha: u8) -> u8 { - let bg_ratio = (u8::max_value() - fg_alpha) as u32; - let fg_ratio = fg_alpha as u32; - let color = bg as u32 * bg_ratio + fg as u32 * fg_ratio; - // Rounding divide the number by 255 - ((color + 127) / 255) as u8 -} +/// Computed value type for the specified RGBAColor. +pub type RGBAColor = RGBA; impl Color { /// Returns a numeric color representing the given RGBA value. @@ -72,6 +69,15 @@ impl Color { if self.is_currentcolor() { return fg_color.clone(); } + + fn blend_color_component(bg: u8, fg: u8, fg_alpha: u8) -> u8 { + let bg_ratio = (u8::max_value() - fg_alpha) as u32; + let fg_ratio = fg_alpha as u32; + let color = bg as u32 * bg_ratio + fg as u32 * fg_ratio; + // Rounding divide the number by 255 + ((color + 127) / 255) as u8 + } + // Common case that alpha channel is equal (usually both are opaque). let fg_ratio = self.foreground_ratio; if self.color.alpha == fg_color.alpha { @@ -140,5 +146,47 @@ impl ToCss for Color { } } -/// Computed value type for the specified RGBAColor. -pub type RGBAColor = RGBA; +impl ToAnimatedValue for Color { + type AnimatedValue = AnimatedColor; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + AnimatedColor { + color: self.color.to_animated_value(), + foreground_ratio: self.foreground_ratio as f32 * (1. / 255.), + } + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + Color { + color: RGBA::from_animated_value(animated.color), + foreground_ratio: (animated.foreground_ratio * 255.).round() as u8, + } + } +} + +impl ToAnimatedValue for RGBA { + type AnimatedValue = AnimatedRGBA; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + AnimatedRGBA::new( + self.red_f32(), + self.green_f32(), + self.blue_f32(), + self.alpha_f32(), + ) + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + // RGBA::from_floats clamps each component values. + RGBA::from_floats( + animated.red, + animated.green, + animated.blue, + animated.alpha, + ) + } +}