use crate::{GpuDevice, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FilterType {
GaussianBlur,
BoxBlur,
Median,
Bilateral,
UnsharpMask,
Sobel,
Scharr,
Laplacian,
Custom,
}
pub struct ConvolutionKernel {
kernel: Vec<f32>,
width: u32,
height: u32,
normalize: bool,
}
impl ConvolutionKernel {
pub fn new(kernel: Vec<f32>, width: u32, height: u32, normalize: bool) -> Result<Self> {
if kernel.len() != (width * height) as usize {
return Err(crate::GpuError::Internal(
"Kernel size mismatch".to_string(),
));
}
if width % 2 == 0 || height % 2 == 0 {
return Err(crate::GpuError::Internal(
"Kernel dimensions must be odd".to_string(),
));
}
Ok(Self {
kernel,
width,
height,
normalize,
})
}
#[must_use]
pub fn sobel_x() -> Self {
Self::new(
vec![-1.0, 0.0, 1.0, -2.0, 0.0, 2.0, -1.0, 0.0, 1.0],
3,
3,
false,
)
.unwrap()
}
#[must_use]
pub fn sobel_y() -> Self {
Self::new(
vec![-1.0, -2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0],
3,
3,
false,
)
.unwrap()
}
#[must_use]
pub fn laplacian() -> Self {
Self::new(
vec![0.0, 1.0, 0.0, 1.0, -4.0, 1.0, 0.0, 1.0, 0.0],
3,
3,
false,
)
.unwrap()
}
pub fn box_blur(size: u32) -> Result<Self> {
let total = (size * size) as usize;
let value = 1.0 / total as f32;
let kernel = vec![value; total];
Self::new(kernel, size, size, false)
}
#[must_use]
pub fn sharpen() -> Self {
Self::new(
vec![0.0, -1.0, 0.0, -1.0, 5.0, -1.0, 0.0, -1.0, 0.0],
3,
3,
false,
)
.unwrap()
}
#[must_use]
pub fn data(&self) -> &[f32] {
&self.kernel
}
#[must_use]
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
#[must_use]
pub fn is_normalized(&self) -> bool {
self.normalize
}
pub fn apply(
&self,
device: &GpuDevice,
input: &[u8],
output: &mut [u8],
width: u32,
height: u32,
) -> Result<()> {
crate::ops::FilterOperation::convolve(
device,
input,
output,
width,
height,
&self.kernel,
self.normalize,
)
}
}
pub struct FilterKernel {
filter_type: FilterType,
sigma: f32,
kernel_size: u32,
}
impl FilterKernel {
#[must_use]
pub fn new(filter_type: FilterType, sigma: f32, kernel_size: u32) -> Self {
Self {
filter_type,
sigma,
kernel_size,
}
}
#[must_use]
pub fn gaussian_blur(sigma: f32) -> Self {
let kernel_size = Self::gaussian_kernel_size(sigma);
Self::new(FilterType::GaussianBlur, sigma, kernel_size)
}
#[must_use]
pub fn box_blur(radius: u32) -> Self {
let kernel_size = radius * 2 + 1;
Self::new(FilterType::BoxBlur, 0.0, kernel_size)
}
#[must_use]
pub fn sharpen(amount: f32) -> Self {
Self::new(FilterType::UnsharpMask, amount, 5)
}
#[must_use]
pub fn sobel() -> Self {
Self::new(FilterType::Sobel, 0.0, 3)
}
#[must_use]
pub fn bilateral(sigma_spatial: f32, _sigma_range: f32) -> Self {
let kernel_size = Self::gaussian_kernel_size(sigma_spatial);
Self::new(FilterType::Bilateral, sigma_spatial, kernel_size)
}
pub fn execute(
&self,
device: &GpuDevice,
input: &[u8],
output: &mut [u8],
width: u32,
height: u32,
) -> Result<()> {
match self.filter_type {
FilterType::GaussianBlur => crate::ops::FilterOperation::gaussian_blur(
device, input, output, width, height, self.sigma,
),
FilterType::UnsharpMask => crate::ops::FilterOperation::sharpen(
device, input, output, width, height, self.sigma,
),
FilterType::Sobel | FilterType::Scharr | FilterType::Laplacian => {
crate::ops::FilterOperation::edge_detect(device, input, output, width, height)
}
_ => Err(crate::GpuError::NotSupported(format!(
"Filter type {:?} not yet implemented",
self.filter_type
))),
}
}
#[must_use]
pub fn filter_type(&self) -> FilterType {
self.filter_type
}
#[must_use]
pub fn sigma(&self) -> f32 {
self.sigma
}
#[must_use]
pub fn kernel_size(&self) -> u32 {
self.kernel_size
}
fn gaussian_kernel_size(sigma: f32) -> u32 {
let radius = (3.0 * sigma).ceil() as u32;
2 * radius + 1
}
#[must_use]
pub fn estimate_flops(width: u32, height: u32, kernel_size: u32) -> u64 {
let pixels = u64::from(width) * u64::from(height);
let ops_per_pixel = u64::from(kernel_size) * u64::from(kernel_size) * 4; pixels * ops_per_pixel * 2 }
}
pub struct SeparableFilter {
horizontal_kernel: Vec<f32>,
vertical_kernel: Vec<f32>,
}
impl SeparableFilter {
#[must_use]
pub fn new(horizontal: Vec<f32>, vertical: Vec<f32>) -> Self {
Self {
horizontal_kernel: horizontal,
vertical_kernel: vertical,
}
}
#[must_use]
pub fn gaussian(sigma: f32, size: u32) -> Self {
let kernel = Self::gaussian_kernel_1d(sigma, size);
Self::new(kernel.clone(), kernel)
}
fn gaussian_kernel_1d(sigma: f32, size: u32) -> Vec<f32> {
let radius = (size / 2) as i32;
let mut kernel = Vec::with_capacity(size as usize);
let two_sigma_sq = 2.0 * sigma * sigma;
let mut sum = 0.0;
for i in -radius..=radius {
let value = (-((i * i) as f32) / two_sigma_sq).exp();
kernel.push(value);
sum += value;
}
for value in &mut kernel {
*value /= sum;
}
kernel
}
#[must_use]
pub fn horizontal(&self) -> &[f32] {
&self.horizontal_kernel
}
#[must_use]
pub fn vertical(&self) -> &[f32] {
&self.vertical_kernel
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_convolution_kernel_creation() {
let kernel = ConvolutionKernel::sobel_x();
assert_eq!(kernel.dimensions(), (3, 3));
assert_eq!(kernel.data().len(), 9);
let kernel = ConvolutionKernel::laplacian();
assert_eq!(kernel.dimensions(), (3, 3));
}
#[test]
fn test_filter_kernel_creation() {
let filter = FilterKernel::gaussian_blur(2.0);
assert_eq!(filter.filter_type(), FilterType::GaussianBlur);
assert_eq!(filter.sigma(), 2.0);
let filter = FilterKernel::sobel();
assert_eq!(filter.filter_type(), FilterType::Sobel);
assert_eq!(filter.kernel_size(), 3);
}
#[test]
fn test_separable_filter() {
let filter = SeparableFilter::gaussian(1.0, 5);
assert_eq!(filter.horizontal().len(), 5);
assert_eq!(filter.vertical().len(), 5);
let sum: f32 = filter.horizontal().iter().sum();
assert!((sum - 1.0).abs() < 0.001);
}
#[test]
fn test_box_blur_kernel() {
let kernel = ConvolutionKernel::box_blur(3).unwrap();
assert_eq!(kernel.dimensions(), (3, 3));
let expected_value = 1.0 / 9.0;
for &value in kernel.data() {
assert!((value - expected_value).abs() < 0.001);
}
}
}