1 | use num_traits::clamp; |
2 | |
3 | use crate::{ImageBuffer, Pixel, Primitive}; |
4 | |
5 | /// Approximation of Gaussian blur after |
6 | /// Kovesi, P.: Fast Almost-Gaussian Filtering The Australian Pattern |
7 | /// Recognition Society Conference: DICTA 2010. December 2010. Sydney. |
8 | /// This method assumes alpha pre-multiplication for images that contain non-constant alpha. |
9 | #[must_use ] |
10 | pub fn fast_blur<P: Pixel>( |
11 | image_buffer: &ImageBuffer<P, Vec<P::Subpixel>>, |
12 | sigma: f32, |
13 | ) -> ImageBuffer<P, Vec<P::Subpixel>> { |
14 | let (width, height) = image_buffer.dimensions(); |
15 | |
16 | if width == 0 || height == 0 { |
17 | return image_buffer.clone(); |
18 | } |
19 | let mut samples = image_buffer.as_flat_samples().samples.to_vec(); |
20 | let num_passes = 3; |
21 | |
22 | let boxes = boxes_for_gauss(sigma, num_passes); |
23 | |
24 | for radius in boxes.iter().take(num_passes) { |
25 | let horizontally_blurred_transposed = horizontal_fast_blur_half::<P::Subpixel>( |
26 | &samples, |
27 | width as usize, |
28 | height as usize, |
29 | (*radius - 1) / 2, |
30 | P::CHANNEL_COUNT as usize, |
31 | ); |
32 | samples = horizontal_fast_blur_half::<P::Subpixel>( |
33 | &horizontally_blurred_transposed, |
34 | height as usize, |
35 | width as usize, |
36 | (*radius - 1) / 2, |
37 | P::CHANNEL_COUNT as usize, |
38 | ); |
39 | } |
40 | ImageBuffer::from_raw(width, height, samples).unwrap() |
41 | } |
42 | |
43 | fn boxes_for_gauss(sigma: f32, n: usize) -> Vec<usize> { |
44 | let w_ideal: f32 = f32::sqrt((12.0 * sigma.powi(2) / (n as f32)) + 1.0); |
45 | let mut w_l: f32 = w_ideal.floor(); |
46 | if w_l % 2.0 == 0.0 { |
47 | w_l -= 1.0; |
48 | }; |
49 | let w_u: f32 = w_l + 2.0; |
50 | |
51 | let m_ideal: f32 = 0.25 * (n as f32) * (w_l + 3.0) - 3.0 * sigma.powi(2) * (w_l + 1.0).recip(); |
52 | |
53 | let m: usize = f32::round(self:m_ideal) as usize; |
54 | |
55 | (0..n) |
56 | .map(|i: usize| if i < m { w_l as usize } else { w_u as usize }) |
57 | .collect::<Vec<_>>() |
58 | } |
59 | |
60 | fn channel_idx(channel: usize, idx: usize, channel_num: usize) -> usize { |
61 | channel_num * idx + channel |
62 | } |
63 | |
64 | fn horizontal_fast_blur_half<P: Primitive>( |
65 | samples: &[P], |
66 | width: usize, |
67 | height: usize, |
68 | r: usize, |
69 | channel_num: usize, |
70 | ) -> Vec<P> { |
71 | let channel_size = width * height; |
72 | |
73 | let mut out_samples = vec![P::from(0).unwrap(); channel_size * channel_num]; |
74 | let mut vals = vec![0.0; channel_num]; |
75 | |
76 | let min_value = P::DEFAULT_MIN_VALUE.to_f32().unwrap(); |
77 | let max_value = P::DEFAULT_MAX_VALUE.to_f32().unwrap(); |
78 | |
79 | for row in 0..height { |
80 | for (channel, value) in vals.iter_mut().enumerate().take(channel_num) { |
81 | *value = ((-(r as isize))..(r + 1) as isize) |
82 | .map(|x| { |
83 | extended_f( |
84 | samples, |
85 | width, |
86 | height, |
87 | x, |
88 | row as isize, |
89 | channel, |
90 | channel_num, |
91 | ) |
92 | .to_f32() |
93 | .unwrap_or(0.0) |
94 | }) |
95 | .sum(); |
96 | } |
97 | |
98 | for column in 0..width { |
99 | for (channel, channel_val) in vals.iter_mut().enumerate() { |
100 | let val = *channel_val / (2.0 * r as f32 + 1.0); |
101 | let val = clamp(val, min_value, max_value); |
102 | let val = P::from(val).unwrap(); |
103 | |
104 | let destination_row = column; |
105 | let destination_column = row; |
106 | let destination_sample_index = channel_idx( |
107 | channel, |
108 | destination_column + destination_row * height, |
109 | channel_num, |
110 | ); |
111 | out_samples[destination_sample_index] = val; |
112 | *channel_val = *channel_val |
113 | - extended_f( |
114 | samples, |
115 | width, |
116 | height, |
117 | column as isize - r as isize, |
118 | row as isize, |
119 | channel, |
120 | channel_num, |
121 | ) |
122 | .to_f32() |
123 | .unwrap_or(0.0) |
124 | + extended_f( |
125 | samples, |
126 | width, |
127 | height, |
128 | { column + r + 1 } as isize, |
129 | row as isize, |
130 | channel, |
131 | channel_num, |
132 | ) |
133 | .to_f32() |
134 | .unwrap_or(0.0); |
135 | } |
136 | } |
137 | } |
138 | |
139 | out_samples |
140 | } |
141 | |
142 | fn extended_f<P: Primitive>( |
143 | samples: &[P], |
144 | width: usize, |
145 | height: usize, |
146 | x: isize, |
147 | y: isize, |
148 | channel: usize, |
149 | channel_num: usize, |
150 | ) -> P { |
151 | let x: usize = clamp(input:x, min:0, max:width as isize - 1) as usize; |
152 | let y: usize = clamp(input:y, min:0, max:height as isize - 1) as usize; |
153 | samples[channel_idx(channel, idx:y * width + x, channel_num)] |
154 | } |
155 | |