rsvg/filters/
convolve_matrix.rs

1use cssparser::Parser;
2use markup5ever::{expanded_name, local_name, ns};
3use nalgebra::{DMatrix, Dyn, VecStorage};
4
5use crate::bench_only::{
6    EdgeMode, ExclusiveImageSurface, ImageSurfaceDataExt, Pixel, PixelRectangle, Pixels,
7};
8use crate::document::AcquiredNodes;
9use crate::element::{ElementTrait, set_attribute};
10use crate::error::*;
11use crate::node::{CascadedValues, Node};
12use crate::parse_identifiers;
13use crate::parsers::{CommaSeparatedList, NumberOptionalNumber, Parse, ParseValue};
14use crate::properties::ColorInterpolationFilters;
15use crate::rect::IRect;
16use crate::rsvg_log;
17use crate::session::Session;
18use crate::util::clamp;
19use crate::xml::Attributes;
20
21use super::bounds::BoundsBuilder;
22use super::context::{FilterContext, FilterOutput};
23use super::{
24    FilterEffect, FilterError, FilterResolveError, Input, InputRequirements, Primitive,
25    PrimitiveParams, ResolvedPrimitive,
26};
27
28/// The `feConvolveMatrix` filter primitive.
29#[derive(Default)]
30pub struct FeConvolveMatrix {
31    base: Primitive,
32    params: ConvolveMatrix,
33}
34
35/// Resolved `feConvolveMatrix` primitive for rendering.
36#[derive(Clone)]
37pub struct ConvolveMatrix {
38    in1: Input,
39    order: NumberOptionalNumber<u32>,
40    kernel_matrix: CommaSeparatedList<f64, 0, 400>, // #691: Limit list to 400 (20x20) to mitigate malicious SVGs
41    divisor: f64,
42    bias: f64,
43    target_x: Option<u32>,
44    target_y: Option<u32>,
45    edge_mode: EdgeMode,
46    kernel_unit_length: KernelUnitLength,
47    preserve_alpha: bool,
48    color_interpolation_filters: ColorInterpolationFilters,
49}
50
51#[derive(Clone, Default)]
52pub struct KernelUnitLength(pub Option<(f64, f64)>);
53
54impl Parse for KernelUnitLength {
55    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
56        let loc = parser.current_source_location();
57        let v: Result<NumberOptionalNumber<f64>, _> = NumberOptionalNumber::parse(parser);
58        match v {
59            Ok(NumberOptionalNumber(x, y)) if x > 0.0 && y > 0.0 => {
60                Ok(KernelUnitLength(Some((x, y))))
61            } // Only accept positive values
62            Ok(_) => {
63                Err(loc.new_custom_error(ValueErrorKind::value_error("expected positive values")))
64            }
65            Err(e) => Err(e),
66        }
67    }
68}
69
70impl Default for ConvolveMatrix {
71    /// Constructs a new `ConvolveMatrix` with empty properties.
72    #[inline]
73    fn default() -> ConvolveMatrix {
74        ConvolveMatrix {
75            in1: Default::default(),
76            order: NumberOptionalNumber(3, 3),
77            kernel_matrix: CommaSeparatedList(Vec::new()),
78            divisor: 0.0,
79            bias: 0.0,
80            target_x: None,
81            target_y: None,
82            // Note that per the spec, `edgeMode` has a different initial value
83            // in feConvolveMatrix than feGaussianBlur.
84            edge_mode: EdgeMode::Duplicate,
85            kernel_unit_length: KernelUnitLength::default(),
86            preserve_alpha: false,
87            color_interpolation_filters: Default::default(),
88        }
89    }
90}
91
92impl ElementTrait for FeConvolveMatrix {
93    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
94        self.params.in1 = self.base.parse_one_input(attrs, session);
95
96        for (attr, value) in attrs.iter() {
97            match attr.expanded() {
98                expanded_name!("", "order") => {
99                    set_attribute(&mut self.params.order, attr.parse(value), session)
100                }
101                expanded_name!("", "kernelMatrix") => {
102                    set_attribute(&mut self.params.kernel_matrix, attr.parse(value), session)
103                }
104                expanded_name!("", "divisor") => {
105                    set_attribute(&mut self.params.divisor, attr.parse(value), session)
106                }
107                expanded_name!("", "bias") => {
108                    set_attribute(&mut self.params.bias, attr.parse(value), session)
109                }
110                expanded_name!("", "targetX") => {
111                    set_attribute(&mut self.params.target_x, attr.parse(value), session)
112                }
113                expanded_name!("", "targetY") => {
114                    set_attribute(&mut self.params.target_y, attr.parse(value), session)
115                }
116                expanded_name!("", "edgeMode") => {
117                    set_attribute(&mut self.params.edge_mode, attr.parse(value), session)
118                }
119                expanded_name!("", "kernelUnitLength") => set_attribute(
120                    &mut self.params.kernel_unit_length,
121                    attr.parse(value),
122                    session,
123                ),
124                expanded_name!("", "preserveAlpha") => {
125                    set_attribute(&mut self.params.preserve_alpha, attr.parse(value), session);
126                }
127
128                _ => (),
129            }
130        }
131    }
132}
133
134impl ConvolveMatrix {
135    pub fn render(
136        &self,
137        bounds_builder: BoundsBuilder,
138        ctx: &FilterContext,
139    ) -> Result<FilterOutput, FilterError> {
140        #![allow(clippy::many_single_char_names)]
141
142        let input_1 = ctx.get_input(&self.in1, self.color_interpolation_filters)?;
143        let mut bounds: IRect = bounds_builder
144            .add_input(&input_1)
145            .compute(ctx)
146            .clipped
147            .into();
148        let original_bounds = bounds;
149
150        let target_x = match self.target_x {
151            Some(x) if x >= self.order.0 => {
152                return Err(FilterError::InvalidParameter(
153                    "targetX must be less than orderX".to_string(),
154                ));
155            }
156            Some(x) => x,
157            None => self.order.0 / 2,
158        };
159
160        let target_y = match self.target_y {
161            Some(y) if y >= self.order.1 => {
162                return Err(FilterError::InvalidParameter(
163                    "targetY must be less than orderY".to_string(),
164                ));
165            }
166            Some(y) => y,
167            None => self.order.1 / 2,
168        };
169
170        let mut input_surface = if self.preserve_alpha {
171            // preserve_alpha means we need to premultiply and unpremultiply the values.
172            input_1.surface().unpremultiply(bounds)?
173        } else {
174            input_1.surface().clone()
175        };
176
177        let scale = self
178            .kernel_unit_length
179            .0
180            .map(|(dx, dy)| ctx.paffine().transform_distance(dx, dy));
181
182        if let Some((ox, oy)) = scale {
183            // Scale the input surface to match kernel_unit_length.
184            let (new_surface, new_bounds) = input_surface.scale(bounds, 1.0 / ox, 1.0 / oy)?;
185
186            input_surface = new_surface;
187            bounds = new_bounds;
188        }
189
190        let cols = self.order.0 as usize;
191        let rows = self.order.1 as usize;
192        let number_of_elements = cols * rows;
193        let numbers = self.kernel_matrix.0.clone();
194
195        if numbers.len() != number_of_elements && numbers.len() != 400 {
196            // "If the result of orderX * orderY is not equal to the the number of entries
197            // in the value list, the filter primitive acts as a pass through filter."
198            //
199            // https://drafts.fxtf.org/filter-effects/#element-attrdef-feconvolvematrix-kernelmatrix
200            rsvg_log!(
201                ctx.session(),
202                "feConvolveMatrix got {} elements when it expected {}; ignoring it",
203                numbers.len(),
204                number_of_elements
205            );
206            return Ok(FilterOutput {
207                surface: input_1.surface().clone(),
208                bounds: original_bounds,
209            });
210        }
211
212        let matrix = DMatrix::from_data(VecStorage::new(Dyn(rows), Dyn(cols), numbers));
213
214        let divisor = if self.divisor != 0.0 {
215            self.divisor
216        } else {
217            let d = matrix.iter().sum();
218
219            if d != 0.0 { d } else { 1.0 }
220        };
221
222        let mut surface = ExclusiveImageSurface::new(
223            input_surface.width(),
224            input_surface.height(),
225            input_1.surface().surface_type(),
226        )?;
227
228        surface.modify(&mut |data, stride| {
229            for (x, y, pixel) in Pixels::within(&input_surface, bounds) {
230                // Compute the convolution rectangle bounds.
231                let kernel_bounds = IRect::new(
232                    x as i32 - target_x as i32,
233                    y as i32 - target_y as i32,
234                    x as i32 - target_x as i32 + self.order.0 as i32,
235                    y as i32 - target_y as i32 + self.order.1 as i32,
236                );
237
238                // Do the convolution.
239                let mut r = 0.0;
240                let mut g = 0.0;
241                let mut b = 0.0;
242                let mut a = 0.0;
243
244                for (x, y, pixel) in
245                    PixelRectangle::within(&input_surface, bounds, kernel_bounds, self.edge_mode)
246                {
247                    let kernel_x = (kernel_bounds.x1 - x - 1) as usize;
248                    let kernel_y = (kernel_bounds.y1 - y - 1) as usize;
249
250                    r += f64::from(pixel.r) / 255.0 * matrix[(kernel_y, kernel_x)];
251                    g += f64::from(pixel.g) / 255.0 * matrix[(kernel_y, kernel_x)];
252                    b += f64::from(pixel.b) / 255.0 * matrix[(kernel_y, kernel_x)];
253
254                    if !self.preserve_alpha {
255                        a += f64::from(pixel.a) / 255.0 * matrix[(kernel_y, kernel_x)];
256                    }
257                }
258
259                // If preserve_alpha is true, set a to the source alpha value.
260                if self.preserve_alpha {
261                    a = f64::from(pixel.a) / 255.0;
262                } else {
263                    a = a / divisor + self.bias;
264                }
265
266                let clamped_a = clamp(a, 0.0, 1.0);
267
268                let compute = |x| {
269                    let x = x / divisor + self.bias * a;
270
271                    let x = if self.preserve_alpha {
272                        // Premultiply the output value.
273                        clamp(x, 0.0, 1.0) * clamped_a
274                    } else {
275                        clamp(x, 0.0, clamped_a)
276                    };
277
278                    ((x * 255.0) + 0.5) as u8
279                };
280
281                let output_pixel = Pixel {
282                    r: compute(r),
283                    g: compute(g),
284                    b: compute(b),
285                    a: ((clamped_a * 255.0) + 0.5) as u8,
286                };
287
288                data.set_pixel(stride, output_pixel, x, y);
289            }
290        });
291
292        let mut surface = surface.share()?;
293
294        if let Some((ox, oy)) = scale {
295            // Scale the output surface back.
296            surface = surface.scale_to(
297                ctx.source_graphic().width(),
298                ctx.source_graphic().height(),
299                original_bounds,
300                ox,
301                oy,
302            )?;
303
304            bounds = original_bounds;
305        }
306
307        Ok(FilterOutput { surface, bounds })
308    }
309
310    pub fn get_input_requirements(&self) -> InputRequirements {
311        self.in1.get_requirements()
312    }
313}
314
315impl FilterEffect for FeConvolveMatrix {
316    fn resolve(
317        &self,
318        _acquired_nodes: &mut AcquiredNodes<'_>,
319        node: &Node,
320    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
321        let cascaded = CascadedValues::new_from_node(node);
322        let values = cascaded.get();
323
324        let mut params = self.params.clone();
325        params.color_interpolation_filters = values.color_interpolation_filters();
326
327        Ok(vec![ResolvedPrimitive {
328            primitive: self.base.clone(),
329            params: PrimitiveParams::ConvolveMatrix(params),
330        }])
331    }
332}
333
334// Used for the preserveAlpha attribute
335impl Parse for bool {
336    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
337        Ok(parse_identifiers!(
338            parser,
339            "false" => false,
340            "true" => true,
341        )?)
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348
349    #[test]
350    fn kernel_unit_length_expects_positive_numbers() {
351        assert!(KernelUnitLength::parse_str("-1").is_err());
352        assert!(KernelUnitLength::parse_str("1 -1").is_err());
353        assert!(KernelUnitLength::parse_str("-1 -1").is_err());
354    }
355
356    #[test]
357    fn kernel_unit_length_catches_errors() {
358        assert!(KernelUnitLength::parse_str("foo").is_err());
359    }
360}