music-reader/src/color_mode.rs

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,
)
}