147 lines
3.7 KiB
Rust
147 lines
3.7 KiB
Rust
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,
|
|
)
|
|
}
|