servo: Merge #19554 - layout: support tiled gradients (from pyfisch:tiled-gradients1); r=mbrubeck
authorPyfisch <pyfisch@gmail.com>
Thu, 14 Dec 2017 13:20:34 -0600
changeset 448251 a9ba24bfa82d533904745f0bd233dbfde69c8f7b
parent 448250 6cb9414b5d77ff1bc333274b554d7296ab3d5f09
child 448252 950963c8a8e193b0c52f72c5563f38f6b6f0056e
push id8527
push userCallek@gmail.com
push dateThu, 11 Jan 2018 21:05:50 +0000
treeherdermozilla-beta@95342d212a7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #19554 - layout: support tiled gradients (from pyfisch:tiled-gradients1); r=mbrubeck Use background-size, background-position properties to render CSS gradients. Some cleanup in display_list_builder.rs related to gradient calculations. Adds two wpt tests for tiled gradients. Note: For now even gradients with background-repeat: no-repeat are repeated. Sometimes the gradient is not repeated everywhere. Resolves partially #19482. (See the mentioned website for example gradients with these features) See also: #16657 and #10412 Some glitches can be seen in the attached file. I am unsure what the exact intended semantics of [`push_gradient`](https://doc.servo.org/webrender_api/struct.DisplayListBuilder.html#method.push_gradient) are and want to ask the webrender team before building in "workarounds" for the missing gradients. ![half-rhombes](https://user-images.githubusercontent.com/2781017/33958051-b16f964a-e043-11e7-8218-b28388e2cf8d.png) Source-Repo: https://github.com/servo/servo Source-Revision: 6ee8e6a1684d6dbc65933da11ce1a2c8ba660442
servo/components/gfx/display_list/mod.rs
servo/components/layout/display_list_builder.rs
servo/components/layout/webrender_helpers.rs
--- a/servo/components/gfx/display_list/mod.rs
+++ b/servo/components/gfx/display_list/mod.rs
@@ -715,16 +715,25 @@ pub struct Gradient {
 
 #[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
 pub struct GradientDisplayItem {
     /// Fields common to all display item.
     pub base: BaseDisplayItem,
 
     /// Contains all gradient data. Included start, end point and color stops.
     pub gradient: Gradient,
+
+    /// The size of a single gradient tile.
+    ///
+    /// The gradient may fill an entire element background
+    /// but it can be composed from many smaller copys of
+    /// the same gradient.
+    ///
+    /// Without tiles, the tile will be the same size as the background.
+    pub tile: Size2D<Au>,
 }
 
 /// Paints a radial gradient.
 #[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
 pub struct RadialGradient {
     /// The center point of the gradient.
     pub center: Point2D<Au>,
 
@@ -740,16 +749,25 @@ pub struct RadialGradient {
 
 #[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
 pub struct RadialGradientDisplayItem {
     /// Fields common to all display item.
     pub base: BaseDisplayItem,
 
     /// Contains all gradient data.
     pub gradient: RadialGradient,
+
+    /// The size of a single gradient tile.
+    ///
+    /// The gradient may fill an entire element background
+    /// but it can be composed from many smaller copys of
+    /// the same gradient.
+    ///
+    /// Without tiles, the tile will be the same size as the background.
+    pub tile: Size2D<Au>,
 }
 
 /// A normal border, supporting CSS border styles.
 #[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
 pub struct NormalBorder {
     /// Border colors.
     pub color: SideOffsets2D<ColorF>,
 
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -498,40 +498,26 @@ pub trait FragmentDisplayListBuilding {
     /// If the worklet has missing image URLs, it passes them to the image cache for loading.
     fn get_webrender_image_for_paint_worklet(&self,
                                              state: &mut DisplayListBuildState,
                                              style: &ComputedValues,
                                              paint_worklet: &PaintWorklet,
                                              size: Size2D<Au>)
                                              -> Option<WebRenderImageInfo>;
 
-    fn convert_linear_gradient(&self,
-                               bounds: &Rect<Au>,
-                               stops: &[GradientItem],
-                               direction: &LineDirection,
-                               repeating: bool)
-                               -> display_list::Gradient;
-
-    fn convert_radial_gradient(&self,
-                               bounds: &Rect<Au>,
-                               stops: &[GradientItem],
-                               shape: &EndingShape,
-                               center: &Position,
-                               repeating: bool)
-                               -> display_list::RadialGradient;
-
     /// Adds the display items necessary to paint the background linear gradient of this fragment
     /// to the appropriate section of the display list.
     fn build_display_list_for_background_gradient(&self,
                                                   state: &mut DisplayListBuildState,
                                                   display_list_section: DisplayListSection,
                                                   absolute_bounds: &Rect<Au>,
                                                   clip: &LocalClip,
                                                   gradient: &Gradient,
-                                                  style: &ComputedValues);
+                                                  style: &ComputedValues,
+                                                  index: usize);
 
     /// Adds the display items necessary to paint the borders of this fragment to a display list if
     /// necessary.
     fn build_display_list_for_borders_if_applicable(
         &self,
         state: &mut DisplayListBuildState,
         style: &ComputedValues,
         inline_node_info: Option<InlineNodeBorderInfo>,
@@ -814,16 +800,130 @@ fn convert_gradient_stops(gradient_items
         stops.push(GradientStop {
             offset: offset,
             color: stop.color.to_gfx_color()
         })
     }
     stops
 }
 
+fn convert_linear_gradient(size: Size2D<Au>,
+                           stops: &[GradientItem],
+                           direction: LineDirection,
+                           repeating: bool)
+                           -> display_list::Gradient {
+    let angle = match direction {
+        LineDirection::Angle(angle) => angle.radians(),
+        LineDirection::Horizontal(x) => {
+            match x {
+                X::Left => Angle::Deg(270.).radians(),
+                X::Right => Angle::Deg(90.).radians(),
+            }
+        },
+        LineDirection::Vertical(y) => {
+            match y {
+                Y::Top => Angle::Deg(0.).radians(),
+                Y::Bottom => Angle::Deg(180.).radians(),
+            }
+        },
+        LineDirection::Corner(horizontal, vertical) => {
+            // This the angle for one of the diagonals of the box. Our angle
+            // will either be this one, this one + PI, or one of the other
+            // two perpendicular angles.
+            let atan = (size.height.to_f32_px() /
+                        size.width.to_f32_px()).atan();
+            match (horizontal, vertical) {
+                (X::Right, Y::Bottom)
+                    => f32::consts::PI - atan,
+                (X::Left, Y::Bottom)
+                    => f32::consts::PI + atan,
+                (X::Right, Y::Top)
+                    => atan,
+                (X::Left, Y::Top)
+                    => -atan,
+            }
+        }
+    };
+
+    // Get correct gradient line length, based on:
+    // https://drafts.csswg.org/css-images-3/#linear-gradients
+    let dir = Point2D::new(angle.sin(), -angle.cos());
+
+    let line_length = (dir.x * size.width.to_f32_px()).abs() +
+                        (dir.y * size.height.to_f32_px()).abs();
+
+    let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt();
+
+    // This is the vector between the center and the ending point; i.e. half
+    // of the distance between the starting point and the ending point.
+    let delta = Vector2D::new(Au::from_f32_px(dir.x * inv_dir_length * line_length / 2.0),
+                                Au::from_f32_px(dir.y * inv_dir_length * line_length / 2.0));
+
+    // This is the length of the gradient line.
+    let length = Au::from_f32_px(
+        (delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));
+
+    let mut stops = convert_gradient_stops(stops, length);
+
+    // Only clamped gradients need to be fixed because in repeating gradients
+    // there is no "first" or "last" stop because they repeat infinitly in
+    // both directions, so the rendering is always correct.
+    if !repeating {
+        fix_gradient_stops(&mut stops);
+    }
+
+    let center = Point2D::new(size.width / 2, size.height / 2);
+
+    display_list::Gradient {
+        start_point: center - delta,
+        end_point: center + delta,
+        stops: stops,
+        repeating: repeating,
+    }
+}
+
+fn convert_radial_gradient(size: Size2D<Au>,
+                           stops: &[GradientItem],
+                           shape: EndingShape,
+                           center: Position,
+                           repeating: bool)
+                           -> display_list::RadialGradient {
+    let center = Point2D::new(center.horizontal.to_used_value(size.width),
+                                center.vertical.to_used_value(size.height));
+    let radius = match shape {
+        GenericEndingShape::Circle(Circle::Radius(length)) => {
+            let length = Au::from(length);
+            Size2D::new(length, length)
+        },
+        GenericEndingShape::Circle(Circle::Extent(extent)) => {
+            convert_circle_size_keyword(extent, &size, &center)
+        },
+        GenericEndingShape::Ellipse(Ellipse::Radii(x, y)) => {
+            Size2D::new(x.to_used_value(size.width), y.to_used_value(size.height))
+        },
+        GenericEndingShape::Ellipse(Ellipse::Extent(extent)) => {
+            convert_ellipse_size_keyword(extent, &size, &center)
+        },
+    };
+
+    let mut stops = convert_gradient_stops(stops, radius.width);
+    // Repeating gradients have no last stops that can be ignored. So
+    // fixup is not necessary but may actually break the gradient.
+    if !repeating {
+        fix_gradient_stops(&mut stops);
+    }
+
+    display_list::RadialGradient {
+        center: center,
+        radius: radius,
+        stops: stops,
+        repeating: repeating,
+    }
+}
+
 #[inline]
 /// Duplicate the first and last stops if necessary.
 ///
 /// Explanation by pyfisch:
 /// If the last stop is at the same position as the previous stop the
 /// last color is ignored by webrender. This differs from the spec
 /// (I think so). The  implementations of Chrome and Firefox seem
 /// to have the same problem but work fine if the position of the last
@@ -1021,17 +1121,18 @@ impl FragmentDisplayListBuilding for Fra
             match *background_image {
                 Either::First(_) => {}
                 Either::Second(Image::Gradient(ref gradient)) => {
                     self.build_display_list_for_background_gradient(state,
                                                                     display_list_section,
                                                                     &absolute_bounds,
                                                                     &clip,
                                                                     gradient,
-                                                                    style);
+                                                                    style,
+                                                                    i);
                 }
                 Either::Second(Image::Url(ref image_url)) => {
                     if let Some(url) = image_url.url() {
                         let webrender_image =  state.layout_context
                             .get_webrender_image_for_url(self.node,
                                                          url.clone(),
                                                          UsePlaceholder::No);
                         if let Some(webrender_image) = webrender_image {
@@ -1305,172 +1406,77 @@ impl FragmentDisplayListBuilding for Fra
         for url in draw_result.missing_image_urls.drain(..) {
             debug!("Requesting missing image URL {}.", url);
             state.layout_context.get_webrender_image_for_url(self.node, url, UsePlaceholder::No);
         }
 
         Some(webrender_image)
     }
 
-    fn convert_linear_gradient(&self,
-                               bounds: &Rect<Au>,
-                               stops: &[GradientItem],
-                               direction: &LineDirection,
-                               repeating: bool)
-                               -> display_list::Gradient {
-        let angle = match *direction {
-            LineDirection::Angle(angle) => angle.radians(),
-            LineDirection::Horizontal(x) => {
-                match x {
-                    X::Left => Angle::Deg(270.).radians(),
-                    X::Right => Angle::Deg(90.).radians(),
-                }
-            },
-            LineDirection::Vertical(y) => {
-                match y {
-                    Y::Top => Angle::Deg(0.).radians(),
-                    Y::Bottom => Angle::Deg(180.).radians(),
-                }
-            },
-            LineDirection::Corner(horizontal, vertical) => {
-                // This the angle for one of the diagonals of the box. Our angle
-                // will either be this one, this one + PI, or one of the other
-                // two perpendicular angles.
-                let atan = (bounds.size.height.to_f32_px() /
-                            bounds.size.width.to_f32_px()).atan();
-                match (horizontal, vertical) {
-                    (X::Right, Y::Bottom)
-                        => f32::consts::PI - atan,
-                    (X::Left, Y::Bottom)
-                        => f32::consts::PI + atan,
-                    (X::Right, Y::Top)
-                        => atan,
-                    (X::Left, Y::Top)
-                        => -atan,
-                }
-            }
-        };
-
-        // Get correct gradient line length, based on:
-        // https://drafts.csswg.org/css-images-3/#linear-gradients
-        let dir = Point2D::new(angle.sin(), -angle.cos());
-
-        let line_length = (dir.x * bounds.size.width.to_f32_px()).abs() +
-                          (dir.y * bounds.size.height.to_f32_px()).abs();
-
-        let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt();
-
-        // This is the vector between the center and the ending point; i.e. half
-        // of the distance between the starting point and the ending point.
-        let delta = Vector2D::new(Au::from_f32_px(dir.x * inv_dir_length * line_length / 2.0),
-                                  Au::from_f32_px(dir.y * inv_dir_length * line_length / 2.0));
-
-        // This is the length of the gradient line.
-        let length = Au::from_f32_px(
-            (delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));
-
-        let mut stops = convert_gradient_stops(stops, length);
-
-        // Only clamped gradients need to be fixed because in repeating gradients
-        // there is no "first" or "last" stop because they repeat infinitly in
-        // both directions, so the rendering is always correct.
-        if !repeating {
-            fix_gradient_stops(&mut stops);
-        }
-
-        let center = Point2D::new(bounds.size.width / 2, bounds.size.height / 2);
-
-        display_list::Gradient {
-            start_point: center - delta,
-            end_point: center + delta,
-            stops: stops,
-            repeating: repeating,
-        }
-    }
-
-    fn convert_radial_gradient(&self,
-                               bounds: &Rect<Au>,
-                               stops: &[GradientItem],
-                               shape: &EndingShape,
-                               center: &Position,
-                               repeating: bool)
-                               -> display_list::RadialGradient {
-        let center = Point2D::new(center.horizontal.to_used_value(bounds.size.width),
-                                  center.vertical.to_used_value(bounds.size.height));
-        let radius = match *shape {
-            GenericEndingShape::Circle(Circle::Radius(length)) => {
-                let length = Au::from(length);
-                Size2D::new(length, length)
-            },
-            GenericEndingShape::Circle(Circle::Extent(extent)) => {
-                convert_circle_size_keyword(extent, &bounds.size, &center)
-            },
-            GenericEndingShape::Ellipse(Ellipse::Radii(x, y)) => {
-                Size2D::new(x.to_used_value(bounds.size.width), y.to_used_value(bounds.size.height))
-            },
-            GenericEndingShape::Ellipse(Ellipse::Extent(extent)) => {
-                convert_ellipse_size_keyword(extent, &bounds.size, &center)
-            },
-        };
-
-        let mut stops = convert_gradient_stops(stops, radius.width);
-        // Repeating gradients have no last stops that can be ignored. So
-        // fixup is not necessary but may actually break the gradient.
-        if !repeating {
-            fix_gradient_stops(&mut stops);
-        }
-
-        display_list::RadialGradient {
-            center: center,
-            radius: radius,
-            stops: stops,
-            repeating: repeating,
-        }
-    }
-
     fn build_display_list_for_background_gradient(&self,
                                                   state: &mut DisplayListBuildState,
                                                   display_list_section: DisplayListSection,
                                                   absolute_bounds: &Rect<Au>,
                                                   clip: &LocalClip,
                                                   gradient: &Gradient,
-                                                  style: &ComputedValues) {
+                                                  style: &ComputedValues,
+                                                  index: usize) {
+        let bg = style.get_background();
+        let bg_size = get_cyclic(&bg.background_size.0, index).clone();
+        let bg_position_x = get_cyclic(&bg.background_position_x.0, index).clone();
+        let bg_position_y = get_cyclic(&bg.background_position_y.0, index).clone();
         let border = self.border_width().to_physical(style.writing_mode);
+
         let mut bounds = *absolute_bounds;
-        bounds.origin.x = bounds.origin.x + border.left;
-        bounds.origin.y = bounds.origin.y + border.top;
+        bounds.origin.x = bounds.origin.x + border.left + bg_position_x.to_used_value(bounds.size.width);
+        bounds.origin.y = bounds.origin.y + border.top + bg_position_y.to_used_value(bounds.size.height);
         bounds.size.width = bounds.size.width - border.horizontal();
         bounds.size.height = bounds.size.height - border.vertical();
 
+        let tile = match bg_size {
+            BackgroundSize::Cover | BackgroundSize::Contain => bounds.size,
+            BackgroundSize::Explicit { width, height } => {
+                Size2D::new(
+                    MaybeAuto::from_style(width, bounds.size.width)
+                        .specified_or_default(bounds.size.width),
+                    MaybeAuto::from_style(height, bounds.size.height)
+                        .specified_or_default(bounds.size.height))
+            }
+        };
+
         let base = state.create_base_display_item(&bounds,
                                                   *clip,
                                                   self.node,
                                                   style.get_cursor(Cursor::Default),
                                                   display_list_section);
 
         let display_item = match gradient.kind {
-            GradientKind::Linear(ref angle_or_corner) => {
-                let gradient = self.convert_linear_gradient(&bounds,
-                                                            &gradient.items[..],
-                                                            angle_or_corner,
-                                                            gradient.repeating);
+            GradientKind::Linear(angle_or_corner) => {
+                let gradient = convert_linear_gradient(
+                    tile,
+                    &gradient.items[..],
+                    angle_or_corner,
+                    gradient.repeating);
                 DisplayItem::Gradient(Box::new(GradientDisplayItem {
                     base: base,
                     gradient: gradient,
+                    tile: tile,
                 }))
             }
-            GradientKind::Radial(ref shape, ref center, _angle) => {
-                let gradient = self.convert_radial_gradient(&bounds,
-                                                            &gradient.items[..],
-                                                            shape,
-                                                            center,
-                                                            gradient.repeating);
+            GradientKind::Radial(shape, center, _angle) => {
+                let gradient = convert_radial_gradient(
+                    tile,
+                    &gradient.items[..],
+                    shape,
+                    center,
+                    gradient.repeating);
                 DisplayItem::RadialGradient(Box::new(RadialGradientDisplayItem {
                     base: base,
                     gradient: gradient,
+                    tile: tile,
                 }))
             }
         };
         state.add_display_item(display_item);
     }
 
     fn build_display_list_for_box_shadow_if_applicable(&self,
                                                        state: &mut DisplayListBuildState,
@@ -1589,38 +1595,38 @@ impl FragmentDisplayListBuilding for Fra
                         style: border_style,
                         radius: build_border_radius(&bounds, border_style_struct),
                     }),
                 })));
             }
             Either::Second(Image::Gradient(ref gradient)) => {
                 match gradient.kind {
                     GradientKind::Linear(angle_or_corner) => {
-                        let grad = self.convert_linear_gradient(&bounds,
-                                                                &gradient.items[..],
-                                                                &angle_or_corner,
-                                                                gradient.repeating);
+                        let grad = convert_linear_gradient(bounds.size,
+                                                           &gradient.items[..],
+                                                           angle_or_corner,
+                                                           gradient.repeating);
 
                         state.add_display_item(DisplayItem::Border(Box::new(BorderDisplayItem {
                             base: base,
                             border_widths: border.to_physical(style.writing_mode),
                             details: BorderDetails::Gradient(display_list::GradientBorder {
                                 gradient: grad,
 
                                 // TODO(gw): Support border-image-outset
                                 outset: SideOffsets2D::zero(),
                             }),
                         })));
                     }
-                    GradientKind::Radial(ref shape, ref center, _angle) => {
-                        let grad = self.convert_radial_gradient(&bounds,
-                                                                &gradient.items[..],
-                                                                shape,
-                                                                center,
-                                                                gradient.repeating);
+                    GradientKind::Radial(shape, center, _angle) => {
+                        let grad = convert_radial_gradient(bounds.size,
+                                                           &gradient.items[..],
+                                                           shape,
+                                                           center,
+                                                           gradient.repeating);
                         state.add_display_item(DisplayItem::Border(Box::new(BorderDisplayItem {
                             base: base,
                             border_widths: border.to_physical(style.writing_mode),
                             details: BorderDetails::RadialGradient(
                                 display_list::RadialGradientBorder {
                                     gradient: grad,
 
                                     // TODO(gw): Support border-image-outset
--- a/servo/components/layout/webrender_helpers.rs
+++ b/servo/components/layout/webrender_helpers.rs
@@ -423,49 +423,47 @@ impl WebRenderDisplayItemConverter for D
                            outset: gradient.outset,
                        })
                     }
                 };
 
                 builder.push_border(&self.prim_info(), widths, details);
             }
             DisplayItem::Gradient(ref item) => {
-                let rect = item.base.bounds;
                 let start_point = item.gradient.start_point.to_pointf();
                 let end_point = item.gradient.end_point.to_pointf();
                 let extend_mode = if item.gradient.repeating {
                     ExtendMode::Repeat
                 } else {
                     ExtendMode::Clamp
                 };
                 let gradient = builder.create_gradient(start_point,
                                                        end_point,
                                                        item.gradient.stops.clone(),
                                                        extend_mode);
                 builder.push_gradient(&self.prim_info(),
                                       gradient,
-                                      rect.size.to_sizef(),
+                                      item.tile.to_sizef(),
                                       webrender_api::LayoutSize::zero());
             }
             DisplayItem::RadialGradient(ref item) => {
-                let rect = item.base.bounds;
                 let center = item.gradient.center.to_pointf();
                 let radius = item.gradient.radius.to_sizef();
                 let extend_mode = if item.gradient.repeating {
                     ExtendMode::Repeat
                 } else {
                     ExtendMode::Clamp
                 };
                 let gradient = builder.create_radial_gradient(center,
                                                               radius,
                                                               item.gradient.stops.clone(),
                                                               extend_mode);
                 builder.push_radial_gradient(&self.prim_info(),
                                              gradient,
-                                             rect.size.to_sizef(),
+                                             item.tile.to_sizef(),
                                              webrender_api::LayoutSize::zero());
             }
             DisplayItem::Line(ref item) => {
                 builder.push_line(&self.prim_info(),
                                   // TODO(gw): Use a better estimate for wavy line thickness.
                                   (0.33 * item.base.bounds.size.height.to_f32_px()).ceil(),
                                   webrender_api::LineOrientation::Horizontal,
                                   &item.color,