use image::{ImageBuffer, RgbaImage};
use rayon::prelude::*;
pub fn kuwahara_filter(
img: RgbaImage,
radius: i32,
) -> RgbaImage {
let (width, height) = img.dimensions();
let width_usize = width as usize;
let height_i32 = height as i32;
let width_i32 = width as i32;
let pixels = img.as_raw();
let mut output = vec![0u8; pixels.len()];
output
.par_chunks_mut(width_usize * 4)
.enumerate()
.for_each(|(y, row_chunk)| {
let y_i32 = y as i32;
for x in 0..width_usize {
let x_i32 = x as i32;
let mut best_var = f64::INFINITY;
let mut best_pixel = [0u8; 4];
for &(dx_min, dx_max, dy_min, dy_max) in &[
(-radius, 0, -radius, 0),
(0, radius, -radius, 0),
(-radius, 0, 0, radius),
(0, radius, 0, radius),
] {
let x0 = (x_i32 + dx_min).max(0);
let x1 = (x_i32 + dx_max).min(width_i32 - 1);
let y0 = (y_i32 + dy_min).max(0);
let y1 = (y_i32 + dy_max).min(height_i32 - 1);
let mut sum_r = 0u64;
let mut sum_g = 0u64;
let mut sum_b = 0u64;
let mut sum_r2 = 0u64;
let mut sum_g2 = 0u64;
let mut sum_b2 = 0u64;
let mut count = 0u64;
for py in y0..=y1 {
let row_start = (py as usize) * width_usize * 4;
for px in x0..=x1 {
let idx = row_start + (px as usize) * 4;
let r = pixels[idx] as u64;
let g = pixels[idx + 1] as u64;
let b = pixels[idx + 2] as u64;
sum_r += r;
sum_g += g;
sum_b += b;
sum_r2 += r * r;
sum_g2 += g * g;
sum_b2 += b * b;
count += 1;
}
}
if count == 0 {
continue;
}
let count_f = count as f64;
let mean_r = sum_r as f64 / count_f;
let mean_g = sum_g as f64 / count_f;
let mean_b = sum_b as f64 / count_f;
let var_r = (sum_r2 as f64 / count_f) - mean_r * mean_r;
let var_g = (sum_g2 as f64 / count_f) - mean_g * mean_g;
let var_b = (sum_b2 as f64 / count_f) - mean_b * mean_b;
let total_var = var_r + var_g + var_b;
if total_var < best_var {
best_var = total_var;
best_pixel[0] = mean_r.clamp(0.0, 255.0).round() as u8;
best_pixel[1] = mean_g.clamp(0.0, 255.0).round() as u8;
best_pixel[2] = mean_b.clamp(0.0, 255.0).round() as u8;
best_pixel[3] = pixels[y * width_usize * 4 + x * 4 + 3]; }
}
let pixel_idx = x * 4;
row_chunk[pixel_idx] = best_pixel[0];
row_chunk[pixel_idx + 1] = best_pixel[1];
row_chunk[pixel_idx + 2] = best_pixel[2];
row_chunk[pixel_idx + 3] = best_pixel[3];
}
});
ImageBuffer::from_raw(width, height, output).unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
use image::Rgba;
#[test]
fn test_kuwahara_filter() {
let mut img = ImageBuffer::new(3, 3);
for pixel in img.pixels_mut() {
*pixel = Rgba([100, 150, 200, 255]);
}
let result = kuwahara_filter(img, 1);
for pixel in result.pixels() {
assert_eq!(pixel[0], 100);
assert_eq!(pixel[1], 150);
assert_eq!(pixel[2], 200);
assert_eq!(pixel[3], 255);
}
}
}