/src/image/src/imageops/fast_blur.rs
Line | Count | Source (jump to first uncovered line) |
1 | | use num_traits::Bounded; |
2 | | |
3 | | use crate::imageops::filter_1d::{SafeAdd, SafeMul}; |
4 | | use crate::{ImageBuffer, Pixel, Primitive}; |
5 | | |
6 | | /// Approximation of Gaussian blur. |
7 | | /// |
8 | | /// # Arguments |
9 | | /// |
10 | | /// * `image_buffer` - source image. |
11 | | /// * `sigma` - value controls image flattening level. |
12 | | /// |
13 | | /// This method assumes alpha pre-multiplication for images that contain non-constant alpha. |
14 | | /// |
15 | | /// This method typically assumes that the input is scene-linear light. |
16 | | /// If it is not, color distortion may occur. |
17 | | /// |
18 | | /// Source: Kovesi, P.: Fast Almost-Gaussian Filtering The Australian Pattern |
19 | | /// Recognition Society Conference: DICTA 2010. December 2010. Sydney. |
20 | | #[must_use] |
21 | 0 | pub fn fast_blur<P: Pixel>( |
22 | 0 | image_buffer: &ImageBuffer<P, Vec<P::Subpixel>>, |
23 | 0 | sigma: f32, |
24 | 0 | ) -> ImageBuffer<P, Vec<P::Subpixel>> { |
25 | 0 | let (width, height) = image_buffer.dimensions(); |
26 | 0 |
|
27 | 0 | if width == 0 || height == 0 { |
28 | 0 | return image_buffer.clone(); |
29 | 0 | } |
30 | 0 |
|
31 | 0 | let num_passes = 3; |
32 | 0 |
|
33 | 0 | let boxes = boxes_for_gauss(sigma, num_passes); |
34 | 0 | if boxes.is_empty() { |
35 | 0 | return image_buffer.clone(); |
36 | 0 | } |
37 | 0 |
|
38 | 0 | let samples = image_buffer.as_flat_samples().samples; |
39 | | |
40 | 0 | let destination_size = match (width as usize) |
41 | 0 | .safe_mul(height as usize) |
42 | 0 | .and_then(|x| x.safe_mul(P::CHANNEL_COUNT as usize)) Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Rgb<f32>>::{closure#0} Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Rgb<u8>>::{closure#0} Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Rgb<u16>>::{closure#0} Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Luma<u8>>::{closure#0} Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Luma<u16>>::{closure#0} Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Rgba<f32>>::{closure#0} Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Rgba<u8>>::{closure#0} Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Rgba<u16>>::{closure#0} Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::LumaA<u8>>::{closure#0} Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::LumaA<u16>>::{closure#0} |
43 | | { |
44 | 0 | Ok(s) => s, |
45 | 0 | Err(_) => panic!("Width and height and channels count exceeded pointer size"), |
46 | | }; |
47 | | |
48 | 0 | let first_box = boxes[0]; |
49 | 0 |
|
50 | 0 | let mut transient = vec![P::Subpixel::min_value(); destination_size]; |
51 | 0 | let mut dst = vec![P::Subpixel::min_value(); destination_size]; |
52 | 0 |
|
53 | 0 | // If destination_size isn't failed this one must not fail either |
54 | 0 | let stride = width as usize * P::CHANNEL_COUNT as usize; |
55 | 0 |
|
56 | 0 | // bound + radius + 1 must fit in a pointer size |
57 | 0 | test_radius_size(width as usize, first_box); |
58 | 0 | test_radius_size(height as usize, first_box); |
59 | 0 |
|
60 | 0 | box_blur_horizontal_pass_strategy::<P, P::Subpixel>( |
61 | 0 | samples, |
62 | 0 | stride, |
63 | 0 | &mut transient, |
64 | 0 | stride, |
65 | 0 | width, |
66 | 0 | first_box, |
67 | 0 | ); |
68 | 0 |
|
69 | 0 | box_blur_vertical_pass_strategy::<P, P::Subpixel>( |
70 | 0 | &transient, stride, &mut dst, stride, width, height, first_box, |
71 | 0 | ); |
72 | | |
73 | 0 | for &box_container in boxes.iter().skip(1) { |
74 | 0 | // bound + radius + 1 must fit in a pointer size |
75 | 0 | test_radius_size(width as usize, box_container); |
76 | 0 | test_radius_size(height as usize, box_container); |
77 | 0 |
|
78 | 0 | box_blur_horizontal_pass_strategy::<P, P::Subpixel>( |
79 | 0 | &dst, |
80 | 0 | stride, |
81 | 0 | &mut transient, |
82 | 0 | stride, |
83 | 0 | width, |
84 | 0 | box_container, |
85 | 0 | ); |
86 | 0 |
|
87 | 0 | box_blur_vertical_pass_strategy::<P, P::Subpixel>( |
88 | 0 | &transient, |
89 | 0 | stride, |
90 | 0 | &mut dst, |
91 | 0 | stride, |
92 | 0 | width, |
93 | 0 | height, |
94 | 0 | box_container, |
95 | 0 | ); |
96 | 0 | } |
97 | 0 | ImageBuffer::from_raw(width, height, dst).unwrap() |
98 | 0 | } Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Rgb<f32>> Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Rgb<u8>> Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Rgb<u16>> Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Luma<u8>> Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Luma<u16>> Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Rgba<f32>> Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Rgba<u8>> Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::Rgba<u16>> Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::LumaA<u8>> Unexecuted instantiation: image::imageops::fast_blur::fast_blur::<image::color::LumaA<u16>> |
99 | | |
100 | | #[inline] |
101 | 0 | fn test_radius_size(bound: usize, radius: usize) { |
102 | 0 | match bound.safe_add(radius) { |
103 | 0 | Ok(_) => {} |
104 | 0 | Err(_) => panic!("Radius overflowed maximum possible size"), |
105 | | } |
106 | 0 | } |
107 | | |
108 | 0 | fn boxes_for_gauss(sigma: f32, n: usize) -> Vec<usize> { |
109 | 0 | let w_ideal = f32::sqrt((12.0 * sigma.powi(2) / (n as f32)) + 1.0); |
110 | 0 | let mut w_l = w_ideal.floor(); |
111 | 0 | if w_l % 2.0 == 0.0 { |
112 | 0 | w_l -= 1.0; |
113 | 0 | } |
114 | 0 | let w_u = w_l + 2.0; |
115 | 0 |
|
116 | 0 | let m_ideal = 0.25 * (n as f32) * (w_l + 3.0) - 3.0 * sigma.powi(2) * (w_l + 1.0).recip(); |
117 | 0 |
|
118 | 0 | let m = f32::round(m_ideal) as usize; |
119 | 0 |
|
120 | 0 | (0..n) |
121 | 0 | .map(|i| if i < m { w_l as usize } else { w_u as usize }) |
122 | 0 | .map(|i| ceil_to_odd(i.saturating_sub(1) / 2)) |
123 | 0 | .collect::<Vec<_>>() |
124 | 0 | } |
125 | | |
126 | | #[inline] |
127 | 0 | fn ceil_to_odd(x: usize) -> usize { |
128 | 0 | if x % 2 == 0 { |
129 | 0 | x + 1 |
130 | | } else { |
131 | 0 | x |
132 | | } |
133 | 0 | } |
134 | | |
135 | | #[inline] |
136 | | #[allow(clippy::manual_clamp)] |
137 | 0 | fn rounding_saturating_mul<T: Primitive>(v: f32, w: f32) -> T { |
138 | 0 | // T::DEFAULT_MAX_VALUE is equal to 1.0 only in cases where storage type if `f32/f64`, |
139 | 0 | // that means it should be safe to round here. |
140 | 0 | if T::DEFAULT_MAX_VALUE.to_f32().unwrap() != 1.0 { |
141 | 0 | T::from( |
142 | 0 | (v * w) |
143 | 0 | .round() |
144 | 0 | .min(T::DEFAULT_MAX_VALUE.to_f32().unwrap()) |
145 | 0 | .max(T::DEFAULT_MIN_VALUE.to_f32().unwrap()), |
146 | 0 | ) |
147 | 0 | .unwrap() |
148 | | } else { |
149 | 0 | T::from( |
150 | 0 | (v * w) |
151 | 0 | .min(T::DEFAULT_MAX_VALUE.to_f32().unwrap()) |
152 | 0 | .max(T::DEFAULT_MIN_VALUE.to_f32().unwrap()), |
153 | 0 | ) |
154 | 0 | .unwrap() |
155 | | } |
156 | 0 | } Unexecuted instantiation: image::imageops::fast_blur::rounding_saturating_mul::<f32> Unexecuted instantiation: image::imageops::fast_blur::rounding_saturating_mul::<u8> Unexecuted instantiation: image::imageops::fast_blur::rounding_saturating_mul::<u16> |
157 | | |
158 | 0 | fn box_blur_horizontal_pass_strategy<T, P: Primitive>( |
159 | 0 | src: &[P], |
160 | 0 | src_stride: usize, |
161 | 0 | dst: &mut [P], |
162 | 0 | dst_stride: usize, |
163 | 0 | width: u32, |
164 | 0 | radius: usize, |
165 | 0 | ) where |
166 | 0 | T: Pixel, |
167 | 0 | { |
168 | 0 | if T::CHANNEL_COUNT == 1 { |
169 | 0 | box_blur_horizontal_pass_impl::<P, 1>(src, src_stride, dst, dst_stride, width, radius); |
170 | 0 | } else if T::CHANNEL_COUNT == 2 { |
171 | 0 | box_blur_horizontal_pass_impl::<P, 2>(src, src_stride, dst, dst_stride, width, radius); |
172 | 0 | } else if T::CHANNEL_COUNT == 3 { |
173 | 0 | box_blur_horizontal_pass_impl::<P, 3>(src, src_stride, dst, dst_stride, width, radius); |
174 | 0 | } else if T::CHANNEL_COUNT == 4 { |
175 | 0 | box_blur_horizontal_pass_impl::<P, 4>(src, src_stride, dst, dst_stride, width, radius); |
176 | 0 | } else { |
177 | 0 | unimplemented!("More than 4 channels is not yet implemented"); |
178 | | } |
179 | 0 | } Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_strategy::<image::color::Rgb<f32>, f32> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_strategy::<image::color::Rgb<u8>, u8> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_strategy::<image::color::Rgb<u16>, u16> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_strategy::<image::color::Luma<u8>, u8> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_strategy::<image::color::Luma<u16>, u16> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_strategy::<image::color::Rgba<f32>, f32> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_strategy::<image::color::Rgba<u8>, u8> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_strategy::<image::color::Rgba<u16>, u16> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_strategy::<image::color::LumaA<u8>, u8> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_strategy::<image::color::LumaA<u16>, u16> |
180 | | |
181 | 0 | fn box_blur_vertical_pass_strategy<T: Pixel, P: Primitive>( |
182 | 0 | src: &[P], |
183 | 0 | src_stride: usize, |
184 | 0 | dst: &mut [P], |
185 | 0 | dst_stride: usize, |
186 | 0 | width: u32, |
187 | 0 | height: u32, |
188 | 0 | radius: usize, |
189 | 0 | ) { |
190 | 0 | if T::CHANNEL_COUNT == 1 { |
191 | 0 | box_blur_vertical_pass_impl::<P, 1>( |
192 | 0 | src, src_stride, dst, dst_stride, width, height, radius, |
193 | 0 | ); |
194 | 0 | } else if T::CHANNEL_COUNT == 2 { |
195 | 0 | box_blur_vertical_pass_impl::<P, 2>( |
196 | 0 | src, src_stride, dst, dst_stride, width, height, radius, |
197 | 0 | ); |
198 | 0 | } else if T::CHANNEL_COUNT == 3 { |
199 | 0 | box_blur_vertical_pass_impl::<P, 3>( |
200 | 0 | src, src_stride, dst, dst_stride, width, height, radius, |
201 | 0 | ); |
202 | 0 | } else if T::CHANNEL_COUNT == 4 { |
203 | 0 | box_blur_vertical_pass_impl::<P, 4>( |
204 | 0 | src, src_stride, dst, dst_stride, width, height, radius, |
205 | 0 | ); |
206 | 0 | } else { |
207 | 0 | unimplemented!("More than 4 channels is not yet implemented"); |
208 | | } |
209 | 0 | } Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_strategy::<image::color::Rgb<f32>, f32> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_strategy::<image::color::Rgb<u8>, u8> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_strategy::<image::color::Rgb<u16>, u16> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_strategy::<image::color::Luma<u8>, u8> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_strategy::<image::color::Luma<u16>, u16> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_strategy::<image::color::Rgba<f32>, f32> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_strategy::<image::color::Rgba<u8>, u8> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_strategy::<image::color::Rgba<u16>, u16> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_strategy::<image::color::LumaA<u8>, u8> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_strategy::<image::color::LumaA<u16>, u16> |
210 | | |
211 | 0 | fn box_blur_horizontal_pass_impl<T, const CN: usize>( |
212 | 0 | src: &[T], |
213 | 0 | src_stride: usize, |
214 | 0 | dst: &mut [T], |
215 | 0 | dst_stride: usize, |
216 | 0 | width: u32, |
217 | 0 | radius: usize, |
218 | 0 | ) where |
219 | 0 | T: Primitive, |
220 | 0 | { |
221 | 0 | assert!(width > 0, "Width must be sanitized before this method"); |
222 | 0 | test_radius_size(width as usize, radius); |
223 | 0 |
|
224 | 0 | let kernel_size = radius * 2 + 1; |
225 | 0 | let edge_count = ((kernel_size / 2) + 1) as f32; |
226 | 0 | let half_kernel = kernel_size / 2; |
227 | 0 |
|
228 | 0 | let weight = 1f32 / (radius * 2 + 1) as f32; |
229 | 0 |
|
230 | 0 | let width_bound = width as usize - 1; |
231 | | |
232 | | // Horizontal blurring consists from 4 phases |
233 | | // 1 - Fill initial sliding window |
234 | | // 2 - Blur dangerous leading zone where clamping is required |
235 | | // 3 - Blur *normal* zone where clamping is not required |
236 | | // 4 - Blur dangerous trailing zone where clamping is required |
237 | | |
238 | 0 | for (dst, src) in dst |
239 | 0 | .chunks_exact_mut(dst_stride) |
240 | 0 | .zip(src.chunks_exact(src_stride)) |
241 | | { |
242 | 0 | let mut weight1: f32 = 0.; |
243 | 0 | let mut weight2: f32 = 0.; |
244 | 0 | let mut weight3: f32 = 0.; |
245 | 0 |
|
246 | 0 | let chunk0 = &src[..CN]; |
247 | 0 |
|
248 | 0 | // replicate edge |
249 | 0 | let mut weight0 = chunk0[0].to_f32().unwrap() * edge_count; |
250 | 0 | if CN > 1 { |
251 | 0 | weight1 = chunk0[1].to_f32().unwrap() * edge_count; |
252 | 0 | } |
253 | 0 | if CN > 2 { |
254 | 0 | weight2 = chunk0[2].to_f32().unwrap() * edge_count; |
255 | 0 | } |
256 | 0 | if CN == 4 { |
257 | 0 | weight3 = chunk0[3].to_f32().unwrap() * edge_count; |
258 | 0 | } |
259 | | |
260 | 0 | for x in 1..=half_kernel { |
261 | 0 | let px = x.min(width_bound) * CN; |
262 | 0 | let chunk0 = &src[px..px + CN]; |
263 | 0 | weight0 += chunk0[0].to_f32().unwrap(); |
264 | 0 | if CN > 1 { |
265 | 0 | weight1 += chunk0[1].to_f32().unwrap(); |
266 | 0 | } |
267 | 0 | if CN > 2 { |
268 | 0 | weight2 += chunk0[2].to_f32().unwrap(); |
269 | 0 | } |
270 | 0 | if CN == 4 { |
271 | 0 | weight3 += chunk0[3].to_f32().unwrap(); |
272 | 0 | } |
273 | | } |
274 | | |
275 | 0 | for x in 0..half_kernel.min(width as usize) { |
276 | 0 | let next = (x + half_kernel + 1).min(width_bound) * CN; |
277 | 0 | let previous = (x as i64 - half_kernel as i64).max(0) as usize * CN; |
278 | 0 |
|
279 | 0 | let dst_chunk = &mut dst[x * CN..x * CN + CN]; |
280 | 0 | dst_chunk[0] = rounding_saturating_mul(weight0, weight); |
281 | 0 | if CN > 1 { |
282 | 0 | dst_chunk[1] = rounding_saturating_mul(weight1, weight); |
283 | 0 | } |
284 | 0 | if CN > 2 { |
285 | 0 | dst_chunk[2] = rounding_saturating_mul(weight2, weight); |
286 | 0 | } |
287 | 0 | if CN == 4 { |
288 | 0 | dst_chunk[3] = rounding_saturating_mul(weight3, weight); |
289 | 0 | } |
290 | | |
291 | 0 | let next_chunk = &src[next..next + CN]; |
292 | 0 | let previous_chunk = &src[previous..previous + CN]; |
293 | 0 |
|
294 | 0 | weight0 += next_chunk[0].to_f32().unwrap(); |
295 | 0 | if CN > 1 { |
296 | 0 | weight1 += next_chunk[1].to_f32().unwrap(); |
297 | 0 | } |
298 | 0 | if CN > 2 { |
299 | 0 | weight2 += next_chunk[2].to_f32().unwrap(); |
300 | 0 | } |
301 | 0 | if CN == 4 { |
302 | 0 | weight3 += next_chunk[3].to_f32().unwrap(); |
303 | 0 | } |
304 | | |
305 | 0 | weight0 -= previous_chunk[0].to_f32().unwrap(); |
306 | 0 | if CN > 1 { |
307 | 0 | weight1 -= previous_chunk[1].to_f32().unwrap(); |
308 | 0 | } |
309 | 0 | if CN > 2 { |
310 | 0 | weight2 -= previous_chunk[2].to_f32().unwrap(); |
311 | 0 | } |
312 | 0 | if CN == 4 { |
313 | 0 | weight3 -= previous_chunk[3].to_f32().unwrap(); |
314 | 0 | } |
315 | | } |
316 | | |
317 | 0 | let max_x_before_clamping = width_bound.saturating_sub(half_kernel + 1); |
318 | 0 | let row_length = src.len(); |
319 | 0 |
|
320 | 0 | let mut last_processed_item = half_kernel; |
321 | 0 |
|
322 | 0 | if ((half_kernel * 2 + 1) * CN < row_length) && ((max_x_before_clamping * CN) < row_length) |
323 | | { |
324 | 0 | let data_section = src; |
325 | 0 | let advanced_kernel_part = &data_section[(half_kernel * 2 + 1) * CN..]; |
326 | 0 | let section_length = max_x_before_clamping - half_kernel; |
327 | 0 | let dst = &mut dst[half_kernel * CN..(half_kernel * CN + section_length * CN)]; |
328 | | |
329 | 0 | for ((dst, src_previous), src_next) in dst |
330 | 0 | .chunks_exact_mut(CN) |
331 | 0 | .zip(data_section.chunks_exact(CN)) |
332 | 0 | .zip(advanced_kernel_part.chunks_exact(CN)) |
333 | | { |
334 | 0 | let dst_chunk = &mut dst[..CN]; |
335 | 0 | dst_chunk[0] = rounding_saturating_mul(weight0, weight); |
336 | 0 | if CN > 1 { |
337 | 0 | dst_chunk[1] = rounding_saturating_mul(weight1, weight); |
338 | 0 | } |
339 | 0 | if CN > 2 { |
340 | 0 | dst_chunk[2] = rounding_saturating_mul(weight2, weight); |
341 | 0 | } |
342 | 0 | if CN == 4 { |
343 | 0 | dst_chunk[3] = rounding_saturating_mul(weight3, weight); |
344 | 0 | } |
345 | | |
346 | 0 | weight0 += src_next[0].to_f32().unwrap(); |
347 | 0 | if CN > 1 { |
348 | 0 | weight1 += src_next[1].to_f32().unwrap(); |
349 | 0 | } |
350 | 0 | if CN > 2 { |
351 | 0 | weight2 += src_next[2].to_f32().unwrap(); |
352 | 0 | } |
353 | 0 | if CN == 4 { |
354 | 0 | weight3 += src_next[3].to_f32().unwrap(); |
355 | 0 | } |
356 | | |
357 | 0 | weight0 -= src_previous[0].to_f32().unwrap(); |
358 | 0 | if CN > 1 { |
359 | 0 | weight1 -= src_previous[1].to_f32().unwrap(); |
360 | 0 | } |
361 | 0 | if CN > 2 { |
362 | 0 | weight2 -= src_previous[2].to_f32().unwrap(); |
363 | 0 | } |
364 | 0 | if CN == 4 { |
365 | 0 | weight3 -= src_previous[3].to_f32().unwrap(); |
366 | 0 | } |
367 | | } |
368 | | |
369 | 0 | last_processed_item = max_x_before_clamping; |
370 | 0 | } |
371 | | |
372 | 0 | for x in last_processed_item..width as usize { |
373 | 0 | let next = (x + half_kernel + 1).min(width_bound) * CN; |
374 | 0 | let previous = (x as i64 - half_kernel as i64).max(0) as usize * CN; |
375 | 0 | let dst_chunk = &mut dst[x * CN..x * CN + CN]; |
376 | 0 | dst_chunk[0] = rounding_saturating_mul(weight0, weight); |
377 | 0 | if CN > 1 { |
378 | 0 | dst_chunk[1] = rounding_saturating_mul(weight1, weight); |
379 | 0 | } |
380 | 0 | if CN > 2 { |
381 | 0 | dst_chunk[2] = rounding_saturating_mul(weight2, weight); |
382 | 0 | } |
383 | 0 | if CN == 4 { |
384 | 0 | dst_chunk[3] = rounding_saturating_mul(weight3, weight); |
385 | 0 | } |
386 | | |
387 | 0 | let next_chunk = &src[next..next + CN]; |
388 | 0 | let previous_chunk = &src[previous..previous + CN]; |
389 | 0 |
|
390 | 0 | weight0 += next_chunk[0].to_f32().unwrap(); |
391 | 0 | if CN > 1 { |
392 | 0 | weight1 += next_chunk[1].to_f32().unwrap(); |
393 | 0 | } |
394 | 0 | if CN > 2 { |
395 | 0 | weight2 += next_chunk[2].to_f32().unwrap(); |
396 | 0 | } |
397 | 0 | if CN == 4 { |
398 | 0 | weight3 += next_chunk[3].to_f32().unwrap(); |
399 | 0 | } |
400 | | |
401 | 0 | weight0 -= previous_chunk[0].to_f32().unwrap(); |
402 | 0 | if CN > 1 { |
403 | 0 | weight1 -= previous_chunk[1].to_f32().unwrap(); |
404 | 0 | } |
405 | 0 | if CN > 2 { |
406 | 0 | weight2 -= previous_chunk[2].to_f32().unwrap(); |
407 | 0 | } |
408 | 0 | if CN == 4 { |
409 | 0 | weight3 -= previous_chunk[3].to_f32().unwrap(); |
410 | 0 | } |
411 | | } |
412 | | } |
413 | 0 | } Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_impl::<f32, 1> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_impl::<f32, 2> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_impl::<f32, 3> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_impl::<f32, 4> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_impl::<u8, 1> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_impl::<u8, 2> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_impl::<u8, 3> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_impl::<u8, 4> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_impl::<u16, 1> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_impl::<u16, 2> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_impl::<u16, 3> Unexecuted instantiation: image::imageops::fast_blur::box_blur_horizontal_pass_impl::<u16, 4> |
414 | | |
415 | 0 | fn box_blur_vertical_pass_impl<T: Primitive, const CN: usize>( |
416 | 0 | src: &[T], |
417 | 0 | src_stride: usize, |
418 | 0 | dst: &mut [T], |
419 | 0 | dst_stride: usize, |
420 | 0 | width: u32, |
421 | 0 | height: u32, |
422 | 0 | radius: usize, |
423 | 0 | ) { |
424 | 0 | assert!(width > 0, "Width must be sanitized before this method"); |
425 | 0 | assert!(height > 0, "Height must be sanitized before this method"); |
426 | 0 | test_radius_size(width as usize, radius); |
427 | 0 |
|
428 | 0 | let kernel_size = radius * 2 + 1; |
429 | 0 |
|
430 | 0 | let edge_count = ((kernel_size / 2) + 1) as f32; |
431 | 0 | let half_kernel = kernel_size / 2; |
432 | 0 |
|
433 | 0 | let weight = 1f32 / (radius * 2 + 1) as f32; |
434 | 0 |
|
435 | 0 | let buf_size = width as usize * CN; |
436 | 0 |
|
437 | 0 | let buf_cap = buf_size; |
438 | 0 |
|
439 | 0 | let height_bound = height as usize - 1; |
440 | 0 |
|
441 | 0 | // Instead of summing each column separately we use here transient buffer that |
442 | 0 | // averages columns in row manner. |
443 | 0 | // So, we make the initial buffer at the top edge |
444 | 0 | // and then doing blur by averaging the whole row ( which is in buffer ) |
445 | 0 | // and subtracting and adding next and previous rows in horizontal manner. |
446 | 0 |
|
447 | 0 | let mut buffer = vec![0f32; buf_cap]; |
448 | | |
449 | 0 | for (x, (v, bf)) in src.iter().zip(buffer.iter_mut()).enumerate() { |
450 | 0 | let mut w = v.to_f32().unwrap() * edge_count; |
451 | 0 | for y in 1..=half_kernel { |
452 | 0 | let y_src_shift = y.min(height_bound) * src_stride; |
453 | 0 | w += src[y_src_shift + x].to_f32().unwrap(); |
454 | 0 | } |
455 | 0 | *bf = w; |
456 | | } |
457 | | |
458 | 0 | for (dst, y) in dst.chunks_exact_mut(dst_stride).zip(0..height as usize) { |
459 | 0 | let next = (y + half_kernel + 1).min(height_bound) * src_stride; |
460 | 0 | let previous = (y as i64 - half_kernel as i64).max(0) as usize * src_stride; |
461 | 0 |
|
462 | 0 | let next_row = &src[next..next + width as usize * CN]; |
463 | 0 | let previous_row = &src[previous..previous + width as usize * CN]; |
464 | | |
465 | 0 | for (((src_next, src_previous), buffer), dst) in next_row |
466 | 0 | .iter() |
467 | 0 | .zip(previous_row.iter()) |
468 | 0 | .zip(buffer.iter_mut()) |
469 | 0 | .zip(dst.iter_mut()) |
470 | 0 | { |
471 | 0 | let mut weight0 = *buffer; |
472 | 0 |
|
473 | 0 | *dst = rounding_saturating_mul(weight0, weight); |
474 | 0 |
|
475 | 0 | weight0 += src_next.to_f32().unwrap(); |
476 | 0 | weight0 -= src_previous.to_f32().unwrap(); |
477 | 0 |
|
478 | 0 | *buffer = weight0; |
479 | 0 | } |
480 | | } |
481 | 0 | } Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_impl::<f32, 1> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_impl::<f32, 2> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_impl::<f32, 3> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_impl::<f32, 4> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_impl::<u8, 1> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_impl::<u8, 2> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_impl::<u8, 3> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_impl::<u8, 4> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_impl::<u16, 1> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_impl::<u16, 2> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_impl::<u16, 3> Unexecuted instantiation: image::imageops::fast_blur::box_blur_vertical_pass_impl::<u16, 4> |
482 | | |
483 | | #[cfg(test)] |
484 | | mod tests { |
485 | | use crate::{DynamicImage, GrayAlphaImage, GrayImage, RgbImage, RgbaImage}; |
486 | | use std::time::{SystemTime, UNIX_EPOCH}; |
487 | | |
488 | | struct Rng { |
489 | | state: u64, |
490 | | } |
491 | | |
492 | | impl Rng { |
493 | | fn new(seed: u64) -> Self { |
494 | | Self { state: seed } |
495 | | } |
496 | | fn next_u32(&mut self) -> u32 { |
497 | | self.state = self.state.wrapping_mul(6364136223846793005).wrapping_add(1); |
498 | | (self.state >> 32) as u32 |
499 | | } |
500 | | |
501 | | fn next_u8(&mut self) -> u8 { |
502 | | (self.next_u32() % 256) as u8 |
503 | | } |
504 | | |
505 | | fn next_f32_in_range(&mut self, a: f32, b: f32) -> f32 { |
506 | | let u = self.next_u32(); |
507 | | let unit = (u as f32) / (u32::MAX as f32 + 1.0); |
508 | | a + (b - a) * unit |
509 | | } |
510 | | } |
511 | | |
512 | | #[test] |
513 | | fn test_box_blur() { |
514 | | let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); |
515 | | let mut rng = Rng::new((now.as_millis() & 0xffff_ffff_ffff_ffff) as u64); |
516 | | for _ in 0..35 { |
517 | | let width = rng.next_u8(); |
518 | | let height = rng.next_u8(); |
519 | | let sigma = rng.next_f32_in_range(0., 100.); |
520 | | let px = rng.next_u8(); |
521 | | let cn = rng.next_u8(); |
522 | | if width == 0 || height == 0 || sigma <= 0. { |
523 | | continue; |
524 | | } |
525 | | match cn % 4 { |
526 | | 0 => { |
527 | | let vc = vec![px; width as usize * height as usize]; |
528 | | let image = DynamicImage::from( |
529 | | GrayImage::from_vec(u32::from(width), u32::from(height), vc).unwrap(), |
530 | | ); |
531 | | let res = image.fast_blur(sigma); |
532 | | for clr in res.as_bytes() { |
533 | | assert_eq!(*clr, px); |
534 | | } |
535 | | } |
536 | | 1 => { |
537 | | let vc = vec![px; width as usize * height as usize * 2]; |
538 | | let image = DynamicImage::from( |
539 | | GrayAlphaImage::from_vec(u32::from(width), u32::from(height), vc).unwrap(), |
540 | | ); |
541 | | let res = image.fast_blur(sigma); |
542 | | for clr in res.as_bytes() { |
543 | | assert_eq!(*clr, px); |
544 | | } |
545 | | } |
546 | | 2 => { |
547 | | let vc = vec![px; width as usize * height as usize * 3]; |
548 | | let image = DynamicImage::from( |
549 | | RgbImage::from_vec(u32::from(width), u32::from(height), vc).unwrap(), |
550 | | ); |
551 | | let res = image.fast_blur(sigma); |
552 | | for clr in res.as_bytes() { |
553 | | assert_eq!(*clr, px); |
554 | | } |
555 | | } |
556 | | 3 => { |
557 | | let vc = vec![px; width as usize * height as usize * 4]; |
558 | | let image = DynamicImage::from( |
559 | | RgbaImage::from_vec(u32::from(width), u32::from(height), vc).unwrap(), |
560 | | ); |
561 | | let res = image.fast_blur(sigma); |
562 | | for clr in res.as_bytes() { |
563 | | assert_eq!(*clr, px); |
564 | | } |
565 | | } |
566 | | _ => {} |
567 | | } |
568 | | } |
569 | | } |
570 | | } |