1use std::cmp::min;
5use umath::FF32;
6
7pub 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]
313fn round(mut x: FF32) -> FF32 {
315 x += 12582912.0;
316 x -= 12582912.0;
317 x
318}