image/imageops/
fast_blur.rs
1use num_traits::clamp;
2
3use crate::{ImageBuffer, Pixel, Primitive};
4
5#[must_use]
10pub 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
43fn boxes_for_gauss(sigma: f32, n: usize) -> Vec<usize> {
44 let w_ideal = f32::sqrt((12.0 * sigma.powi(2) / (n as f32)) + 1.0);
45 let mut w_l = w_ideal.floor();
46 if w_l % 2.0 == 0.0 {
47 w_l -= 1.0;
48 };
49 let w_u = w_l + 2.0;
50
51 let m_ideal = 0.25 * (n as f32) * (w_l + 3.0) - 3.0 * sigma.powi(2) * (w_l + 1.0).recip();
52
53 let m = f32::round(m_ideal) as usize;
54
55 (0..n)
56 .map(|i| if i < m { w_l as usize } else { w_u as usize })
57 .collect::<Vec<_>>()
58}
59
60fn channel_idx(channel: usize, idx: usize, channel_num: usize) -> usize {
61 channel_num * idx + channel
62}
63
64fn 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
142fn 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 = clamp(x, 0, width as isize - 1) as usize;
152 let y = clamp(y, 0, height as isize - 1) as usize;
153 samples[channel_idx(channel, y * width + x, channel_num)]
154}