Skip to main content

oximedia_gpu/kernels/
filter.rs

1//! Image filtering and convolution kernels
2
3use crate::{GpuDevice, Result};
4
5/// Filter operation type
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum FilterType {
8    /// Gaussian blur
9    GaussianBlur,
10    /// Box blur (simple average)
11    BoxBlur,
12    /// Median filter
13    Median,
14    /// Bilateral filter (edge-preserving)
15    Bilateral,
16    /// Unsharp mask (sharpening)
17    UnsharpMask,
18    /// Sobel edge detection
19    Sobel,
20    /// Scharr edge detection
21    Scharr,
22    /// Laplacian edge detection
23    Laplacian,
24    /// Custom convolution
25    Custom,
26}
27
28/// Convolution kernel
29pub struct ConvolutionKernel {
30    kernel: Vec<f32>,
31    width: u32,
32    height: u32,
33    normalize: bool,
34}
35
36impl ConvolutionKernel {
37    /// Create a new convolution kernel
38    ///
39    /// # Arguments
40    ///
41    /// * `kernel` - Kernel weights (must be width * height in size)
42    /// * `width` - Kernel width (must be odd)
43    /// * `height` - Kernel height (must be odd)
44    /// * `normalize` - Whether to normalize the kernel
45    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    /// Create a Sobel X kernel (3x3)
67    #[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    /// Create a Sobel Y kernel (3x3)
79    #[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    /// Create a Laplacian kernel (3x3)
91    #[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    /// Create a box blur kernel
103    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    /// Create a sharpening kernel
111    #[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    /// Get the kernel data
123    #[must_use]
124    pub fn data(&self) -> &[f32] {
125        &self.kernel
126    }
127
128    /// Get kernel dimensions
129    #[must_use]
130    pub fn dimensions(&self) -> (u32, u32) {
131        (self.width, self.height)
132    }
133
134    /// Check if normalization is enabled
135    #[must_use]
136    pub fn is_normalized(&self) -> bool {
137        self.normalize
138    }
139
140    /// Apply the convolution kernel
141    ///
142    /// # Arguments
143    ///
144    /// * `device` - GPU device
145    /// * `input` - Input image buffer
146    /// * `output` - Output image buffer
147    /// * `width` - Image width
148    /// * `height` - Image height
149    ///
150    /// # Errors
151    ///
152    /// Returns an error if the operation fails.
153    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
173/// Image filter kernel
174pub struct FilterKernel {
175    filter_type: FilterType,
176    sigma: f32,
177    kernel_size: u32,
178}
179
180impl FilterKernel {
181    /// Create a new filter kernel
182    #[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    /// Create a Gaussian blur filter
192    #[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    /// Create a box blur filter
199    #[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    /// Create an unsharp mask filter (sharpening)
206    #[must_use]
207    pub fn sharpen(amount: f32) -> Self {
208        Self::new(FilterType::UnsharpMask, amount, 5)
209    }
210
211    /// Create a Sobel edge detection filter
212    #[must_use]
213    pub fn sobel() -> Self {
214        Self::new(FilterType::Sobel, 0.0, 3)
215    }
216
217    /// Create a bilateral filter
218    #[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    /// Execute the filter operation
225    ///
226    /// # Arguments
227    ///
228    /// * `device` - GPU device
229    /// * `input` - Input image buffer
230    /// * `output` - Output image buffer
231    /// * `width` - Image width
232    /// * `height` - Image height
233    ///
234    /// # Errors
235    ///
236    /// Returns an error if the operation fails.
237    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    /// Get the filter type
263    #[must_use]
264    pub fn filter_type(&self) -> FilterType {
265        self.filter_type
266    }
267
268    /// Get the sigma parameter
269    #[must_use]
270    pub fn sigma(&self) -> f32 {
271        self.sigma
272    }
273
274    /// Get the kernel size
275    #[must_use]
276    pub fn kernel_size(&self) -> u32 {
277        self.kernel_size
278    }
279
280    /// Calculate Gaussian kernel size from sigma
281    fn gaussian_kernel_size(sigma: f32) -> u32 {
282        let radius = (3.0 * sigma).ceil() as u32;
283        2 * radius + 1
284    }
285
286    /// Estimate FLOPS for filter operation
287    #[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; // 4 channels
291        pixels * ops_per_pixel * 2 // multiply + add
292    }
293}
294
295/// Separable filter for optimized 2D filtering
296pub struct SeparableFilter {
297    horizontal_kernel: Vec<f32>,
298    vertical_kernel: Vec<f32>,
299}
300
301impl SeparableFilter {
302    /// Create a new separable filter
303    #[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    /// Create a Gaussian separable filter
312    #[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    /// Generate 1D Gaussian kernel
319    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        // Normalize
332        for value in &mut kernel {
333            *value /= sum;
334        }
335
336        kernel
337    }
338
339    /// Get the horizontal kernel
340    #[must_use]
341    pub fn horizontal(&self) -> &[f32] {
342        &self.horizontal_kernel
343    }
344
345    /// Get the vertical kernel
346    #[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        // Check normalization
384        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}