use image::{ImageBuffer, Rgba};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PrivacyTool {
#[default]
Mosaic,
Blur,
SmartErase,
}
#[derive(Debug, Clone)]
pub struct PrivacyRegion {
pub rect: egui::Rect,
pub tool: PrivacyTool,
pub block_size: u32,
pub blur_radius: u32,
}
impl PrivacyRegion {
pub fn new(rect: egui::Rect, tool: PrivacyTool) -> Self {
Self { rect, tool, block_size: 10, blur_radius: 5 }
}
pub fn mosaic(rect: egui::Rect, block_size: u32) -> Self {
Self { rect, tool: PrivacyTool::Mosaic, block_size, blur_radius: 5 }
}
pub fn blur(rect: egui::Rect, blur_radius: u32) -> Self {
Self { rect, tool: PrivacyTool::Blur, block_size: 10, blur_radius }
}
pub fn smart_erase(rect: egui::Rect) -> Self {
Self { rect, tool: PrivacyTool::SmartErase, block_size: 10, blur_radius: 5 }
}
}
pub fn apply_mosaic(
image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
x: u32,
y: u32,
w: u32,
h: u32,
block_size: u32,
) {
let img_width = image.width();
let img_height = image.height();
let x = x.min(img_width);
let y = y.min(img_height);
let w = w.min(img_width - x);
let h = h.min(img_height - y);
if w == 0 || h == 0 || block_size == 0 {
return;
}
let block_size = block_size.max(1);
for by in (y..y + h).step_by(block_size as usize) {
for bx in (x..x + w).step_by(block_size as usize) {
let block_end_x = (bx + block_size).min(x + w).min(img_width);
let block_end_y = (by + block_size).min(y + h).min(img_height);
let mut r_sum = 0u32;
let mut g_sum = 0u32;
let mut b_sum = 0u32;
let mut a_sum = 0u32;
let mut count = 0u32;
for py in by..block_end_y {
for px in bx..block_end_x {
let pixel = image.get_pixel(px, py);
r_sum += pixel[0] as u32;
g_sum += pixel[1] as u32;
b_sum += pixel[2] as u32;
a_sum += pixel[3] as u32;
count += 1;
}
}
if count > 0 {
let avg = Rgba([
(r_sum / count) as u8,
(g_sum / count) as u8,
(b_sum / count) as u8,
(a_sum / count) as u8,
]);
for py in by..block_end_y {
for px in bx..block_end_x {
image.put_pixel(px, py, avg);
}
}
}
}
}
}
pub fn apply_blur(
image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
x: u32,
y: u32,
w: u32,
h: u32,
radius: u32,
) {
let img_width = image.width();
let img_height = image.height();
let x = x.min(img_width);
let y = y.min(img_height);
let w = w.min(img_width - x);
let h = h.min(img_height - y);
if w == 0 || h == 0 || radius == 0 {
return;
}
for _ in 0..3 {
horizontal_blur(image, x, y, w, h, radius);
vertical_blur(image, x, y, w, h, radius);
}
}
fn horizontal_blur(
image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
x: u32,
y: u32,
w: u32,
h: u32,
radius: u32,
) {
let img_width = image.width();
let kernel_size = (2 * radius + 1) as usize;
let mut row_buffer = Vec::with_capacity(w as usize);
for py in y..y + h {
row_buffer.clear();
for px in x..x + w {
let mut r_sum = 0u32;
let mut g_sum = 0u32;
let mut b_sum = 0u32;
let mut a_sum = 0u32;
let mut count = 0u32;
let start_x = px.saturating_sub(radius);
let end_x = (px + radius + 1).min(img_width);
for sx in start_x..end_x {
let sample_x = sx.clamp(x, x + w - 1);
let pixel = image.get_pixel(sample_x, py);
r_sum += pixel[0] as u32;
g_sum += pixel[1] as u32;
b_sum += pixel[2] as u32;
a_sum += pixel[3] as u32;
count += 1;
}
let avg = Rgba([
(r_sum / count.max(1)) as u8,
(g_sum / count.max(1)) as u8,
(b_sum / count.max(1)) as u8,
(a_sum / count.max(1)) as u8,
]);
row_buffer.push(avg);
}
for (i, pixel) in row_buffer.iter().enumerate() {
image.put_pixel(x + i as u32, py, *pixel);
}
}
let _ = kernel_size; }
fn vertical_blur(
image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
x: u32,
y: u32,
w: u32,
h: u32,
radius: u32,
) {
let img_height = image.height();
let mut col_buffer = Vec::with_capacity(h as usize);
for px in x..x + w {
col_buffer.clear();
for py in y..y + h {
let mut r_sum = 0u32;
let mut g_sum = 0u32;
let mut b_sum = 0u32;
let mut a_sum = 0u32;
let mut count = 0u32;
let start_y = py.saturating_sub(radius);
let end_y = (py + radius + 1).min(img_height);
for sy in start_y..end_y {
let sample_y = sy.clamp(y, y + h - 1);
let pixel = image.get_pixel(px, sample_y);
r_sum += pixel[0] as u32;
g_sum += pixel[1] as u32;
b_sum += pixel[2] as u32;
a_sum += pixel[3] as u32;
count += 1;
}
let avg = Rgba([
(r_sum / count.max(1)) as u8,
(g_sum / count.max(1)) as u8,
(b_sum / count.max(1)) as u8,
(a_sum / count.max(1)) as u8,
]);
col_buffer.push(avg);
}
for (i, pixel) in col_buffer.iter().enumerate() {
image.put_pixel(px, y + i as u32, *pixel);
}
}
}
pub fn apply_smart_erase(
image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
x: u32,
y: u32,
w: u32,
h: u32,
) {
let img_width = image.width();
let img_height = image.height();
let x = x.min(img_width);
let y = y.min(img_height);
let w = w.min(img_width - x);
let h = h.min(img_height - y);
if w == 0 || h == 0 {
return;
}
let mut colors: Vec<Rgba<u8>> = Vec::new();
for px in x..x + w {
colors.push(*image.get_pixel(px, y));
if h > 1 {
colors.push(*image.get_pixel(px, y + h - 1));
}
}
for py in (y + 1)..(y + h).saturating_sub(1) {
colors.push(*image.get_pixel(x, py));
if w > 1 {
colors.push(*image.get_pixel(x + w - 1, py));
}
}
let bg_color = most_common_color(&colors);
for py in y..y + h {
for px in x..x + w {
image.put_pixel(px, py, bg_color);
}
}
}
fn most_common_color(colors: &[Rgba<u8>]) -> Rgba<u8> {
use std::collections::HashMap;
if colors.is_empty() {
return Rgba([255, 255, 255, 255]);
}
let quantize = |c: &Rgba<u8>| -> (u8, u8, u8) {
((c[0] / 8) * 8, (c[1] / 8) * 8, (c[2] / 8) * 8)
};
let mut counts: HashMap<(u8, u8, u8), (u32, Rgba<u8>)> = HashMap::new();
for color in colors {
let key = quantize(color);
counts.entry(key).or_insert((0, *color)).0 += 1;
}
counts
.values()
.max_by_key(|(count, _)| *count)
.map(|(_, color)| *color)
.unwrap_or(Rgba([255, 255, 255, 255]))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mosaic() {
let mut img =
ImageBuffer::from_fn(20, 20, |x, y| Rgba([(x * 10) as u8, (y * 10) as u8, 128, 255]));
apply_mosaic(&mut img, 0, 0, 20, 20, 5);
let p00 = *img.get_pixel(0, 0);
let p11 = *img.get_pixel(1, 1);
let p44 = *img.get_pixel(4, 4);
assert_eq!(p00, p11);
assert_eq!(p00, p44);
}
#[test]
fn test_most_common_color() {
let colors = vec![
Rgba([255, 0, 0, 255]),
Rgba([255, 0, 0, 255]),
Rgba([255, 0, 0, 255]),
Rgba([0, 255, 0, 255]),
Rgba([0, 0, 255, 255]),
];
let result = most_common_color(&colors);
assert_eq!(result[0], 255);
assert_eq!(result[1], 0);
assert_eq!(result[2], 0);
}
}