1use crate::color::Rgba8;
8use crate::rendering_buffer::RowAccessor;
9
10#[rustfmt::skip]
17const STACK_BLUR8_MUL: [u32; 255] = [
18 512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,
19 454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,
20 482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,
21 437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,
22 497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,
23 320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,
24 446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,
25 329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,
26 505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,
27 399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,
28 324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,
29 268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,
30 451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,
31 385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,
32 332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,
33 289,287,285,282,280,278,275,273,271,269,267,265,263,261,259,
34];
35
36#[rustfmt::skip]
39const STACK_BLUR8_SHR: [u32; 255] = [
40 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
41 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
42 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
43 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
44 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
45 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
46 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
47 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
48 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
49 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
50 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
51 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
52 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
53 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
54 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
55 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
56];
57
58pub fn stack_blur_rgba32(rbuf: &mut RowAccessor, mut rx: u32, mut ry: u32) {
70 let w = rbuf.width() as usize;
71 let h = rbuf.height() as usize;
72 if w == 0 || h == 0 {
73 return;
74 }
75 let wm = w - 1;
76 let hm = h - 1;
77
78 if rx > 0 {
80 if rx > 254 {
81 rx = 254;
82 }
83 let rx = rx as usize;
84 let div = rx * 2 + 1;
85 let mul_sum = STACK_BLUR8_MUL[rx] as u64;
86 let shr_sum = STACK_BLUR8_SHR[rx];
87
88 let mut stack = vec![[0u8; 4]; div];
89
90 for y in 0..h {
91 let row = unsafe {
92 let ptr = rbuf.row_ptr(y as i32);
93 std::slice::from_raw_parts_mut(ptr, w * 4)
94 };
95
96 let mut sum_r: u64 = 0;
97 let mut sum_g: u64 = 0;
98 let mut sum_b: u64 = 0;
99 let mut sum_a: u64 = 0;
100 let mut sum_in_r: u64 = 0;
101 let mut sum_in_g: u64 = 0;
102 let mut sum_in_b: u64 = 0;
103 let mut sum_in_a: u64 = 0;
104 let mut sum_out_r: u64 = 0;
105 let mut sum_out_g: u64 = 0;
106 let mut sum_out_b: u64 = 0;
107 let mut sum_out_a: u64 = 0;
108
109 let src_r = row[0] as u64;
111 let src_g = row[1] as u64;
112 let src_b = row[2] as u64;
113 let src_a = row[3] as u64;
114
115 for i in 0..=rx {
116 stack[i] = [row[0], row[1], row[2], row[3]];
117 let w = (i + 1) as u64;
118 sum_r += src_r * w;
119 sum_g += src_g * w;
120 sum_b += src_b * w;
121 sum_a += src_a * w;
122 sum_out_r += src_r;
123 sum_out_g += src_g;
124 sum_out_b += src_b;
125 sum_out_a += src_a;
126 }
127
128 let mut src_off = 0usize; for i in 1..=rx {
130 if i <= wm {
131 src_off = i * 4;
132 }
133 stack[i + rx] = [row[src_off], row[src_off + 1], row[src_off + 2], row[src_off + 3]];
134 let w = (rx + 1 - i) as u64;
135 sum_r += row[src_off] as u64 * w;
136 sum_g += row[src_off + 1] as u64 * w;
137 sum_b += row[src_off + 2] as u64 * w;
138 sum_a += row[src_off + 3] as u64 * w;
139 sum_in_r += row[src_off] as u64;
140 sum_in_g += row[src_off + 1] as u64;
141 sum_in_b += row[src_off + 2] as u64;
142 sum_in_a += row[src_off + 3] as u64;
143 }
144
145 let mut stack_ptr = rx;
146 let mut xp = rx;
147 if xp > wm {
148 xp = wm;
149 }
150 src_off = xp * 4;
151
152 for x in 0..w {
153 let dst_off = x * 4;
154 row[dst_off] = ((sum_r * mul_sum) >> shr_sum) as u8;
155 row[dst_off + 1] = ((sum_g * mul_sum) >> shr_sum) as u8;
156 row[dst_off + 2] = ((sum_b * mul_sum) >> shr_sum) as u8;
157 row[dst_off + 3] = ((sum_a * mul_sum) >> shr_sum) as u8;
158
159 sum_r -= sum_out_r;
160 sum_g -= sum_out_g;
161 sum_b -= sum_out_b;
162 sum_a -= sum_out_a;
163
164 let mut stack_start = stack_ptr + div - rx;
165 if stack_start >= div {
166 stack_start -= div;
167 }
168
169 let sp = &stack[stack_start];
170 sum_out_r -= sp[0] as u64;
171 sum_out_g -= sp[1] as u64;
172 sum_out_b -= sp[2] as u64;
173 sum_out_a -= sp[3] as u64;
174
175 if xp < wm {
176 src_off += 4;
177 xp += 1;
178 }
179
180 stack[stack_start] = [row[src_off], row[src_off + 1], row[src_off + 2], row[src_off + 3]];
181
182 sum_in_r += row[src_off] as u64;
183 sum_in_g += row[src_off + 1] as u64;
184 sum_in_b += row[src_off + 2] as u64;
185 sum_in_a += row[src_off + 3] as u64;
186 sum_r += sum_in_r;
187 sum_g += sum_in_g;
188 sum_b += sum_in_b;
189 sum_a += sum_in_a;
190
191 stack_ptr += 1;
192 if stack_ptr >= div {
193 stack_ptr = 0;
194 }
195
196 let sp = &stack[stack_ptr];
197 sum_out_r += sp[0] as u64;
198 sum_out_g += sp[1] as u64;
199 sum_out_b += sp[2] as u64;
200 sum_out_a += sp[3] as u64;
201 sum_in_r -= sp[0] as u64;
202 sum_in_g -= sp[1] as u64;
203 sum_in_b -= sp[2] as u64;
204 sum_in_a -= sp[3] as u64;
205 }
206 }
207 }
208
209 if ry > 0 {
211 if ry > 254 {
212 ry = 254;
213 }
214 let ry = ry as usize;
215 let div = ry * 2 + 1;
216 let mul_sum = STACK_BLUR8_MUL[ry] as u64;
217 let shr_sum = STACK_BLUR8_SHR[ry];
218
219 let mut stack = vec![[0u8; 4]; div];
220 let stride = rbuf.stride() as isize;
221
222 for x in 0..w {
223 let base_ptr = unsafe { rbuf.row_ptr(0).add(x * 4) };
224
225 let mut sum_r: u64 = 0;
226 let mut sum_g: u64 = 0;
227 let mut sum_b: u64 = 0;
228 let mut sum_a: u64 = 0;
229 let mut sum_in_r: u64 = 0;
230 let mut sum_in_g: u64 = 0;
231 let mut sum_in_b: u64 = 0;
232 let mut sum_in_a: u64 = 0;
233 let mut sum_out_r: u64 = 0;
234 let mut sum_out_g: u64 = 0;
235 let mut sum_out_b: u64 = 0;
236 let mut sum_out_a: u64 = 0;
237
238 let src_pix = unsafe { std::slice::from_raw_parts(base_ptr, 4) };
239 for i in 0..=ry {
240 stack[i] = [src_pix[0], src_pix[1], src_pix[2], src_pix[3]];
241 let w = (i + 1) as u64;
242 sum_r += src_pix[0] as u64 * w;
243 sum_g += src_pix[1] as u64 * w;
244 sum_b += src_pix[2] as u64 * w;
245 sum_a += src_pix[3] as u64 * w;
246 sum_out_r += src_pix[0] as u64;
247 sum_out_g += src_pix[1] as u64;
248 sum_out_b += src_pix[2] as u64;
249 sum_out_a += src_pix[3] as u64;
250 }
251
252 let mut src_ptr = base_ptr;
253 for i in 1..=ry {
254 if i <= hm {
255 src_ptr = unsafe { src_ptr.offset(stride) };
256 }
257 let p = unsafe { std::slice::from_raw_parts(src_ptr, 4) };
258 stack[i + ry] = [p[0], p[1], p[2], p[3]];
259 let w = (ry + 1 - i) as u64;
260 sum_r += p[0] as u64 * w;
261 sum_g += p[1] as u64 * w;
262 sum_b += p[2] as u64 * w;
263 sum_a += p[3] as u64 * w;
264 sum_in_r += p[0] as u64;
265 sum_in_g += p[1] as u64;
266 sum_in_b += p[2] as u64;
267 sum_in_a += p[3] as u64;
268 }
269
270 let mut stack_ptr = ry;
271 let mut yp = ry;
272 if yp > hm {
273 yp = hm;
274 }
275 src_ptr = unsafe { base_ptr.offset(yp as isize * stride) };
276 let mut dst_ptr = base_ptr;
277
278 for _y in 0..h {
279 let dst = unsafe { std::slice::from_raw_parts_mut(dst_ptr, 4) };
280 dst[0] = ((sum_r * mul_sum) >> shr_sum) as u8;
281 dst[1] = ((sum_g * mul_sum) >> shr_sum) as u8;
282 dst[2] = ((sum_b * mul_sum) >> shr_sum) as u8;
283 dst[3] = ((sum_a * mul_sum) >> shr_sum) as u8;
284 dst_ptr = unsafe { dst_ptr.offset(stride) };
285
286 sum_r -= sum_out_r;
287 sum_g -= sum_out_g;
288 sum_b -= sum_out_b;
289 sum_a -= sum_out_a;
290
291 let mut stack_start = stack_ptr + div - ry;
292 if stack_start >= div {
293 stack_start -= div;
294 }
295
296 let sp = &stack[stack_start];
297 sum_out_r -= sp[0] as u64;
298 sum_out_g -= sp[1] as u64;
299 sum_out_b -= sp[2] as u64;
300 sum_out_a -= sp[3] as u64;
301
302 if yp < hm {
303 src_ptr = unsafe { src_ptr.offset(stride) };
304 yp += 1;
305 }
306
307 let p = unsafe { std::slice::from_raw_parts(src_ptr, 4) };
308 stack[stack_start] = [p[0], p[1], p[2], p[3]];
309
310 sum_in_r += p[0] as u64;
311 sum_in_g += p[1] as u64;
312 sum_in_b += p[2] as u64;
313 sum_in_a += p[3] as u64;
314 sum_r += sum_in_r;
315 sum_g += sum_in_g;
316 sum_b += sum_in_b;
317 sum_a += sum_in_a;
318
319 stack_ptr += 1;
320 if stack_ptr >= div {
321 stack_ptr = 0;
322 }
323
324 let sp = &stack[stack_ptr];
325 sum_out_r += sp[0] as u64;
326 sum_out_g += sp[1] as u64;
327 sum_out_b += sp[2] as u64;
328 sum_out_a += sp[3] as u64;
329 sum_in_r -= sp[0] as u64;
330 sum_in_g -= sp[1] as u64;
331 sum_in_b -= sp[2] as u64;
332 sum_in_a -= sp[3] as u64;
333 }
334 }
335 }
336}
337
338#[derive(Clone, Copy, Default)]
346struct RecursiveBlurCalcRgba {
347 r: f64,
348 g: f64,
349 b: f64,
350 a: f64,
351}
352
353impl RecursiveBlurCalcRgba {
354 fn from_pix(c: &Rgba8) -> Self {
355 Self {
356 r: c.r as f64,
357 g: c.g as f64,
358 b: c.b as f64,
359 a: c.a as f64,
360 }
361 }
362
363 fn calc(
364 b_coeff: f64,
365 b1: f64,
366 b2: f64,
367 b3: f64,
368 c1: &Self,
369 c2: &Self,
370 c3: &Self,
371 c4: &Self,
372 ) -> Self {
373 Self {
374 r: b_coeff * c1.r + b1 * c2.r + b2 * c3.r + b3 * c4.r,
375 g: b_coeff * c1.g + b1 * c2.g + b2 * c3.g + b3 * c4.g,
376 b: b_coeff * c1.b + b1 * c2.b + b2 * c3.b + b3 * c4.b,
377 a: b_coeff * c1.a + b1 * c2.a + b2 * c3.a + b3 * c4.a,
378 }
379 }
380
381 fn to_pix(&self) -> Rgba8 {
382 Rgba8::new(
383 self.r as u32,
384 self.g as u32,
385 self.b as u32,
386 self.a as u32,
387 )
388 }
389}
390
391pub fn recursive_blur_rgba32(rbuf: &mut RowAccessor, radius: f64) {
397 recursive_blur_rgba32_x(rbuf, radius);
398 recursive_blur_rgba32_y(rbuf, radius);
399}
400
401pub fn recursive_blur_rgba32_x(rbuf: &mut RowAccessor, radius: f64) {
403 if radius < 0.62 {
404 return;
405 }
406 let w = rbuf.width() as usize;
407 let h = rbuf.height() as usize;
408 if w < 3 {
409 return;
410 }
411
412 let s = radius * 0.5;
413 let q = if s < 2.5 {
414 3.97156 - 4.14554 * (1.0 - 0.26891 * s).sqrt()
415 } else {
416 0.98711 * s - 0.96330
417 };
418
419 let q2 = q * q;
420 let q3 = q2 * q;
421
422 let b0 = 1.0 / (1.578250 + 2.444130 * q + 1.428100 * q2 + 0.422205 * q3);
423 let mut b1 = 2.44413 * q + 2.85619 * q2 + 1.26661 * q3;
424 let mut b2 = -1.42810 * q2 - 1.26661 * q3;
425 let mut b3 = 0.422205 * q3;
426 let b = 1.0 - (b1 + b2 + b3) * b0;
427
428 b1 *= b0;
429 b2 *= b0;
430 b3 *= b0;
431
432 let wm = w as i32 - 1;
433
434 let mut sum1 = vec![RecursiveBlurCalcRgba::default(); w];
435 let mut sum2 = vec![RecursiveBlurCalcRgba::default(); w];
436 let mut buf = vec![Rgba8::new(0, 0, 0, 0); w];
437
438 for y in 0..h {
439 let row = unsafe {
441 let ptr = rbuf.row_ptr(y as i32);
442 std::slice::from_raw_parts(ptr, w * 4)
443 };
444
445 let pix = |x: usize| -> Rgba8 {
446 let off = x * 4;
447 Rgba8::new(
448 row[off] as u32,
449 row[off + 1] as u32,
450 row[off + 2] as u32,
451 row[off + 3] as u32,
452 )
453 };
454
455 let c = RecursiveBlurCalcRgba::from_pix(&pix(0));
457 sum1[0] = RecursiveBlurCalcRgba::calc(b, b1, b2, b3, &c, &c, &c, &c);
458 let c = RecursiveBlurCalcRgba::from_pix(&pix(1));
459 sum1[1] = RecursiveBlurCalcRgba::calc(b, b1, b2, b3, &c, &sum1[0], &sum1[0], &sum1[0]);
460 let c = RecursiveBlurCalcRgba::from_pix(&pix(2));
461 sum1[2] = RecursiveBlurCalcRgba::calc(b, b1, b2, b3, &c, &sum1[1], &sum1[0], &sum1[0]);
462
463 for x in 3..w {
464 let c = RecursiveBlurCalcRgba::from_pix(&pix(x));
465 sum1[x] = RecursiveBlurCalcRgba::calc(
466 b, b1, b2, b3, &c, &sum1[x - 1], &sum1[x - 2], &sum1[x - 3],
467 );
468 }
469
470 let wmi = wm as usize;
472 sum2[wmi] = RecursiveBlurCalcRgba::calc(
473 b, b1, b2, b3, &sum1[wmi], &sum1[wmi], &sum1[wmi], &sum1[wmi],
474 );
475 sum2[wmi - 1] = RecursiveBlurCalcRgba::calc(
476 b, b1, b2, b3, &sum1[wmi - 1], &sum2[wmi], &sum2[wmi], &sum2[wmi],
477 );
478 sum2[wmi - 2] = RecursiveBlurCalcRgba::calc(
479 b, b1, b2, b3, &sum1[wmi - 2], &sum2[wmi - 1], &sum2[wmi], &sum2[wmi],
480 );
481 buf[wmi] = sum2[wmi].to_pix();
482 buf[wmi - 1] = sum2[wmi - 1].to_pix();
483 buf[wmi - 2] = sum2[wmi - 2].to_pix();
484
485 for x in (0..=(wmi as i32 - 3)).rev() {
486 let x = x as usize;
487 sum2[x] = RecursiveBlurCalcRgba::calc(
488 b, b1, b2, b3, &sum1[x], &sum2[x + 1], &sum2[x + 2], &sum2[x + 3],
489 );
490 buf[x] = sum2[x].to_pix();
491 }
492
493 let row = unsafe {
495 let ptr = rbuf.row_ptr(y as i32);
496 std::slice::from_raw_parts_mut(ptr, w * 4)
497 };
498 for x in 0..w {
499 let off = x * 4;
500 row[off] = buf[x].r;
501 row[off + 1] = buf[x].g;
502 row[off + 2] = buf[x].b;
503 row[off + 3] = buf[x].a;
504 }
505 }
506}
507
508pub fn recursive_blur_rgba32_y(rbuf: &mut RowAccessor, radius: f64) {
510 if radius < 0.62 {
511 return;
512 }
513 let w = rbuf.width() as usize;
514 let h = rbuf.height() as usize;
515 if h < 3 {
516 return;
517 }
518
519 let s = radius * 0.5;
520 let q = if s < 2.5 {
521 3.97156 - 4.14554 * (1.0 - 0.26891 * s).sqrt()
522 } else {
523 0.98711 * s - 0.96330
524 };
525
526 let q2 = q * q;
527 let q3 = q2 * q;
528
529 let b0 = 1.0 / (1.578250 + 2.444130 * q + 1.428100 * q2 + 0.422205 * q3);
530 let mut b1 = 2.44413 * q + 2.85619 * q2 + 1.26661 * q3;
531 let mut b2 = -1.42810 * q2 - 1.26661 * q3;
532 let mut b3 = 0.422205 * q3;
533 let b = 1.0 - (b1 + b2 + b3) * b0;
534
535 b1 *= b0;
536 b2 *= b0;
537 b3 *= b0;
538
539 let hm = h as i32 - 1;
540 let stride = rbuf.stride() as isize;
541
542 let mut sum1 = vec![RecursiveBlurCalcRgba::default(); h];
543 let mut sum2 = vec![RecursiveBlurCalcRgba::default(); h];
544 let mut buf = vec![Rgba8::new(0, 0, 0, 0); h];
545
546 for x in 0..w {
547 let base_ptr = unsafe { rbuf.row_ptr(0).add(x * 4) };
548
549 let pix = |yi: usize| -> Rgba8 {
550 let p = unsafe { std::slice::from_raw_parts(base_ptr.offset(yi as isize * stride), 4) };
551 Rgba8::new(p[0] as u32, p[1] as u32, p[2] as u32, p[3] as u32)
552 };
553
554 let c = RecursiveBlurCalcRgba::from_pix(&pix(0));
556 sum1[0] = RecursiveBlurCalcRgba::calc(b, b1, b2, b3, &c, &c, &c, &c);
557 let c = RecursiveBlurCalcRgba::from_pix(&pix(1));
558 sum1[1] = RecursiveBlurCalcRgba::calc(b, b1, b2, b3, &c, &sum1[0], &sum1[0], &sum1[0]);
559 let c = RecursiveBlurCalcRgba::from_pix(&pix(2));
560 sum1[2] = RecursiveBlurCalcRgba::calc(b, b1, b2, b3, &c, &sum1[1], &sum1[0], &sum1[0]);
561
562 for yi in 3..h {
563 let c = RecursiveBlurCalcRgba::from_pix(&pix(yi));
564 sum1[yi] = RecursiveBlurCalcRgba::calc(
565 b, b1, b2, b3, &c, &sum1[yi - 1], &sum1[yi - 2], &sum1[yi - 3],
566 );
567 }
568
569 let hmi = hm as usize;
571 sum2[hmi] = RecursiveBlurCalcRgba::calc(
572 b, b1, b2, b3, &sum1[hmi], &sum1[hmi], &sum1[hmi], &sum1[hmi],
573 );
574 sum2[hmi - 1] = RecursiveBlurCalcRgba::calc(
575 b, b1, b2, b3, &sum1[hmi - 1], &sum2[hmi], &sum2[hmi], &sum2[hmi],
576 );
577 sum2[hmi - 2] = RecursiveBlurCalcRgba::calc(
578 b, b1, b2, b3, &sum1[hmi - 2], &sum2[hmi - 1], &sum2[hmi], &sum2[hmi],
579 );
580 buf[hmi] = sum2[hmi].to_pix();
581 buf[hmi - 1] = sum2[hmi - 1].to_pix();
582 buf[hmi - 2] = sum2[hmi - 2].to_pix();
583
584 for yi in (0..=(hmi as i32 - 3)).rev() {
585 let yi = yi as usize;
586 sum2[yi] = RecursiveBlurCalcRgba::calc(
587 b, b1, b2, b3, &sum1[yi], &sum2[yi + 1], &sum2[yi + 2], &sum2[yi + 3],
588 );
589 buf[yi] = sum2[yi].to_pix();
590 }
591
592 for yi in 0..h {
594 let p = unsafe {
595 std::slice::from_raw_parts_mut(base_ptr.offset(yi as isize * stride), 4)
596 };
597 p[0] = buf[yi].r;
598 p[1] = buf[yi].g;
599 p[2] = buf[yi].b;
600 p[3] = buf[yi].a;
601 }
602 }
603}
604
605#[cfg(test)]
606mod tests {
607 use super::*;
608 use crate::rendering_buffer::RowAccessor;
609
610 fn make_buffer(w: u32, h: u32) -> (Vec<u8>, RowAccessor) {
611 let stride = (w * 4) as i32;
612 let buf = vec![0u8; (h * w * 4) as usize];
613 let mut ra = RowAccessor::new();
614 unsafe {
615 ra.attach(buf.as_ptr() as *mut u8, w, h, stride);
616 }
617 (buf, ra)
618 }
619
620 fn set_pixel(ra: &mut RowAccessor, x: usize, y: usize, r: u8, g: u8, b: u8, a: u8) {
621 let row = unsafe {
622 let ptr = ra.row_ptr(y as i32);
623 std::slice::from_raw_parts_mut(ptr, (ra.width() as usize) * 4)
624 };
625 let off = x * 4;
626 row[off] = r;
627 row[off + 1] = g;
628 row[off + 2] = b;
629 row[off + 3] = a;
630 }
631
632 fn get_pixel(ra: &RowAccessor, x: usize, y: usize) -> [u8; 4] {
633 let row = unsafe {
634 let ptr = ra.row_ptr(y as i32);
635 std::slice::from_raw_parts(ptr, (ra.width() as usize) * 4)
636 };
637 let off = x * 4;
638 [row[off], row[off + 1], row[off + 2], row[off + 3]]
639 }
640
641 #[test]
642 fn test_stack_blur_zero_radius() {
643 let (_buf, mut ra) = make_buffer(10, 10);
644 set_pixel(&mut ra, 5, 5, 255, 0, 0, 255);
645
646 let before = get_pixel(&ra, 5, 5);
647 stack_blur_rgba32(&mut ra, 0, 0);
648 let after = get_pixel(&ra, 5, 5);
649 assert_eq!(before, after);
650 }
651
652 #[test]
653 fn test_stack_blur_spreads_pixel() {
654 let (_buf, mut ra) = make_buffer(20, 20);
655 set_pixel(&mut ra, 10, 10, 255, 255, 255, 255);
657
658 stack_blur_rgba32(&mut ra, 3, 3);
659
660 let center = get_pixel(&ra, 10, 10);
662 assert!(center[0] > 0, "center should have some red after blur");
663
664 let neighbor = get_pixel(&ra, 11, 10);
666 assert!(neighbor[0] > 0, "neighbor should have some red after blur");
667 }
668
669 #[test]
670 fn test_stack_blur_uniform_stays_uniform() {
671 let (_buf, mut ra) = make_buffer(10, 10);
672 for y in 0..10 {
674 for x in 0..10 {
675 set_pixel(&mut ra, x, y, 128, 128, 128, 255);
676 }
677 }
678
679 stack_blur_rgba32(&mut ra, 2, 2);
680
681 for y in 0..10 {
683 for x in 0..10 {
684 let p = get_pixel(&ra, x, y);
685 assert!(
686 (p[0] as i32 - 128).abs() <= 1,
687 "pixel ({x},{y}) r={} should be ~128",
688 p[0]
689 );
690 }
691 }
692 }
693
694 #[test]
695 fn test_recursive_blur_zero_radius() {
696 let (_buf, mut ra) = make_buffer(10, 10);
697 set_pixel(&mut ra, 5, 5, 255, 0, 0, 255);
698
699 let before = get_pixel(&ra, 5, 5);
700 recursive_blur_rgba32(&mut ra, 0.0); let after = get_pixel(&ra, 5, 5);
702 assert_eq!(before, after);
703 }
704
705 #[test]
706 fn test_recursive_blur_spreads_pixel() {
707 let (_buf, mut ra) = make_buffer(20, 20);
708 set_pixel(&mut ra, 10, 10, 255, 255, 255, 255);
709
710 recursive_blur_rgba32(&mut ra, 3.0);
711
712 let center = get_pixel(&ra, 10, 10);
713 assert!(center[0] > 0);
714
715 let neighbor = get_pixel(&ra, 11, 10);
716 assert!(neighbor[0] > 0, "neighbor should have some value after blur");
717 }
718
719 #[test]
720 fn test_recursive_blur_uniform_stays_uniform() {
721 let (_buf, mut ra) = make_buffer(10, 10);
722 for y in 0..10 {
723 for x in 0..10 {
724 set_pixel(&mut ra, x, y, 100, 100, 100, 255);
725 }
726 }
727
728 recursive_blur_rgba32(&mut ra, 2.0);
729
730 for y in 0..10 {
731 for x in 0..10 {
732 let p = get_pixel(&ra, x, y);
733 assert!(
734 (p[0] as i32 - 100).abs() <= 2,
735 "pixel ({x},{y}) r={} should be ~100",
736 p[0]
737 );
738 }
739 }
740 }
741}