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