Spaces:
Sleeping
Sleeping
| use crate::error::Result; | |
| use image::DynamicImage; | |
| pub enum ColorGrade { | |
| CinematicDark, | |
| CoolTones, | |
| WarmTones, | |
| Desaturated, | |
| HighContrast, | |
| None, | |
| } | |
| pub struct PostProcessor { | |
| pub color_grade: ColorGrade, | |
| pub contrast: f32, | |
| pub brightness: f32, | |
| pub saturation: f32, | |
| } | |
| impl Default for PostProcessor { | |
| fn default() -> Self { | |
| Self { | |
| color_grade: ColorGrade::CinematicDark, | |
| contrast: 1.1, | |
| brightness: 0.95, | |
| saturation: 1.1, | |
| } | |
| } | |
| } | |
| impl PostProcessor { | |
| pub fn new(color_grade: ColorGrade) -> Self { | |
| Self { | |
| color_grade, | |
| ..Default::default() | |
| } | |
| } | |
| /// Appliquer tous les traitements de post-processing | |
| pub fn process(&self, img: DynamicImage) -> Result<DynamicImage> { | |
| let mut result = img; | |
| // 1. Appliquer le grade de couleur | |
| result = self.apply_color_grade(result)?; | |
| // 2. Ajuster contraste/luminosité | |
| result = self.adjust_contrast_brightness(result)?; | |
| // 3. Ajuster saturation | |
| result = self.adjust_saturation(result)?; | |
| Ok(result) | |
| } | |
| fn apply_color_grade(&self, img: DynamicImage) -> Result<DynamicImage> { | |
| match self.color_grade { | |
| ColorGrade::CinematicDark => self.apply_cinematic_dark(img), | |
| ColorGrade::CoolTones => self.apply_cool_tones(img), | |
| ColorGrade::WarmTones => self.apply_warm_tones(img), | |
| ColorGrade::Desaturated => self.apply_desaturated(img), | |
| ColorGrade::HighContrast => self.apply_high_contrast(img), | |
| ColorGrade::None => Ok(img), | |
| } | |
| } | |
| fn apply_cinematic_dark(&self, img: DynamicImage) -> Result<DynamicImage> { | |
| let rgba = img.to_rgba8(); | |
| let mut output = rgba.clone(); | |
| for pixel in output.pixels_mut() { | |
| let [r, g, b, a] = pixel.0; | |
| // Ajouter une teinte bleuée et assombrir | |
| pixel.0 = [ | |
| (r as f32 * 0.95) as u8, | |
| (g as f32 * 0.98) as u8, | |
| (b as f32 * 1.05).min(255.0) as u8, | |
| a, | |
| ]; | |
| } | |
| Ok(DynamicImage::ImageRgba8(output)) | |
| } | |
| fn apply_cool_tones(&self, img: DynamicImage) -> Result<DynamicImage> { | |
| let rgba = img.to_rgba8(); | |
| let mut output = rgba.clone(); | |
| for pixel in output.pixels_mut() { | |
| let [r, g, b, a] = pixel.0; | |
| pixel.0 = [ | |
| (r as f32 * 0.9) as u8, | |
| (g as f32 * 0.98) as u8, | |
| (b as f32 * 1.1).min(255.0) as u8, | |
| a, | |
| ]; | |
| } | |
| Ok(DynamicImage::ImageRgba8(output)) | |
| } | |
| fn apply_warm_tones(&self, img: DynamicImage) -> Result<DynamicImage> { | |
| let rgba = img.to_rgba8(); | |
| let mut output = rgba.clone(); | |
| for pixel in output.pixels_mut() { | |
| let [r, g, b, a] = pixel.0; | |
| pixel.0 = [ | |
| (r as f32 * 1.1).min(255.0) as u8, | |
| (g as f32 * 1.05).min(255.0) as u8, | |
| (b as f32 * 0.9) as u8, | |
| a, | |
| ]; | |
| } | |
| Ok(DynamicImage::ImageRgba8(output)) | |
| } | |
| fn apply_desaturated(&self, img: DynamicImage) -> Result<DynamicImage> { | |
| let rgba = img.to_rgba8(); | |
| let mut output = rgba.clone(); | |
| for pixel in output.pixels_mut() { | |
| let [r, g, b, a] = pixel.0; | |
| let gray = ((r as f32 + g as f32 + b as f32) / 3.0) as u8; | |
| // 70% saturé = 30% gris | |
| let sat = 0.7; | |
| pixel.0 = [ | |
| ((r as f32 * sat + gray as f32 * (1.0 - sat)) as u8), | |
| ((g as f32 * sat + gray as f32 * (1.0 - sat)) as u8), | |
| ((b as f32 * sat + gray as f32 * (1.0 - sat)) as u8), | |
| a, | |
| ]; | |
| } | |
| Ok(DynamicImage::ImageRgba8(output)) | |
| } | |
| fn apply_high_contrast(&self, img: DynamicImage) -> Result<DynamicImage> { | |
| let rgba = img.to_rgba8(); | |
| let mut output = rgba.clone(); | |
| for pixel in output.pixels_mut() { | |
| let [r, g, b, a] = pixel.0; | |
| // Formule: (val - 128) * 1.5 + 128 | |
| let contrast = 1.5; | |
| let offset = 128.0; | |
| pixel.0 = [ | |
| ((r as f32 - offset) * contrast + offset).clamp(0.0, 255.0) as u8, | |
| ((g as f32 - offset) * contrast + offset).clamp(0.0, 255.0) as u8, | |
| ((b as f32 - offset) * contrast + offset).clamp(0.0, 255.0) as u8, | |
| a, | |
| ]; | |
| } | |
| Ok(DynamicImage::ImageRgba8(output)) | |
| } | |
| fn adjust_contrast_brightness(&self, img: DynamicImage) -> Result<DynamicImage> { | |
| let rgba = img.to_rgba8(); | |
| let mut output = rgba.clone(); | |
| for pixel in output.pixels_mut() { | |
| let [r, g, b, a] = pixel.0; | |
| pixel.0 = [ | |
| ((r as f32 - 128.0) * self.contrast + 128.0 + self.brightness * 255.0) | |
| .clamp(0.0, 255.0) as u8, | |
| ((g as f32 - 128.0) * self.contrast + 128.0 + self.brightness * 255.0) | |
| .clamp(0.0, 255.0) as u8, | |
| ((b as f32 - 128.0) * self.contrast + 128.0 + self.brightness * 255.0) | |
| .clamp(0.0, 255.0) as u8, | |
| a, | |
| ]; | |
| } | |
| Ok(DynamicImage::ImageRgba8(output)) | |
| } | |
| fn adjust_saturation(&self, img: DynamicImage) -> Result<DynamicImage> { | |
| let rgba = img.to_rgba8(); | |
| let mut output = rgba.clone(); | |
| for pixel in output.pixels_mut() { | |
| let [r, g, b, a] = pixel.0; | |
| let gray = (r as f32 + g as f32 + b as f32) / 3.0; | |
| pixel.0 = [ | |
| (r as f32 + (gray - r as f32) * (1.0 - self.saturation)).clamp(0.0, 255.0) as u8, | |
| (g as f32 + (gray - g as f32) * (1.0 - self.saturation)).clamp(0.0, 255.0) as u8, | |
| (b as f32 + (gray - b as f32) * (1.0 - self.saturation)).clamp(0.0, 255.0) as u8, | |
| a, | |
| ]; | |
| } | |
| Ok(DynamicImage::ImageRgba8(output)) | |
| } | |
| } | |