rsvg/filters/
morphology.rs

1use std::cmp::{max, min};
2
3use cssparser::Parser;
4use markup5ever::{expanded_name, local_name, ns};
5
6use crate::document::AcquiredNodes;
7use crate::element::{set_attribute, ElementTrait};
8use crate::error::*;
9use crate::node::Node;
10use crate::parse_identifiers;
11use crate::parsers::{NumberOptionalNumber, Parse, ParseValue};
12use crate::properties::ColorInterpolationFilters;
13use crate::rect::IRect;
14use crate::session::Session;
15use crate::surface_utils::{
16    iterators::{PixelRectangle, Pixels},
17    shared_surface::ExclusiveImageSurface,
18    EdgeMode, ImageSurfaceDataExt, Pixel,
19};
20use crate::xml::Attributes;
21
22use super::bounds::BoundsBuilder;
23use super::context::{FilterContext, FilterOutput};
24use super::{
25    FilterEffect, FilterError, FilterResolveError, Input, InputRequirements, Primitive,
26    PrimitiveParams, ResolvedPrimitive,
27};
28
29/// Enumeration of the possible morphology operations.
30#[derive(Default, Clone)]
31enum Operator {
32    #[default]
33    Erode,
34    Dilate,
35}
36
37/// The `feMorphology` filter primitive.
38#[derive(Default)]
39pub struct FeMorphology {
40    base: Primitive,
41    params: Morphology,
42}
43
44/// Resolved `feMorphology` primitive for rendering.
45#[derive(Clone)]
46pub struct Morphology {
47    in1: Input,
48    operator: Operator,
49    radius: NumberOptionalNumber<f64>,
50}
51
52// We need this because NumberOptionalNumber doesn't impl Default
53impl Default for Morphology {
54    fn default() -> Morphology {
55        Morphology {
56            in1: Default::default(),
57            operator: Default::default(),
58            radius: NumberOptionalNumber(0.0, 0.0),
59        }
60    }
61}
62
63impl ElementTrait for FeMorphology {
64    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
65        self.params.in1 = self.base.parse_one_input(attrs, session);
66
67        for (attr, value) in attrs.iter() {
68            match attr.expanded() {
69                expanded_name!("", "operator") => {
70                    set_attribute(&mut self.params.operator, attr.parse(value), session);
71                }
72                expanded_name!("", "radius") => {
73                    set_attribute(&mut self.params.radius, attr.parse(value), session);
74                }
75                _ => (),
76            }
77        }
78    }
79}
80
81impl Morphology {
82    pub fn render(
83        &self,
84        bounds_builder: BoundsBuilder,
85        ctx: &FilterContext,
86    ) -> Result<FilterOutput, FilterError> {
87        // Although https://www.w3.org/TR/filter-effects/#propdef-color-interpolation-filters does not mention
88        // feMorphology as being one of the primitives that does *not* use that property,
89        // the SVG1.1 test for filters-morph-01-f.svg fails if we pass the value from the ComputedValues here (that
90        // document does not specify the color-interpolation-filters property, so it defaults to linearRGB).
91        // So, we pass Auto, which will get resolved to SRGB, and that makes that test pass.
92        //
93        // I suppose erosion/dilation doesn't care about the color space of the source image?
94
95        let input_1 = ctx.get_input(&self.in1, ColorInterpolationFilters::Auto)?;
96        let bounds: IRect = bounds_builder
97            .add_input(&input_1)
98            .compute(ctx)
99            .clipped
100            .into();
101
102        let NumberOptionalNumber(rx, ry) = self.radius;
103
104        if rx <= 0.0 && ry <= 0.0 {
105            return Ok(FilterOutput {
106                surface: input_1.surface().clone(),
107                bounds,
108            });
109        }
110
111        let (rx, ry) = ctx.paffine().transform_distance(rx, ry);
112
113        // The radii can become negative here due to the transform.
114        // Additionally The radii being excessively large causes cpu hangups
115        let (rx, ry) = (rx.abs().min(10.0), ry.abs().min(10.0));
116
117        let mut surface = ExclusiveImageSurface::new(
118            ctx.source_graphic().width(),
119            ctx.source_graphic().height(),
120            input_1.surface().surface_type(),
121        )?;
122
123        surface.modify(&mut |data, stride| {
124            for (x, y, _pixel) in Pixels::within(input_1.surface(), bounds) {
125                // Compute the kernel rectangle bounds.
126                let kernel_bounds = IRect::new(
127                    (f64::from(x) - rx).floor() as i32,
128                    (f64::from(y) - ry).floor() as i32,
129                    (f64::from(x) + rx).ceil() as i32 + 1,
130                    (f64::from(y) + ry).ceil() as i32 + 1,
131                );
132
133                // Compute the new pixel values.
134                let initial = match self.operator {
135                    Operator::Erode => u8::MAX,
136                    Operator::Dilate => u8::MIN,
137                };
138
139                let mut output_pixel = Pixel {
140                    r: initial,
141                    g: initial,
142                    b: initial,
143                    a: initial,
144                };
145
146                for (_x, _y, pixel) in
147                    PixelRectangle::within(input_1.surface(), bounds, kernel_bounds, EdgeMode::None)
148                {
149                    let op = match self.operator {
150                        Operator::Erode => min,
151                        Operator::Dilate => max,
152                    };
153
154                    output_pixel.r = op(output_pixel.r, pixel.r);
155                    output_pixel.g = op(output_pixel.g, pixel.g);
156                    output_pixel.b = op(output_pixel.b, pixel.b);
157                    output_pixel.a = op(output_pixel.a, pixel.a);
158                }
159
160                data.set_pixel(stride, output_pixel, x, y);
161            }
162        });
163
164        Ok(FilterOutput {
165            surface: surface.share()?,
166            bounds,
167        })
168    }
169
170    pub fn get_input_requirements(&self) -> InputRequirements {
171        self.in1.get_requirements()
172    }
173}
174
175impl FilterEffect for FeMorphology {
176    fn resolve(
177        &self,
178        _acquired_nodes: &mut AcquiredNodes<'_>,
179        _node: &Node,
180    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
181        Ok(vec![ResolvedPrimitive {
182            primitive: self.base.clone(),
183            params: PrimitiveParams::Morphology(self.params.clone()),
184        }])
185    }
186}
187
188impl Parse for Operator {
189    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
190        Ok(parse_identifiers!(
191            parser,
192            "erode" => Operator::Erode,
193            "dilate" => Operator::Dilate,
194        )?)
195    }
196}