1mod median;
4pub use self::median::median_filter;
5
6mod sharpen;
7pub use self::sharpen::*;
8
9use image::{GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Pixel, Primitive};
10
11use crate::definitions::{Clamp, Image};
12use crate::integral_image::{column_running_sum, row_running_sum};
13use crate::map::{ChannelMap, WithChannel};
14use num::{abs, pow, Num};
15
16use crate::math::cast;
17use conv::ValueInto;
18use std::cmp::{max, min};
19use std::f32;
20
21#[must_use = "the function does not modify the original image"]
53pub fn bilateral_filter(
54 image: &GrayImage,
55 window_size: u32,
56 sigma_color: f32,
57 sigma_spatial: f32,
58) -> Image<Luma<u8>> {
59 fn gaussian_weight(x: f32, sigma_squared: f32) -> f32 {
61 (-0.5 * x.powi(2) / sigma_squared).exp()
62 }
63
64 fn window_coords(window_size: u32) -> (Vec<i32>, Vec<i32>) {
66 let window_start = (-(window_size as f32) / 2.0).floor() as i32;
67 let window_end = (window_size as f32 / 2.0).floor() as i32 + 1;
68 let window_range = window_start..window_end;
69 let v = window_range.collect::<Vec<i32>>();
70 let cc: Vec<i32> = v
71 .iter()
72 .cycle()
73 .take(v.len() * v.len())
74 .into_iter()
75 .cloned()
76 .collect();
77 let mut rr = Vec::new();
78 let window_range = window_start..window_end;
79 for i in window_range {
80 rr.append(&mut vec![i; (window_size + 1) as usize]);
81 }
82 (rr, cc)
83 }
84
85 fn compute_color_lut(bins: u32, sigma: f32, max_value: f32) -> Vec<f32> {
87 let v = (0..bins as i32).collect::<Vec<i32>>();
88 let step_size = max_value / bins as f32;
89 let vals = v.iter().map(|&x| x as f32 * step_size).collect::<Vec<_>>();
90 let sigma_squared = sigma.powi(2);
91 let gauss_weights = vals
92 .iter()
93 .map(|&x| gaussian_weight(x, sigma_squared))
94 .collect::<Vec<_>>();
95 gauss_weights
96 }
97
98 fn compute_spatial_lut(window_size: u32, sigma: f32) -> Vec<f32> {
100 let (rr, cc) = window_coords(window_size);
101 let mut gauss_weights = Vec::new();
102 let it = rr.iter().zip(cc.iter());
103 let sigma_squared = sigma.powi(2);
104 for (r, c) in it {
105 let dist = f32::sqrt(pow(*r as f32, 2) + pow(*c as f32, 2));
106 gauss_weights.push(gaussian_weight(dist, sigma_squared));
107 }
108 gauss_weights
109 }
110
111 let (width, height) = image.dimensions();
112 let mut out = ImageBuffer::new(width, height);
113 let max_value = *image.iter().max().unwrap() as f32;
114 let n_bins: u32 = 255; let color_lut = compute_color_lut(n_bins, sigma_color, max_value);
116 let color_dist_scale = n_bins as f32 / max_value;
117 let max_color_bin = (n_bins - 1) as usize;
118 let range_lut = compute_spatial_lut(window_size, sigma_spatial);
119 let window_size = window_size as i32;
120 let window_extent = (window_size - 1) / 2;
121 let height = height as i32;
122 let width = width as i32;
123 for row in 0..height {
124 for col in 0..width {
125 let mut total_val: f32 = 0.;
126 let mut total_weight: f32 = 0.;
127 let window_center_val = image.get_pixel(col as u32, row as u32)[0] as i32;
128 for window_row in -window_extent..window_extent + 1 {
129 let window_row_abs: i32 = row + window_row;
130 let window_row_abs: i32 = min(height - 1, max(0, window_row_abs)); let kr: i32 = window_row + window_extent;
132 for window_col in -window_extent..window_extent + 1 {
133 let window_col_abs: i32 = col + window_col;
134 let window_col_abs: i32 = min(width - 1, max(0, window_col_abs)); let kc: i32 = window_col + window_extent;
136 let range_bin = (kr * window_size + kc) as usize;
137 let range_weight: f32 = range_lut[range_bin];
138 let val: i32 =
139 image.get_pixel(window_col_abs as u32, window_row_abs as u32)[0] as i32;
140 let color_dist: i32 = abs(window_center_val - val);
141 let color_bin = (color_dist as f32 * color_dist_scale) as usize;
142 let color_bin: usize = min(color_bin, max_color_bin);
143 let color_weight: f32 = color_lut[color_bin];
144 let weight: f32 = range_weight * color_weight;
145 total_val += val as f32 * weight;
146 total_weight += weight;
147 }
148 }
149 let new_val = (total_val / total_weight).round() as u8;
150 out.put_pixel(col as u32, row as u32, Luma([new_val]));
151 }
152 }
153 out
154}
155
156#[must_use = "the function does not modify the original image"]
167pub fn box_filter(image: &GrayImage, x_radius: u32, y_radius: u32) -> Image<Luma<u8>> {
168 let (width, height) = image.dimensions();
169 let mut out = ImageBuffer::new(width, height);
170 if width == 0 || height == 0 {
171 return out;
172 }
173
174 let kernel_width = 2 * x_radius + 1;
175 let kernel_height = 2 * y_radius + 1;
176
177 let mut row_buffer = vec![0; (width + 2 * x_radius) as usize];
178 for y in 0..height {
179 row_running_sum(image, y, &mut row_buffer, x_radius);
180 let val = row_buffer[(2 * x_radius) as usize] / kernel_width;
181 unsafe {
182 out.unsafe_put_pixel(0, y, Luma([val as u8]));
183 }
184 for x in 1..width {
185 let u = (x + 2 * x_radius) as usize;
188 let l = (x - 1) as usize;
189 let val = (row_buffer[u] - row_buffer[l]) / kernel_width;
190 unsafe {
191 out.unsafe_put_pixel(x, y, Luma([val as u8]));
192 }
193 }
194 }
195
196 let mut col_buffer = vec![0; (height + 2 * y_radius) as usize];
197 for x in 0..width {
198 column_running_sum(&out, x, &mut col_buffer, y_radius);
199 let val = col_buffer[(2 * y_radius) as usize] / kernel_height;
200 unsafe {
201 out.unsafe_put_pixel(x, 0, Luma([val as u8]));
202 }
203 for y in 1..height {
204 let u = (y + 2 * y_radius) as usize;
205 let l = (y - 1) as usize;
206 let val = (col_buffer[u] - col_buffer[l]) / kernel_height;
207 unsafe {
208 out.unsafe_put_pixel(x, y, Luma([val as u8]));
209 }
210 }
211 }
212
213 out
214}
215
216pub struct Kernel<'a, K> {
218 data: &'a [K],
219 width: u32,
220 height: u32,
221}
222
223impl<'a, K: Num + Copy + 'a> Kernel<'a, K> {
224 pub fn new(data: &'a [K], width: u32, height: u32) -> Kernel<'a, K> {
227 assert!(width > 0 && height > 0, "width and height must be non-zero");
228 assert!(
229 width * height == data.len() as u32,
230 "Invalid kernel len: expecting {}, found {}",
231 width * height,
232 data.len()
233 );
234 Kernel {
235 data,
236 width,
237 height,
238 }
239 }
240
241 pub fn filter<P, F, Q>(&self, image: &Image<P>, mut f: F) -> Image<Q>
244 where
245 P: Pixel,
246 <P as Pixel>::Subpixel: ValueInto<K>,
247 Q: Pixel,
248 F: FnMut(&mut Q::Subpixel, K),
249 {
250 let (width, height) = image.dimensions();
251 let mut out = Image::<Q>::new(width, height);
252 let num_channels = P::CHANNEL_COUNT as usize;
253 let zero = K::zero();
254 let mut acc = vec![zero; num_channels];
255 let (k_width, k_height) = (self.width as i64, self.height as i64);
256 let (width, height) = (width as i64, height as i64);
257
258 for y in 0..height {
259 for x in 0..width {
260 for k_y in 0..k_height {
261 let y_p = min(height - 1, max(0, y + k_y - k_height / 2)) as u32;
262 for k_x in 0..k_width {
263 let x_p = min(width - 1, max(0, x + k_x - k_width / 2)) as u32;
264 accumulate(
265 &mut acc,
266 unsafe { &image.unsafe_get_pixel(x_p, y_p) },
267 unsafe { *self.data.get_unchecked((k_y * k_width + k_x) as usize) },
268 );
269 }
270 }
271 let out_channels = out.get_pixel_mut(x as u32, y as u32).channels_mut();
272 for (a, c) in acc.iter_mut().zip(out_channels.iter_mut()) {
273 f(c, *a);
274 *a = zero;
275 }
276 }
277 }
278
279 out
280 }
281}
282
283#[inline]
284fn gaussian(x: f32, r: f32) -> f32 {
285 ((2.0 * f32::consts::PI).sqrt() * r).recip() * (-x.powi(2) / (2.0 * r.powi(2))).exp()
286}
287
288fn gaussian_kernel_f32(sigma: f32) -> Vec<f32> {
291 let kernel_radius = (2.0 * sigma).ceil() as usize;
292 let mut kernel_data = vec![0.0; 2 * kernel_radius + 1];
293 for i in 0..kernel_radius + 1 {
294 let value = gaussian(i as f32, sigma);
295 kernel_data[kernel_radius + i] = value;
296 kernel_data[kernel_radius - i] = value;
297 }
298 kernel_data
299}
300
301#[must_use = "the function does not modify the original image"]
310pub fn gaussian_blur_f32<P>(image: &Image<P>, sigma: f32) -> Image<P>
311where
312 P: Pixel,
313 <P as Pixel>::Subpixel: ValueInto<f32> + Clamp<f32>,
314{
315 assert!(sigma > 0.0, "sigma must be > 0.0");
316 let kernel = gaussian_kernel_f32(sigma);
317 separable_filter_equal(image, &kernel)
318}
319
320#[must_use = "the function does not modify the original image"]
323pub fn separable_filter<P, K>(image: &Image<P>, h_kernel: &[K], v_kernel: &[K]) -> Image<P>
324where
325 P: Pixel,
326 <P as Pixel>::Subpixel: ValueInto<K> + Clamp<K>,
327 K: Num + Copy,
328{
329 let h = horizontal_filter(image, h_kernel);
330 vertical_filter(&h, v_kernel)
331}
332
333#[must_use = "the function does not modify the original image"]
336pub fn separable_filter_equal<P, K>(image: &Image<P>, kernel: &[K]) -> Image<P>
337where
338 P: Pixel,
339 <P as Pixel>::Subpixel: ValueInto<K> + Clamp<K>,
340 K: Num + Copy,
341{
342 separable_filter(image, kernel, kernel)
343}
344
345#[must_use = "the function does not modify the original image"]
348pub fn filter3x3<P, K, S>(image: &Image<P>, kernel: &[K]) -> Image<ChannelMap<P, S>>
349where
350 P::Subpixel: ValueInto<K>,
351 S: Clamp<K> + Primitive,
352 P: WithChannel<S>,
353 K: Num + Copy,
354{
355 let kernel = Kernel::new(kernel, 3, 3);
356 kernel.filter(image, |channel, acc| *channel = S::clamp(acc))
357}
358
359#[must_use = "the function does not modify the original image"]
363pub fn horizontal_filter<P, K>(image: &Image<P>, kernel: &[K]) -> Image<P>
364where
365 P: Pixel,
366 <P as Pixel>::Subpixel: ValueInto<K> + Clamp<K>,
367 K: Num + Copy,
368{
369 let (width, height) = image.dimensions();
373 let mut out = Image::<P>::new(width, height);
374 let zero = K::zero();
375 let mut acc = vec![zero; P::CHANNEL_COUNT as usize];
376 let k_width = kernel.len() as i32;
377
378 if k_width >= width as i32 {
381 for y in 0..height {
382 for x in 0..width {
383 for (i, k) in kernel.iter().enumerate() {
384 let x_unchecked = (x as i32) + i as i32 - k_width / 2;
385 let x_p = max(0, min(x_unchecked, width as i32 - 1)) as u32;
386 let p = unsafe { image.unsafe_get_pixel(x_p, y) };
387 accumulate(&mut acc, &p, *k);
388 }
389
390 let out_channels = out.get_pixel_mut(x, y).channels_mut();
391 for (a, c) in acc.iter_mut().zip(out_channels.iter_mut()) {
392 *c = <P as Pixel>::Subpixel::clamp(*a);
393 *a = zero;
394 }
395 }
396 }
397
398 return out;
399 }
400
401 let half_k = k_width / 2;
402
403 for y in 0..height {
404 for x in 0..half_k {
406 for (i, k) in kernel.iter().enumerate() {
407 let x_unchecked = (x as i32) + i as i32 - k_width / 2;
408 let x_p = max(0, x_unchecked) as u32;
409 let p = unsafe { image.unsafe_get_pixel(x_p, y) };
410 accumulate(&mut acc, &p, *k);
411 }
412
413 let out_channels = out.get_pixel_mut(x as u32, y).channels_mut();
414 for (a, c) in acc.iter_mut().zip(out_channels.iter_mut()) {
415 *c = <P as Pixel>::Subpixel::clamp(*a);
416 *a = zero;
417 }
418 }
419
420 for x in half_k..(width as i32 - half_k) {
422 for (i, k) in kernel.iter().enumerate() {
423 let x_unchecked = (x as i32) + i as i32 - k_width / 2;
424 let x_p = x_unchecked as u32;
425 let p = unsafe { image.unsafe_get_pixel(x_p, y) };
426 accumulate(&mut acc, &p, *k);
427 }
428
429 let out_channels = out.get_pixel_mut(x as u32, y).channels_mut();
430 for (a, c) in acc.iter_mut().zip(out_channels.iter_mut()) {
431 *c = <P as Pixel>::Subpixel::clamp(*a);
432 *a = zero;
433 }
434 }
435
436 for x in (width as i32 - half_k)..(width as i32) {
438 for (i, k) in kernel.iter().enumerate() {
439 let x_unchecked = (x as i32) + i as i32 - k_width / 2;
440 let x_p = min(x_unchecked, width as i32 - 1) as u32;
441 let p = unsafe { image.unsafe_get_pixel(x_p, y) };
442 accumulate(&mut acc, &p, *k);
443 }
444
445 let out_channels = out.get_pixel_mut(x as u32, y).channels_mut();
446 for (a, c) in acc.iter_mut().zip(out_channels.iter_mut()) {
447 *c = <P as Pixel>::Subpixel::clamp(*a);
448 *a = zero;
449 }
450 }
451 }
452
453 out
454}
455
456#[must_use = "the function does not modify the original image"]
459pub fn vertical_filter<P, K>(image: &Image<P>, kernel: &[K]) -> Image<P>
460where
461 P: Pixel,
462 <P as Pixel>::Subpixel: ValueInto<K> + Clamp<K>,
463 K: Num + Copy,
464{
465 let (width, height) = image.dimensions();
469 let mut out = Image::<P>::new(width, height);
470 let zero = K::zero();
471 let mut acc = vec![zero; P::CHANNEL_COUNT as usize];
472 let k_height = kernel.len() as i32;
473
474 if k_height >= height as i32 {
477 for y in 0..height {
478 for x in 0..width {
479 for (i, k) in kernel.iter().enumerate() {
480 let y_unchecked = (y as i32) + i as i32 - k_height / 2;
481 let y_p = max(0, min(y_unchecked, height as i32 - 1)) as u32;
482 let p = unsafe { image.unsafe_get_pixel(x, y_p) };
483 accumulate(&mut acc, &p, *k);
484 }
485
486 let out_channels = out.get_pixel_mut(x, y).channels_mut();
487 for (a, c) in acc.iter_mut().zip(out_channels.iter_mut()) {
488 *c = <P as Pixel>::Subpixel::clamp(*a);
489 *a = zero;
490 }
491 }
492 }
493
494 return out;
495 }
496
497 let half_k = k_height / 2;
498
499 for y in 0..half_k {
501 for x in 0..width {
502 for (i, k) in kernel.iter().enumerate() {
503 let y_unchecked = (y as i32) + i as i32 - k_height / 2;
504 let y_p = max(0, y_unchecked) as u32;
505 let p = unsafe { image.unsafe_get_pixel(x, y_p) };
506 accumulate(&mut acc, &p, *k);
507 }
508
509 let out_channels = out.get_pixel_mut(x, y as u32).channels_mut();
510 for (a, c) in acc.iter_mut().zip(out_channels.iter_mut()) {
511 *c = <P as Pixel>::Subpixel::clamp(*a);
512 *a = zero;
513 }
514 }
515 }
516
517 for y in half_k..(height as i32 - half_k) {
519 for x in 0..width {
520 for (i, k) in kernel.iter().enumerate() {
521 let y_unchecked = (y as i32) + i as i32 - k_height / 2;
522 let y_p = y_unchecked as u32;
523 let p = unsafe { image.unsafe_get_pixel(x, y_p) };
524 accumulate(&mut acc, &p, *k);
525 }
526
527 let out_channels = out.get_pixel_mut(x, y as u32).channels_mut();
528 for (a, c) in acc.iter_mut().zip(out_channels.iter_mut()) {
529 *c = <P as Pixel>::Subpixel::clamp(*a);
530 *a = zero;
531 }
532 }
533 }
534
535 for y in (height as i32 - half_k)..(height as i32) {
537 for x in 0..width {
538 for (i, k) in kernel.iter().enumerate() {
539 let y_unchecked = (y as i32) + i as i32 - k_height / 2;
540 let y_p = min(y_unchecked, height as i32 - 1) as u32;
541 let p = unsafe { image.unsafe_get_pixel(x, y_p) };
542 accumulate(&mut acc, &p, *k);
543 }
544
545 let out_channels = out.get_pixel_mut(x, y as u32).channels_mut();
546 for (a, c) in acc.iter_mut().zip(out_channels.iter_mut()) {
547 *c = <P as Pixel>::Subpixel::clamp(*a);
548 *a = zero;
549 }
550 }
551 }
552
553 out
554}
555
556fn accumulate<P, K>(acc: &mut [K], pixel: &P, weight: K)
557where
558 P: Pixel,
559 <P as Pixel>::Subpixel: ValueInto<K>,
560 K: Num + Copy,
561{
562 for i in 0..(P::CHANNEL_COUNT as usize) {
563 acc[i as usize] = acc[i as usize] + cast(pixel.channels()[i]) * weight;
564 }
565}
566
567#[cfg(test)]
568mod tests {
569 use super::*;
570 use crate::definitions::{Clamp, Image};
571 use crate::utils::{gray_bench_image, rgb_bench_image};
572 use image::imageops::blur;
573 use image::{GenericImage, GrayImage, ImageBuffer, Luma, Rgb};
574 use std::cmp::{max, min};
575 use test::{black_box, Bencher};
576
577 #[bench]
578 fn bench_bilateral_filter(b: &mut Bencher) {
579 let image = gray_bench_image(500, 500);
580 b.iter(|| {
581 let filtered = bilateral_filter(&image, 10, 10., 3.);
582 black_box(filtered);
583 });
584 }
585
586 #[test]
587 fn test_box_filter_handles_empty_images() {
588 let _ = box_filter(&GrayImage::new(0, 0), 3, 3);
589 let _ = box_filter(&GrayImage::new(1, 0), 3, 3);
590 let _ = box_filter(&GrayImage::new(0, 1), 3, 3);
591 }
592
593 #[test]
594 fn test_box_filter() {
595 let image = gray_image!(
596 1, 2, 3;
597 4, 5, 6;
598 7, 8, 9);
599
600 let expected = gray_image!(
605 2, 3, 3;
606 4, 5, 5;
607 6, 7, 7);
608
609 assert_pixels_eq!(box_filter(&image, 1, 1), expected);
610 }
611
612 #[bench]
613 fn bench_box_filter(b: &mut Bencher) {
614 let image = gray_bench_image(500, 500);
615 b.iter(|| {
616 let filtered = box_filter(&image, 7, 7);
617 black_box(filtered);
618 });
619 }
620
621 #[test]
622 fn test_separable_filter() {
623 let image = gray_image!(
624 1, 2, 3;
625 4, 5, 6;
626 7, 8, 9);
627
628 let expected = gray_image!(
630 2, 3, 3;
631 4, 5, 5;
632 6, 7, 7);
633
634 let kernel = vec![1f32 / 3f32; 3];
635 let filtered = separable_filter_equal(&image, &kernel);
636
637 assert_pixels_eq!(filtered, expected);
638 }
639
640 #[test]
641 fn test_separable_filter_integer_kernel() {
642 let image = gray_image!(
643 1, 2, 3;
644 4, 5, 6;
645 7, 8, 9);
646
647 let expected = gray_image!(
648 21, 27, 33;
649 39, 45, 51;
650 57, 63, 69);
651
652 let kernel = vec![1i32; 3];
653 let filtered = separable_filter_equal(&image, &kernel);
654
655 assert_pixels_eq!(filtered, expected);
656 }
657
658 #[bench]
659 fn bench_separable_filter(b: &mut Bencher) {
660 let image = gray_bench_image(300, 300);
661 let h_kernel = vec![1f32 / 5f32; 5];
662 let v_kernel = vec![0.1f32, 0.4f32, 0.3f32, 0.1f32, 0.1f32];
663 b.iter(|| {
664 let filtered = separable_filter(&image, &h_kernel, &v_kernel);
665 black_box(filtered);
666 });
667 }
668
669 fn horizontal_filter_reference(image: &GrayImage, kernel: &[f32]) -> GrayImage {
672 let (width, height) = image.dimensions();
673 let mut out = GrayImage::new(width, height);
674
675 for y in 0..height {
676 for x in 0..width {
677 let mut acc = 0f32;
678
679 for k in 0..kernel.len() {
680 let mut x_unchecked = x as i32 + k as i32 - (kernel.len() / 2) as i32;
681 x_unchecked = max(0, x_unchecked);
682 x_unchecked = min(x_unchecked, width as i32 - 1);
683
684 let x_checked = x_unchecked as u32;
685 let color = image.get_pixel(x_checked, y)[0];
686 let weight = kernel[k];
687
688 acc += color as f32 * weight;
689 }
690
691 let clamped = <u8 as Clamp<f32>>::clamp(acc);
692 out.put_pixel(x, y, Luma([clamped]));
693 }
694 }
695
696 out
697 }
698
699 fn vertical_filter_reference(image: &GrayImage, kernel: &[f32]) -> GrayImage {
702 let (width, height) = image.dimensions();
703 let mut out = GrayImage::new(width, height);
704
705 for y in 0..height {
706 for x in 0..width {
707 let mut acc = 0f32;
708
709 for k in 0..kernel.len() {
710 let mut y_unchecked = y as i32 + k as i32 - (kernel.len() / 2) as i32;
711 y_unchecked = max(0, y_unchecked);
712 y_unchecked = min(y_unchecked, height as i32 - 1);
713
714 let y_checked = y_unchecked as u32;
715 let color = image.get_pixel(x, y_checked)[0];
716 let weight = kernel[k];
717
718 acc += color as f32 * weight;
719 }
720
721 let clamped = <u8 as Clamp<f32>>::clamp(acc);
722 out.put_pixel(x, y, Luma([clamped]));
723 }
724 }
725
726 out
727 }
728
729 macro_rules! test_against_reference_implementation {
730 ($test_name:ident, $under_test:ident, $reference_impl:ident) => {
731 #[test]
732 fn $test_name() {
733 for height in 0..5 {
738 for width in 0..5 {
739 for kernel_length in 0..15 {
740 let image = gray_bench_image(width, height);
741 let kernel: Vec<f32> =
742 (0..kernel_length).map(|i| i as f32 % 1.35).collect();
743
744 let expected = $reference_impl(&image, &kernel);
745 let actual = $under_test(&image, &kernel);
746
747 assert_pixels_eq!(actual, expected);
748 }
749 }
750 }
751 }
752 };
753 }
754
755 test_against_reference_implementation!(
756 test_horizontal_filter_matches_reference_implementation,
757 horizontal_filter,
758 horizontal_filter_reference
759 );
760
761 test_against_reference_implementation!(
762 test_vertical_filter_matches_reference_implementation,
763 vertical_filter,
764 vertical_filter_reference
765 );
766
767 #[test]
768 fn test_horizontal_filter() {
769 let image = gray_image!(
770 1, 4, 1;
771 4, 7, 4;
772 1, 4, 1);
773
774 let expected = gray_image!(
775 2, 2, 2;
776 5, 5, 5;
777 2, 2, 2);
778
779 let kernel = vec![1f32 / 3f32; 3];
780 let filtered = horizontal_filter(&image, &kernel);
781
782 assert_pixels_eq!(filtered, expected);
783 }
784
785 #[test]
786 fn test_horizontal_filter_with_kernel_wider_than_image_does_not_panic() {
787 let image = gray_image!(
788 1, 4, 1;
789 4, 7, 4;
790 1, 4, 1);
791
792 let kernel = vec![1f32 / 10f32; 10];
793 black_box(horizontal_filter(&image, &kernel));
794 }
795
796 #[bench]
797 fn bench_horizontal_filter(b: &mut Bencher) {
798 let image = gray_bench_image(500, 500);
799 let kernel = vec![1f32 / 5f32; 5];
800 b.iter(|| {
801 let filtered = horizontal_filter(&image, &kernel);
802 black_box(filtered);
803 });
804 }
805
806 #[test]
807 fn test_vertical_filter() {
808 let image = gray_image!(
809 1, 4, 1;
810 4, 7, 4;
811 1, 4, 1);
812
813 let expected = gray_image!(
814 2, 5, 2;
815 2, 5, 2;
816 2, 5, 2);
817
818 let kernel = vec![1f32 / 3f32; 3];
819 let filtered = vertical_filter(&image, &kernel);
820
821 assert_pixels_eq!(filtered, expected);
822 }
823
824 #[test]
825 fn test_vertical_filter_with_kernel_taller_than_image_does_not_panic() {
826 let image = gray_image!(
827 1, 4, 1;
828 4, 7, 4;
829 1, 4, 1);
830
831 let kernel = vec![1f32 / 10f32; 10];
832 black_box(vertical_filter(&image, &kernel));
833 }
834
835 #[bench]
836 fn bench_vertical_filter(b: &mut Bencher) {
837 let image = gray_bench_image(500, 500);
838 let kernel = vec![1f32 / 5f32; 5];
839 b.iter(|| {
840 let filtered = vertical_filter(&image, &kernel);
841 black_box(filtered);
842 });
843 }
844
845 #[test]
846 fn test_filter3x3_with_results_outside_input_channel_range() {
847 #[rustfmt::skip]
848 let kernel: Vec<i32> = vec![
849 -1, 0, 1,
850 -2, 0, 2,
851 -1, 0, 1
852 ];
853
854 let image = gray_image!(
855 3, 2, 1;
856 6, 5, 4;
857 9, 8, 7);
858
859 let expected = gray_image!(type: i16,
860 -4, -8, -4;
861 -4, -8, -4;
862 -4, -8, -4
863 );
864
865 let filtered = filter3x3(&image, &kernel);
866 assert_pixels_eq!(filtered, expected);
867 }
868
869 #[test]
870 #[should_panic]
871 fn test_kernel_must_be_nonempty() {
872 let k: Vec<u8> = Vec::new();
873 let _ = Kernel::new(&k, 0, 0);
874 }
875
876 #[test]
877 fn test_kernel_filter_with_even_kernel_side() {
878 let image = gray_image!(
879 3, 2;
880 4, 1);
881
882 let k = vec![1u8, 2u8];
883 let kernel = Kernel::new(&k, 2, 1);
884 let filtered = kernel.filter(&image, |c, a| *c = a);
885
886 let expected = gray_image!(
887 9, 7;
888 12, 6);
889
890 assert_pixels_eq!(filtered, expected);
891 }
892
893 #[test]
894 fn test_kernel_filter_with_empty_image() {
895 let image = gray_image!();
896
897 let k = vec![2u8];
898 let kernel = Kernel::new(&k, 1, 1);
899 let filtered = kernel.filter(&image, |c, a| *c = a);
900
901 let expected = gray_image!();
902 assert_pixels_eq!(filtered, expected);
903 }
904
905 #[test]
906 fn test_kernel_filter_with_kernel_dimensions_larger_than_image() {
907 let image = gray_image!(
908 9, 4;
909 8, 1);
910
911 #[rustfmt::skip]
912 let k: Vec<f32> = vec![
913 0.1, 0.2, 0.1,
914 0.2, 0.4, 0.2,
915 0.1, 0.2, 0.1
916 ];
917 let kernel = Kernel::new(&k, 3, 3);
918 let filtered: Image<Luma<u8>> =
919 kernel.filter(&image, |c, a| *c = <u8 as Clamp<f32>>::clamp(a));
920
921 let expected = gray_image!(
922 11, 7;
923 10, 5);
924
925 assert_pixels_eq!(filtered, expected);
926 }
927
928 #[bench]
929 fn bench_filter3x3_i32_filter(b: &mut Bencher) {
930 let image = gray_bench_image(500, 500);
931 #[rustfmt::skip]
932 let kernel: Vec<i32> = vec![
933 -1, 0, 1,
934 -2, 0, 2,
935 -1, 0, 1
936 ];
937
938 b.iter(|| {
939 let filtered: ImageBuffer<Luma<i16>, Vec<i16>> =
940 filter3x3::<_, _, i16>(&image, &kernel);
941 black_box(filtered);
942 });
943 }
944
945 fn gaussian_baseline_rgb<I>(image: &I, stdev: f32) -> Image<Rgb<u8>>
948 where
949 I: GenericImage<Pixel = Rgb<u8>>,
950 {
951 blur(image, stdev)
952 }
953
954 #[bench]
955 #[ignore] fn bench_baseline_gaussian_stdev_1(b: &mut Bencher) {
957 let image = rgb_bench_image(100, 100);
958 b.iter(|| {
959 let blurred = gaussian_baseline_rgb(&image, 1f32);
960 black_box(blurred);
961 });
962 }
963
964 #[bench]
965 #[ignore] fn bench_baseline_gaussian_stdev_3(b: &mut Bencher) {
967 let image = rgb_bench_image(100, 100);
968 b.iter(|| {
969 let blurred = gaussian_baseline_rgb(&image, 3f32);
970 black_box(blurred);
971 });
972 }
973
974 #[bench]
975 #[ignore] fn bench_baseline_gaussian_stdev_10(b: &mut Bencher) {
977 let image = rgb_bench_image(100, 100);
978 b.iter(|| {
979 let blurred = gaussian_baseline_rgb(&image, 10f32);
980 black_box(blurred);
981 });
982 }
983
984 #[bench]
985 fn bench_gaussian_f32_stdev_1(b: &mut Bencher) {
986 let image = rgb_bench_image(100, 100);
987 b.iter(|| {
988 let blurred = gaussian_blur_f32(&image, 1f32);
989 black_box(blurred);
990 });
991 }
992
993 #[bench]
994 fn bench_gaussian_f32_stdev_3(b: &mut Bencher) {
995 let image = rgb_bench_image(100, 100);
996 b.iter(|| {
997 let blurred = gaussian_blur_f32(&image, 3f32);
998 black_box(blurred);
999 });
1000 }
1001
1002 #[bench]
1003 fn bench_gaussian_f32_stdev_10(b: &mut Bencher) {
1004 let image = rgb_bench_image(100, 100);
1005 b.iter(|| {
1006 let blurred = gaussian_blur_f32(&image, 10f32);
1007 black_box(blurred);
1008 });
1009 }
1010
1011 #[test]
1012 #[should_panic]
1013 fn test_gaussian_blur_f32_rejects_zero_sigma() {
1014 let image = gray_image!(
1015 1, 2, 3;
1016 4, 5, 6;
1017 7, 8, 9
1018 );
1019 let _ = gaussian_blur_f32(&image, 0.0);
1020 }
1021
1022 #[test]
1023 #[should_panic]
1024 fn test_gaussian_blur_f32_rejects_negative_sigma() {
1025 let image = gray_image!(
1026 1, 2, 3;
1027 4, 5, 6;
1028 7, 8, 9
1029 );
1030 let _ = gaussian_blur_f32(&image, -0.5);
1031 }
1032}