Bug 1362897 - Make filter property animatable. r?hiro draft
authorMantaroh Yoshinaga <mantaroh@gmail.com>
Fri, 09 Jun 2017 16:13:26 +0900
changeset 591571 0de1ca5ba95858911f523f72212b54ecf7943c34
parent 591570 e7639cf1a66941390d3ef27a9ed9495b726c6cd8
child 591572 34107e019362b97e926d15da6a3ae7bf4357fad5
push id63091
push usermantaroh@gmail.com
push dateFri, 09 Jun 2017 07:21:29 +0000
reviewershiro
bugs1362897
milestone55.0a1
Bug 1362897 - Make filter property animatable. r?hiro We will use FilterDropShadow instead of Shadow in order to use the Intermediate function. This patch is interpolating the filter, and separate the intermediate define into shadow and shadow_list, because we can't specify the shadow list to DropShadow. MozReview-Commit-ID: GwANDBY1Gfb
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/properties/longhand/effects.mako.rs
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -3453,16 +3453,25 @@ fn static_assert() {
             } else {
                 Some(Au(self.gecko.mClip.x + self.gecko.mClip.width))
             };
 
             Either::First(ClipRect { top: top, right: right, bottom: bottom, left: left, })
         }
     }
 
+    <%
+    # This array is several filter function which has percentage or
+    # number value for function of clone / set.
+    # The setting / cloning process of other function(e.g. Blur / HueRotate) is
+    # different from these function. So this array don't include such function.
+    FILTER_FUNCTIONS = [ 'Brightness', 'Contrast', 'Grayscale', 'Invert',
+                         'Opacity', 'Saturate', 'Sepia' ]
+     %>
+
     pub fn set_filter(&mut self, v: longhands::filter::computed_value::T) {
         use properties::longhands::filter::computed_value::Filter::*;
         use gecko_bindings::structs::nsCSSShadowArray;
         use gecko_bindings::structs::nsStyleFilter;
         use gecko_bindings::structs::NS_STYLE_FILTER_BLUR;
         use gecko_bindings::structs::NS_STYLE_FILTER_BRIGHTNESS;
         use gecko_bindings::structs::NS_STYLE_FILTER_CONTRAST;
         use gecko_bindings::structs::NS_STYLE_FILTER_GRAYSCALE;
@@ -3479,75 +3488,124 @@ fn static_assert() {
         }
 
         unsafe {
             Gecko_ResetFilters(&mut self.gecko, v.filters.len());
         }
         debug_assert!(v.filters.len() == self.gecko.mFilters.len());
 
         for (servo, gecko_filter) in v.filters.into_iter().zip(self.gecko.mFilters.iter_mut()) {
-            //TODO: URL, drop-shadow
             match servo {
-                Blur(len)          => fill_filter(NS_STYLE_FILTER_BLUR,
-                                                  CoordDataValue::Coord(len.0),
-                                                  gecko_filter),
-                Brightness(factor) => fill_filter(NS_STYLE_FILTER_BRIGHTNESS,
-                                                  CoordDataValue::Factor(factor),
-                                                  gecko_filter),
-                Contrast(factor)   => fill_filter(NS_STYLE_FILTER_CONTRAST,
-                                                  CoordDataValue::Factor(factor),
-                                                  gecko_filter),
-                Grayscale(factor)  => fill_filter(NS_STYLE_FILTER_GRAYSCALE,
-                                                  CoordDataValue::Factor(factor),
-                                                  gecko_filter),
-                HueRotate(angle)   => fill_filter(NS_STYLE_FILTER_HUE_ROTATE,
-                                                  CoordDataValue::from(angle),
-                                                  gecko_filter),
-                Invert(factor)     => fill_filter(NS_STYLE_FILTER_INVERT,
-                                                  CoordDataValue::Factor(factor),
-                                                  gecko_filter),
-                Opacity(factor)    => fill_filter(NS_STYLE_FILTER_OPACITY,
-                                                  CoordDataValue::Factor(factor),
-                                                  gecko_filter),
-                Saturate(factor)   => fill_filter(NS_STYLE_FILTER_SATURATE,
-                                                  CoordDataValue::Factor(factor),
-                                                  gecko_filter),
-                Sepia(factor)      => fill_filter(NS_STYLE_FILTER_SEPIA,
-                                                  CoordDataValue::Factor(factor),
-                                                  gecko_filter),
+                % for func in FILTER_FUNCTIONS:
+                ${func}(factor) => fill_filter(NS_STYLE_FILTER_${func.upper()},
+                                               CoordDataValue::Factor(factor),
+                                               gecko_filter),
+                % endfor
+                Blur(length) => fill_filter(NS_STYLE_FILTER_BLUR,
+                                            CoordDataValue::Coord(length.0),
+                                            gecko_filter),
+
+                HueRotate(angle) => fill_filter(NS_STYLE_FILTER_HUE_ROTATE,
+                                                CoordDataValue::from(angle),
+                                                gecko_filter),
+
                 DropShadow(shadow) => {
                     gecko_filter.mType = NS_STYLE_FILTER_DROP_SHADOW;
 
                     fn init_shadow(filter: &mut nsStyleFilter) -> &mut nsCSSShadowArray {
                         unsafe {
                             let ref mut union = filter.__bindgen_anon_1;
                             let mut shadow_array: &mut *mut nsCSSShadowArray = union.mDropShadow.as_mut();
                             *shadow_array = Gecko_NewCSSShadowArray(1);
 
                             &mut **shadow_array
                         }
                     }
 
                     let mut gecko_shadow = init_shadow(gecko_filter);
                     gecko_shadow.mArray[0].set_from_shadow(shadow);
-                }
+                },
                 Url(ref url) => {
                     unsafe {
                         bindings::Gecko_nsStyleFilter_SetURLValue(gecko_filter, url.for_ffi());
                     }
-                }
+                },
             }
         }
     }
 
     pub fn copy_filter_from(&mut self, other: &Self) {
         unsafe {
             Gecko_CopyFiltersFrom(&other.gecko as *const _ as *mut _, &mut self.gecko);
         }
     }
