libblur/gaussian/
declaration.rs

1// Copyright (c) Radzivon Bartoshyk. All rights reserved.
2//
3// Redistribution and use in source and binary forms, with or without modification,
4// are permitted provided that the following conditions are met:
5//
6// 1.  Redistributions of source code must retain the above copyright notice, this
7// list of conditions and the following disclaimer.
8//
9// 2.  Redistributions in binary form must reproduce the above copyright notice,
10// this list of conditions and the following disclaimer in the documentation
11// and/or other materials provided with the distribution.
12//
13// 3.  Neither the name of the copyright holder nor the names of its
14// contributors may be used to endorse or promote products derived from
15// this software without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28use 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    /// X-axis kernel size
42    pub x_kernel: u32,
43    /// X-axis sigma
44    pub x_sigma: f64,
45    /// Y-axis kernel size
46    pub y_kernel: u32,
47    /// Y-axis sigma
48    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        // Check which odd integer is closer
58        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    /// Kernel expected to be odd.
73    /// Sigma must be > 0.
74    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    /// Sigma must be > 0 and not equal to `0.8`.
84    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    /// Kernel must be > 0.
91    /// Kernel will be rounded to nearest odd, it is safe to pass any kernel here.
92    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    /// Kernel must be > 0.
99    /// Kernel will be rounded to nearest odd, it is safe to pass any kernel here.
100    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    /// Kernel expected to be odd.
114    /// Sigma must be > 0.
115    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    /// Sigma must be > 0.
130    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
213/// Performs gaussian blur on the image.
214///
215/// This performs a gaussian kernel filter on the image producing beautiful looking result.
216/// Preferred if you need to perform an advanced signal analysis after.
217/// This is always perform accurate pure analytical gaussian filter.
218/// O(R) complexity.
219///
220/// # Arguments
221///
222/// * `src` - Source image.
223/// * `dst` - Destination image.
224/// * `params` - See [GaussianBlurParams] for more info.
225/// * `edge_mode` - Rule to handle edge mode, sse [EdgeMode] for more info.
226/// * `threading_policy` - Threading policy according to [ThreadingPolicy].
227/// * `hint` - see [ConvolutionMode] for more info.
228///
229/// # Panics
230/// Panic is stride/width/height/channel configuration do not match provided.
231/// Panics if sigma = 0.8 and kernel size = 0.
232pub 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
282/// Performs gaussian blur on the image.
283///
284/// This performs a gaussian kernel filter on the image producing beautiful looking result.
285/// Preferred if you need to perform an advanced signal analysis after.
286/// This is always perform accurate pure analytical gaussian filter.
287/// O(R) complexity.
288///
289/// # Arguments
290///
291/// * `src` - Source image.
292/// * `dst` - Destination image.
293/// * `params` - See [GaussianBlurParams] for more info.
294/// * `edge_mode` - Rule to handle edge mode, sse [EdgeMode] for more info.
295/// * `threading_policy` - Threading policy according to [ThreadingPolicy].
296/// * `hint` - see [ConvolutionMode] for more info.
297///
298/// This method always clamp into [0, 65535], if other bit-depth is used
299/// consider additional clamp into required range.
300///
301/// # Panics
302/// Panic is stride/width/height/channel configuration do not match provided.
303/// Panics if sigma = 0.8 and kernel size = 0.
304pub 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
354/// Performs gaussian blur on the image.
355///
356/// This performs a gaussian kernel filter on the image producing beautiful looking result.
357/// Preferred if you need to perform an advanced signal analysis after.
358/// This is always perform accurate pure analytical gaussian filter.
359/// O(R) complexity.
360///
361/// # Arguments
362///
363/// * `src` - Source image.
364/// * `dst` - Destination image.
365/// * `params` - See [GaussianBlurParams] for more info.
366/// * `edge_modes` - Rule to handle edge mode, sse [EdgeMode] and [EdgeMode2D] for more info.
367/// * `threading_policy` - Threading policy according to [ThreadingPolicy].
368/// * `convolution_mode` - See [IeeeBinaryConvolutionMode] for more info.
369///
370/// # Panics
371/// Panic is stride/width/height/channel configuration do not match provided.
372/// Panics if sigma = 0.8 and kernel size = 0.
373pub 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/// Performs gaussian blur on the image.
424///
425/// This performs a gaussian kernel filter on the image producing beautiful looking result.
426/// Preferred if you need to perform an advanced signal analysis after.
427/// This is always perform accurate pure analytical gaussian filter.
428/// O(R) complexity.
429///
430/// # Arguments
431///
432/// * `src` - Source image.
433/// * `dst` - Destination image.
434/// * `params` - See [GaussianBlurParams] for more info.
435/// * `edge_modes` - Rule to handle edge mode, sse [EdgeMode] and [EdgeMode2D] for more info.
436/// * `threading_policy` - Threading policy according to [ThreadingPolicy].
437///
438/// # Panics
439/// Panic is stride/width/height/channel configuration do not match provided
440/// Panics if sigma = 0.8 and kernel size = 0.
441#[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}