1use core::cmp;
8
9use crate::{ImageRefMut, RGBA8};
10
11const STEPS: usize = 5;
12
13pub fn box_blur(
23 sigma_x: f64,
24 sigma_y: f64,
25 mut src: ImageRefMut,
26) {
27 let boxes_horz = create_box_gauss(sigma_x as f32);
28 let boxes_vert = create_box_gauss(sigma_y as f32);
29 let mut backbuf = src.data.to_vec();
30 let mut backbuf = ImageRefMut::new(&mut backbuf, src.width, src.height);
31
32 for (box_size_horz, box_size_vert) in boxes_horz.iter().zip(boxes_vert.iter()) {
33 let radius_horz = ((box_size_horz - 1) / 2) as usize;
34 let radius_vert = ((box_size_vert - 1) / 2) as usize;
35 box_blur_impl(radius_horz, radius_vert, &mut backbuf, &mut src);
36 }
37}
38
39#[inline(never)]
40fn create_box_gauss(sigma: f32) -> [i32; STEPS] {
41 if sigma > 0.0 {
42 let n_float = STEPS as f32;
43
44 let w_ideal = (12.0 * sigma * sigma / n_float).sqrt() + 1.0;
46 let mut wl = w_ideal.floor() as i32;
47 if wl % 2 == 0 {
48 wl -= 1;
49 }
50
51 let wu = wl + 2;
52
53 let wl_float = wl as f32;
54 let m_ideal =
55 ( 12.0 * sigma * sigma
56 - n_float * wl_float * wl_float
57 - 4.0 * n_float * wl_float
58 - 3.0 * n_float)
59 / (-4.0 * wl_float - 4.0);
60 let m = m_ideal.round() as usize;
61
62 let mut sizes = [0; STEPS];
63 for i in 0..STEPS {
64 if i < m {
65 sizes[i] = wl;
66 } else {
67 sizes[i] = wu;
68 }
69 }
70
71 sizes
72 } else {
73 [1; STEPS]
74 }
75}
76
77#[inline]
78fn box_blur_impl(
79 blur_radius_horz: usize,
80 blur_radius_vert: usize,
81 backbuf: &mut ImageRefMut,
82 frontbuf: &mut ImageRefMut,
83) {
84 box_blur_vert(blur_radius_vert, frontbuf, backbuf);
85 box_blur_horz(blur_radius_horz, backbuf, frontbuf);
86}
87
88#[inline]
89fn box_blur_vert(
90 blur_radius: usize,
91 backbuf: &ImageRefMut,
92 frontbuf: &mut ImageRefMut,
93) {
94 if blur_radius == 0 {
95 frontbuf.data.copy_from_slice(backbuf.data);
96 return;
97 }
98
99 let width = backbuf.width as usize;
100 let height = backbuf.height as usize;
101
102 let iarr = 1.0 / (blur_radius + blur_radius + 1) as f32;
103 let blur_radius_prev = blur_radius as isize - height as isize;
104 let blur_radius_next = blur_radius as isize + 1;
105
106 for i in 0..width {
107 let col_start = i; let col_end = i + width * (height - 1); let mut ti = i;
110 let mut li = ti;
111 let mut ri = ti + blur_radius * width;
112
113 let fv = RGBA8::default();
114 let lv = RGBA8::default();
115
116 let mut val_r = blur_radius_next * (fv.r as isize);
117 let mut val_g = blur_radius_next * (fv.g as isize);
118 let mut val_b = blur_radius_next * (fv.b as isize);
119 let mut val_a = blur_radius_next * (fv.a as isize);
120
121 let get_top = |i| {
124 if i < col_start {
125 fv
126 } else {
127 backbuf.data[i]
128 }
129 };
130
131 let get_bottom = |i| {
134 if i > col_end {
135 lv
136 } else {
137 backbuf.data[i]
138 }
139 };
140
141 for j in 0..cmp::min(blur_radius, height) {
142 let bb = backbuf.data[ti + j * width];
143 val_r += bb.r as isize;
144 val_g += bb.g as isize;
145 val_b += bb.b as isize;
146 val_a += bb.a as isize;
147 }
148 if blur_radius > height {
149 val_r += blur_radius_prev * (lv.r as isize);
150 val_g += blur_radius_prev * (lv.g as isize);
151 val_b += blur_radius_prev * (lv.b as isize);
152 val_a += blur_radius_prev * (lv.a as isize);
153 }
154
155 for _ in 0..cmp::min(height, blur_radius + 1) {
156 let bb = get_bottom(ri);
157 ri += width;
158 val_r += sub(bb.r, fv.r);
159 val_g += sub(bb.g, fv.g);
160 val_b += sub(bb.b, fv.b);
161 val_a += sub(bb.a, fv.a);
162
163 frontbuf.data[ti] = RGBA8 {
164 r: round(val_r as f32 * iarr) as u8,
165 g: round(val_g as f32 * iarr) as u8,
166 b: round(val_b as f32 * iarr) as u8,
167 a: round(val_a as f32 * iarr) as u8,
168 };
169 ti += width;
170 }
171
172 if height <= blur_radius {
173 continue;
175 }
176
177 for _ in (blur_radius + 1)..(height - blur_radius) {
178 let bb1 = backbuf.data[ri];
179 ri += width;
180 let bb2 = backbuf.data[li];
181 li += width;
182
183 val_r += sub(bb1.r, bb2.r);
184 val_g += sub(bb1.g, bb2.g);
185 val_b += sub(bb1.b, bb2.b);
186 val_a += sub(bb1.a, bb2.a);
187
188 frontbuf.data[ti] = RGBA8 {
189 r: round(val_r as f32 * iarr) as u8,
190 g: round(val_g as f32 * iarr) as u8,
191 b: round(val_b as f32 * iarr) as u8,
192 a: round(val_a as f32 * iarr) as u8,
193 };
194 ti += width;
195 }
196
197 for _ in 0..cmp::min(height - blur_radius - 1, blur_radius) {
198 let bb = get_top(li);
199 li += width;
200
201 val_r += sub(lv.r, bb.r);
202 val_g += sub(lv.g, bb.g);
203 val_b += sub(lv.b, bb.b);
204 val_a += sub(lv.a, bb.a);
205
206 frontbuf.data[ti] = RGBA8 {
207 r: round(val_r as f32 * iarr) as u8,
208 g: round(val_g as f32 * iarr) as u8,
209 b: round(val_b as f32 * iarr) as u8,
210 a: round(val_a as f32 * iarr) as u8,
211 };
212 ti += width;
213 }
214 }
215}
216
217#[inline]
218fn box_blur_horz(
219 blur_radius: usize,
220 backbuf: &ImageRefMut,
221 frontbuf: &mut ImageRefMut,
222) {
223 if blur_radius == 0 {
224 frontbuf.data.copy_from_slice(backbuf.data);
225 return;
226 }
227
228 let width = backbuf.width as usize;
229 let height = backbuf.height as usize;
230
231 let iarr = 1.0 / (blur_radius + blur_radius + 1) as f32;
232 let blur_radius_prev = blur_radius as isize - width as isize;
233 let blur_radius_next = blur_radius as isize + 1;
234
235 for i in 0..height {
236 let row_start = i * width; let row_end = (i + 1) * width - 1; let mut ti = i * width; let mut li = ti;
240 let mut ri = ti + blur_radius;
241
242 let fv = RGBA8::default();
243 let lv = RGBA8::default();
244
245 let mut val_r = blur_radius_next * (fv.r as isize);
246 let mut val_g = blur_radius_next * (fv.g as isize);
247 let mut val_b = blur_radius_next * (fv.b as isize);
248 let mut val_a = blur_radius_next * (fv.a as isize);
249
250 let get_left = |i| {
253 if i < row_start {
254 fv
255 } else {
256 backbuf.data[i]
257 }
258 };
259
260 let get_right = |i| {
263 if i > row_end {
264 lv
265 } else {
266 backbuf.data[i]
267 }
268 };
269
270 for j in 0..cmp::min(blur_radius, width) {
271 let bb = backbuf.data[ti + j]; val_r += bb.r as isize;
273 val_g += bb.g as isize;
274 val_b += bb.b as isize;
275 val_a += bb.a as isize;
276 }
277 if blur_radius > width {
278 val_r += blur_radius_prev * (lv.r as isize);
279 val_g += blur_radius_prev * (lv.g as isize);
280 val_b += blur_radius_prev * (lv.b as isize);
281 val_a += blur_radius_prev * (lv.a as isize);
282 }
283
284 for _ in 0..cmp::min(width, blur_radius + 1) {
286 let bb = get_right(ri);
287 ri += 1;
288 val_r += sub(bb.r, fv.r);
289 val_g += sub(bb.g, fv.g);
290 val_b += sub(bb.b, fv.b);
291 val_a += sub(bb.a, fv.a);
292
293 frontbuf.data[ti] = RGBA8 {
294 r: round(val_r as f32 * iarr) as u8,
295 g: round(val_g as f32 * iarr) as u8,
296 b: round(val_b as f32 * iarr) as u8,
297 a: round(val_a as f32 * iarr) as u8,
298 };
299 ti += 1; }
301
302 if width <= blur_radius {
303 continue;
305 }
306
307 for _ in (blur_radius + 1)..(width - blur_radius) {
310 let bb1 = backbuf.data[ri];
311 ri += 1;
312 let bb2 = backbuf.data[li];
313 li += 1;
314
315 val_r += sub(bb1.r, bb2.r);
316 val_g += sub(bb1.g, bb2.g);
317 val_b += sub(bb1.b, bb2.b);
318 val_a += sub(bb1.a, bb2.a);
319
320 frontbuf.data[ti] = RGBA8 {
321 r: round(val_r as f32 * iarr) as u8,
322 g: round(val_g as f32 * iarr) as u8,
323 b: round(val_b as f32 * iarr) as u8,
324 a: round(val_a as f32 * iarr) as u8,
325 };
326 ti += 1;
327 }
328
329 for _ in 0..cmp::min(width - blur_radius - 1, blur_radius) {
331 let bb = get_left(li);
332 li += 1;
333
334 val_r += sub(lv.r, bb.r);
335 val_g += sub(lv.g, bb.g);
336 val_b += sub(lv.b, bb.b);
337 val_a += sub(lv.a, bb.a);
338
339 frontbuf.data[ti] = RGBA8 {
340 r: round(val_r as f32 * iarr) as u8,
341 g: round(val_g as f32 * iarr) as u8,
342 b: round(val_b as f32 * iarr) as u8,
343 a: round(val_a as f32 * iarr) as u8,
344 };
345 ti += 1;
346 }
347 }
348}
349
350#[inline]
355fn round(mut x: f32) -> f32 {
356 x += 12582912.0;
357 x -= 12582912.0;
358 x
359}
360
361#[inline]
362fn sub(c1: u8, c2: u8) -> isize {
363 c1 as isize - c2 as isize
364}