1
use std::cmp::min;
2
use std::f64;
3

            
4
use markup5ever::{expanded_name, local_name, namespace_url, ns};
5
use nalgebra::{DMatrix, Dyn, VecStorage};
6

            
7
use crate::document::AcquiredNodes;
8
use crate::drawing_ctx::DrawingCtx;
9
use crate::element::{set_attribute, ElementTrait};
10
use crate::node::{CascadedValues, Node};
11
use crate::parsers::{NumberOptionalNumber, ParseValue};
12
use crate::properties::ColorInterpolationFilters;
13
use crate::rect::IRect;
14
use crate::session::Session;
15
use crate::surface_utils::{
16
    shared_surface::{BlurDirection, Horizontal, SharedImageSurface, Vertical},
17
    EdgeMode,
18
};
19
use crate::xml::Attributes;
20

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

            
28
/// The maximum gaussian blur kernel size.
29
///
30
/// The value of 500 is used in webkit.
31
const MAXIMUM_KERNEL_SIZE: usize = 500;
32

            
33
/// The `feGaussianBlur` filter primitive.
34
39
#[derive(Default)]
35
pub struct FeGaussianBlur {
36
39
    base: Primitive,
37
39
    params: GaussianBlur,
38
}
39

            
40
/// Resolved `feGaussianBlur` primitive for rendering.
41
82
#[derive(Clone)]
42
pub struct GaussianBlur {
43
41
    pub in1: Input,
44
41
    pub std_deviation: NumberOptionalNumber<f64>,
45
41
    pub edge_mode: EdgeMode,
46
41
    pub color_interpolation_filters: ColorInterpolationFilters,
47
}
48

            
49
// We need this because NumberOptionalNumber doesn't impl Default
50
impl Default for GaussianBlur {
51
44
    fn default() -> GaussianBlur {
52
44
        GaussianBlur {
53
44
            in1: Default::default(),
54
44
            std_deviation: NumberOptionalNumber(0.0, 0.0),
55
            // Note that per the spec, `edgeMode` has a different initial value
56
            // in feGaussianBlur than feConvolveMatrix.
57
44
            edge_mode: EdgeMode::None,
58
44
            color_interpolation_filters: Default::default(),
59
        }
60
44
    }
61
}
62

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

            
67
118
        for (attr, value) in attrs.iter() {
68
79
            match attr.expanded() {
69
                expanded_name!("", "stdDeviation") => {
70
39
                    set_attribute(&mut self.params.std_deviation, attr.parse(value), session)
71
                }
72
                expanded_name!("", "edgeMode") => {
73
1
                    set_attribute(&mut self.params.edge_mode, attr.parse(value), session)
74
                }
75

            
76
                _ => (),
77
            }
78
79
        }
79
39
    }
