rsvg/filters/
mod.rs

1//! Entry point for the CSS filters infrastructure.
2
3use cssparser::{BasicParseError, Parser};
4use markup5ever::{expanded_name, local_name, ns};
5use std::rc::Rc;
6use std::time::Instant;
7
8use crate::bbox::BoundingBox;
9use crate::document::AcquiredNodes;
10use crate::drawing_ctx::{DrawingCtx, Viewport};
11use crate::element::{set_attribute, ElementTrait};
12use crate::error::{InternalRenderingError, ParseError};
13use crate::filter::UserSpaceFilter;
14use crate::length::*;
15use crate::node::Node;
16use crate::parse_identifiers;
17use crate::parsers::{CustomIdent, Parse, ParseValue};
18use crate::properties::ColorInterpolationFilters;
19use crate::rsvg_log;
20use crate::session::Session;
21use crate::surface_utils::{
22    shared_surface::{SharedImageSurface, SurfaceType},
23    EdgeMode,
24};
25use crate::xml::Attributes;
26
27mod bounds;
28use self::bounds::BoundsBuilder;
29
30pub mod context;
31use self::context::{FilterContext, FilterOutput, FilterResult};
32
33mod error;
34use self::error::FilterError;
35pub use self::error::FilterResolveError;
36
37/// A filter primitive interface.
38pub trait FilterEffect: ElementTrait {
39    fn resolve(
40        &self,
41        acquired_nodes: &mut AcquiredNodes<'_>,
42        node: &Node,
43    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError>;
44}
45
46pub mod blend;
47pub mod color_matrix;
48pub mod component_transfer;
49pub mod composite;
50pub mod convolve_matrix;
51pub mod displacement_map;
52pub mod drop_shadow;
53pub mod flood;
54pub mod gaussian_blur;
55pub mod image;
56pub mod lighting;
57pub mod merge;
58pub mod morphology;
59pub mod offset;
60pub mod tile;
61pub mod turbulence;
62
63/// Parameters to apply a list of SVG filter primitives onto a surface.
64///
65/// This is almost everything needed to take a surface and apply a list of SVG filter
66/// primitives to it.
67pub struct FilterSpec {
68    /// Human-readable identifier for the filter, for logging/debugging purposes.
69    pub name: String,
70
71    /// Coordinates and bounds.
72    pub user_space_filter: UserSpaceFilter,
73
74    /// List of filter primitives to apply to the surface, in order.
75    pub primitives: Vec<UserSpacePrimitive>,
76}
77
78/// Parameters using while rendering a whole `filter` property.
79///
80/// The `filter` property may contain a single primitive, like `filter="blur(2px)"`, or a
81/// list of filter specs like `filter="blur(2px) url(#filter_id) drop_shadow(5 5)"`.  Each
82/// of those specs may produce more than one primitive; for example, the `url(#filter_id)`
83/// there may refer to a `<filter>` element that has several primitives inside it.  Also,
84/// the `drop_shadow()` function will expand to the few primitives used to implement a
85/// drop shadow.
86///
87/// Each filter spec will be rendered within a [`FilterContext`], so that the context can maintain
88/// the list of named outputs within a `<filter>` element.
89///
90/// While rendering all those [`FilterContext`]s, there are some immutable parameters.
91/// This `FilterPlan` struct contains those parameters.
92pub struct FilterPlan {
93    session: Session,
94
95    /// Current viewport at the time the filter is invoked.
96    pub viewport: Viewport,
97
98    /// Surface corresponding to the background image snapshot, for `in="BackgroundImage"`.
99    background_image: Option<SharedImageSurface>,
100
101    /// Surface filled with the current stroke paint, for `in="StrokePaint"`.
102    ///
103    /// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#attr-valuedef-in-strokepaint>
104    stroke_paint_image: Option<SharedImageSurface>,
105
106    /// Surface filled with the current fill paint, for `in="FillPaint"`.
107    ///
108    /// Filter Effects 1: <https://www.w3.org/TR/filter-effects/#attr-valuedef-in-fillpaint>
109    fill_paint_image: Option<SharedImageSurface>,
110}
111
112impl FilterPlan {
113    pub fn new(
114        session: &Session,
115        viewport: Viewport,
116        requirements: InputRequirements,
117        background_image: Option<SharedImageSurface>,
118        stroke_paint_image: Option<SharedImageSurface>,
119        fill_paint_image: Option<SharedImageSurface>,
120    ) -> Result<FilterPlan, Box<InternalRenderingError>> {
121        assert_eq!(
122            requirements.needs_background_image || requirements.needs_background_alpha,
123            background_image.is_some()
124        );
125
126        assert_eq!(
127            requirements.needs_stroke_paint_image,
128            stroke_paint_image.is_some()
129        );
130
131        assert_eq!(
132            requirements.needs_fill_paint_image,
133            fill_paint_image.is_some()
134        );
135
136        Ok(FilterPlan {
137            session: session.clone(),
138            viewport,
139            background_image,
140            stroke_paint_image,
141            fill_paint_image,
142        })
143    }
144}
145
146/// Which surfaces need to be provided as inputs for a [`FilterPlan`].
147///
148/// The various filters in a `filter` property may require different source images that
149/// the calling [`DrawingCtx`] is able to compute.  For example, if a primitive inside a
150/// `<filter>` element has `in="FillPaint"`, then the calling [`DrawingCtx`] must supply a
151/// surface filled as per the `fill` property of the element being filtered.
152///
153/// This struct holds the requirements for which such surfaces are needed.  The caller is
154/// expected to construct it from an array of [`FilterSpec`], and then to create the
155/// corresponding [`Input`] to create a [`FilterPlan`].
156#[derive(Debug, Default, PartialEq)]
157pub struct InputRequirements {
158    pub needs_source_alpha: bool,
159    pub needs_background_image: bool,
160    pub needs_background_alpha: bool,
161    pub needs_stroke_paint_image: bool,
162    pub needs_fill_paint_image: bool,
163}
164
165impl InputRequirements {
166    pub fn new_from_filter_specs(specs: &[FilterSpec]) -> InputRequirements {
167        specs
168            .iter()
169            .flat_map(|spec| spec.primitives.iter())
170            .map(|primitive| primitive.params.get_input_requirements())
171            .fold(InputRequirements::default(), |a, b| a.fold(b))
172    }
173
174    #[rustfmt::skip]
175    fn fold(self, r: InputRequirements) -> InputRequirements {
176        InputRequirements {
177            needs_source_alpha:       self.needs_source_alpha       || r.needs_source_alpha,
178            needs_background_image:   self.needs_background_image   || r.needs_background_image,
179            needs_background_alpha:   self.needs_background_alpha   || r.needs_background_alpha,
180            needs_stroke_paint_image: self.needs_stroke_paint_image || r.needs_stroke_paint_image,
181            needs_fill_paint_image:   self.needs_fill_paint_image   || r.needs_fill_paint_image,
182        }
183    }
184}
185
186/// Resolved parameters for each filter primitive.
187///
188/// These gather all the data that a primitive may need during rendering:
189/// the `feFoo` element's attributes, any computed values from its properties,
190/// and parameters extracted from the element's children (for example,
191/// `feMerge` gathers info from its `feMergNode` children).
192pub enum PrimitiveParams {
193    Blend(blend::Blend),
194    ColorMatrix(color_matrix::ColorMatrix),
195    ComponentTransfer(component_transfer::ComponentTransfer),
196    Composite(composite::Composite),
197    ConvolveMatrix(convolve_matrix::ConvolveMatrix),
198    DiffuseLighting(lighting::DiffuseLighting),
199    DisplacementMap(displacement_map::DisplacementMap),
200    Flood(flood::Flood),
201    GaussianBlur(gaussian_blur::GaussianBlur),
202    Image(image::Image),
203    Merge(merge::Merge),
204    Morphology(morphology::Morphology),
205    Offset(offset::Offset),
206    SpecularLighting(lighting::SpecularLighting),
207    Tile(tile::Tile),
208    Turbulence(turbulence::Turbulence),
209}
210
211impl PrimitiveParams {
212    /// Returns a human-readable name for a primitive.
213    #[rustfmt::skip]
214    fn name(&self) -> &'static str {
215        use PrimitiveParams::*;
216        match self {
217            Blend(..)             => "feBlend",
218            ColorMatrix(..)       => "feColorMatrix",
219            ComponentTransfer(..) => "feComponentTransfer",
220            Composite(..)         => "feComposite",
221            ConvolveMatrix(..)    => "feConvolveMatrix",
222            DiffuseLighting(..)   => "feDiffuseLighting",
223            DisplacementMap(..)   => "feDisplacementMap",
224            Flood(..)             => "feFlood",
225            GaussianBlur(..)      => "feGaussianBlur",
226            Image(..)             => "feImage",
227            Merge(..)             => "feMerge",
228            Morphology(..)        => "feMorphology",
229            Offset(..)            => "feOffset",
230            SpecularLighting(..)  => "feSpecularLighting",
231            Tile(..)              => "feTile",
232            Turbulence(..)        => "feTurbulence",
233        }
234    }
235
236    #[rustfmt::skip]
237    fn get_input_requirements(&self) -> InputRequirements {
238        use PrimitiveParams::*;
239        match self {
240            Blend(p)             => p.get_input_requirements(),
241            ColorMatrix(p)       => p.get_input_requirements(),
242            ComponentTransfer(p) => p.get_input_requirements(),
243            Composite(p)         => p.get_input_requirements(),
244            ConvolveMatrix(p)    => p.get_input_requirements(),
245            DiffuseLighting(p)   => p.get_input_requirements(),
246            DisplacementMap(p)   => p.get_input_requirements(),
247            Flood(p)             => p.get_input_requirements(),
248            GaussianBlur(p)      => p.get_input_requirements(),
249            Image(p)             => p.get_input_requirements(),
250            Merge(p)             => p.get_input_requirements(),
251            Morphology(p)        => p.get_input_requirements(),
252            Offset(p)            => p.get_input_requirements(),
253            SpecularLighting(p)  => p.get_input_requirements(),
254            Tile(p)              => p.get_input_requirements(),
255            Turbulence(p)        => p.get_input_requirements(),
256        }
257    }
258}
259
260/// The base filter primitive node containing common properties.
261#[derive(Default, Clone)]
262pub struct Primitive {
263    pub x: Option<Length<Horizontal>>,
264    pub y: Option<Length<Vertical>>,
265    pub width: Option<ULength<Horizontal>>,
266    pub height: Option<ULength<Vertical>>,
267    pub result: Option<CustomIdent>,
268}
269
270pub struct ResolvedPrimitive {
271    pub primitive: Primitive,
272    pub params: PrimitiveParams,
273}
274
275/// A fully resolved filter primitive in user-space coordinates.
276pub struct UserSpacePrimitive {
277    x: Option<f64>,
278    y: Option<f64>,
279    width: Option<f64>,
280    height: Option<f64>,
281    result: Option<CustomIdent>,
282
283    params: PrimitiveParams,
284}
285
286/// An enumeration of possible inputs for a filter primitive.
287#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
288pub enum Input {
289    #[default]
290    Unspecified,
291    SourceGraphic,
292    SourceAlpha,
293    BackgroundImage,
294    BackgroundAlpha,
295    FillPaint,
296    StrokePaint,
297    FilterOutput(CustomIdent),
298}
299
300impl Input {
301    pub fn get_requirements(&self) -> InputRequirements {
302        use Input::*;
303
304        let mut reqs = InputRequirements::default();
305
306        match self {
307            SourceAlpha => reqs.needs_source_alpha = true,
308            BackgroundImage => reqs.needs_background_image = true,
309            BackgroundAlpha => reqs.needs_background_alpha = true,
310            FillPaint => reqs.needs_fill_paint_image = true,
311            StrokePaint => reqs.needs_stroke_paint_image = true,
312            _ => (),
313        }
314
315        reqs
316    }
317}
318
319impl Parse for Input {
320    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
321        parser
322            .try_parse(|p| {
323                parse_identifiers!(
324                    p,
325                    "SourceGraphic" => Input::SourceGraphic,
326                    "SourceAlpha" => Input::SourceAlpha,
327                    "BackgroundImage" => Input::BackgroundImage,
328                    "BackgroundAlpha" => Input::BackgroundAlpha,
329                    "FillPaint" => Input::FillPaint,
330                    "StrokePaint" => Input::StrokePaint,
331                )
332            })
333            .or_else(|_: BasicParseError<'_>| {
334                let ident = CustomIdent::parse(parser)?;
335                Ok(Input::FilterOutput(ident))
336            })
337    }
338}
339
340impl ResolvedPrimitive {
341    pub fn into_user_space(self, params: &NormalizeParams) -> UserSpacePrimitive {
342        let x = self.primitive.x.map(|l| l.to_user(params));
343        let y = self.primitive.y.map(|l| l.to_user(params));
344        let width = self.primitive.width.map(|l| l.to_user(params));
345        let height = self.primitive.height.map(|l| l.to_user(params));
346
347        UserSpacePrimitive {
348            x,
349            y,
350            width,
351            height,
352            result: self.primitive.result,
353            params: self.params,
354        }
355    }
356}
357
358impl UserSpacePrimitive {
359    /// Validates attributes and returns the `BoundsBuilder` for bounds computation.
360    #[inline]
361    fn get_bounds(&self, ctx: &FilterContext) -> BoundsBuilder {
362        BoundsBuilder::new(self.x, self.y, self.width, self.height, ctx.paffine())
363    }
364}
365
366impl Primitive {
367    fn parse_standard_attributes(
368        &mut self,
369        attrs: &Attributes,
370        session: &Session,
371    ) -> (Input, Input) {
372        let mut input_1 = Input::Unspecified;
373        let mut input_2 = Input::Unspecified;
374
375        for (attr, value) in attrs.iter() {
376            match attr.expanded() {
377                expanded_name!("", "x") => set_attribute(&mut self.x, attr.parse(value), session),
378                expanded_name!("", "y") => set_attribute(&mut self.y, attr.parse(value), session),
379                expanded_name!("", "width") => {
380                    set_attribute(&mut self.width, attr.parse(value), session)
381                }
382                expanded_name!("", "height") => {
383                    set_attribute(&mut self.height, attr.parse(value), session)
384                }
385                expanded_name!("", "result") => {
386                    set_attribute(&mut self.result, attr.parse(value), session)
387                }
388                expanded_name!("", "in") => set_attribute(&mut input_1, attr.parse(value), session),
389                expanded_name!("", "in2") => {
390                    set_attribute(&mut input_2, attr.parse(value), session)
391                }
392                _ => (),
393            }
394        }
395
396        (input_1, input_2)
397    }
398
399    pub fn parse_no_inputs(&mut self, attrs: &Attributes, session: &Session) {
400        let (_, _) = self.parse_standard_attributes(attrs, session);
401    }
402
403    pub fn parse_one_input(&mut self, attrs: &Attributes, session: &Session) -> Input {
404        let (input_1, _) = self.parse_standard_attributes(attrs, session);
405        input_1
406    }
407
408    pub fn parse_two_inputs(&mut self, attrs: &Attributes, session: &Session) -> (Input, Input) {
409        self.parse_standard_attributes(attrs, session)
410    }
411}
412
413/// Applies a filter and returns the resulting surface.
414pub fn render(
415    plan: Rc<FilterPlan>,
416    filter: &FilterSpec,
417    source_surface: SharedImageSurface,
418    acquired_nodes: &mut AcquiredNodes<'_>,
419    draw_ctx: &mut DrawingCtx,
420    node_bbox: &BoundingBox,
421) -> Result<SharedImageSurface, Box<InternalRenderingError>> {
422    let session = draw_ctx.session().clone();
423
424    let surface_width = source_surface.width();
425    let surface_height = source_surface.height();
426
427    FilterContext::new(&filter.user_space_filter, plan, source_surface, *node_bbox)
428        .and_then(|mut filter_ctx| {
429            // the message has an unclosed parenthesis; we'll close it below.
430            rsvg_log!(
431                session,
432                "(filter \"{}\" with effects_region={:?}",
433                filter.name,
434                filter_ctx.effects_region()
435            );
436            for user_space_primitive in &filter.primitives {
437                let start = Instant::now();
438
439                match render_primitive(user_space_primitive, &filter_ctx, acquired_nodes, draw_ctx)
440                {
441                    Ok(output) => {
442                        let elapsed = start.elapsed();
443                        rsvg_log!(
444                            session,
445                            "(rendered filter primitive {} in {} seconds)",
446                            user_space_primitive.params.name(),
447                            elapsed.as_secs() as f64 + f64::from(elapsed.subsec_nanos()) / 1e9
448                        );
449
450                        filter_ctx.store_result(FilterResult {
451                            name: user_space_primitive.result.clone(),
452                            output,
453                        });
454                    }
455
456                    Err(err) => {
457                        rsvg_log!(
458                            session,
459                            "(filter primitive {} returned an error: {})",
460                            user_space_primitive.params.name(),
461                            err
462                        );
463
464                        // close the opening parenthesis from the message at the start of this function
465                        rsvg_log!(session, ")");
466
467                        // Exit early on Cairo errors. Continue rendering otherwise.
468                        if let FilterError::CairoError(status) = err {
469                            return Err(FilterError::CairoError(status));
470                        }
471                    }
472                }
473            }
474
475            // close the opening parenthesis from the message at the start of this function
476            rsvg_log!(session, ")");
477
478            Ok(filter_ctx.into_output()?)
479        })
480        .or_else(|err| match err {
481            FilterError::CairoError(status) => {
482                // Exit early on Cairo errors
483                Err(Box::new(InternalRenderingError::from(status)))
484            }
485
486            _ => {
487                // ignore other filter errors and just return an empty surface
488                Ok(SharedImageSurface::empty(
489                    surface_width,
490                    surface_height,
491                    SurfaceType::AlphaOnly,
492                )?)
493            }
494        })
495}
496
497#[rustfmt::skip]
498fn render_primitive(
499    primitive: &UserSpacePrimitive,
500    ctx: &FilterContext,
501    acquired_nodes: &mut AcquiredNodes<'_>,
502    draw_ctx: &mut DrawingCtx,
503) -> Result<FilterOutput, FilterError> {
504    use PrimitiveParams::*;
505
506    let bounds_builder = primitive.get_bounds(ctx);
507
508    // Note that feDropShadow is not handled here.  When its FilterElement::resolve() is called,
509    // it returns a series of lower-level primitives (flood, blur, offset, etc.) that make up
510    // the drop-shadow effect.
511
512    match primitive.params {
513        Blend(ref p)             => p.render(bounds_builder, ctx),
514        ColorMatrix(ref p)       => p.render(bounds_builder, ctx),
515        ComponentTransfer(ref p) => p.render(bounds_builder, ctx),
516        Composite(ref p)         => p.render(bounds_builder, ctx),
517        ConvolveMatrix(ref p)    => p.render(bounds_builder, ctx),
518        DiffuseLighting(ref p)   => p.render(bounds_builder, ctx),
519        DisplacementMap(ref p)   => p.render(bounds_builder, ctx),
520        Flood(ref p)             => p.render(bounds_builder, ctx),
521        GaussianBlur(ref p)      => p.render(bounds_builder, ctx),
522        Image(ref p)             => p.render(bounds_builder, ctx, acquired_nodes, draw_ctx),
523        Merge(ref p)             => p.render(bounds_builder, ctx),
524        Morphology(ref p)        => p.render(bounds_builder, ctx),
525        Offset(ref p)            => p.render(bounds_builder, ctx),
526        SpecularLighting(ref p)  => p.render(bounds_builder, ctx),
527        Tile(ref p)              => p.render(bounds_builder, ctx),
528        Turbulence(ref p)        => p.render(bounds_builder, ctx),
529    }
530}
531
532impl From<ColorInterpolationFilters> for SurfaceType {
533    fn from(c: ColorInterpolationFilters) -> Self {
534        match c {
535            ColorInterpolationFilters::LinearRgb => SurfaceType::LinearRgb,
536            _ => SurfaceType::SRgb,
537        }
538    }
539}
540
541impl Parse for EdgeMode {
542    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
543        Ok(parse_identifiers!(
544            parser,
545            "duplicate" => EdgeMode::Duplicate,
546            "wrap" => EdgeMode::Wrap,
547            "none" => EdgeMode::None,
548        )?)
549    }
550}
551
552#[cfg(test)]
553mod tests {
554    use super::*;
555
556    use crate::color::{Color, RGBA};
557    use crate::document::Document;
558    use crate::dpi::Dpi;
559    use crate::node::NodeBorrow;
560    use crate::properties::Filter;
561
562    fn get_input_requirements_for_node(document: &Document, node_id: &str) -> InputRequirements {
563        let node = document.lookup_internal_node(node_id).unwrap();
564        let elt = node.borrow_element();
565        let values = elt.get_computed_values();
566
567        let session = Session::default();
568        let mut acquired_nodes = AcquiredNodes::new(&document, None::<gio::Cancellable>);
569
570        let viewport = Viewport::new(Dpi::new(96.0, 96.0), 100.0, 100.0);
571
572        let filter = values.filter();
573
574        let filter_list = match filter {
575            Filter::None => {
576                panic!("the referenced node should have a filter property that is not 'none'")
577            }
578            Filter::List(filter_list) => filter_list,
579        };
580
581        let params = NormalizeParams::new(&values, &viewport);
582
583        let filter_specs = filter_list
584            .iter()
585            .map(|filter_value| {
586                filter_value.to_filter_spec(
587                    &mut acquired_nodes,
588                    &params,
589                    Color::Rgba(RGBA::new(0, 0, 0, 1.0)),
590                    &viewport,
591                    &session,
592                    "rect",
593                )
594            })
595            .collect::<Result<Vec<FilterSpec>, _>>()
596            .unwrap();
597
598        InputRequirements::new_from_filter_specs(&filter_specs)
599    }
600
601    fn input_requirements_with_only_source_alpha() -> InputRequirements {
602        InputRequirements {
603            needs_source_alpha: true,
604            needs_background_image: false,
605            needs_background_alpha: false,
606            needs_stroke_paint_image: false,
607            needs_fill_paint_image: false,
608        }
609    }
610
611    #[test]
612    fn detects_source_alpha() {
613        let document = Document::load_from_bytes(include_bytes!("test_input_requirements.svg"));
614
615        assert_eq!(
616            get_input_requirements_for_node(&document, "rect_1"),
617            input_requirements_with_only_source_alpha(),
618        );
619
620        assert_eq!(
621            get_input_requirements_for_node(&document, "rect_2"),
622            input_requirements_with_only_source_alpha(),
623        );
624
625        assert_eq!(
626            get_input_requirements_for_node(&document, "rect_3"),
627            InputRequirements {
628                needs_source_alpha: false,
629                needs_background_image: true,
630                needs_background_alpha: true,
631                needs_stroke_paint_image: true,
632                needs_fill_paint_image: true,
633            }
634        );
635    }
636}