image/imageops/
fast_blur.rs

1use num_traits::clamp;
2
3use crate::{ImageBuffer, Pixel, Primitive};
4
5/// Approximation of Gaussian blur after
6/// Kovesi, P.:  Fast Almost-Gaussian Filtering The Australian Pattern
7/// Recognition Society Conference: DICTA 2010. December 2010. Sydney.
8pub fn fast_blur<P: Pixel>(
9    image_buffer: &ImageBuffer<P, Vec<P::Subpixel>>,
10    sigma: f32,
11) -> ImageBuffer<P, Vec<P::Subpixel>> {
12    let (width, height) = image_buffer.dimensions();
13
14    if width == 0 || height == 0 {
15        return image_buffer.clone();
16    }
17    let mut samples = image_buffer.as_flat_samples().samples.to_vec();
18    let num_passes = 3;
19
20    let boxes = boxes_for_gauss(sigma, num_passes);
21
22    for radius in boxes.iter().take(num_passes) {
23        let horizontally_blurred_transposed = horizontal_fast_blur_half::<P::Subpixel>(
24            &samples,
25            width as usize,
26            height as usize,
27            (*radius - 1) / 2,
28            P::CHANNEL_COUNT as usize,
29        );
30        samples = horizontal_fast_blur_half::<P::Subpixel>(
31            &horizontally_blurred_transposed,
32            height as usize,
33            width as usize,
34            (*radius - 1) / 2,
35            P::CHANNEL_COUNT as usize,
36        );
37    }
38    ImageBuffer::from_raw(width, height, samples).unwrap()
39}
40
41fn boxes_for_gauss(sigma: f32, n: usize) -> Vec<usize> {
42    let w_ideal = f32::sqrt((12.0 * sigma.powi(2) / (n as f32)) + 1.0);
43    let mut w_l = w_ideal.floor();
44    if w_l % 2.0 == 0.0 {
45        w_l -= 1.0
46    };
47    let w_u = w_l + 2.0;
48
49    let m_ideal = 0.25 * (n as f32) * (w_l + 3.0) - 3.0 * sigma.powi(2) * (w_l + 1.0).recip();
50
51    let m = f32::round(m_ideal) as usize;
52
53    (0..n)
54        .map(|i| if i < m { w_l as usize } else { w_u as usize })
55        .collect::<Vec<_>>()
56}
57
58fn channel_idx(channel: usize, idx: usize, channel_num: usize) -> usize {
59    channel_num * idx + channel
60}
61
62fn horizontal_fast_blur_half<P: Primitive>(
63    samples: &[P],
64    width: usize,
65    height: usize,
66    r: usize,
67    channel_num: usize,
68) -> Vec<P> {
69    let channel_size = width * height;
70
71    let mut out_samples = vec![P::from(0).unwrap(); channel_size * channel_num];
72    let mut vals = vec![0.0; channel_num];
73
74    let min_value = P::DEFAULT_MIN_VALUE.to_f32().unwrap();
75    let max_value = P::DEFAULT_MAX_VALUE.to_f32().unwrap();
76
77    for row in 0..height {
78        for (channel, value) in vals.iter_mut().enumerate().take(channel_num) {
79            *value = ((-(r as isize))..(r + 1) as isize)
80                .map(|x| {
81                    extended_f(
82                        samples,
83                        width,
84                        height,
85                        x,
86                        row as isize,
87                        channel,
88                        channel_num,
89                    )
90                    .to_f32()
91                    .unwrap_or(0.0)
92                })
93                .sum()
94        }
95
96        for column in 0..width {
97            for (channel, channel_val) in vals.iter_mut().enumerate() {
98                let val = *channel_val / (2.0 * r as f32 + 1.0);
99                let val = clamp(val, min_value, max_value);
100                let val = P::from(val).unwrap();
101
102                let destination_row = column;
103                let destination_column = row;
104                let destination_sample_index = channel_idx(
105                    channel,
106                    destination_column + destination_row * height,
107                    channel_num,
108                );
109                out_samples[destination_sample_index] = val;
110                *channel_val = *channel_val
111                    - extended_f(
112                        samples,
113                        width,
114                        height,
115                        column as isize - r as isize,
116                        row as isize,
117                        channel,
118                        channel_num,
119                    )
120                    .to_f32()
121                    .unwrap_or(0.0)
122                    + extended_f(
123                        samples,
124                        width,
125                        height,
126                        { column + r + 1 } as isize,
127                        row as isize,
128                        channel,
129                        channel_num,
130                    )
131                    .to_f32()
132                    .unwrap_or(0.0)
133            }
134        }
135    }
136
137    out_samples
138}
139
140fn extended_f<P: Primitive>(
141    samples: &[P],
142    width: usize,
143    height: usize,
144    x: isize,
145    y: isize,
146    channel: usize,
147    channel_num: usize,
148) -> P {
149    let x = clamp(x, 0, width as isize - 1) as usize;
150    let y = clamp(y, 0, height as isize - 1) as usize;
151    samples[channel_idx(channel, y * width + x, channel_num)]
152}