rsvg/filters/
gaussian_blur.rs

1use std::cmp::min;
2use std::f64;
3
4use markup5ever::{expanded_name, local_name, ns};
5use nalgebra::{DMatrix, Dyn, VecStorage};
6
7use crate::document::AcquiredNodes;
8use crate::element::{set_attribute, ElementTrait};
9use crate::node::{CascadedValues, Node};
10use crate::parsers::{NumberOptionalNumber, ParseValue};
11use crate::properties::ColorInterpolationFilters;
12use crate::rect::IRect;
13use crate::session::Session;
14use crate::surface_utils::{
15    shared_surface::{BlurDirection, Horizontal, SharedImageSurface, Vertical},
16    EdgeMode,
17};
18use crate::xml::Attributes;
19
20use super::bounds::BoundsBuilder;
21use super::context::{FilterContext, FilterOutput};
22use super::{
23    FilterEffect, FilterError, FilterResolveError, Input, InputRequirements, Primitive,
24    PrimitiveParams, ResolvedPrimitive,
25};
26
27/// The maximum gaussian blur kernel size.
28///
29/// The value of 500 is used in webkit.
30const MAXIMUM_KERNEL_SIZE: usize = 500;
31
32/// The `feGaussianBlur` filter primitive.
33#[derive(Default)]
34pub struct FeGaussianBlur {
35    base: Primitive,
36    params: GaussianBlur,
37}
38
39/// Resolved `feGaussianBlur` primitive for rendering.
40#[derive(Clone)]
41pub struct GaussianBlur {
42    pub in1: Input,
43    pub std_deviation: NumberOptionalNumber<f64>,
44    pub edge_mode: EdgeMode,
45    pub color_interpolation_filters: ColorInterpolationFilters,
46}
47
48// We need this because NumberOptionalNumber doesn't impl Default
49impl Default for GaussianBlur {
50    fn default() -> GaussianBlur {
51        GaussianBlur {
52            in1: Default::default(),
53            std_deviation: NumberOptionalNumber(0.0, 0.0),
54            // Note that per the spec, `edgeMode` has a different initial value
55            // in feGaussianBlur than feConvolveMatrix.
56            edge_mode: EdgeMode::None,
57            color_interpolation_filters: Default::default(),
58        }
59    }
60}
61
62impl ElementTrait for FeGaussianBlur {
63    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
64        self.params.in1 = self.base.parse_one_input(attrs, session);
65
66        for (attr, value) in attrs.iter() {
67            match attr.expanded() {
68                expanded_name!("", "stdDeviation") => {
69                    set_attribute(&mut self.params.std_deviation, attr.parse(value), session)
70                }
71                expanded_name!("", "edgeMode") => {
72                    set_attribute(&mut self.params.edge_mode, attr.parse(value), session)
73                }
74
75                _ => (),
76            }
77        }
78    }
79}
80
81/// Computes a gaussian kernel line for the given standard deviation.
82fn gaussian_kernel(std_deviation: f64) -> Vec<f64> {
83    assert!(std_deviation > 0.0);
84
85    // Make sure there aren't any infinities.
86    let maximal_deviation = (MAXIMUM_KERNEL_SIZE / 2) as f64 / 3.0;
87
88    // Values further away than std_deviation * 3 are too small to contribute anything meaningful.
89    let radius = ((std_deviation.min(maximal_deviation) * 3.0) + 0.5) as usize;
90    // Clamp the radius rather than diameter because `MAXIMUM_KERNEL_SIZE` might be even and we
91    // want an odd-sized kernel.
92    let radius = min(radius, (MAXIMUM_KERNEL_SIZE - 1) / 2);
93    let diameter = radius * 2 + 1;
94
95    let mut kernel = Vec::with_capacity(diameter);
96
97    let gauss_point = |x: f64| (-x.powi(2) / (2.0 * std_deviation.powi(2))).exp();
98
99    // Fill the matrix by doing numerical integration approximation from -2*std_dev to 2*std_dev,
100    // sampling 50 points per pixel. We do the bottom half, mirror it to the top half, then compute
101    // the center point. Otherwise asymmetric quantization errors will occur. The formula to
102    // integrate is e^-(x^2/2s^2).
103    for i in 0..diameter / 2 {
104        let base_x = (diameter / 2 + 1 - i) as f64 - 0.5;
105
106        let mut sum = 0.0;
107        for j in 1..=50 {
108            let r = base_x + 0.02 * f64::from(j);
109            sum += gauss_point(r);
110        }
111
112        kernel.push(sum / 50.0);
113    }
114
115    // We'll compute the middle point later.
116    kernel.push(0.0);
117
118    // Mirror the bottom half to the top half.
119    for i in 0..diameter / 2 {
120        let x = kernel[diameter / 2 - 1 - i];
121        kernel.push(x);
122    }
123
124    // Find center val -- calculate an odd number of quanta to make it symmetric, even if the
125    // center point is weighted slightly higher than others.
126    let mut sum = 0.0;
127    for j in 0..=50 {
128        let r = -0.5 + 0.02 * f64::from(j);
129        sum += gauss_point(r);
130    }
131    kernel[diameter / 2] = sum / 51.0;
132
133    // Normalize the distribution by scaling the total sum to 1.
134    let sum = kernel.iter().sum::<f64>();
135    kernel.iter_mut().for_each(|x| *x /= sum);
136
137    kernel
138}
139
140/// Returns a size of the box blur kernel to approximate the gaussian blur.
141fn box_blur_kernel_size(std_deviation: f64) -> usize {
142    let d = (std_deviation * 3.0 * (2.0 * f64::consts::PI).sqrt() / 4.0 + 0.5).floor();
143    let d = d.min(MAXIMUM_KERNEL_SIZE as f64);
144    d as usize
145}
146
147/// Applies three box blurs to approximate the gaussian blur.
148///
149/// This is intended to be used in two steps, horizontal and vertical.
150fn three_box_blurs<B: BlurDirection>(
151    surface: &SharedImageSurface,
152    bounds: IRect,
153    std_deviation: f64,
154) -> Result<SharedImageSurface, FilterError> {
155    let d = box_blur_kernel_size(std_deviation);
156    if d == 0 {
157        return Ok(surface.clone());
158    }
159
160    let surface = if d % 2 == 1 {
161        // Odd kernel sizes just get three successive box blurs.
162        let mut surface = surface.clone();
163
164        for _ in 0..3 {
165            surface = surface.box_blur::<B>(bounds, d, d / 2)?;
166        }
167
168        surface
169    } else {
170        // Even kernel sizes have a more interesting scheme.
171        let surface = surface.box_blur::<B>(bounds, d, d / 2)?;
172        let surface = surface.box_blur::<B>(bounds, d, d / 2 - 1)?;
173
174        let d = d + 1;
175        surface.box_blur::<B>(bounds, d, d / 2)?
176    };
177
178    Ok(surface)
179}
180
181/// Applies the gaussian blur.
182///
183/// This is intended to be used in two steps, horizontal and vertical.
184fn gaussian_blur(
185    input_surface: &SharedImageSurface,
186    bounds: IRect,
187    std_deviation: f64,
188    edge_mode: EdgeMode,
189    vertical: bool,
190) -> Result<SharedImageSurface, FilterError> {
191    let kernel = gaussian_kernel(std_deviation);
192    let (rows, cols) = if vertical {
193        (kernel.len(), 1)
194    } else {
195        (1, kernel.len())
196    };
197    let kernel = DMatrix::from_data(VecStorage::new(Dyn(rows), Dyn(cols), kernel));
198
199    Ok(input_surface.convolve(
200        bounds,
201        ((cols / 2) as i32, (rows / 2) as i32),
202        &kernel,
203        edge_mode,
204    )?)
205}
206
207impl GaussianBlur {
208    pub fn render(
209        &self,
210        bounds_builder: BoundsBuilder,
211        ctx: &FilterContext,
212    ) -> Result<FilterOutput, FilterError> {
213        let input_1 = ctx.get_input(&self.in1, self.color_interpolation_filters)?;
214        let bounds: IRect = bounds_builder
215            .add_input(&input_1)
216            .compute(ctx)
217            .clipped
218            .into();
219
220        let NumberOptionalNumber(std_x, std_y) = self.std_deviation;
221
222        // "A negative value or a value of zero disables the effect of
223        // the given filter primitive (i.e., the result is the filter
224        // input image)."
225        if std_x <= 0.0 && std_y <= 0.0 {
226            return Ok(FilterOutput {
227                surface: input_1.surface().clone(),
228                bounds,
229            });
230        }
231
232        let (std_x, std_y) = ctx.paffine().transform_distance(std_x, std_y);
233
234        // The deviation can become negative here due to the transform.
235        let std_x = std_x.abs();
236        let std_y = std_y.abs();
237
238        // Performance TODO: gaussian blur is frequently used for shadows, operating on SourceAlpha
239        // (so the image is alpha-only). We can use this to not waste time processing the other
240        // channels.
241
242        // Horizontal convolution.
243        let horiz_result_surface = if std_x >= 2.0 {
244            // The spec says for deviation >= 2.0 three box blurs can be used as an optimization.
245            three_box_blurs::<Horizontal>(input_1.surface(), bounds, std_x)?
246        } else if std_x != 0.0 {
247            gaussian_blur(input_1.surface(), bounds, std_x, self.edge_mode, false)?
248        } else {
249            input_1.surface().clone()
250        };
251
252        // Vertical convolution.
253        let output_surface = if std_y >= 2.0 {
254            // The spec says for deviation >= 2.0 three box blurs can be used as an optimization.
255            three_box_blurs::<Vertical>(&horiz_result_surface, bounds, std_y)?
256        } else if std_y != 0.0 {
257            gaussian_blur(&horiz_result_surface, bounds, std_y, self.edge_mode, true)?
258        } else {
259            horiz_result_surface
260        };
261
262        Ok(FilterOutput {
263            surface: output_surface,
264            bounds,
265        })
266    }
267
268    pub fn get_input_requirements(&self) -> InputRequirements {
269        self.in1.get_requirements()
270    }
271}
272
273impl FilterEffect for FeGaussianBlur {
274    fn resolve(
275        &self,
276        _acquired_nodes: &mut AcquiredNodes<'_>,
277        node: &Node,
278    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
279        let cascaded = CascadedValues::new_from_node(node);
280        let values = cascaded.get();
281
282        let mut params = self.params.clone();
283        params.color_interpolation_filters = values.color_interpolation_filters();
284
285        Ok(vec![ResolvedPrimitive {
286            primitive: self.base.clone(),
287            params: PrimitiveParams::GaussianBlur(params),
288        }])
289    }
290}