diff options
Diffstat (limited to 'rsvg/src/filters/mod.rs')
-rw-r--r-- | rsvg/src/filters/mod.rs | 381 |
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, + } + } +} |