+
+    pub fn clone_filter(&self) -> longhands::filter::computed_value::T {
+        use properties::longhands::filter::computed_value::Filter::*;
+        use values::specified::url::SpecifiedUrl;
+        use gecko_bindings::structs::NS_STYLE_FILTER_BLUR;
+        use gecko_bindings::structs::NS_STYLE_FILTER_BRIGHTNESS;
+        use gecko_bindings::structs::NS_STYLE_FILTER_CONTRAST;
+        use gecko_bindings::structs::NS_STYLE_FILTER_GRAYSCALE;
+        use gecko_bindings::structs::NS_STYLE_FILTER_INVERT;
+        use gecko_bindings::structs::NS_STYLE_FILTER_OPACITY;
+        use gecko_bindings::structs::NS_STYLE_FILTER_SATURATE;
+        use gecko_bindings::structs::NS_STYLE_FILTER_SEPIA;
+        use gecko_bindings::structs::NS_STYLE_FILTER_HUE_ROTATE;
+        use gecko_bindings::structs::NS_STYLE_FILTER_DROP_SHADOW;
+        use gecko_bindings::structs::NS_STYLE_FILTER_URL;
+
+        let mut filters = Vec::new();
+        for filter in self.gecko.mFilters.iter(){
+            match filter.mType {
+                % for func in FILTER_FUNCTIONS:
+                NS_STYLE_FILTER_${func.upper()} => {
+                    filters.push(${func}(
+                        GeckoStyleCoordConvertible::from_gecko_style_coord(
+                            &filter.mFilterParameter).unwrap()));
+                },
+                % endfor
+                NS_STYLE_FILTER_BLUR => {
+                    filters.push(Blur(Au::from_gecko_style_coord(
+                        &filter.mFilterParameter).unwrap()));
+                },
+                NS_STYLE_FILTER_HUE_ROTATE => {
+                    filters.push(HueRotate(
+                        GeckoStyleCoordConvertible::from_gecko_style_coord(
+                            &filter.mFilterParameter).unwrap()));
+                },
+                NS_STYLE_FILTER_DROP_SHADOW => {
+                    unsafe {
+                        let ref shadow =
+                            (**filter.__bindgen_anon_1.mDropShadow.as_ref()).mArray[0];
+
+                        filters.push(DropShadow(Shadow {
+                            offset_x: Au(shadow.mXOffset),
+                            offset_y: Au(shadow.mYOffset),
+                            blur_radius: Au(shadow.mRadius),
+                            spread_radius: Au(shadow.mSpread),
+                            inset: shadow.mInset,
+                            color: convert_nscolor_to_rgba(shadow.mColor).into(),
+                        }));
+                    }
+                },
+                NS_STYLE_FILTER_URL => {
+                    unsafe {
+                        let ref url = (**filter.__bindgen_anon_1.mURL.as_ref())._base;
+                        let specified =
+                            (SpecifiedUrl::from_url_value_data(url)).unwrap();
+                        filters.push(Url(specified));
+                    }
+                }
+                _ => {},
+            }
+        }
+        longhands::filter::computed_value::T::new(filters)
+    }
+
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="InheritedBox"
                   skip_longhands="image-orientation">
     // FIXME: Gecko uses a tricky way to store computed value of image-orientation
     //        within an u8. We could inline following glue codes by implementing all
     //        those tricky parts for Servo as well. But, it's not done yet just for
     //        convenience.
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -9,19 +9,22 @@
 use app_units::Au;
 use cssparser::{Parser, RGBA, serialize_identifier};
 use euclid::{Point2D, Size2D};
 #[cfg(feature = "gecko")] use gecko_bindings::bindings::RawServoAnimationValueMap;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::RawGeckoGfxMatrix4x4;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID;
 #[cfg(feature = "gecko")] use gecko_bindings::sugar::ownership::{HasFFI, HasSimpleFFI};
 #[cfg(feature = "gecko")] use gecko_string_cache::Atom;
