Webrender: Are Border Gradients Sufficiently Expressive?

Created on 13 Oct 2017  路  4Comments  路  Source: servo/webrender

I'm still taking this all in, but here's a weird case that @mstange believes can't be captured with the arguments we pass to wr:

screen shot 2017-10-13 at 4 03 59 pm

<!DOCTYPE html>
<meta charset="utf-8">
<title>border-image with linear-gradient</title>
<style>

div {
  border: 30px solid;
  box-sizing: border-box;
  width: 200px;
  height: 200px;
  border-image: linear-gradient(45deg, red 50px, yellow 50px, yellow 150px, red 150px) 50;
}

</style>

<div></div>
moderate enhancement

Most helpful comment

Here's the relevant part of the API:

#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct NinePatchDescriptor {
    pub width: u32,
    pub height: u32,
    pub slice: SideOffsets2D<u32>,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct ImageBorder {
    pub image_key: ImageKey,
    pub patch: NinePatchDescriptor,
    /// Controls whether the center of the 9 patch image is
    /// rendered or ignored.
    pub fill: bool,
    pub outset: SideOffsets2D<f32>,
    pub repeat_horizontal: RepeatMode,
    pub repeat_vertical: RepeatMode,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct GradientBorder {
    pub gradient: Gradient,
    pub outset: SideOffsets2D<f32>,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct RadialGradientBorder {
    pub gradient: RadialGradient,
    pub outset: SideOffsets2D<f32>,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum BorderDetails {
    Normal(NormalBorder),
    Image(ImageBorder),
    Gradient(GradientBorder),
    RadialGradient(RadialGradientBorder),
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct BorderDisplayItem {
    pub widths: BorderWidths,
    pub details: BorderDetails,
}

So NinePatchDescriptor, which is used by ImageBorder but not by the gradient borders, does have width and height properties that describe the source image size. These values will also be needed for gradient borders. Furthermore, these properties ofImageBorder are needed for the gradient variants as well:

    pub fill: bool,
    pub repeat_horizontal: RepeatMode,
    pub repeat_vertical: RepeatMode,

And I don't believe the outset property is even needed for ImageBorder; it's a layout thing that can be baked into the border rect and the border widths by the caller. So here's what I'd recommend as the new API:

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum NinePartBorderSource {
    Image(ImageKey),
    Gradient(Gradient),
    RadialGradient(RadialGradient),
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct NinePartBorder {
    /// Describes what to use as the 9-part source image. If this is an image,
    /// it will be stretched to fill the size given by width x height. If this
    /// is a type of gradient, the 9-part source image is obtained by rendering
    /// the gradient into a conceptual surface of dimensions width x height.
    pub source: NinePartBorderSource,
    /// The width of the 9-part image.
    pub width: u32,
    /// The height of the 9-part image.
    pub height: u32,
    /// Distances from each edge where the image should be sliced up. These
    /// values are in 9-part-image space (the same space as width and height),
    /// and the resulting image parts will be used to fill the corresponding
    /// parts of the border as given by the border widths. This can lead to
    /// stretching.
    /// Slices can be overlapping. In that case, the same pixels from the
    /// 9-part image will show up in multiple parts of the resulting border.
    pub slice: SideOffsets2D<u32>,
    /// Controls whether the center of the 9 patch image is rendered or
    /// ignored. The center is never rendered if the slices are overlapping.
    pub fill: bool,
    /// Determines what happens if the horizontal side parts of the 9-part
    /// image have a different size than the horizontal parts of the border.
    pub repeat_horizontal: RepeatMode,
    /// Determines what happens if the vertical side parts of the 9-part
    /// image have a different size than the vertical parts of the border.
    pub repeat_vertical: RepeatMode,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum BorderDetails {
    Normal(NormalBorder),
    NinePart(NinePartBorder),
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct BorderDisplayItem {
    pub widths: BorderWidths,
    pub details: BorderDetails,
}

All 4 comments

CC @eqrion @emilio

In particular I don't think the current API (or at least the part of the API that is exposed through wrench YAML) allows us to specify the intrinsic size of the gradient image.

For correct rendering of border-image gradients, the following pieces of information are necessary:

  • The size of the gradient image
  • The gradient color stops, with their positions as fractions relative to "gradient image" space
  • The slice insets in "gradient image" space
  • The size of the resulting rendering, in local space
  • The slice insets for the result, in local space
  • General border-image features like "should the middle of the 9-piece be filled" and "should I stretch, repeat or round the side parts"

Here's the relevant part of the API:

#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct NinePatchDescriptor {
    pub width: u32,
    pub height: u32,
    pub slice: SideOffsets2D<u32>,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct ImageBorder {
    pub image_key: ImageKey,
    pub patch: NinePatchDescriptor,
    /// Controls whether the center of the 9 patch image is
    /// rendered or ignored.
    pub fill: bool,
    pub outset: SideOffsets2D<f32>,
    pub repeat_horizontal: RepeatMode,
    pub repeat_vertical: RepeatMode,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct GradientBorder {
    pub gradient: Gradient,
    pub outset: SideOffsets2D<f32>,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct RadialGradientBorder {
    pub gradient: RadialGradient,
    pub outset: SideOffsets2D<f32>,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum BorderDetails {
    Normal(NormalBorder),
    Image(ImageBorder),
    Gradient(GradientBorder),
    RadialGradient(RadialGradientBorder),
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct BorderDisplayItem {
    pub widths: BorderWidths,
    pub details: BorderDetails,
}

So NinePatchDescriptor, which is used by ImageBorder but not by the gradient borders, does have width and height properties that describe the source image size. These values will also be needed for gradient borders. Furthermore, these properties ofImageBorder are needed for the gradient variants as well:

    pub fill: bool,
    pub repeat_horizontal: RepeatMode,
    pub repeat_vertical: RepeatMode,

And I don't believe the outset property is even needed for ImageBorder; it's a layout thing that can be baked into the border rect and the border widths by the caller. So here's what I'd recommend as the new API:

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum NinePartBorderSource {
    Image(ImageKey),
    Gradient(Gradient),
    RadialGradient(RadialGradient),
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct NinePartBorder {
    /// Describes what to use as the 9-part source image. If this is an image,
    /// it will be stretched to fill the size given by width x height. If this
    /// is a type of gradient, the 9-part source image is obtained by rendering
    /// the gradient into a conceptual surface of dimensions width x height.
    pub source: NinePartBorderSource,
    /// The width of the 9-part image.
    pub width: u32,
    /// The height of the 9-part image.
    pub height: u32,
    /// Distances from each edge where the image should be sliced up. These
    /// values are in 9-part-image space (the same space as width and height),
    /// and the resulting image parts will be used to fill the corresponding
    /// parts of the border as given by the border widths. This can lead to
    /// stretching.
    /// Slices can be overlapping. In that case, the same pixels from the
    /// 9-part image will show up in multiple parts of the resulting border.
    pub slice: SideOffsets2D<u32>,
    /// Controls whether the center of the 9 patch image is rendered or
    /// ignored. The center is never rendered if the slices are overlapping.
    pub fill: bool,
    /// Determines what happens if the horizontal side parts of the 9-part
    /// image have a different size than the horizontal parts of the border.
    pub repeat_horizontal: RepeatMode,
    /// Determines what happens if the vertical side parts of the 9-part
    /// image have a different size than the vertical parts of the border.
    pub repeat_vertical: RepeatMode,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum BorderDetails {
    Normal(NormalBorder),
    NinePart(NinePartBorder),
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct BorderDisplayItem {
    pub widths: BorderWidths,
    pub details: BorderDetails,
}

Thanks for doing that detailed write up, sounds good!

Was this page helpful?
0 / 5 - 0 ratings