1
use std::cmp::{max, min};
2

            
3
use cssparser::Parser;
4
use markup5ever::{expanded_name, local_name, namespace_url, ns};
5

            
6
use crate::document::AcquiredNodes;
7
use crate::drawing_ctx::DrawingCtx;
8
use crate::element::{set_attribute, ElementTrait};
9
use crate::error::*;
10
use crate::node::Node;
11
use crate::parse_identifiers;
12
use crate::parsers::{NumberOptionalNumber, Parse, ParseValue};
13
use crate::properties::ColorInterpolationFilters;
14
use crate::rect::IRect;
15
use crate::session::Session;
16
use crate::surface_utils::{
17
    iterators::{PixelRectangle, Pixels},
18
    shared_surface::ExclusiveImageSurface,
19
    EdgeMode, ImageSurfaceDataExt, Pixel,
20
};
21
use crate::xml::Attributes;
22

            
23
use super::bounds::BoundsBuilder;
24
use super::context::{FilterContext, FilterOutput};
25
use super::{
26
    FilterEffect, FilterError, FilterResolveError, Input, Primitive, PrimitiveParams,
27
    ResolvedPrimitive,
28
};
29

            
30
/// Enumeration of the possible morphology operations.
31
15
#[derive(Default, Clone)]
32
enum Operator {
33
    #[default]
34
5
    Erode,
35
    Dilate,
36
}
37

            
38
/// The `feMorphology` filter primitive.
39
5
#[derive(Default)]
40
pub struct FeMorphology {
41
5
    base: Primitive,
42
5
    params: Morphology,
43
}
44

            
45
/// Resolved `feMorphology` primitive for rendering.
46
10
#[derive(Clone)]
47
pub struct Morphology {
48
5
    in1: Input,
49
5
    operator: Operator,
50
5
    radius: NumberOptionalNumber<f64>,
51
}
52

            
53
// We need this because NumberOptionalNumber doesn't impl Default
54
impl Default for Morphology {
55
5
    fn default() -> Morphology {
56
5
        Morphology {
57
5
            in1: Default::default(),
58
5
            operator: Default::default(),
59
5
            radius: NumberOptionalNumber(0.0, 0.0),
60
        }
61
5
    }
62
}
63

            
64
impl ElementTrait for FeMorphology {
65
5
    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
66
5
        self.params.in1 = self.base.parse_one_input(attrs, session);
67

            
68
17
        for (attr, value) in attrs.iter() {
69
12
            match attr.expanded() {
70
                expanded_name!("", "operator") => {
71
5
                    set_attribute(&mut self.params.operator, attr.parse(value), session);
72
                }
73
                expanded_name!("", "radius") => {
74
5
                    set_attribute(&mut self.params.radius, attr.parse(value), session);
75
                }
76
                _ => (),
77
            }
78
12
        }
79
5
    }
80
}
81

            
82
impl Morphology {
83
5
    pub fn render(
84
        &self,
85
        bounds_builder: BoundsBuilder,
86
        ctx: &FilterContext,
87
        acquired_nodes: &mut AcquiredNodes<'_>,
88
        draw_ctx: &mut DrawingCtx,
89
    ) -> Result<FilterOutput, FilterError> {
90
        // Although https://www.w3.org/TR/filter-effects/#propdef-color-interpolation-filters does not mention
91
        // feMorphology as being one of the primitives that does *not* use that property,
92
        // the SVG1.1 test for filters-morph-01-f.svg fails if we pass the value from the ComputedValues here (that
93
        // document does not specify the color-interpolation-filters property, so it defaults to linearRGB).
94
        // So, we pass Auto, which will get resolved to SRGB, and that makes that test pass.
95
        //
96
        // I suppose erosion/dilation doesn't care about the color space of the source image?
97

            
98
10
        let input_1 = ctx.get_input(
99
            acquired_nodes,
100
            draw_ctx,
101
            &self.in1,
102
5
            ColorInterpolationFilters::Auto,
103
        )?;
104
5
        let bounds: IRect = bounds_builder
105
            .add_input(&input_1)
106
            .compute(ctx)
107
            .clipped
108
            .into();
109

            
110
5
        let NumberOptionalNumber(rx, ry) = self.radius;
111

            
112
5
        if rx <= 0.0 && ry <= 0.0 {
113
            return Ok(FilterOutput {
114
                surface: input_1.surface().clone(),
115
                bounds,
116
            });
117
        }
118

            
119
5
        let (rx, ry) = ctx.paffine().transform_distance(rx, ry);
120

            
121
        // The radii can become negative here due to the transform.
122
        // Additionally The radii being excessively large causes cpu hangups
123
5
        let (rx, ry) = (rx.abs().min(10.0), ry.abs().min(10.0));
124

            
125
5
        let mut surface = ExclusiveImageSurface::new(
126
5
            ctx.source_graphic().width(),
127
5
            ctx.source_graphic().height(),
128
5
            input_1.surface().surface_type(),
129
        )?;
130

            
131
10
        surface.modify(&mut |data, stride| {
132
75057
            for (x, y, _pixel) in Pixels::within(input_1.surface(), bounds) {
133
                // Compute the kernel rectangle bounds.
134
75052
                let kernel_bounds = IRect::new(
135
75052
                    (f64::from(x) - rx).floor() as i32,
136
75052
                    (f64::from(y) - ry).floor() as i32,
137
75052
                    (f64::from(x) + rx).ceil() as i32 + 1,
138
75052
                    (f64::from(y) + ry).ceil() as i32 + 1,
139
                );
140

            
141
                // Compute the new pixel values.
142
75052
                let initial = match self.operator {
143
32526
                    Operator::Erode => u8::max_value(),
144
42526
                    Operator::Dilate => u8::min_value(),
145
                };
146

            
147
75052
                let mut output_pixel = Pixel {
148
75052
                    r: initial,
149
75052
                    g: initial,
150
75052
                    b: initial,
151
75052
                    a: initial,
152
                };
153

            
154
3567444
                for (_x, _y, pixel) in
155
1821248
                    PixelRectangle::within(input_1.surface(), bounds, kernel_bounds, EdgeMode::None)
156
                {
157
1746196
                    let op = match self.operator {
158
552942
                        Operator::Erode => min,
159
1193254
                        Operator::Dilate => max,
160
                    };
161

            
162
1746196
                    output_pixel.r = op(output_pixel.r, pixel.r);
163
1746196
                    output_pixel.g = op(output_pixel.g, pixel.g);
164
1746196
                    output_pixel.b = op(output_pixel.b, pixel.b);
165
1746196
                    output_pixel.a = op(output_pixel.a, pixel.a);
166
                }
167

            
168
75052
                data.set_pixel(stride, output_pixel, x, y);
169
            }
170
5
        });
171

            
172
5
        Ok(FilterOutput {
173
5
            surface: surface.share()?,
174
5
            bounds,
175
        })
176
5
    }
177
}
178

            
179
impl FilterEffect for FeMorphology {
180
5
    fn resolve(
181
        &self,
182
        _acquired_nodes: &mut AcquiredNodes<'_>,
183
        _node: &Node,
184
    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
185
10
        Ok(vec![ResolvedPrimitive {
186
5
            primitive: self.base.clone(),
187
5
            params: PrimitiveParams::Morphology(self.params.clone()),
188
        }])
189
5
    }
190
}
191

            
192
impl Parse for Operator {
193
5
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
194
10
        Ok(parse_identifiers!(
195
            parser,
196
            "erode" => Operator::Erode,
197
            "dilate" => Operator::Dilate,
198
        )?)
199
5
    }
200
}