80
}
81

            
82
/// Computes a gaussian kernel line for the given standard deviation.
83
9
fn gaussian_kernel(std_deviation: f64) -> Vec<f64> {
84
9
    assert!(std_deviation > 0.0);
85

            
86
    // Make sure there aren't any infinities.
87
9
    let maximal_deviation = (MAXIMUM_KERNEL_SIZE / 2) as f64 / 3.0;
88

            
89
    // Values further away than std_deviation * 3 are too small to contribute anything meaningful.
90
9
    let radius = ((std_deviation.min(maximal_deviation) * 3.0) + 0.5) as usize;
91
    // Clamp the radius rather than diameter because `MAXIMUM_KERNEL_SIZE` might be even and we
92
    // want an odd-sized kernel.
93
9
    let radius = min(radius, (MAXIMUM_KERNEL_SIZE - 1) / 2);
94
9
    let diameter = radius * 2 + 1;
95

            
96
9
    let mut kernel = Vec::with_capacity(diameter);
97

            
98
1218
    let gauss_point = |x: f64| (-x.powi(2) / (2.0 * std_deviation.powi(2))).exp();
99

            
100
    // Fill the matrix by doing numerical integration approximation from -2*std_dev to 2*std_dev,
101
    // sampling 50 points per pixel. We do the bottom half, mirror it to the top half, then compute
102
    // the center point. Otherwise asymmetric quantization errors will occur. The formula to
103
    // integrate is e^-(x^2/2s^2).
104
24
    for i in 0..diameter / 2 {
105
15
        let base_x = (diameter / 2 + 1 - i) as f64 - 0.5;
106

            
107
15
        let mut sum = 0.0;
108
765
        for j in 1..=50 {
109
750
            let r = base_x + 0.02 * f64::from(j);
110
750
            sum += gauss_point(r);
111
        }
112

            
113
15
        kernel.push(sum / 50.0);
114
    }
115

            
116
    // We'll compute the middle point later.
117
9
    kernel.push(0.0);
118

            
119
    // Mirror the bottom half to the top half.
120
24
    for i in 0..diameter / 2 {
121
15
        let x = kernel[diameter / 2 - 1 - i];
122
15
        kernel.push(x);
123
    }
124

            
125
    // Find center val -- calculate an odd number of quanta to make it symmetric, even if the
126
    // center point is weighted slightly higher than others.
127
9
    let mut sum = 0.0;
128
468
    for j in 0..=50 {
129
459
        let r = -0.5 + 0.02 * f64::from(j);
130
459
        sum += gauss_point(r);
131
    }
132
9
    kernel[diameter / 2] = sum / 51.0;
133

            
134
    // Normalize the distribution by scaling the total sum to 1.
135
9
    let sum = kernel.iter().sum::<f64>();
136
48
    kernel.iter_mut().for_each(|x| *x /= sum);
137

            
138
9
    kernel
139
9
}
140

            
141
/// Returns a size of the box blur kernel to approximate the gaussian blur.
142
76
fn box_blur_kernel_size(std_deviation: f64) -> usize {
143
76
    let d = (std_deviation * 3.0 * (2.0 * f64::consts::PI).sqrt() / 4.0 + 0.5).floor();
144
76
    let d = d.min(MAXIMUM_KERNEL_SIZE as f64);
145
76
    d as usize
146
76
}
147

            
148
/// Applies three box blurs to approximate the gaussian blur.
149
///
150
/// This is intended to be used in two steps, horizontal and vertical.
151
76
fn three_box_blurs<B: BlurDirection>(
152
    surface: &SharedImageSurface,
153
    bounds: IRect,
154
    std_deviation: f64,
155
) -> Result<SharedImageSurface, FilterError> {
156
76
    let d = box_blur_kernel_size(std_deviation);
157
76
    if d == 0 {
158
        return Ok(surface.clone());
159
    }
160

            
161
111
    let surface = if d % 2 == 1 {
162
        // Odd kernel sizes just get three successive box blurs.
163
35
        let mut surface = surface.clone();
164

            
165
140
        for _ in 0..3 {
166
105
            surface = surface.box_blur::<B>(bounds, d, d / 2)?;
167
        }
168

            
169
35
        surface
170
    } else {
171
        // Even kernel sizes have a more interesting scheme.
172
41
        let surface = surface.box_blur::<B>(bounds, d, d / 2)?;
173
41
        let surface = surface.box_blur::<B>(bounds, d, d / 2 - 1)?;
174

            
175
41
        let d = d + 1;
176
41
        surface.box_blur::<B>(bounds, d, d / 2)?
177
41
    };
178

            
179
76
    Ok(surface)
180
76
}
181

            
182
/// Applies the gaussian blur.
183
///
184
/// This is intended to be used in two steps, horizontal and vertical.
185
9
fn gaussian_blur(
186
    input_surface: &SharedImageSurface,
187
    bounds: IRect,
188
    std_deviation: f64,
189
    edge_mode: EdgeMode,
190
    vertical: bool,
191
) -> Result<SharedImageSurface, FilterError> {
192
9
    let kernel = gaussian_kernel(std_deviation);
193
18
    let (rows, cols) = if vertical {
194
5
        (kernel.len(), 1)
195
    } else {
196
4
        (1, kernel.len())
197
    };
198
9
    let kernel = DMatrix::from_data(VecStorage::new(Dyn(rows), Dyn(cols), kernel));
199

            
200
9
    Ok(input_surface.convolve(
201
        bounds,
202
9
        ((cols / 2) as i32, (rows / 2) as i32),
203
        &kernel,
204
        edge_mode,
205
    )?)
206
9
}
207

            
208
impl GaussianBlur {
209
46
    pub fn render(
210
        &self,
211
        bounds_builder: BoundsBuilder,
212
        ctx: &FilterContext,
213
        acquired_nodes: &mut AcquiredNodes<'_>,
214
        draw_ctx: &mut DrawingCtx,
215
    ) -> Result<FilterOutput, FilterError> {
216
92
        let input_1 = ctx.get_input(
217
            acquired_nodes,
218
            draw_ctx,
219
            &self.in1,
220
46
            self.color_interpolation_filters,
221
        )?;
222
46
        let bounds: IRect = bounds_builder
223
            .add_input(&input_1)
224
            .compute(ctx)
225
            .clipped
226
            .into();
227

            
228
46
        let NumberOptionalNumber(std_x, std_y) = self.std_deviation;
229

            
230
        // "A negative value or a value of zero disables the effect of
231
        // the given filter primitive (i.e., the result is the filter
232
        // input image)."
233
46
        if std_x <= 0.0 && std_y <= 0.0 {
234
2
            return Ok(FilterOutput {
235
2
                surface: input_1.surface().clone(),
236
                bounds,
237
            });
238
        }
239

            
240
44
        let (std_x, std_y) = ctx.paffine().transform_distance(std_x, std_y);
241

            
242
        // The deviation can become negative here due to the transform.
243
44
        let std_x = std_x.abs();
244
44
        let std_y = std_y.abs();
245

            
246
        // Performance TODO: gaussian blur is frequently used for shadows, operating on SourceAlpha
247
        // (so the image is alpha-only). We can use this to not waste time processing the other
248
        // channels.
249

            
250
        // Horizontal convolution.
251
83
        let horiz_result_surface = if std_x >= 2.0 {
252
            // The spec says for deviation >= 2.0 three box blurs can be used as an optimization.
253
39
            three_box_blurs::<Horizontal>(input_1.surface(), bounds, std_x)?
254
10
        } else if std_x != 0.0 {
255
4
            gaussian_blur(input_1.surface(), bounds, std_x, self.edge_mode, false)?
256
        } else {
257
1
            input_1.surface().clone()
258
        };
259

            
260
        // Vertical convolution.
261
81
        let output_surface = if std_y >= 2.0 {
262
            // The spec says for deviation >= 2.0 three box blurs can be used as an optimization.
263
37
            three_box_blurs::<Vertical>(&horiz_result_surface, bounds, std_y)?
264
12
        } else if std_y != 0.0 {
265
5
            gaussian_blur(&horiz_result_surface, bounds, std_y, self.edge_mode, true)?
266
        } else {
267
2
            horiz_result_surface
268
        };
269

            
270
44
        Ok(FilterOutput {
271
44
            surface: output_surface,
272
            bounds,
273
        })
274
46
    }
275
}
276

            
277
impl FilterEffect for FeGaussianBlur {
278
41
    fn resolve(
279
        &self,
280
        _acquired_nodes: &mut AcquiredNodes<'_>,
281
        node: &Node,
282
    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
283
41
        let cascaded = CascadedValues::new_from_node(node);
284
41
        let values = cascaded.get();
285

            
286
41
        let mut params = self.params.clone();
287
41
        params.color_interpolation_filters = values.color_interpolation_filters();
288

            
289
41
        Ok(vec![ResolvedPrimitive {
290
41
            primitive: self.base.clone(),
291
41
            params: PrimitiveParams::GaussianBlur(params),
292
        }])
293
41
    }
294
}