1use crate::channels_configuration::FastBlurChannels;
29use crate::gaussian::gaussian_hint::IeeeBinaryConvolutionMode;
30use crate::gaussian::gaussian_kernel::gaussian_kernel_1d;
31use crate::gaussian::gaussian_util::{kernel_size as get_kernel_size, kernel_size_d};
32use crate::{
33 filter_1d_approx, filter_1d_exact, gaussian_kernel_1d_f64, sigma_size, sigma_size_d, BlurError,
34 BlurImage, BlurImageMut, ConvolutionMode, EdgeMode2D, Scalar, ThreadingPolicy,
35};
36#[cfg(feature = "nightly_f16")]
37use core::f16;
38
39#[derive(Copy, Clone, Debug)]
40pub struct GaussianBlurParams {
41 pub x_kernel: u32,
43 pub x_sigma: f64,
45 pub y_kernel: u32,
47 pub y_sigma: f64,
49}
50
51#[inline]
52fn round_to_nearest_odd(x: f64) -> i64 {
53 let n = x.round() as i64;
54 if n % 2 != 0 {
55 n
56 } else {
57 let lower = n - 1;
59 let upper = n + 1;
60
61 let dist_lower = (x - lower as f64).abs();
62 let dist_upper = (x - upper as f64).abs();
63
64 if dist_lower <= dist_upper {
65 lower
66 } else {
67 upper
68 }
69 }
70}
71impl GaussianBlurParams {
72 pub fn new(kernel: u32, sigma: f64) -> GaussianBlurParams {
75 GaussianBlurParams {
76 x_kernel: kernel,
77 x_sigma: sigma,
78 y_kernel: kernel,
79 y_sigma: sigma,
80 }
81 }
82
83 pub fn new_from_sigma(sigma: f64) -> GaussianBlurParams {
85 assert!(sigma > 0.);
86 let kernel_size = kernel_size_d(sigma);
87 Self::new(kernel_size, sigma)
88 }
89
90 pub fn new_from_kernel(kernel: f64) -> GaussianBlurParams {
93 assert!(kernel > 0.);
94 let sigma = sigma_size_d(kernel);
95 Self::new(round_to_nearest_odd(kernel) as u32, sigma)
96 }
97
98 pub fn new_asymmetric_from_kernels(x_kernel: f64, y_kernel: f64) -> GaussianBlurParams {
101 assert!(x_kernel > 0.);
102 assert!(y_kernel > 0.);
103 let x_sigma = sigma_size_d(x_kernel);
104 let y_sigma = sigma_size_d(y_kernel);
105 Self::new_asymmetric(
106 round_to_nearest_odd(x_kernel) as u32,
107 x_sigma,
108 round_to_nearest_odd(y_kernel) as u32,
109 y_sigma,
110 )
111 }
112
113 pub fn new_asymmetric(
116 x_kernel: u32,
117 x_sigma: f64,
118 y_kernel: u32,
119 y_sigma: f64,
120 ) -> GaussianBlurParams {
121 GaussianBlurParams {
122 x_kernel,
123 x_sigma,
124 y_kernel,
125 y_sigma,
126 }
127 }
128
129 pub fn new_asymmetric_from_sigma(x_sigma: f64, y_sigma: f64) -> GaussianBlurParams {
131 GaussianBlurParams {
132 x_kernel: kernel_size_d(x_sigma),
133 x_sigma,
134 y_kernel: kernel_size_d(y_sigma),
135 y_sigma,
136 }
137 }
138
139 fn make_f32_kernel(&self, kernel_size: u32, sigma: f32) -> Vec<f32> {
140 assert!(
141 kernel_size != 0 || sigma > 0.0,
142 "Either sigma or kernel size must be set"
143 );
144 if kernel_size != 0 {
145 assert_ne!(kernel_size % 2, 0, "Kernel size must be odd");
146 }
147 let sigma = if sigma <= 0. {
148 sigma_size(kernel_size as f32)
149 } else {
150 sigma
151 };
152 let kernel_size = if kernel_size == 0 {
153 get_kernel_size(sigma)
154 } else {
155 kernel_size
156 };
157 gaussian_kernel_1d(kernel_size, sigma)
158 }
159
160 fn make_f64_kernel(&self, kernel_size: u32, sigma: f64) -> Vec<f64> {
161 assert!(
162 kernel_size != 0 || sigma > 0.0,
163 "Either sigma or kernel size must be set"
164 );
165 if kernel_size != 0 {
166 assert_ne!(kernel_size % 2, 0, "Kernel size must be odd");
167 }
168 let sigma = if sigma <= 0. {
169 sigma_size_d(kernel_size as f64)
170 } else {
171 sigma
172 };
173 let kernel_size = if kernel_size == 0 {
174 kernel_size_d(sigma)
175 } else {
176 kernel_size
177 };
178 gaussian_kernel_1d_f64(kernel_size, sigma)
179 }
180
181 fn make_f32_kernels(&self) -> (Vec<f32>, Vec<f32>) {
182 let vx_kernel = self.make_f32_kernel(self.x_kernel, self.x_sigma as f32);
183 let vy_kernel = self.make_f32_kernel(self.y_kernel, self.y_sigma as f32);
184 (vx_kernel, vy_kernel)
185 }
186
187 fn make_f64_kernels(&self) -> (Vec<f64>, Vec<f64>) {
188 let vx_kernel = self.make_f64_kernel(self.x_kernel, self.x_sigma);
189 let vy_kernel = self.make_f64_kernel(self.y_kernel, self.y_sigma);
190 (vx_kernel, vy_kernel)
191 }
192
193 fn validate(&self) -> Result<(), BlurError> {
194 if self.x_sigma < 0. || self.y_sigma < 0. {
195 return Err(BlurError::NegativeOrZeroSigma);
196 }
197 if self.x_kernel > 0 && self.x_kernel % 2 == 0 {
198 return Err(BlurError::OddKernel(self.x_kernel as usize));
199 }
200 if self.y_kernel > 0 && self.y_kernel % 2 == 0 {
201 return Err(BlurError::OddKernel(self.y_kernel as usize));
202 }
203 if self.x_sigma == 0. && self.x_kernel == 0 {
204 return Err(BlurError::InvalidArguments);
205 }
206 if self.y_sigma == 0. && self.y_kernel == 0 {
207 return Err(BlurError::InvalidArguments);
208 }
209 Ok(())
210 }
211}
212
213pub fn gaussian_blur(
233 src: &BlurImage<u8>,
234 dst: &mut BlurImageMut<u8>,
235 params: GaussianBlurParams,
236 edge_modes: EdgeMode2D,
237 threading_policy: ThreadingPolicy,
238 hint: ConvolutionMode,
239) -> Result<(), BlurError> {
240 src.check_layout()?;
241 dst.check_layout(Some(src))?;
242 src.size_matches_mut(dst)?;
243 params.validate()?;
244 let (x_kernel, y_kernel) = params.make_f32_kernels();
245 match hint {
246 ConvolutionMode::Exact => {
247 let _dispatcher = match src.channels {
248 FastBlurChannels::Plane => filter_1d_exact::<u8, f32, 1>,
249 FastBlurChannels::Channels3 => filter_1d_exact::<u8, f32, 3>,
250 FastBlurChannels::Channels4 => filter_1d_exact::<u8, f32, 4>,
251 };
252 _dispatcher(
253 src,
254 dst,
255 &x_kernel,
256 &y_kernel,
257 edge_modes,
258 Scalar::default(),
259 threading_policy,
260 )?;
261 }
262 ConvolutionMode::FixedPoint => {
263 let _dispatcher = match src.channels {
264 FastBlurChannels::Plane => filter_1d_approx::<u8, f32, i32, 1>,
265 FastBlurChannels::Channels3 => filter_1d_approx::<u8, f32, i32, 3>,
266 FastBlurChannels::Channels4 => filter_1d_approx::<u8, f32, i32, 4>,
267 };
268 _dispatcher(
269 src,
270 dst,
271 &x_kernel,
272 &y_kernel,
273 edge_modes,
274 Scalar::default(),
275 threading_policy,
276 )?;
277 }
278 }
279 Ok(())
280}
281
282pub fn gaussian_blur_u16(
305 src: &BlurImage<u16>,
306 dst: &mut BlurImageMut<u16>,
307 params: GaussianBlurParams,
308 edge_modes: EdgeMode2D,
309 threading_policy: ThreadingPolicy,
310 hint: ConvolutionMode,
311) -> Result<(), BlurError> {
312 src.check_layout()?;
313 dst.check_layout(Some(src))?;
314 src.size_matches_mut(dst)?;
315 params.validate()?;
316 let (x_kernel, y_kernel) = params.make_f32_kernels();
317 match hint {
318 ConvolutionMode::Exact => {
319 let _dispatcher = match src.channels {
320 FastBlurChannels::Plane => filter_1d_exact::<u16, f32, 1>,
321 FastBlurChannels::Channels3 => filter_1d_exact::<u16, f32, 3>,
322 FastBlurChannels::Channels4 => filter_1d_exact::<u16, f32, 4>,
323 };
324 _dispatcher(
325 src,
326 dst,
327 &x_kernel,
328 &y_kernel,
329 edge_modes,
330 Scalar::default(),
331 threading_policy,
332 )
333 }
334 ConvolutionMode::FixedPoint => {
335 use crate::filter1d::filter_1d_approx;
336 let _dispatcher = match src.channels {
337 FastBlurChannels::Plane => filter_1d_approx::<u16, f32, u32, 1>,
338 FastBlurChannels::Channels3 => filter_1d_approx::<u16, f32, u32, 3>,
339 FastBlurChannels::Channels4 => filter_1d_approx::<u16, f32, u32, 4>,
340 };
341 _dispatcher(
342 src,
343 dst,
344 &x_kernel,
345 &y_kernel,
346 edge_modes,
347 Scalar::default(),
348 threading_policy,
349 )
350 }
351 }
352}
353
354pub fn gaussian_blur_f32(
374 src: &BlurImage<f32>,
375 dst: &mut BlurImageMut<f32>,
376 params: GaussianBlurParams,
377 edge_modes: EdgeMode2D,
378 threading_policy: ThreadingPolicy,
379 convolution_mode: IeeeBinaryConvolutionMode,
380) -> Result<(), BlurError> {
381 src.check_layout()?;
382 dst.check_layout(Some(src))?;
383 src.size_matches_mut(dst)?;
384 params.validate()?;
385 match convolution_mode {
386 IeeeBinaryConvolutionMode::Normal => {
387 let (x_kernel, y_kernel) = params.make_f32_kernels();
388 let _dispatcher = match src.channels {
389 FastBlurChannels::Plane => filter_1d_exact::<f32, f32, 1>,
390 FastBlurChannels::Channels3 => filter_1d_exact::<f32, f32, 3>,
391 FastBlurChannels::Channels4 => filter_1d_exact::<f32, f32, 4>,
392 };
393 _dispatcher(
394 src,
395 dst,
396 &x_kernel,
397 &y_kernel,
398 edge_modes,
399 Scalar::default(),
400 threading_policy,
401 )
402 }
403 IeeeBinaryConvolutionMode::Zealous => {
404 let (x_kernel, y_kernel) = params.make_f64_kernels();
405 let _dispatcher = match src.channels {
406 FastBlurChannels::Plane => filter_1d_exact::<f32, f64, 1>,
407 FastBlurChannels::Channels3 => filter_1d_exact::<f32, f64, 3>,
408 FastBlurChannels::Channels4 => filter_1d_exact::<f32, f64, 4>,
409 };
410 _dispatcher(
411 src,
412 dst,
413 &x_kernel,
414 &y_kernel,
415 edge_modes,
416 Scalar::default(),
417 threading_policy,
418 )
419 }
420 }
421}
422
423#[cfg(feature = "nightly_f16")]
442#[cfg_attr(docsrs, doc(cfg(feature = "nightly_f16")))]
443pub fn gaussian_blur_f16(
444 src: &BlurImage<f16>,
445 dst: &mut BlurImageMut<f16>,
446 params: GaussianBlurParams,
447 edge_modes: EdgeMode2D,
448 threading_policy: ThreadingPolicy,
449) -> Result<(), BlurError> {
450 src.check_layout()?;
451 dst.check_layout(Some(src))?;
452 src.size_matches_mut(dst)?;
453 params.validate()?;
454 let (x_kernel, y_kernel) = params.make_f32_kernels();
455 let _dispatcher = match src.channels {
456 FastBlurChannels::Plane => filter_1d_exact::<f16, f32, 1>,
457 FastBlurChannels::Channels3 => filter_1d_exact::<f16, f32, 3>,
458 FastBlurChannels::Channels4 => filter_1d_exact::<f16, f32, 4>,
459 };
460 _dispatcher(
461 src,
462 dst,
463 &x_kernel,
464 &y_kernel,
465 edge_modes,
466 Scalar::default(),
467 threading_policy,
468 )
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474 use crate::EdgeMode;
475 use crate::{gaussian_kernel_1d_f64, sigma_size_d};
476
477 macro_rules! compare_u8_stat {
478 ($dst: expr) => {
479 for (i, cn) in $dst.data.borrow_mut().chunks_exact(3).enumerate() {
480 let diff0 = (cn[0] as i32 - 126).abs();
481 assert!(
482 diff0 <= 3,
483 "Diff expected to be less than 3, but it was {diff0} at {i} in channel 0"
484 );
485 let diff1 = (cn[1] as i32 - 66).abs();
486 assert!(
487 diff1 <= 3,
488 "Diff expected to be less than 3, but it was {diff1} at {i} in channel 1"
489 );
490 let diff2 = (cn[2] as i32 - 77).abs();
491 assert!(
492 diff2 <= 3,
493 "Diff expected to be less than 3, but it was {diff2} at {i} in channel 2"
494 );
495 }
496 };
497 }
498
499 #[test]
500 fn test_gauss_u8_q_k5() {
501 let width: usize = 148;
502 let height: usize = 148;
503 let mut src = vec![126; width * height * 3];
504 for dst in src.chunks_exact_mut(3) {
505 dst[0] = 126;
506 dst[1] = 66;
507 dst[2] = 77;
508 }
509 let src_image = BlurImage::borrow(
510 &src,
511 width as u32,
512 height as u32,
513 FastBlurChannels::Channels3,
514 );
515 let mut dst = BlurImageMut::default();
516 gaussian_blur(
517 &src_image,
518 &mut dst,
519 GaussianBlurParams::new_from_kernel(5.),
520 EdgeMode2D::new(EdgeMode::Clamp),
521 ThreadingPolicy::Single,
522 ConvolutionMode::FixedPoint,
523 )
524 .unwrap();
525 compare_u8_stat!(dst);
526 }
527
528 #[test]
529 fn test_gauss_u8_q_k3() {
530 let width: usize = 148;
531 let height: usize = 148;
532 let mut src = vec![126; width * height * 3];
533 for dst in src.chunks_exact_mut(3) {
534 dst[0] = 126;
535 dst[1] = 66;
536 dst[2] = 77;
537 }
538 let src_image = BlurImage::borrow(
539 &src,
540 width as u32,
541 height as u32,
542 FastBlurChannels::Channels3,
543 );
544 let mut dst = BlurImageMut::default();
545 gaussian_blur(
546 &src_image,
547 &mut dst,
548 GaussianBlurParams::new_from_kernel(3.),
549 EdgeMode2D::new(EdgeMode::Clamp),
550 ThreadingPolicy::Single,
551 ConvolutionMode::FixedPoint,
552 )
553 .unwrap();
554 println!("{}", dst.data.borrow_mut()[0]);
555 compare_u8_stat!(dst);
556 }
557
558 #[test]
559 fn test_gauss_u8_q_k7() {
560 let width: usize = 148;
561 let height: usize = 148;
562 let mut src = vec![126; width * height * 3];
563 for dst in src.chunks_exact_mut(3) {
564 dst[0] = 126;
565 dst[1] = 66;
566 dst[2] = 77;
567 }
568 let src_image = BlurImage::borrow(
569 &src,
570 width as u32,
571 height as u32,
572 FastBlurChannels::Channels3,
573 );
574 let mut dst = BlurImageMut::default();
575 gaussian_blur(
576 &src_image,
577 &mut dst,
578 GaussianBlurParams::new_from_kernel(7.),
579 EdgeMode2D::new(EdgeMode::Clamp),
580 ThreadingPolicy::Single,
581 ConvolutionMode::FixedPoint,
582 )
583 .unwrap();
584 compare_u8_stat!(dst);
585 }
586
587 #[test]
588 fn test_gauss_u8_fp_k5() {
589 let width: usize = 148;
590 let height: usize = 148;
591 let mut src = vec![126; width * height * 3];
592 for dst in src.chunks_exact_mut(3) {
593 dst[0] = 126;
594 dst[1] = 66;
595 dst[2] = 77;
596 }
597 let src_image = BlurImage::borrow(
598 &src,
599 width as u32,
600 height as u32,
601 FastBlurChannels::Channels3,
602 );
603 let mut dst = BlurImageMut::default();
604 gaussian_blur(
605 &src_image,
606 &mut dst,
607 GaussianBlurParams::new_from_kernel(5.),
608 EdgeMode2D::new(EdgeMode::Clamp),
609 ThreadingPolicy::Single,
610 ConvolutionMode::Exact,
611 )
612 .unwrap();
613 compare_u8_stat!(dst);
614 }
615
616 #[test]
617 fn test_gauss_u8_q_k31() {
618 let width: usize = 148;
619 let height: usize = 148;
620 let mut src = vec![126; width * height * 3];
621 for dst in src.chunks_exact_mut(3) {
622 dst[0] = 126;
623 dst[1] = 66;
624 dst[2] = 77;
625 }
626 let src_image = BlurImage::borrow(
627 &src,
628 width as u32,
629 height as u32,
630 FastBlurChannels::Channels3,
631 );
632 let mut dst = BlurImageMut::default();
633 gaussian_blur(
634 &src_image,
635 &mut dst,
636 GaussianBlurParams::new_from_kernel(31.),
637 EdgeMode2D::new(EdgeMode::Clamp),
638 ThreadingPolicy::Single,
639 ConvolutionMode::FixedPoint,
640 )
641 .unwrap();
642 compare_u8_stat!(dst);
643 }
644
645 #[test]
646 fn test_gauss_u8_fp_k31() {
647 let width: usize = 148;
648 let height: usize = 148;
649 let mut src = vec![126; width * height * 3];
650 for dst in src.chunks_exact_mut(3) {
651 dst[0] = 126;
652 dst[1] = 66;
653 dst[2] = 77;
654 }
655 let src_image = BlurImage::borrow(
656 &src,
657 width as u32,
658 height as u32,
659 FastBlurChannels::Channels3,
660 );
661 let mut dst = BlurImageMut::default();
662 gaussian_blur(
663 &src_image,
664 &mut dst,
665 GaussianBlurParams::new_from_kernel(31.),
666 EdgeMode2D::new(EdgeMode::Clamp),
667 ThreadingPolicy::Single,
668 ConvolutionMode::Exact,
669 )
670 .unwrap();
671 compare_u8_stat!(dst);
672 }
673
674 macro_rules! compare_u16_stat {
675 ($dst: expr) => {
676 for (i, cn) in $dst.data.borrow_mut().chunks_exact(3).enumerate() {
677 let diff0 = (cn[0] as i32 - 17234i32).abs();
678 assert!(
679 diff0 <= 16,
680 "Diff expected to be less than 16, but it was {diff0} at {i} in channel 0"
681 );
682 let diff1 = (cn[1] as i32 - 5322).abs();
683 assert!(
684 diff1 <= 16,
685 "Diff expected to be less than 16, but it was {diff1} at {i} in channel 1"
686 );
687 let diff2 = (cn[2] as i32 - 7652).abs();
688 assert!(
689 diff2 <= 16,
690 "Diff expected to be less than 16, but it was {diff2} at {i} in channel 2"
691 );
692 }
693 };
694 }
695
696 #[test]
697 fn test_gauss_u16_q_k31() {
698 let width: usize = 148;
699 let height: usize = 148;
700 let mut src = vec![17234u16; width * height * 3];
701 for dst in src.chunks_exact_mut(3) {
702 dst[0] = 17234u16;
703 dst[1] = 5322;
704 dst[2] = 7652;
705 }
706 let src_image = BlurImage::borrow(
707 &src,
708 width as u32,
709 height as u32,
710 FastBlurChannels::Channels3,
711 );
712 let mut dst = BlurImageMut::default();
713 gaussian_blur_u16(
714 &src_image,
715 &mut dst,
716 GaussianBlurParams::new_from_kernel(31.),
717 EdgeMode2D::new(EdgeMode::Clamp),
718 ThreadingPolicy::Single,
719 ConvolutionMode::FixedPoint,
720 )
721 .unwrap();
722 compare_u16_stat!(dst);
723 }
724
725 #[test]
726 fn test_gauss_u16_fp_k31() {
727 let width: usize = 148;
728 let height: usize = 148;
729 let mut src = vec![17234u16; width * height * 3];
730 for dst in src.chunks_exact_mut(3) {
731 dst[0] = 17234u16;
732 dst[1] = 5322;
733 dst[2] = 7652;
734 }
735 let src_image = BlurImage::borrow(
736 &src,
737 width as u32,
738 height as u32,
739 FastBlurChannels::Channels3,
740 );
741 let mut dst = BlurImageMut::default();
742 gaussian_blur_u16(
743 &src_image,
744 &mut dst,
745 GaussianBlurParams::new_from_kernel(31.),
746 EdgeMode2D::new(EdgeMode::Clamp),
747 ThreadingPolicy::Single,
748 ConvolutionMode::Exact,
749 )
750 .unwrap();
751 compare_u16_stat!(dst);
752 }
753
754 macro_rules! compare_f32_stat {
755 ($dst: expr) => {
756 for (i, cn) in $dst.data.borrow_mut().chunks_exact(3).enumerate() {
757 let diff0 = (cn[0] as f32 - 0.532).abs();
758 assert!(
759 diff0 <= 1e-4,
760 "Diff expected to be less than 1e-4, but it was {diff0} at {i} in channel 0"
761 );
762 let diff1 = (cn[1] as f32 - 0.123).abs();
763 assert!(
764 diff1 <= 1e-4,
765 "Diff expected to be less than 1e-4, but it was {diff1} at {i} in channel 1"
766 );
767 let diff2 = (cn[2] as f32 - 0.654).abs();
768 assert!(
769 diff2 <= 1e-4,
770 "Diff expected to be less than 1e-4, but it was {diff2} at {i} in channel 2"
771 );
772 }
773 };
774 }
775
776 #[test]
777 fn test_gauss_f32_k31() {
778 let width: usize = 148;
779 let height: usize = 148;
780 let mut src = vec![0.532; width * height * 3];
781 for dst in src.chunks_exact_mut(3) {
782 dst[0] = 0.532;
783 dst[1] = 0.123;
784 dst[2] = 0.654;
785 }
786 let src_image = BlurImage::borrow(
787 &src,
788 width as u32,
789 height as u32,
790 FastBlurChannels::Channels3,
791 );
792 let mut dst = BlurImageMut::default();
793 gaussian_blur_f32(
794 &src_image,
795 &mut dst,
796 GaussianBlurParams::new_from_kernel(31.),
797 EdgeMode2D::new(EdgeMode::Clamp),
798 ThreadingPolicy::Single,
799 IeeeBinaryConvolutionMode::Normal,
800 )
801 .unwrap();
802 compare_f32_stat!(dst);
803 dst.data.borrow_mut().fill(0.);
804 gaussian_blur_f32(
805 &src_image,
806 &mut dst,
807 GaussianBlurParams::new_from_kernel(31.),
808 EdgeMode2D::new(EdgeMode::Clamp),
809 ThreadingPolicy::Single,
810 IeeeBinaryConvolutionMode::Zealous,
811 )
812 .unwrap();
813 compare_f32_stat!(dst);
814 }
815
816 #[test]
817 fn test_gauss_f32_f64_k31() {
818 let width: usize = 148;
819 let height: usize = 148;
820 let mut src = vec![0.532; width * height * 3];
821 for dst in src.chunks_exact_mut(3) {
822 dst[0] = 0.532;
823 dst[1] = 0.123;
824 dst[2] = 0.654;
825 }
826 let src_image = BlurImage::borrow(
827 &src,
828 width as u32,
829 height as u32,
830 FastBlurChannels::Channels3,
831 );
832
833 let kernel = gaussian_kernel_1d_f64(31, sigma_size_d(2.5));
834
835 let mut dst = BlurImageMut::default();
836 filter_1d_exact::<f32, f64, 3>(
837 &src_image,
838 &mut dst,
839 &kernel,
840 &kernel,
841 EdgeMode2D::new(EdgeMode::Clamp),
842 Scalar::default(),
843 ThreadingPolicy::Adaptive,
844 )
845 .unwrap();
846 compare_f32_stat!(dst);
847 }
848}