Coverage Report

Created: 2025-07-18 06:49

/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
}