use core::fmt; use cairo::ImageSurface; use strum::EnumString; #[derive(Clone, EnumString, Debug)] pub enum ColorMode { Normal, Dark, Sepia, } impl fmt::Display for ColorMode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } impl Default for ColorMode { fn default() -> Self { ColorMode::Normal } } impl ColorMode { fn transform_color(&self, r: u8, g: u8, b: u8) -> (u8, u8, u8) { // l of black is 0.13 (approx 0x22) match self { ColorMode::Normal => (r, g, b), ColorMode::Dark => { let (mut h, mut s, mut l) = rgb_to_hsl(r, g, b); l = (1.0 - l) * (1.0 - 0.13) + 0.13; hsl_to_rgb(h, s, l) } ColorMode::Sepia => { let (mut h, mut s, mut l) = rgb_to_hsl(r, g, b); h = h + 30.0; if h < 0.0 { h += 360.0; } s += 0.2; if s > 1.0 { s = 1.0; } l = l * (1.0 - 0.13); hsl_to_rgb(h, s, l) } } } /// Apply color mode transformation to an RGB24 surface. pub fn apply_to_rgb24_surface(&self, surface: &mut ImageSurface) { let width = surface.width(); let height = surface.height(); let stride = surface.stride(); let mut data = surface.data().expect("Failed to get source surface data"); for y in 0..height { for x in 0..width { let offset = (y * stride + x * 4) as usize; // Rgb24: 3 bytes per pixel (R, G, B) // Read source pixel let r = data[offset + 2]; let g = data[offset + 1]; let b = data[offset]; // Apply color transformation let (tr, tg, tb) = self.transform_color(r, g, b); // Write transformed pixel to destination surface data[offset + 2] = tr; data[offset + 1] = tg; data[offset] = tb; } } } } // h from 0 to 360 // s from 0 to 1 // l from 0 to 1 fn rgb_to_hsl(r: u8, g: u8, b: u8) -> (f64, f64, f64) { let r = r as f64 / 255.0; let g = g as f64 / 255.0; let b = b as f64 / 255.0; let max = r.max(g).max(b); let min = r.min(g).min(b); let delta = max - min; // Calculate Lightness let l = (max + min) / 2.0; // Calculate Saturation let s = if delta == 0.0 { 0.0 } else if l < 0.5 { delta / (max + min) } else { delta / (2.0 - max - min) }; // Calculate Hue let h = if delta == 0.0 { 0.0 } else if max == r { 60.0 * (((g - b) / delta) % 6.0) } else if max == g { 60.0 * (((b - r) / delta) + 2.0) } else { 60.0 * (((r - g) / delta) + 4.0) }; let h = if h < 0.0 { h + 360.0 } else { h }; (h, s, l) } fn hsl_to_rgb(h: f64, s: f64, l: f64) -> (u8, u8, u8) { let c = (1.0 - (2.0 * l - 1.0).abs()) * s; // Chroma let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs()); let m = l - c / 2.0; let (r, g, b) = if (0.0..60.0).contains(&h) { (c, x, 0.0) } else if (60.0..120.0).contains(&h) { (x, c, 0.0) } else if (120.0..180.0).contains(&h) { (0.0, c, x) } else if (180.0..240.0).contains(&h) { (0.0, x, c) } else if (240.0..300.0).contains(&h) { (x, 0.0, c) } else { (c, 0.0, x) }; // Convert back to [0, 255] ( ((r + m) * 255.0).round() as u8, ((g + m) * 255.0).round() as u8, ((b + m) * 255.0).round() as u8, ) }