forked from gallant/theme-rs
Use L*a*b* instead of HSV for distance calculations
This commit is contained in:
parent
b4af44189c
commit
0a20a35e4f
BIN
output.png
BIN
output.png
Binary file not shown.
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 100 KiB |
163
src/main.rs
163
src/main.rs
|
@ -1,32 +1,27 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Result;
|
use serde_json::Result;
|
||||||
|
|
||||||
use image::{
|
use image::{io::Reader as ImageReader, Rgb};
|
||||||
io::Reader as ImageReader,
|
|
||||||
Rgb
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct Scheme{
|
struct Scheme {
|
||||||
name: String,
|
name: String,
|
||||||
author: String,
|
author: String,
|
||||||
pub color: Vec<String>,
|
pub color: Vec<String>,
|
||||||
pub foreground: String,
|
pub foreground: String,
|
||||||
pub background: String
|
pub background: String,
|
||||||
}
|
}
|
||||||
impl Scheme {
|
impl Scheme {
|
||||||
// add code here
|
// add code here
|
||||||
fn new(data: String) -> Result<Self>
|
fn new(data: String) -> Result<Self> {
|
||||||
{
|
|
||||||
let mut scheme: Self = serde_json::from_str(&data)?;
|
let mut scheme: Self = serde_json::from_str(&data)?;
|
||||||
scheme.color.push(scheme.foreground.clone());
|
scheme.color.push(scheme.foreground.clone());
|
||||||
scheme.color.push(scheme.background.clone());
|
scheme.color.push(scheme.background.clone());
|
||||||
Ok(scheme)
|
Ok(scheme)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()>{
|
fn main() -> Result<()> {
|
||||||
let data = r##"{
|
let data = r##"{
|
||||||
"name": "",
|
"name": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
|
@ -59,134 +54,92 @@ fn main() -> Result<()>{
|
||||||
let img_height = rgb_vals.height();
|
let img_height = rgb_vals.height();
|
||||||
let img_width = rgb_vals.width();
|
let img_width = rgb_vals.width();
|
||||||
|
|
||||||
|
for i in 0..img_width {
|
||||||
for i in 0..img_width{
|
for j in 0..img_height {
|
||||||
for j in 0..img_height{
|
let pix = rgb_vals.get_pixel(i, j);
|
||||||
let pix = rgb_vals.get_pixel(i,j);
|
|
||||||
|
|
||||||
let rgb = pix.0;
|
let rgb = pix.0;
|
||||||
let [r,g,b] = rgb;
|
let [r, g, b] = rgb;
|
||||||
|
|
||||||
let (c1,c2,c3) = find_closest_color((r,g,b),scheme.color.clone()).unwrap();
|
|
||||||
rgb_vals.put_pixel(i,j,Rgb([c1,c2,c3]));
|
|
||||||
|
|
||||||
|
let (c1, c2, c3) = find_closest_color((r, g, b), scheme.color.clone()).unwrap();
|
||||||
|
rgb_vals.put_pixel(i, j, Rgb([c1, c2, c3]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rgb_vals.save("output.png").unwrap();
|
rgb_vals.save("output.png").unwrap();
|
||||||
|
|
||||||
|
|
||||||
println!("{}", scheme.foreground);
|
println!("{}", scheme.foreground);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn euclidean_distance(color1: (u8, u8, u8), color2: (f32, f32, f32)) -> f32 {
|
fn euclidean_distance(color1: (u8, u8, u8), color2: (u8, u8, u8)) -> f32 {
|
||||||
let (r1, g1, b1) = rgb_to_hsv(color1);
|
let (L1, a1, b1) = xyz_to_lab(rgb_to_xyz(color1));
|
||||||
let (r2, g2, b2) = color2;
|
let (L2, a2, b2) = xyz_to_lab(rgb_to_xyz(color2));
|
||||||
let squared_distance = (r2 - r1).powi(2) + (g2 - g1).powi(2) + (b2 - b1).powi(2);
|
let squared_distance = (L2 - L1).powi(2) + (a2 - a1).powi(2) + (b2 - b1).powi(2);
|
||||||
squared_distance.sqrt()
|
squared_distance.sqrt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hsv_to_rgb(hsv: (f32, f32, f32)) -> (u8, u8, u8) {
|
fn rgb_to_xyz((r, g, b): (u8, u8, u8)) -> (f32, f32, f32) {
|
||||||
let (hue, saturation, value) = hsv;
|
// from: https://www.image-engineering.de/library/technotes/958-how-to-convert-between-srgb-and-ciexyz
|
||||||
|
let sr = r as f32 / 255.0;
|
||||||
|
let sg = g as f32 / 255.0;
|
||||||
|
let sb = b as f32 / 255.0;
|
||||||
|
|
||||||
let c = value * saturation;
|
let x = (0.4124564 * sr) + (0.3575761 * sg) + (0.1804375 * sb);
|
||||||
let x = c * (1.0 - ((hue / 60.0) % 2.0 - 1.0).abs());
|
let y = (0.2126729 * sr) + (0.7151522 * sg) + (0.0721750 * sb);
|
||||||
let m = value - c;
|
let z = (0.0193339 * sr) + (0.1191920 * sg) + (0.9503041 * sb);
|
||||||
|
|
||||||
let (r, g, b) = match hue {
|
(x, y, z)
|
||||||
hue if hue < 60.0 => (c, x, 0.0),
|
|
||||||
hue if hue < 120.0 => (x, c, 0.0),
|
|
||||||
hue if hue < 180.0 => (0.0, c, x),
|
|
||||||
hue if hue < 240.0 => (0.0, x, c),
|
|
||||||
hue if hue < 300.0 => (x, 0.0, c),
|
|
||||||
_ => (c, 0.0, x),
|
|
||||||
};
|
|
||||||
|
|
||||||
let r = ((r + m) * 255.0) as u8;
|
|
||||||
let g = ((g + m) * 255.0) as u8;
|
|
||||||
let b = ((b + m) * 255.0) as u8;
|
|
||||||
|
|
||||||
(r, g, b)
|
|
||||||
}
|
|
||||||
fn rgb_to_hsv(rgb: (u8, u8, u8)) -> (f32, f32, f32) {
|
|
||||||
let (r, g, b) = rgb;
|
|
||||||
let r_norm = r as f32 / 255.0;
|
|
||||||
let g_norm = g as f32 / 255.0;
|
|
||||||
let b_norm = b as f32 / 255.0;
|
|
||||||
|
|
||||||
let max = r_norm.max(g_norm).max(b_norm);
|
|
||||||
let min = r_norm.min(g_norm).min(b_norm);
|
|
||||||
let delta = max - min;
|
|
||||||
|
|
||||||
let hue = if delta == 0.0 {
|
|
||||||
0.0 // No hue
|
|
||||||
} else if max == r_norm {
|
|
||||||
60.0 * (((g_norm - b_norm) / delta) % 6.0)
|
|
||||||
} else if max == g_norm {
|
|
||||||
60.0 * (((b_norm - r_norm) / delta) + 2.0)
|
|
||||||
} else {
|
|
||||||
60.0 * (((r_norm - g_norm) / delta) + 4.0)
|
|
||||||
};
|
|
||||||
|
|
||||||
let saturation = if max == 0.0 {
|
|
||||||
0.0 // No saturation
|
|
||||||
} else {
|
|
||||||
delta / max
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = max;
|
|
||||||
|
|
||||||
(hue, saturation, value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn xyz_to_lab((x, y, z): (f32, f32, f32)) -> (f32, f32, f32) {
|
||||||
|
// from: https://en.wikipedia.org/wiki/CIELAB_color_space#Converting_between_CIELAB_and_CIEXYZ_coordinates
|
||||||
|
|
||||||
fn hex_to_hsv(hex: &str) -> Option<(f32, f32, f32)> {
|
let f = |t: f32| {
|
||||||
|
let delta: f32 = 6.0 / 29.0;
|
||||||
|
|
||||||
|
if t > delta.powi(3) {
|
||||||
|
t.cbrt()
|
||||||
|
} else {
|
||||||
|
(t / (3.0 * delta * delta)) + (4.0 / 29.0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let xn = 95.0489;
|
||||||
|
let yn = 100.0;
|
||||||
|
let zn = 108.8840;
|
||||||
|
|
||||||
|
let L = 116.0 * f(y / yn) - 16.0;
|
||||||
|
let a = 500.0 * (f(x / xn) - y / yn);
|
||||||
|
let b = 200.0 * (f(y / yn) - f(z / zn));
|
||||||
|
|
||||||
|
(L, a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hex_to_rgb(hex: &str) -> Option<(u8, u8, u8)> {
|
||||||
if hex.len() != 7 || !hex.starts_with('#') {
|
if hex.len() != 7 || !hex.starts_with('#') {
|
||||||
return None; // Invalid hex color format
|
return None; // Invalid hex color format
|
||||||
}
|
}
|
||||||
|
|
||||||
let r = u8::from_str_radix(&hex[1..3], 16).ok()? as f32 / 255.0;
|
let r = u8::from_str_radix(&hex[1..3], 16).ok()?;
|
||||||
let g = u8::from_str_radix(&hex[3..5], 16).ok()? as f32 / 255.0;
|
let g = u8::from_str_radix(&hex[3..5], 16).ok()?;
|
||||||
let b = u8::from_str_radix(&hex[5..7], 16).ok()? as f32 / 255.0;
|
let b = u8::from_str_radix(&hex[5..7], 16).ok()?;
|
||||||
|
|
||||||
let max = r.max(g).max(b);
|
Some((r, g, b))
|
||||||
let min = r.min(g).min(b);
|
|
||||||
let delta = max - min;
|
|
||||||
|
|
||||||
let hue = if delta == 0.0 {
|
|
||||||
0.0 // No hue
|
|
||||||
} 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 saturation = if max == 0.0 {
|
|
||||||
0.0 // No saturation
|
|
||||||
} else {
|
|
||||||
delta / max
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = max;
|
|
||||||
|
|
||||||
Some((hue, saturation, value))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_closest_color(rgb: (u8, u8, u8), color_vec: Vec<String>) -> Option<(u8,u8,u8)> {
|
fn find_closest_color(rgb: (u8, u8, u8), color_vec: Vec<String>) -> Option<(u8, u8, u8)> {
|
||||||
let mut closest_color: Option<(f32,f32,f32)> = None;
|
let mut closest_color: Option<(u8, u8, u8)> = None;
|
||||||
let mut min_distance: f32 = std::f32::MAX;
|
let mut min_distance: f32 = std::f32::MAX;
|
||||||
|
|
||||||
for color in color_vec.into_iter() {
|
for color in color_vec.into_iter() {
|
||||||
if let Some(color_rgb) = hex_to_hsv(&color) {
|
if let Some(color_rgb) = hex_to_rgb(&color) {
|
||||||
let distance = euclidean_distance(rgb, color_rgb);
|
let distance = euclidean_distance(rgb, color_rgb);
|
||||||
if distance < min_distance {
|
if distance < min_distance {
|
||||||
min_distance = distance;
|
min_distance = distance;
|
||||||
closest_color = Some(hex_to_hsv(&color.clone()).unwrap());
|
closest_color = Some(hex_to_rgb(&color.clone()).unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(hsv_to_rgb(closest_color.unwrap()))
|
Some(closest_color.unwrap())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue