use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum PostEffect {
Bloom { threshold: f32, intensity: f32 },
MotionBlur { strength: f32 },
ColorGrading { contrast: f32, saturation: f32, brightness: f32 },
Vignette { intensity: f32, radius: f32 },
ChromaticAberration { offset: f32 },
}
pub struct PostProcessor {
effects: Vec<PostEffect>,
pub enabled: bool,
}
impl PostProcessor {
pub fn new() -> Self {
Self {
effects: Vec::new(),
enabled: true,
}
}
pub fn add_effect(&mut self, effect: PostEffect) {
self.effects.push(effect);
}
pub fn clear_effects(&mut self) {
self.effects.clear();
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn apply(&self, frame: &mut [u8], width: u32, height: u32) {
if !self.enabled {
return;
}
for effect in &self.effects {
match effect {
PostEffect::Bloom { threshold, intensity } => {
self.apply_bloom(frame, width, height, *threshold, *intensity);
}
PostEffect::Vignette { intensity, radius } => {
self.apply_vignette(frame, width, height, *intensity, *radius);
}
PostEffect::ColorGrading { contrast, saturation, brightness } => {
self.apply_color_grading(frame, *contrast, *saturation, *brightness);
}
_ => {}
}
}
}
fn apply_bloom(&self, frame: &mut [u8], width: u32, height: u32, threshold: f32, intensity: f32) {
let mut bright_pixels = vec![0u8; frame.len()];
for i in (0..frame.len()).step_by(4) {
let r = frame[i] as f32 / 255.0;
let g = frame[i + 1] as f32 / 255.0;
let b = frame[i + 2] as f32 / 255.0;
let brightness = (r + g + b) / 3.0;
if brightness > threshold {
bright_pixels[i] = frame[i];
bright_pixels[i + 1] = frame[i + 1];
bright_pixels[i + 2] = frame[i + 2];
}
}
for y in 1..(height - 1) {
for x in 1..(width - 1) {
let idx = ((y * width + x) * 4) as usize;
let mut r_sum = 0u32;
let mut g_sum = 0u32;
let mut b_sum = 0u32;
for dy in -1..=1 {
for dx in -1..=1 {
let neighbor_idx = (((y as i32 + dy) as u32 * width + (x as i32 + dx) as u32) * 4) as usize;
r_sum += bright_pixels[neighbor_idx] as u32;
g_sum += bright_pixels[neighbor_idx + 1] as u32;
b_sum += bright_pixels[neighbor_idx + 2] as u32;
}
}
frame[idx] = ((frame[idx] as f32 + (r_sum / 9) as f32 * intensity).min(255.0)) as u8;
frame[idx + 1] = ((frame[idx + 1] as f32 + (g_sum / 9) as f32 * intensity).min(255.0)) as u8;
frame[idx + 2] = ((frame[idx + 2] as f32 + (b_sum / 9) as f32 * intensity).min(255.0)) as u8;
}
}
}
fn apply_vignette(&self, frame: &mut [u8], width: u32, height: u32, intensity: f32, radius: f32) {
let center_x = width as f32 / 2.0;
let center_y = height as f32 / 2.0;
let max_dist = ((center_x * center_x + center_y * center_y).sqrt()) * radius;
for y in 0..height {
for x in 0..width {
let dx = x as f32 - center_x;
let dy = y as f32 - center_y;
let dist = (dx * dx + dy * dy).sqrt();
let vignette = (1.0 - (dist / max_dist).min(1.0) * intensity).max(0.0);
let idx = ((y * width + x) * 4) as usize;
frame[idx] = (frame[idx] as f32 * vignette) as u8;
frame[idx + 1] = (frame[idx + 1] as f32 * vignette) as u8;
frame[idx + 2] = (frame[idx + 2] as f32 * vignette) as u8;
}
}
}
fn apply_color_grading(&self, frame: &mut [u8], contrast: f32, saturation: f32, brightness: f32) {
for i in (0..frame.len()).step_by(4) {
let r = frame[i] as f32 / 255.0;
let g = frame[i + 1] as f32 / 255.0;
let b = frame[i + 2] as f32 / 255.0;
let mut r = (r + brightness).clamp(0.0, 1.0);
let mut g = (g + brightness).clamp(0.0, 1.0);
let mut b = (b + brightness).clamp(0.0, 1.0);
r = ((r - 0.5) * contrast + 0.5).clamp(0.0, 1.0);
g = ((g - 0.5) * contrast + 0.5).clamp(0.0, 1.0);
b = ((b - 0.5) * contrast + 0.5).clamp(0.0, 1.0);
let gray = 0.299 * r + 0.587 * g + 0.114 * b;
r = (gray + (r - gray) * saturation).clamp(0.0, 1.0);
g = (gray + (g - gray) * saturation).clamp(0.0, 1.0);
b = (gray + (b - gray) * saturation).clamp(0.0, 1.0);
frame[i] = (r * 255.0) as u8;
frame[i + 1] = (g * 255.0) as u8;
frame[i + 2] = (b * 255.0) as u8;
}
}
}