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