blud/
fastblur.rs

1// Forked from <https://github.com/fschutt/fastblur>, in turn based on
2// the article in <http://blog.ivank.net/fastest-gaussian-blur.html>
3
4use std::cmp::min;
5use umath::FF32;
6
7/// Blur an image slice of pixel arrays
8///
9/// In-place blur image provided image pixel data, with any number of channels. Will make a single
10/// allocation for a backing buffer. Expects pixel data as a slice of CHANNELS sized arrays, for
11/// use with a byte slice, use [`gaussian_blur_bytes`][super::gaussian_blur_bytes].
12///
13/// # Arguments
14/// * `CHANNELS`: number of channels in the image data, e.g. 3 for RGB, 4 for RGBA, 1 for luminance
15/// * `data`: pixel data, `width` x `height` in length
16/// * `data` will be modified in-place
17/// * `width`, `height`: in pixels
18///
19/// # Safety
20///
21/// fast math go brr, data must be width * height sized
22pub unsafe fn gaussian_blur<const CHANNELS: usize>(
23    data: &mut [[u8; CHANNELS]],
24    width: usize,
25    height: usize,
26    blur_radius: FF32,
27) {
28    let boxes = create_box_gauss::<CHANNELS>(blur_radius);
29    let mut backbuf = data.to_owned();
30
31    for &box_size in boxes.iter() {
32        let radius = ((box_size - 1) / 2) as usize;
33        box_blur(&mut backbuf, data, width, height, radius, radius);
34    }
35}
36
37#[inline]
38unsafe fn create_box_gauss<const N: usize>(sigma: FF32) -> [i32; N] {
39    if sigma > 0.0 {
40        let n_float = FF32::new(N as f32);
41
42        let w_ideal = (FF32::new(12.0) * sigma * sigma / n_float).sqrt() + 1.0;
43        let mut wl: i32 = w_ideal.floor() as i32;
44
45        if wl % 2 == 0 {
46            wl -= 1;
47        };
48
49        let wu = wl + 2;
50
51        let wl_float = FF32::new(wl as f32);
52        let m_ideal = (FF32::new(12.0) * sigma * sigma
53            - n_float * wl_float * wl_float
54            - FF32::new(4.0) * n_float * wl_float
55            - FF32::new(3.0) * n_float)
56            / (FF32::new(-4.0) * wl_float - FF32::new(4.0));
57        let m: usize = m_ideal.round() as usize;
58
59        let mut sizes = [0; N];
60
61        for (i, pass) in sizes.iter_mut().enumerate() {
62            if i < m {
63                *pass = wl;
64            } else {
65                *pass = wu;
66            }
67        }
68        sizes
69    } else {
70        [1; N]
71    }
72}
73
74#[inline]
75fn box_blur<const CHANNELS: usize>(
76    backbuf: &mut [[u8; CHANNELS]],
77    frontbuf: &mut [[u8; CHANNELS]],
78    width: usize,
79    height: usize,
80    blur_radius_horz: usize,
81    blur_radius_vert: usize,
82) {
83    box_blur_horz(backbuf, frontbuf, width, height, blur_radius_horz);
84    box_blur_vert(frontbuf, backbuf, width, height, blur_radius_vert);
85}
86
87macro_rules! C {
88    ($buf:ident[$n:expr]) => {
89        unsafe { *$buf.get_unchecked($n) }
90    };
91    ($buf:ident[$n:expr] = $e:expr) => {
92        *unsafe { $buf.get_unchecked_mut($n) } = $e
93    };
94    ($buf:ident[$a:expr][$b:expr]) => {
95        unsafe { *$buf.get_unchecked($a).get_unchecked($b) }
96    };
97    ($buf:ident[$a:expr][$b:expr] = $c:expr) => {
98        *unsafe { $buf.get_unchecked_mut($a).get_unchecked_mut($b) } = unsafe { $c }
99    };
100}
101
102#[inline]
103fn box_blur_vert<const CHANNELS: usize>(
104    backbuf: &[[u8; CHANNELS]],
105    frontbuf: &mut [[u8; CHANNELS]],
106    width: usize,
107    height: usize,
108    blur_radius: usize,
109) {
110    if blur_radius == 0 {
111        frontbuf.copy_from_slice(backbuf);
112        return;
113    }
114
115    let iarr = 1.0 / (blur_radius + blur_radius + 1) as f32;
116
117    for i in 0..width {
118        let col_start = i;
119        let col_end = i + width * (height - 1);
120        let mut ti: usize = i;
121        let mut li: usize = ti;
122        let mut ri: usize = ti + blur_radius * width;
123
124        let fv: [u8; CHANNELS] = C!(backbuf[col_start]);
125        let lv: [u8; CHANNELS] = C!(backbuf[col_end]);
126
127        let mut vals: [isize; CHANNELS] = [0; CHANNELS];
128        for i in 0..CHANNELS {
129            vals[i] = (blur_radius as isize + 1) * isize::from(fv[i]);
130        }
131
132        let get_top = |i: usize| {
133            if i < col_start {
134                fv
135            } else {
136                C! { backbuf[i] }
137            }
138        };
139
140        let get_bottom = |i: usize| {
141            if i > col_end {
142                lv
143            } else {
144                C! { backbuf[i] }
145            }
146        };
147
148        for j in 0..min(blur_radius, height) {
149            let bb = C! { backbuf[ti + j * width] };
150            for i in 0..CHANNELS {
151                vals[i] += isize::from(bb[i]);
152            }
153        }
154        if blur_radius > height {
155            for i in 0..CHANNELS {
156                vals[i] += (blur_radius - height) as isize * isize::from(lv[i]);
157            }
158        }
159
160        for _ in 0..min(height, blur_radius + 1) {
161            let bb = get_bottom(ri);
162            ri += width;
163            for i in 0..CHANNELS {
164                vals[i] += isize::from(bb[i]) - isize::from(fv[i]);
165            }
166
167            for i in 0..CHANNELS {
168                C! { frontbuf[ti][i] = *round(FF32::new(vals[i] as f32) * iarr) as u8 };
169            }
170            ti += width;
171        }
172
173        if height > blur_radius {
174            for _ in (blur_radius + 1)..(height - blur_radius) {
175                let bb1 = C! { backbuf[ri] };
176                ri += width;
177                let bb2 = C! { backbuf[li] };
178                li += width;
179
180                for i in 0..CHANNELS {
181                    vals[i] += isize::from(bb1[i]) - isize::from(bb2[i]);
182                }
183
184                for i in 0..CHANNELS {
185                    C! { frontbuf[ti][i] = *round(FF32::new(vals[i] as f32) * iarr) as u8 };
186                }
187                ti += width;
188            }
189
190            for _ in 0..min(height - blur_radius - 1, blur_radius) {
191                let bb = get_top(li);
192                li += width;
193
194                for i in 0..CHANNELS {
195                    vals[i] += isize::from(lv[i]) - isize::from(bb[i]);
196                }
197
198                for i in 0..CHANNELS {
199                    C! { frontbuf[ti][i] = *round(FF32::new(vals[i] as f32) * iarr) as u8 };
200                }
201                ti += width;
202            }
203        }
204    }
205}
206
207#[inline]
208fn box_blur_horz<const CHANNELS: usize>(
209    backbuf: &[[u8; CHANNELS]],
210    frontbuf: &mut [[u8; CHANNELS]],
211    width: usize,
212    height: usize,
213    blur_radius: usize,
214) {
215    if blur_radius == 0 {
216        frontbuf.copy_from_slice(backbuf);
217        return;
218    }
219
220    let iarr = 1.0 / (blur_radius + blur_radius + 1) as f32;
221
222    for i in 0..height {
223        let row_start: usize = i * width;
224        let row_end: usize = i * width + width - 1;
225        let mut ti: usize = i * width;
226        let mut li: usize = ti;
227        let mut ri: usize = ti + blur_radius;
228
229        let fv: [u8; CHANNELS] = C! { backbuf[row_start] };
230        let lv: [u8; CHANNELS] = C! { backbuf[row_end] };
231
232        let mut vals: [isize; CHANNELS] = [0; CHANNELS];
233        for i in 0..CHANNELS {
234            vals[i] = (blur_radius as isize + 1) * isize::from(fv[i]);
235        }
236
237        let get_left = |i: usize| {
238            if i < row_start {
239                fv
240            } else {
241                C! { backbuf[i] }
242            }
243        };
244
245        let get_right = |i: usize| {
246            if i > row_end {
247                lv
248            } else {
249                C! { backbuf[i] }
250            }
251        };
252
253        for j in 0..min(blur_radius, width) {
254            let bb = C! { backbuf[ti + j] };
255            for i in 0..CHANNELS {
256                vals[i] += isize::from(bb[i]);
257            }
258        }
259        if blur_radius > width {
260            for i in 0..CHANNELS {
261                vals[i] += (blur_radius - height) as isize * isize::from(lv[i]);
262            }
263        }
264
265        for _ in 0..min(width, blur_radius + 1) {
266            let bb = get_right(ri);
267            ri += 1;
268            for i in 0..CHANNELS {
269                vals[i] += isize::from(bb[i]) - isize::from(fv[i]);
270            }
271
272            for i in 0..CHANNELS {
273                C! { frontbuf[ti][i] = *round(FF32::new(vals[i] as f32) * iarr) as u8 };
274            }
275            ti += 1;
276        }
277
278        if width > blur_radius {
279            for _ in (blur_radius + 1)..(width - blur_radius) {
280                let bb1 = C! { backbuf[ri] };
281                ri += 1;
282                let bb2 = C! { backbuf[li] };
283                li += 1;
284
285                for i in 0..CHANNELS {
286                    vals[i] += isize::from(bb1[i]) - isize::from(bb2[i]);
287                }
288
289                for i in 0..CHANNELS {
290                    C! { frontbuf[ti][i] = *round(FF32::new(vals[i] as f32) * iarr) as u8 };
291                }
292                ti += 1;
293            }
294
295            for _ in 0..min(width - blur_radius - 1, blur_radius) {
296                let bb = get_left(li);
297                li += 1;
298
299                for i in 0..CHANNELS {
300                    vals[i] += isize::from(lv[i]) - isize::from(bb[i]);
301                }
302
303                for i in 0..CHANNELS {
304                    C! { frontbuf[ti][i] = *round(FF32::new(vals[i] as f32) * iarr) as u8 };
305                }
306                ti += 1;
307            }
308        }
309    }
310}
311
312#[inline]
313/// Source: https://stackoverflow.com/a/42386149/585725
314fn round(mut x: FF32) -> FF32 {
315    x += 12582912.0;
316    x -= 12582912.0;
317    x
318}