+#[cfg(feature = "gecko")] use gecko::url::SpecifiedUrl;
 use properties::{CSSWideKeyword, PropertyDeclaration};
 use properties::longhands;
 use properties::longhands::background_size::computed_value::T as BackgroundSizeList;
+use properties::longhands::filter::computed_value::Filter;
+use properties::longhands::filter::computed_value::T as Filters;
 use properties::longhands::font_weight::computed_value::T as FontWeight;
 use properties::longhands::font_stretch::computed_value::T as FontStretch;
 use properties::longhands::text_shadow::computed_value::T as TextShadowList;
 use properties::longhands::box_shadow::computed_value::T as BoxShadowList;
 use properties::longhands::transform::computed_value::ComputedMatrix;
 use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation;
 use properties::longhands::transform::computed_value::T as TransformList;
 use properties::longhands::vertical_align::computed_value::T as VerticalAlign;
@@ -1325,16 +1328,215 @@ impl Animatable for MaxLength {
              MaxLength::LengthOrPercentageOrNone(ref other)) => {
                 this.compute_distance(other)
             },
             _ => Err(()),
         }
     }
 }
 
+#[derive(Clone, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[allow(missing_docs)]
+/// Intermediate type for filter property.
+/// The difference between normal filter type is that this structure uses
+/// IntermediateColor into DropShadow's value.
+pub struct IntermediateFilters { pub filters: Vec<IntermediateFilter> }
+impl IntermediateFilters {
+    #[inline]
+    fn new(filters: Vec<IntermediateFilter>) -> IntermediateFilters {
+        IntermediateFilters { filters: filters, }
+    }
+}
+
+#[derive(Clone, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[allow(missing_docs)]
+pub enum IntermediateFilter {
+    Blur(Au),
+    Brightness(CSSFloat),
+    Contrast(CSSFloat),
+    Grayscale(CSSFloat),
+    HueRotate(Angle),
+    Invert(CSSFloat),
+    Opacity(CSSFloat),
+    Saturate(CSSFloat),
+    Sepia(CSSFloat),
+    % if product == "gecko":
+    DropShadow(IntermediateShadow),
+    Url(SpecifiedUrl),
+    % endif
+}
+<%
+    FILTER_FUNCTIONS = [ 'Blur', 'Brightness', 'Contrast', 'Grayscale',
+                         'HueRotate', 'Invert', 'Opacity', 'Saturate',
+                         'Sepia' ]
+%>
+impl From<Filters> for IntermediateFilters {
+    fn from(filters: Filters) -> IntermediateFilters {
+        use properties::longhands::filter::computed_value::Filter::*;
+        let mut converted_filters = Vec::new();
+        for filter in filters.filters.iter() {
+            match filter {
+                % for func in FILTER_FUNCTIONS:
+                    &${func}(val) => {
+                        converted_filters.push(IntermediateFilter::${func}(val));
+                    }
+                % endfor
+                % if product == "gecko":
+                    &DropShadow(shadow) => {
+                        converted_filters.push(IntermediateFilter::DropShadow(
+                            IntermediateShadow::from(shadow)));
+                    },
+                    &Url(ref url) => {
+                        converted_filters.push(IntermediateFilter::Url(url.clone()));
+                    },
+                % endif
+            }
+        };
+
+        IntermediateFilters { filters: converted_filters }
+    }
+}
+
+impl From<IntermediateFilters> for Filters {
+    fn from(filters: IntermediateFilters) -> Filters {
+        use properties::longhands::filter::computed_value::Filter::*;
+        let mut converted_filters = Vec::new();
+        for filter in filters.filters.iter() {
+            match filter {
+                % for func in FILTER_FUNCTIONS:
+                    &IntermediateFilter::${func}(val) => {
+                        converted_filters.push(Filter::${func}(val));
+                    }
+                % endfor
+                % if product == "gecko":
+                    &IntermediateFilter::DropShadow(shadow) => {
+                        converted_filters.push(DropShadow(Shadow::from(shadow)));
+                    },
+                    &IntermediateFilter::Url(ref url) => {
+                        converted_filters.push(Filter::Url(url.clone()));
+                    },
+                % endif
+            }
+        };
+
+        Filters { filters: converted_filters }
+    }
+}
+
+/// https://drafts.fxtf.org/filters/#animation-of-filters
+fn add_weighted_filter_function_impl(from: &IntermediateFilter,
+                                     to: &IntermediateFilter,
+                                     self_portion: f64,
+                                     other_portion: f64)
+                                     -> Result<IntermediateFilter, ()> {
+    match (from, to) {
+        % for func in [ 'Blur', 'HueRotate' ]:
+            (&IntermediateFilter::${func}(from_value),
+             &IntermediateFilter::${func}(to_value)) => {
+                Ok(IntermediateFilter::${func}(
+                    try!(from_value.add_weighted(&to_value,
+                                                 self_portion,
+                                                 other_portion))))
+           },
+        % endfor
+        % for func in [ 'Grayscale', 'Invert', 'Sepia' ]:
+            (&IntermediateFilter::${func}(from_value),
+             &IntermediateFilter::${func}(to_value)) => {
+                Ok(IntermediateFilter::${func}(try!(
+                    add_weighted_with_initial_val(&from_value,
+                                                  &to_value,
+                                                  self_portion,
+                                                  other_portion,
+                                                  &1.0))))
+            },
+        % endfor
+        % for func in [ 'Brightness', 'Contrast', 'Opacity', 'Saturate' ]:
+            (&IntermediateFilter::${func}(from_value),
+             &IntermediateFilter::${func}(to_value)) => {
+                Ok(IntermediateFilter::${func}(try!(
+                    add_weighted_with_initial_val(&from_value,
+                                                  &to_value,
+                                                  self_portion,
+                                                  other_portion,
+                                                  &0.0))))
+                },
+        % endfor
+        % if product == "gecko":
+            (&IntermediateFilter::DropShadow(from_value),
+             &IntermediateFilter::DropShadow(to_value)) => {
+                Ok(IntermediateFilter::DropShadow(try!(
+                    from_value.add_weighted(&to_value,
+                                            self_portion,
+                                            other_portion))))
+            },
+        % endif
+        _ => {
+            // We can't interpolate to Url() and Uknown function.
+            Err(())
+        },
+    }
+}
+
+/// https://drafts.fxtf.org/filters/#animation-of-filters
+/// The from and to parameter's space is for skipping mako preprocess.
+fn add_weighted_filter_function(from: Option<<&IntermediateFilter>,
+                                to: Option<<&IntermediateFilter>,
+                                self_portion: f64,
+                                other_portion: f64) -> Result<IntermediateFilter, ()> {
+    match (from, to) {
+        (Some(f), Some(t)) => {
+            add_weighted_filter_function_impl(f, t, self_portion, other_portion)
+        },
+        (Some(f), None) => {
+            add_weighted_filter_function_impl(f, f, self_portion, 0.0)
+        },
+        (None, Some(t)) => {
+            add_weighted_filter_function_impl(t, t, other_portion, 0.0)
+        },
+        _ => { Err(()) }
+    }
+}
+
+
+impl Animatable for IntermediateFilters {
+    #[inline]
+    fn add_weighted(&self, other: &Self,
+                    self_portion: f64, other_portion: f64) -> Result<Self, ()> {
+        let mut filters = Vec::new();
+        let mut from_iter = (&self.filters).iter();
+        let mut to_iter = (&(&other).filters).iter();
+
+        let mut from = from_iter.next();
+        let mut to = to_iter.next();
+        while (from,to) != (None, None) {
+            filters.push(try!(add_weighted_filter_function(from,
+                                                           to,
+                                                           self_portion,
+                                                           other_portion)));
+            if from != None {
+                from = from_iter.next();
+            }
+            if to != None {
+                to = to_iter.next();
+            }
+        }
+
+        Ok(IntermediateFilters::new(filters))
+    }
+
+    fn add(&self, other: &Self) -> Result<Self, ()> {
+        let from_list = &self.filters;
+        let to_list = &(&other).filters;
+        let filters = vec![&from_list[..], &to_list[..]].concat();
+        Ok(IntermediateFilters::new(filters))
+    }
+}
+
 /// http://dev.w3.org/csswg/css-transitions/#animtype-font-weight
 impl Animatable for FontWeight {
     #[inline]
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         let a = (*self as u32) as f64;
         let b = (*other as u32) as f64;
         const NORMAL: f64 = 400.;
         let weight = (a - NORMAL) * self_portion + (b - NORMAL) * other_portion + NORMAL;
--- a/servo/components/style/properties/longhand/effects.mako.rs
+++ b/servo/components/style/properties/longhand/effects.mako.rs
@@ -35,18 +35,17 @@
 ${helpers.predefined_type("clip",
                           "ClipRectOrAuto",
                           "computed::ClipRectOrAuto::auto()",
                           animation_value_type="ComputedValue",
                           boxed="True",
                           allow_quirks=True,
                           spec="https://drafts.fxtf.org/css-masking/#clip-property")}
 
-// FIXME: This prop should be animatable
-<%helpers:longhand name="filter" animation_value_type="none" extra_prefixes="webkit"
+<%helpers:longhand name="filter" animation_value_type="IntermediateFilters" extra_prefixes="webkit"
                    flags="CREATES_STACKING_CONTEXT FIXPOS_CB"
                    spec="https://drafts.fxtf.org/filters/#propdef-filter">
     //pub use self::computed_value::T as SpecifiedValue;
     use std::fmt;
     use style_traits::{HasViewportPercentage, ToCss};
     use values::CSSFloat;
     use values::specified::{Angle, Length};
     #[cfg(feature = "gecko")]