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::{set_attribute, ElementTrait};
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 {
220                d
221            } else {
222                1.0
223            }
224        };
225
226        let mut surface = ExclusiveImageSurface::new(
227            input_surface.width(),
228            input_surface.height(),
229            input_1.surface().surface_type(),
230        )?;
231
232        surface.modify(&mut |data, stride| {
233            for (x, y, pixel) in Pixels::within(&input_surface, bounds) {
234                // Compute the convolution rectangle bounds.
235                let kernel_bounds = IRect::new(
236                    x as i32 - target_x as i32,
237                    y as i32 - target_y as i32,
238                    x as i32 - target_x as i32 + self.order.0 as i32,
239                    y as i32 - target_y as i32 + self.order.1 as i32,
240                );
241
242                // Do the convolution.
243                let mut r = 0.0;
244                let mut g = 0.0;
245                let mut b = 0.0;
246                let mut a = 0.0;
247
248                for (x, y, pixel) in
249                    PixelRectangle::within(&input_surface, bounds, kernel_bounds, self.edge_mode)
250                {
251                    let kernel_x = (kernel_bounds.x1 - x - 1) as usize;
252                    let kernel_y = (kernel_bounds.y1 - y - 1) as usize;
253
254                    r += f64::from(pixel.r) / 255.0 * matrix[(kernel_y, kernel_x)];
255                    g += f64::from(pixel.g) / 255.0 * matrix[(kernel_y, kernel_x)];
256                    b += f64::from(pixel.b) / 255.0 * matrix[(kernel_y, kernel_x)];
257
258                    if !self.preserve_alpha {
259                        a += f64::from(pixel.a) / 255.0 * matrix[(kernel_y, kernel_x)];
260                    }
261                }
262
263                // If preserve_alpha is true, set a to the source alpha value.
264                if self.preserve_alpha {
265                    a = f64::from(pixel.a) / 255.0;
266                } else {
267                    a = a / divisor + self.bias;
268                }
269
270                let clamped_a = clamp(a, 0.0, 1.0);
271
272                let compute = |x| {
273                    let x = x / divisor + self.bias * a;
274
275                    let x = if self.preserve_alpha {
276                        // Premultiply the output value.
277                        clamp(x, 0.0, 1.0) * clamped_a
278                    } else {
279                        clamp(x, 0.0, clamped_a)
280                    };
281
282                    ((x * 255.0) + 0.5) as u8
283                };
284
285                let output_pixel = Pixel {
286                    r: compute(r),
287                    g: compute(g),
288                    b: compute(b),
289                    a: ((clamped_a * 255.0) + 0.5) as u8,
290                };
291
292                data.set_pixel(stride, output_pixel, x, y);
293            }
294        });
295
296        let mut surface = surface.share()?;
297
298        if let Some((ox, oy)) = scale {
299            // Scale the output surface back.
300            surface = surface.scale_to(
301                ctx.source_graphic().width(),
302                ctx.source_graphic().height(),
303                original_bounds,
304                ox,
305                oy,
306            )?;
307
308            bounds = original_bounds;
309        }
310
311        Ok(FilterOutput { surface, bounds })
312    }
313
314    pub fn get_input_requirements(&self) -> InputRequirements {
315        self.in1.get_requirements()
316    }
317}
318
319impl FilterEffect for FeConvolveMatrix {
320    fn resolve(
321        &self,
322        _acquired_nodes: &mut AcquiredNodes<'_>,
323        node: &Node,
324    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
325        let cascaded = CascadedValues::new_from_node(node);
326        let values = cascaded.get();
327
328        let mut params = self.params.clone();
329        params.color_interpolation_filters = values.color_interpolation_filters();
330
331        Ok(vec![ResolvedPrimitive {
332            primitive: self.base.clone(),
333            params: PrimitiveParams::ConvolveMatrix(params),
334        }])
335    }
336}
337
338// Used for the preserveAlpha attribute
339impl Parse for bool {
340    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
341        Ok(parse_identifiers!(
342            parser,
343            "false" => false,
344            "true" => true,
345        )?)
346    }
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352
353    #[test]
354    fn kernel_unit_length_expects_positive_numbers() {
355        assert!(KernelUnitLength::parse_str("-1").is_err());
356        assert!(KernelUnitLength::parse_str("1 -1").is_err());
357        assert!(KernelUnitLength::parse_str("-1 -1").is_err());
358    }
359
360    #[test]
361    fn kernel_unit_length_catches_errors() {
362        assert!(KernelUnitLength::parse_str("foo").is_err());
363    }
364}