From de534b1db0dbf174ed8defef5ab4ee16e29d9a6c Mon Sep 17 00:00:00 2001 From: tittu Date: Sun, 21 Jun 2026 10:05:58 +0530 Subject: [PATCH] refactor color module --- src/colors.rs | 250 +++++++++++++++++++++++--------------------------- 1 file changed, 115 insertions(+), 135 deletions(-) diff --git a/src/colors.rs b/src/colors.rs index 75250b1..eb685a7 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -1,6 +1,6 @@ // --------------------------------------------------------------------- / tittu // wallbash -// a color generation module for HyDE +// a color module for HyDE // @@ -18,7 +18,85 @@ pub struct ColorPalette { } -// --------------------------------------------------------------------- / k‑means +// --------------------------------------------------------------------- / converters + +fn rgb_to_argb(r: u8, g: u8, b: u8) -> u32 { + 0xFF00_0000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32) +} + +fn srgb_to_xyz(r: f64, g: f64, b: f64) -> (f64, f64, f64) { + let linear = |c: f64| -> f64 { + if c <= 0.04045 { c / 12.92 } + else { ((c + 0.055) / 1.055).powf(2.4) } + }; + let rl = linear(r); + let gl = linear(g); + let bl = linear(b); + + ( + 0.4124564 * rl + 0.3575761 * gl + 0.1804375 * bl, + 0.2126729 * rl + 0.7151522 * gl + 0.0721750 * bl, + 0.0193339 * rl + 0.1191920 * gl + 0.9503041 * bl, + ) +} + +fn xyz_to_srgb(x: f64, y: f64, z: f64) -> (u8, u8, u8) { + let r_lin = 3.2404542 * x - 1.5371385 * y - 0.4985314 * z; + let g_lin = -0.9692660 * x + 1.8760108 * y + 0.0415560 * z; + let b_lin = 0.0556434 * x - 0.2040259 * y + 1.0572252 * z; + + let delinearize = |c: f64| -> f64 { + if c <= 0.0031308 { c * 12.92 } else { 1.055 * c.powf(1.0 / 2.4) - 0.055 } + }; + + ( + (delinearize(r_lin.clamp(0.0, 1.0)) * 255.0).round() as u8, + (delinearize(g_lin.clamp(0.0, 1.0)) * 255.0).round() as u8, + (delinearize(b_lin.clamp(0.0, 1.0)) * 255.0).round() as u8, + ) +} + +fn rgb_to_cielab(r: u8, g: u8, b: u8) -> (f64, f64) { + let r = r as f64 / 255.0; + let g = g as f64 / 255.0; + let b = b as f64 / 255.0; + + let (x, y, z) = srgb_to_xyz(r, g, b); + + let xn = 0.95047; + let yn = 1.0; + let zn = 1.08883; + + let fx = (x / xn).powf(1.0 / 3.0); + let fy = (y / yn).powf(1.0 / 3.0); + let fz = (z / zn).powf(1.0 / 3.0); + + let a_val = 500.0 * (fx - fy); + let b_val = 200.0 * (fy - fz); + + (a_val, b_val) +} + +fn cielab_to_rgb(l: f64, a: f64, b: f64) -> (u8, u8, u8) { + let yn = 1.0; + let xn = 0.95047; + let zn = 1.08883; + + let fy = (l + 16.0) / 116.0; + let fx = a / 500.0 + fy; + let fz = fy - b / 200.0; + + let delta: f64 = 6.0 / 29.0; + + let x = if fx > delta { xn * fx.powi(3) } else { (fx - 16.0 / 116.0) * 3.0 * delta * delta * xn }; + let y = if l > 8.0 { yn * fy.powi(3) } else { l / 903.3 * yn }; + let z = if fz > delta { zn * fz.powi(3) } else { (fz - 16.0 / 116.0) * 3.0 * delta * delta * zn }; + + xyz_to_srgb(x, y, z) +} + + +// --------------------------------------------------------------------- / k means pub fn dcol(img: &DynamicImage, palette: &str) { let small = img.resize_exact(64, 64, image::imageops::FilterType::Nearest); @@ -35,17 +113,18 @@ pub fn dcol(img: &DynamicImage, palette: &str) { }).sum(); let avg_l = total_l / (rgb.width() as f64 * rgb.height() as f64) * 100.0; - let mut mode = palette; - if palette == "auto" { - if avg_l > 50.0 { mode = "light" } else { mode = "dark" } - } + let palette: String = match palette { + "dark" => "dark".into(), + "light" => "light".into(), + _ => { if avg_l < 50.0 { "dark".into() } else { "light".into() } }, + }; let pixels: Vec<[f64; 3]> = rgb.pixels() .map(|p| [p[0] as f64, p[1] as f64, p[2] as f64]) .collect(); - if pixels.is_empty() { - print_palette (rgb_to_argb(128, 128, 128), mode) + generate_palette (rgb_to_argb(128, 128, 128), &palette); + return; } let k = 5; @@ -98,97 +177,18 @@ pub fn dcol(img: &DynamicImage, palette: &str) { let r = dom[0].round().clamp(0.0, 255.0) as u8; let g = dom[1].round().clamp(0.0, 255.0) as u8; let b = dom[2].round().clamp(0.0, 255.0) as u8; - print_palette (rgb_to_argb(r, g, b), mode) -} - - -// --------------------------------------------------------------------- / converters - -fn rgb_to_argb(r: u8, g: u8, b: u8) -> u32 { - 0xFF00_0000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32) -} - -fn srgb_to_xyz(r: f64, g: f64, b: f64) -> (f64, f64, f64) { - let linear = |c: f64| -> f64 { - if c <= 0.04045 { c / 12.92 } - else { ((c + 0.055) / 1.055).powf(2.4) } - }; - let rl = linear(r); - let gl = linear(g); - let bl = linear(b); - - ( - 0.4124564 * rl + 0.3575761 * gl + 0.1804375 * bl, - 0.2126729 * rl + 0.7151522 * gl + 0.0721750 * bl, - 0.0193339 * rl + 0.1191920 * gl + 0.9503041 * bl, - ) -} - -fn xyz_to_srgb(x: f64, y: f64, z: f64) -> (u8, u8, u8) { - let r_lin = 3.2404542 * x - 1.5371385 * y - 0.4985314 * z; - let g_lin = -0.9692660 * x + 1.8760108 * y + 0.0415560 * z; - let b_lin = 0.0556434 * x - 0.2040259 * y + 1.0572252 * z; - - let delinearize = |c: f64| -> f64 { - if c <= 0.0031308 { c * 12.92 } else { 1.055 * c.powf(1.0 / 2.4) - 0.055 } - }; - - ( - (delinearize(r_lin.clamp(0.0, 1.0)) * 255.0).round() as u8, - (delinearize(g_lin.clamp(0.0, 1.0)) * 255.0).round() as u8, - (delinearize(b_lin.clamp(0.0, 1.0)) * 255.0).round() as u8, - ) -} - -fn rgb_to_cielab(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 (x, y, z) = srgb_to_xyz(r, g, b); - - let xn = 0.95047; - let yn = 1.0; - let zn = 1.08883; - - let fx = (x / xn).powf(1.0 / 3.0); - let fy = (y / yn).powf(1.0 / 3.0); - let fz = (z / zn).powf(1.0 / 3.0); - - let l = 116.0 * fy - 16.0; - let a = 500.0 * (fx - fy); - let b_val = 200.0 * (fy - fz); - - (l, a, b_val) -} - -fn cielab_to_rgb(l: f64, a: f64, b: f64) -> (u8, u8, u8) { - let yn = 1.0; - let xn = 0.95047; - let zn = 1.08883; - - let fy = (l + 16.0) / 116.0; - let fx = a / 500.0 + fy; - let fz = fy - b / 200.0; - - let delta: f64 = 6.0 / 29.0; - - let x = if fx > delta { xn * fx.powi(3) } else { (fx - 16.0 / 116.0) * 3.0 * delta * delta * xn }; - let y = if l > 8.0 { yn * fy.powi(3) } else { l / 903.3 * yn }; - let z = if fz > delta { zn * fz.powi(3) } else { (fz - 16.0 / 116.0) * 3.0 * delta * delta * zn }; - - xyz_to_srgb(x, y, z) + generate_palette (rgb_to_argb(r, g, b), &palette) } // --------------------------------------------------------------------- / generate palette -pub fn generate_palette(dcol: u32) -> (Vec, Vec) { +pub fn generate_palette(dcol: u32, palette: &str) { let r = ((dcol >> 16) & 0xFF) as u8; let g = ((dcol >> 8) & 0xFF) as u8; let b = (dcol & 0xFF) as u8; - let (_, a_star, b_star) = rgb_to_cielab(r, g, b); + let (a_star, b_star) = rgb_to_cielab(r, g, b); let hue_rad = b_star.atan2(a_star); let chroma = (a_star * a_star + b_star * b_star).sqrt(); @@ -222,66 +222,46 @@ pub fn generate_palette(dcol: u32) -> (Vec, Vec) { ("Error", "On Error Cont.", 10.0, 90.0, 0.0, 2.0), ]; - let mut light = Vec::new(); - let mut dark = Vec::new(); + let mut colors = Vec::new(); for (group, name, light_tone, dark_tone, chroma_factor, min_chroma) in roles { - let (h, c) = if name.starts_with("Error") { - (0.436332, 45.0) - } else if name.contains("Secondary") { - (hue_rad, chroma * chroma_factor) - } else if name.contains("Tertiary") { - (hue_rad + 1.04719755, chroma * chroma_factor) - } else { - (hue_rad, chroma * chroma_factor) + let (h, c) = match group { + "Error" => (0.436332, 45.0), + "Tertiary" => (hue_rad + 1.04719755, chroma * chroma_factor), + _ => (hue_rad, chroma * chroma_factor), }; - // light theme - let chroma_val = c.max(min_chroma); - let a = chroma_val * h.cos(); - let b_val = chroma_val * h.sin(); - let (rr, gg, bb) = cielab_to_rgb(light_tone, a, b_val); - light.push(ColorPalette { group, name, argb: rgb_to_argb(rr, gg, bb) }); - - // dark theme - let dark_min_chroma = if dark_tone <= 30.0 { 4.0 } else { 0.0 }; - let chroma_val = c.max(min_chroma).max(dark_min_chroma); - let a = chroma_val * h.cos(); - let b_val = chroma_val * h.sin(); - let (rr, gg, bb) = cielab_to_rgb(dark_tone, a, b_val); - dark.push(ColorPalette { group, name, argb: rgb_to_argb(rr, gg, bb) }); + if palette == "dark" { + let dark_min_chroma = if dark_tone <= 30.0 { 4.0 } else { 0.0 }; + let chroma_val = c.max(min_chroma).max(dark_min_chroma); + let a_val = chroma_val * h.cos(); + let b_val = chroma_val * h.sin(); + let (rr, gg, bb) = cielab_to_rgb(dark_tone, a_val, b_val); + colors.push(ColorPalette { group, name, argb: rgb_to_argb(rr, gg, bb) }); + } else { + let chroma_val = c.max(min_chroma); + let a_val: f64 = chroma_val * h.cos(); + let b_val = chroma_val * h.sin(); + let (rr, gg, bb) = cielab_to_rgb(light_tone, a_val, b_val); + colors.push(ColorPalette { group, name, argb: rgb_to_argb(rr, gg, bb) }); + }; } - (light, dark) + print!("\x1b[48;2;{};{};{}m \x1b[0m", r, g, b); + println!(" #{:06X} :: HyDE-{:<5} :: Dominant Color", dcol & 0xFFFFFF, palette); + print_palette(colors); } // --------------------------------------------------------------------- / print palette -pub fn print_palette(dcol: u32, mode: &str) { - let (light, dark) = generate_palette(dcol); - let r = ((dcol >> 16) & 0xFF) as u8; - let g = ((dcol >> 8) & 0xFF) as u8; - let b = (dcol & 0xFF) as u8; - let (_, _, l_star) = rgb_to_cielab(r, g, b); - - print!("\x1b[48;2;{};{};{}m \x1b[0m", r, g, b); - if mode == "light" || (mode == "auto" && l_star > 55.0) { - println!(" #{:06X} :: HyDE-Light :: Dominant Color", dcol); - group_palette(&light); - } else { - println!(" #{:06X} :: HyDE-Dark :: Dominant Color", dcol); - group_palette(&dark); - } -} - -fn group_palette(palette: &[ColorPalette]) { - for entry in palette { +fn print_palette(colors: Vec) { + for entry in colors { let r = ((entry.argb >> 16) & 0xFF) as u8; let g = ((entry.argb >> 8) & 0xFF) as u8; let b = (entry.argb & 0xFF) as u8; print!("\x1b[48;2;{};{};{}m \x1b[0m", r, g, b); - println!(" #{:06X} :: {:<10} :: {}", entry.argb, entry.group, entry.name); + println!(" #{:06X} :: {:<10} :: {}", entry.argb & 0xFFFFFF, entry.group, entry.name); } }