1use crate::{GpuDevice, Result};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum FilterType {
8 GaussianBlur,
10 BoxBlur,
12 Median,
14 Bilateral,
16 UnsharpMask,
18 Sobel,
20 Scharr,
22 Laplacian,
24 Custom,
26}
27
28pub struct ConvolutionKernel {
30 kernel: Vec<f32>,
31 width: u32,
32 height: u32,
33 normalize: bool,
34}
35
36impl ConvolutionKernel {
37 pub fn new(kernel: Vec<f32>, width: u32, height: u32, normalize: bool) -> Result<Self> {
46 if kernel.len() != (width * height) as usize {
47 return Err(crate::GpuError::Internal(
48 "Kernel size mismatch".to_string(),
49 ));
50 }
51
52 if width % 2 == 0 || height % 2 == 0 {
53 return Err(crate::GpuError::Internal(
54 "Kernel dimensions must be odd".to_string(),
55 ));
56 }
57
58 Ok(Self {
59 kernel,
60 width,
61 height,
62 normalize,
63 })
64 }
65
66 #[must_use]
68 pub fn sobel_x() -> Self {
69 Self::new(
70 vec![-1.0, 0.0, 1.0, -2.0, 0.0, 2.0, -1.0, 0.0, 1.0],
71 3,
72 3,
73 false,
74 )
75 .unwrap()
76 }
77
78 #[must_use]
80 pub fn sobel_y() -> Self {
81 Self::new(
82 vec![-1.0, -2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0],
83 3,
84 3,
85 false,
86 )
87 .unwrap()
88 }
89
90 #[must_use]
92 pub fn laplacian() -> Self {
93 Self::new(
94 vec![0.0, 1.0, 0.0, 1.0, -4.0, 1.0, 0.0, 1.0, 0.0],
95 3,
96 3,
97 false,
98 )
99 .unwrap()
100 }
101
102 pub fn box_blur(size: u32) -> Result<Self> {
104 let total = (size * size) as usize;
105 let value = 1.0 / total as f32;
106 let kernel = vec![value; total];
107 Self::new(kernel, size, size, false)
108 }
109
110 #[must_use]
112 pub fn sharpen() -> Self {
113 Self::new(
114 vec![0.0, -1.0, 0.0, -1.0, 5.0, -1.0, 0.0, -1.0, 0.0],
115 3,
116 3,
117 false,
118 )
119 .unwrap()
120 }
121
122 #[must_use]
124 pub fn data(&self) -> &[f32] {
125 &self.kernel
126 }
127
128 #[must_use]
130 pub fn dimensions(&self) -> (u32, u32) {
131 (self.width, self.height)
132 }
133
134 #[must_use]
136 pub fn is_normalized(&self) -> bool {
137 self.normalize
138 }
139
140 pub fn apply(
154 &self,
155 device: &GpuDevice,
156 input: &[u8],
157 output: &mut [u8],
158 width: u32,
159 height: u32,
160 ) -> Result<()> {
161 crate::ops::FilterOperation::convolve(
162 device,
163 input,
164 output,
165 width,
166 height,
167 &self.kernel,
168 self.normalize,
169 )
170 }
171}
172
173pub struct FilterKernel {
175 filter_type: FilterType,
176 sigma: f32,
177 kernel_size: u32,
178}
179
180impl FilterKernel {
181 #[must_use]
183 pub fn new(filter_type: FilterType, sigma: f32, kernel_size: u32) -> Self {
184 Self {
185 filter_type,
186 sigma,
187 kernel_size,
188 }
189 }
190
191 #[must_use]
193 pub fn gaussian_blur(sigma: f32) -> Self {
194 let kernel_size = Self::gaussian_kernel_size(sigma);
195 Self::new(FilterType::GaussianBlur, sigma, kernel_size)
196 }
197
198 #[must_use]
200 pub fn box_blur(radius: u32) -> Self {
201 let kernel_size = radius * 2 + 1;
202 Self::new(FilterType::BoxBlur, 0.0, kernel_size)
203 }
204
205 #[must_use]
207 pub fn sharpen(amount: f32) -> Self {
208 Self::new(FilterType::UnsharpMask, amount, 5)
209 }
210
211 #[must_use]
213 pub fn sobel() -> Self {
214 Self::new(FilterType::Sobel, 0.0, 3)
215 }
216
217 #[must_use]
219 pub fn bilateral(sigma_spatial: f32, _sigma_range: f32) -> Self {
220 let kernel_size = Self::gaussian_kernel_size(sigma_spatial);
221 Self::new(FilterType::Bilateral, sigma_spatial, kernel_size)
222 }
223
224 pub fn execute(
238 &self,
239 device: &GpuDevice,
240 input: &[u8],
241 output: &mut [u8],
242 width: u32,
243 height: u32,
244 ) -> Result<()> {
245 match self.filter_type {
246 FilterType::GaussianBlur => crate::ops::FilterOperation::gaussian_blur(
247 device, input, output, width, height, self.sigma,
248 ),
249 FilterType::UnsharpMask => crate::ops::FilterOperation::sharpen(
250 device, input, output, width, height, self.sigma,
251 ),
252 FilterType::Sobel | FilterType::Scharr | FilterType::Laplacian => {
253 crate::ops::FilterOperation::edge_detect(device, input, output, width, height)
254 }
255 _ => Err(crate::GpuError::NotSupported(format!(
256 "Filter type {:?} not yet implemented",
257 self.filter_type
258 ))),
259 }
260 }
261
262 #[must_use]
264 pub fn filter_type(&self) -> FilterType {
265 self.filter_type
266 }
267
268 #[must_use]
270 pub fn sigma(&self) -> f32 {
271 self.sigma
272 }
273
274 #[must_use]
276 pub fn kernel_size(&self) -> u32 {
277 self.kernel_size
278 }
279
280 fn gaussian_kernel_size(sigma: f32) -> u32 {
282 let radius = (3.0 * sigma).ceil() as u32;
283 2 * radius + 1
284 }
285
286 #[must_use]
288 pub fn estimate_flops(width: u32, height: u32, kernel_size: u32) -> u64 {
289 let pixels = u64::from(width) * u64::from(height);
290 let ops_per_pixel = u64::from(kernel_size) * u64::from(kernel_size) * 4; pixels * ops_per_pixel * 2 }
293}
294
295pub struct SeparableFilter {
297 horizontal_kernel: Vec<f32>,
298 vertical_kernel: Vec<f32>,
299}
300
301impl SeparableFilter {
302 #[must_use]
304 pub fn new(horizontal: Vec<f32>, vertical: Vec<f32>) -> Self {
305 Self {
306 horizontal_kernel: horizontal,
307 vertical_kernel: vertical,
308 }
309 }
310
311 #[must_use]
313 pub fn gaussian(sigma: f32, size: u32) -> Self {
314 let kernel = Self::gaussian_kernel_1d(sigma, size);
315 Self::new(kernel.clone(), kernel)
316 }
317
318 fn gaussian_kernel_1d(sigma: f32, size: u32) -> Vec<f32> {
320 let radius = (size / 2) as i32;
321 let mut kernel = Vec::with_capacity(size as usize);
322 let two_sigma_sq = 2.0 * sigma * sigma;
323
324 let mut sum = 0.0;
325 for i in -radius..=radius {
326 let value = (-((i * i) as f32) / two_sigma_sq).exp();
327 kernel.push(value);
328 sum += value;
329 }
330
331 for value in &mut kernel {
333 *value /= sum;
334 }
335
336 kernel
337 }
338
339 #[must_use]
341 pub fn horizontal(&self) -> &[f32] {
342 &self.horizontal_kernel
343 }
344
345 #[must_use]
347 pub fn vertical(&self) -> &[f32] {
348 &self.vertical_kernel
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[test]
357 fn test_convolution_kernel_creation() {
358 let kernel = ConvolutionKernel::sobel_x();
359 assert_eq!(kernel.dimensions(), (3, 3));
360 assert_eq!(kernel.data().len(), 9);
361
362 let kernel = ConvolutionKernel::laplacian();
363 assert_eq!(kernel.dimensions(), (3, 3));
364 }
365
366 #[test]
367 fn test_filter_kernel_creation() {
368 let filter = FilterKernel::gaussian_blur(2.0);
369 assert_eq!(filter.filter_type(), FilterType::GaussianBlur);
370 assert_eq!(filter.sigma(), 2.0);
371
372 let filter = FilterKernel::sobel();
373 assert_eq!(filter.filter_type(), FilterType::Sobel);
374 assert_eq!(filter.kernel_size(), 3);
375 }
376
377 #[test]
378 fn test_separable_filter() {
379 let filter = SeparableFilter::gaussian(1.0, 5);
380 assert_eq!(filter.horizontal().len(), 5);
381 assert_eq!(filter.vertical().len(), 5);
382
383 let sum: f32 = filter.horizontal().iter().sum();
385 assert!((sum - 1.0).abs() < 0.001);
386 }
387
388 #[test]
389 fn test_box_blur_kernel() {
390 let kernel = ConvolutionKernel::box_blur(3).unwrap();
391 assert_eq!(kernel.dimensions(), (3, 3));
392 let expected_value = 1.0 / 9.0;
393 for &value in kernel.data() {
394 assert!((value - expected_value).abs() < 0.001);
395 }
396 }
397}