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