Skip to content

Add capabilities required for SVG filters #1880

Open
@mstange

Description

@mstange

https://bugzilla.mozilla.org/show_bug.cgi?id=1409486

This isn't needed in the short term but it's good to keep in mind for the longer term.

SVG filters allow a bunch of things that CSS filters don't:

  • Instead of building up a single chain of filters, you build up a graph of filter nodes ("filter primitives"). Each node can have zero, one or two input nodes.
  • There are primitive types which don't take an input: Flood, Turbulence, and Image.
  • There are primitive types which take two inputs: Composite, ArithmeticCombine, Blend, and DisplacementMap.
  • You can clip, move, and tile (repeat) things.
  • In SVG filters, each primitive has a color space assigned in which it's supposed to operate. I suggest handling this mostly in the caller, by inserting appropriate color space conversion filters between nodes where the color space changes.

I sat down today and sketched out an API. The biggest problem I see here are the dynamically-sized arrays. Those are needed in three places: Two of the component transfer filters accept a color table with a dynamic length, and the convolve matrix filter accepts a convolution matrix of dynamic size. I'm not sure if they are going to be a problem for serialization, but the way we handle pushing separate iterators (e.g. for the filter list itself) makes me think that they are.

References:

Proposed WebRender API:

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum FilterPrimitiveInput {
    Original,
    TransparentBlack,
    OutputOfPrimitiveIndex(usize),
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct ImagePrimitive {
    pub image: ImageKey,
    pub image_rendering: ImageRendering,
    pub rect: LayoutRect,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct TurbulencePrimitive {
    pub base_frequency: f32,
    pub num_octaves: u32,
    pub stitchable: bool,
    pub seed: u32,
    pub output_rect: LayoutRect,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct ClipPrimitive {
    pub input: FilterPrimitiveInput,
    pub clip_rect: LayoutRect,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct TilePrimitive {
    pub input: FilterPrimitiveInput,
    pub source_rect: LayoutRect,
}

#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub struct LinearTransferFunction {
    pub slope: f32,
    pub intercept: f32,
}

#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub struct GammaTransferFunction {
    pub offset: f32,
    pub amplitude: f32,
    pub exponent: f32,
}

#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub enum ComponentTransferFunction {
    Identity,
    Linear(LinearTransferFunction),
    Gamma(GammaTransferFunction),
    Discrete(Vec<f32>),
    Table(Vec<f32>),
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct ComponentTransferPrimitive {
    pub input: FilterPrimitiveInput,
    pub red_function: ComponentTransferFunction,
    pub green_function: ComponentTransferFunction,
    pub blue_function: ComponentTransferFunction,
    pub alpha_function: ComponentTransferFunction,
}

#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub enum ColorMatrixDetails {
    Brightness(f32),
    Contrast(f32),
    Grayscale(f32),
    HueRotate(f32),
    Invert(f32),
    Saturate(f32),
    Sepia(f32),
    Matrix([f32; 20]),
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct ColorMatrixPrimitive {
    pub input: FilterPrimitiveInput,
    pub details: ColorMatrixDetails,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum MorphologyOp {
    Erode,
    Dilate,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct MorphologyPrimitive {
    pub input: FilterPrimitiveInput,
    pub horizontal_radius: f32,
    pub vertical_radius: f32,
    pub op: MorphologyOp,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum FilterPrimitiveEdgeMode {
    /// Pixels outside the input are treated as transparent black.
    None,
    /// Pixels outside the given rect are treated as having the same color as
    /// the closest pixel along the edge of the rect. Conceptually, the
    /// edge pixel rows and columns are repeated outwards.
    /// Pixels outside the input but inside the given rect are treated as
    /// transparent black.
    RepeatEdgesOfRect(LayoutRect),
    /// Sampling wraps around to the other side of the given rect.
    /// Pixels outside the input but inside the given rect are treated as
    /// transparent black.
    Wrap(LayoutRect),
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct BlurFilterPrimitive {
    pub input: FilterPrimitiveInput,
    pub edge_mode: FilterPrimitiveEdgeMode,
    pub horizontal_stdev: f32,
    pub vertical_stdev: f32,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct ConvolvePrimitive {
    pub input: FilterPrimitiveInput,
    pub edge_mode: FilterPrimitiveEdgeMode,
    pub kernel_size: (u32, u32),
    pub kernel_matrix: Vec<f32>,
    pub divisor: f32,
    pub bias: f32,
    pub target: (u32, u32),
    pub kernel_unit_length: LayoutSize,
    pub preserve_alpha: bool,
}

#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub enum LightingDetails {
    /// Diffuse lighting with the given diffuse constant.
    Diffuse(f32),
    /// Specular lighting with the given specular constant and specular exponent.
    Specular(f32, f32),
}

#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub struct PointLight {
    pub position: (LayoutPixel, LayoutPixel, LayoutPixel),
}

#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub struct SpotLight {
    pub position: (LayoutPixel, LayoutPixel, LayoutPixel),
    pub points_at: (LayoutPixel, LayoutPixel, LayoutPixel),
    pub focus: f32,
    pub limiting_cone_angle: f32,
}

#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub struct DistantLight {
    pub azimuth: f32,
    pub elevation: f32,
}

#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub enum Light {
    Point(PointLight),
    Spot(SpotLight),
    Distant(DistantLight),
}

#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub struct LightingPrimitive {
    pub lighting: LightingDetails,
    pub light: Light,
    pub surface_scale: f32,
    pub kernel_unit_length: LayoutSize,
    pub color: ColorF,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum CompositePrimitiveOp {
    Over,
    Atop,
    Out,
    In,
    Xor,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct CompositePrimitive {
    pub inputs: (FilterPrimitiveInput, FilterPrimitiveInput),
    pub op: CompositePrimitiveOp,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct ArithmeticCombinePrimitive {
    pub inputs: (FilterPrimitiveInput, FilterPrimitiveInput),
    pub k1: f32,
    pub k2: f32,
    pub k3: f32,
    pub k4: f32,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct BlendPrimitive {
    pub inputs: (FilterPrimitiveInput, FilterPrimitiveInput),
    pub mode: MixBlendMode,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum ColorChannel {
    Red,
    Green,
    Blue,
    Alpha,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct DisplacementMapPrimitive {
    pub input: FilterPrimitiveInput,
    pub map: FilterPrimitiveInput,
    pub x_channel: ColorChannel,
    pub y_channel: ColorChannel,
    pub scale: f32,
}

/// The filter graph is stored as a Vec<FilterPrimitive>. Primitives that take
/// inputs refer to other primitives via their index in this Vec.
/// All coordinates are given in LayoutPixels, in the local space of the
/// stacking context that this filter is applied to, *inside* that stacking
/// context's transform. (In the CSS model, filters apply before transforms,
/// e.g. if you blur and skew an element, the result will have an uneven blur.)
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub enum FilterPrimitive {
    // The first three primitive types aren't really filters, they're
    // generators. They render something without needing an input.
    Flood(ColorF),
    Image(ImagePrimitive),
    Turbulence(TurbulencePrimitive),
    // The following primitive types affect the geometry but not the colors.
    Offset(LayoutPoint),
    Clip(ClipPrimitive),
    Tile(TilePrimitive),
    // These two types handle sRGB <-> linearRGB conversion which is required
    // for SVG filters.
    LinearToSRGB(FilterPrimitiveInput),
    SRGBToLinear(FilterPrimitiveInput),
    // This primitive is a wrapper that discards the RGB channels of its inputs
    // and treats them as black.
    ToAlpha(FilterPrimitiveInput)
    // The next primitive types need a single input.
    Opacity(PropertyBinding<f32>),
    ComponentTransfer(ComponentTransferPrimitive),
    ColorMatrix(ColorMatrixPrimitive),
    Morphology(MorphologyPrimitive),
    Blur(BlurFilterPrimitive),
    Convolve(ConvolvePrimitive),
    Lighting(LightingPrimitive),
    // These primitive types combine two inputs.
    Composite(CompositePrimitive),
    ArithmeticCombine(ArithmeticCombinePrimitive),
    Blend(BlendPrimitive),
    DisplacementMap(DisplacementMapPrimitive),
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions