summaryrefslogtreecommitdiff
path: root/rsvg/src/filters/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rsvg/src/filters/mod.rs')
-rw-r--r--rsvg/src/filters/mod.rs381
1 files changed, 381 insertions, 0 deletions
diff --git a/rsvg/src/filters/mod.rs b/rsvg/src/filters/mod.rs
new file mode 100644
index 00000000..f0fee772
--- /dev/null
+++ b/rsvg/src/filters/mod.rs
@@ -0,0 +1,381 @@
+//! Entry point for the CSS filters infrastructure.
+
+use cssparser::{BasicParseError, Parser};
+use markup5ever::{expanded_name, local_name, namespace_url, ns};
+use std::rc::Rc;
+use std::time::Instant;
+
+use crate::bbox::BoundingBox;
+use crate::document::AcquiredNodes;
+use crate::drawing_ctx::DrawingCtx;
+use crate::element::{set_attribute, ElementTrait};
+use crate::error::{ParseError, RenderingError};
+use crate::filter::UserSpaceFilter;
+use crate::length::*;
+use crate::node::Node;
+use crate::paint_server::UserSpacePaintSource;
+use crate::parsers::{CustomIdent, Parse, ParseValue};
+use crate::properties::ColorInterpolationFilters;
+use crate::session::Session;
+use crate::surface_utils::shared_surface::{SharedImageSurface, SurfaceType};
+use crate::transform::Transform;
+use crate::xml::Attributes;
+
+mod bounds;
+use self::bounds::BoundsBuilder;
+
+pub mod context;
+use self::context::{FilterContext, FilterOutput, FilterResult};
+
+mod error;
+use self::error::FilterError;
+pub use self::error::FilterResolveError;
+
+/// A filter primitive interface.
+pub trait FilterEffect: ElementTrait {
+ fn resolve(
+ &self,
+ acquired_nodes: &mut AcquiredNodes<'_>,
+ node: &Node,
+ ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError>;
+}
+
+pub mod blend;
+pub mod color_matrix;
+pub mod component_transfer;
+pub mod composite;
+pub mod convolve_matrix;
+pub mod displacement_map;
+pub mod drop_shadow;
+pub mod flood;
+pub mod gaussian_blur;
+pub mod image;
+pub mod lighting;
+pub mod merge;
+pub mod morphology;
+pub mod offset;
+pub mod tile;
+pub mod turbulence;
+
+pub struct FilterSpec {
+ pub user_space_filter: UserSpaceFilter,
+ pub primitives: Vec<UserSpacePrimitive>,
+}
+
+/// Resolved parameters for each filter primitive.
+///
+/// These gather all the data that a primitive may need during rendering:
+/// the `feFoo` element's attributes, any computed values from its properties,
+/// and parameters extracted from the element's children (for example,
+/// `feMerge` gathers info from its `feMergNode` children).
+pub enum PrimitiveParams {
+ Blend(blend::Blend),
+ ColorMatrix(color_matrix::ColorMatrix),
+ ComponentTransfer(component_transfer::ComponentTransfer),
+ Composite(composite::Composite),
+ ConvolveMatrix(convolve_matrix::ConvolveMatrix),
+ DiffuseLighting(lighting::DiffuseLighting),
+ DisplacementMap(displacement_map::DisplacementMap),
+ Flood(flood::Flood),
+ GaussianBlur(gaussian_blur::GaussianBlur),
+ Image(image::Image),
+ Merge(merge::Merge),
+ Morphology(morphology::Morphology),
+ Offset(offset::Offset),
+ SpecularLighting(lighting::SpecularLighting),
+ Tile(tile::Tile),
+ Turbulence(turbulence::Turbulence),
+}
+
+impl PrimitiveParams {
+ /// Returns a human-readable name for a primitive.
+ #[rustfmt::skip]
+ fn name(&self) -> &'static str {
+ use PrimitiveParams::*;
+ match self {
+ Blend(..) => "feBlend",
+ ColorMatrix(..) => "feColorMatrix",
+ ComponentTransfer(..) => "feComponentTransfer",
+ Composite(..) => "feComposite",
+ ConvolveMatrix(..) => "feConvolveMatrix",
+ DiffuseLighting(..) => "feDiffuseLighting",
+ DisplacementMap(..) => "feDisplacementMap",
+ Flood(..) => "feFlood",
+ GaussianBlur(..) => "feGaussianBlur",
+ Image(..) => "feImage",
+ Merge(..) => "feMerge",
+ Morphology(..) => "feMorphology",
+ Offset(..) => "feOffset",
+ SpecularLighting(..) => "feSpecularLighting",
+ Tile(..) => "feTile",
+ Turbulence(..) => "feTurbulence",
+ }
+ }
+}
+
+/// The base filter primitive node containing common properties.
+#[derive(Default, Clone)]
+pub struct Primitive {
+ pub x: Option<Length<Horizontal>>,
+ pub y: Option<Length<Vertical>>,
+ pub width: Option<ULength<Horizontal>>,
+ pub height: Option<ULength<Vertical>>,
+ pub result: Option<CustomIdent>,
+}
+
+pub struct ResolvedPrimitive {
+ pub primitive: Primitive,
+ pub params: PrimitiveParams,
+}
+
+/// A fully resolved filter primitive in user-space coordinates.
+pub struct UserSpacePrimitive {
+ x: Option<f64>,
+ y: Option<f64>,
+ width: Option<f64>,
+ height: Option<f64>,
+ result: Option<CustomIdent>,
+
+ params: PrimitiveParams,
+}
+
+/// An enumeration of possible inputs for a filter primitive.
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub enum Input {
+ Unspecified,
+ SourceGraphic,
+ SourceAlpha,
+ BackgroundImage,
+ BackgroundAlpha,
+ FillPaint,
+ StrokePaint,
+ FilterOutput(CustomIdent),
+}
+
+enum_default!(Input, Input::Unspecified);
+
+impl Parse for Input {
+ fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
+ parser
+ .try_parse(|p| {
+ parse_identifiers!(
+ p,
+ "SourceGraphic" => Input::SourceGraphic,
+ "SourceAlpha" => Input::SourceAlpha,
+ "BackgroundImage" => Input::BackgroundImage,
+ "BackgroundAlpha" => Input::BackgroundAlpha,
+ "FillPaint" => Input::FillPaint,
+ "StrokePaint" => Input::StrokePaint,
+ )
+ })
+ .or_else(|_: BasicParseError<'_>| {
+ let ident = CustomIdent::parse(parser)?;
+ Ok(Input::FilterOutput(ident))
+ })
+ }
+}
+
+impl ResolvedPrimitive {
+ pub fn into_user_space(self, params: &NormalizeParams) -> UserSpacePrimitive {
+ let x = self.primitive.x.map(|l| l.to_user(params));
+ let y = self.primitive.y.map(|l| l.to_user(params));
+ let width = self.primitive.width.map(|l| l.to_user(params));
+ let height = self.primitive.height.map(|l| l.to_user(params));
+
+ UserSpacePrimitive {
+ x,
+ y,
+ width,
+ height,
+ result: self.primitive.result,
+ params: self.params,
+ }
+ }
+}
+
+impl UserSpacePrimitive {
+ /// Validates attributes and returns the `BoundsBuilder` for bounds computation.
+ #[inline]
+ fn get_bounds(&self, ctx: &FilterContext) -> BoundsBuilder {
+ BoundsBuilder::new(self.x, self.y, self.width, self.height, ctx.paffine())
+ }
+}
+
+impl Primitive {
+ fn parse_standard_attributes(
+ &mut self,
+ attrs: &Attributes,
+ session: &Session,
+ ) -> (Input, Input) {
+ let mut input_1 = Input::Unspecified;
+ let mut input_2 = Input::Unspecified;
+
+ for (attr, value) in attrs.iter() {
+ match attr.expanded() {
+ expanded_name!("", "x") => set_attribute(&mut self.x, attr.parse(value), session),
+ expanded_name!("", "y") => set_attribute(&mut self.y, attr.parse(value), session),
+ expanded_name!("", "width") => {
+ set_attribute(&mut self.width, attr.parse(value), session)
+ }
+ expanded_name!("", "height") => {
+ set_attribute(&mut self.height, attr.parse(value), session)
+ }
+ expanded_name!("", "result") => {
+ set_attribute(&mut self.result, attr.parse(value), session)
+ }
+ expanded_name!("", "in") => set_attribute(&mut input_1, attr.parse(value), session),
+ expanded_name!("", "in2") => {
+ set_attribute(&mut input_2, attr.parse(value), session)
+ }
+ _ => (),
+ }
+ }
+
+ (input_1, input_2)
+ }
+
+ pub fn parse_no_inputs(&mut self, attrs: &Attributes, session: &Session) {
+ let (_, _) = self.parse_standard_attributes(attrs, session);
+ }
+
+ pub fn parse_one_input(&mut self, attrs: &Attributes, session: &Session) -> Input {
+ let (input_1, _) = self.parse_standard_attributes(attrs, session);
+ input_1
+ }
+
+ pub fn parse_two_inputs(&mut self, attrs: &Attributes, session: &Session) -> (Input, Input) {
+ self.parse_standard_attributes(attrs, session)
+ }
+}
+
+/// Applies a filter and returns the resulting surface.
+pub fn render(
+ filter: &FilterSpec,
+ stroke_paint_source: Rc<UserSpacePaintSource>,
+ fill_paint_source: Rc<UserSpacePaintSource>,
+ source_surface: SharedImageSurface,
+ acquired_nodes: &mut AcquiredNodes<'_>,
+ draw_ctx: &mut DrawingCtx,
+ transform: Transform,
+ node_bbox: BoundingBox,
+) -> Result<SharedImageSurface, RenderingError> {
+ let session = draw_ctx.session().clone();
+
+ FilterContext::new(
+ &filter.user_space_filter,
+ stroke_paint_source,
+ fill_paint_source,
+ &source_surface,
+ transform,
+ node_bbox,
+ )
+ .and_then(|mut filter_ctx| {
+ // the message has an unclosed parenthesis; we'll close it below.
+ rsvg_log!(
+ session,
+ "(rendering filter with effects_region={:?}",
+ filter_ctx.effects_region()
+ );
+ for user_space_primitive in &filter.primitives {
+ let start = Instant::now();
+
+ match render_primitive(user_space_primitive, &filter_ctx, acquired_nodes, draw_ctx) {
+ Ok(output) => {
+ let elapsed = start.elapsed();
+ rsvg_log!(
+ session,
+ "(rendered filter primitive {} in\n {} seconds)",
+ user_space_primitive.params.name(),
+ elapsed.as_secs() as f64 + f64::from(elapsed.subsec_nanos()) / 1e9
+ );
+
+ filter_ctx.store_result(FilterResult {
+ name: user_space_primitive.result.clone(),
+ output,
+ });
+ }
+
+ Err(err) => {
+ rsvg_log!(
+ session,
+ "(filter primitive {} returned an error: {})",
+ user_space_primitive.params.name(),
+ err
+ );
+
+ // close the opening parenthesis from the message at the start of this function
+ rsvg_log!(session, ")");
+
+ // Exit early on Cairo errors. Continue rendering otherwise.
+ if let FilterError::CairoError(status) = err {
+ return Err(FilterError::CairoError(status));
+ }
+ }
+ }
+ }
+
+ // close the opening parenthesis from the message at the start of this function
+ rsvg_log!(session, ")");
+
+ Ok(filter_ctx.into_output()?)
+ })
+ .or_else(|err| match err {
+ FilterError::CairoError(status) => {
+ // Exit early on Cairo errors
+ Err(RenderingError::from(status))
+ }
+
+ _ => {
+ // ignore other filter errors and just return an empty surface
+ Ok(SharedImageSurface::empty(
+ source_surface.width(),
+ source_surface.height(),
+ SurfaceType::AlphaOnly,
+ )?)
+ }
+ })
+}
+
+#[rustfmt::skip]
+fn render_primitive(
+ primitive: &UserSpacePrimitive,
+ ctx: &FilterContext,
+ acquired_nodes: &mut AcquiredNodes<'_>,
+ draw_ctx: &mut DrawingCtx,
+) -> Result<FilterOutput, FilterError> {
+ use PrimitiveParams::*;
+
+ let bounds_builder = primitive.get_bounds(ctx);
+
+ // Note that feDropShadow is not handled here. When its FilterElement::resolve() is called,
+ // it returns a series of lower-level primitives (flood, blur, offset, etc.) that make up
+ // the drop-shadow effect.
+
+ match primitive.params {
+ Blend(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ ColorMatrix(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ ComponentTransfer(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ Composite(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ ConvolveMatrix(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ DiffuseLighting(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ DisplacementMap(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ Flood(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ GaussianBlur(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ Image(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ Merge(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ Morphology(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ Offset(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ SpecularLighting(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ Tile(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ Turbulence(ref p) => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
+ }
+}
+
+impl From<ColorInterpolationFilters> for SurfaceType {
+ fn from(c: ColorInterpolationFilters) -> Self {
+ match c {
+ ColorInterpolationFilters::LinearRgb => SurfaceType::LinearRgb,
+ _ => SurfaceType::SRgb,
+ }
+ }
+}