From 8c191f264e3aa089528498811447a115ddc1a4f2 Mon Sep 17 00:00:00 2001 From: Anatoly Bazarov Date: Wed, 29 Mar 2023 23:04:36 -0700 Subject: [PATCH] first commit --- Cargo.toml | 13 ++++ README.md | 53 +++++++++++++ src/args.rs | 161 +++++++++++++++++++++++++++++++++++++++ src/draw.rs | 145 +++++++++++++++++++++++++++++++++++ src/effects.rs | 202 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 64 ++++++++++++++++ src/palette.rs | 78 +++++++++++++++++++ 7 files changed, 716 insertions(+) create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/args.rs create mode 100644 src/draw.rs create mode 100644 src/effects.rs create mode 100644 src/main.rs create mode 100644 src/palette.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7a5f019 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "img2irc" +version = "0.1.0" +authors = ["anatolybazarov"] +github = "https://github.com/anatolybazarov/img2irc" +edition = "2021" + +[dependencies] +tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } +reqwest = "0.11.14" +photon-rs = "0.3.2" +clap = { version = "4.2.0", features = ["cargo", "derive"] } +url = "2.3.1" diff --git a/README.md b/README.md new file mode 100644 index 0000000..99c5b9e --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# img2irc (0.1.0) +![EVA Loader](https://i.imgur.com/wLyj1HH.png) + +img2irc is a utility which converts images to halfblock irc/ansi art, with a lot of post-processing filters + +*halfblock* means that each row will contain two rows worth of pixels, effectively doubling the resolution + +the `irc` mode has 99 colours, the `ansi` mode has 256 + +## usage + +`./img2ansi [OPTIONS]` + +| option | description | default value | +| ------ | ----------- | ------------- | +| `` | image url or file path | none | +| `-r, --render ` | render type (irc, ansi) | irc | +| `-w, --width ` | output image width in columns | 50 | +| `-b, --brightness=` | adjust brightness (-255 to 255) | 0 | +| `-H, --hue=` | adjust hue (-180 to 180) | 0 | +| `-c, --contrast=` | adjust contrast (-255 to 255) | 0 | +| `-s, --saturation=` | adjust saturation (-255 to 255) | 0 | +| `-o, --opacity=` | adjust opacity (-255 to 255) | 0 | +| `-g, --gamma=` | adjust gamma (-255 to 255) | 0 | +| `--dither ` | dithering (1 to 8) | 0 | +| `--pixelize ` | pixelize pixel size | 0 | +| `--gaussian-blur ` | gaussian blur radius | 0 | +| `--oil ` | oil ("[RADIUS],[INTENSITY]") | | +| `--grayscale` | converts image to black and white | +| `--halftone` | made up of small dots creating a continuous-tone illusion | +| `--sepia` | brownish, aged appearance like old photographs | +| `--normalize` | adjusts brightness and contrast for better image quality | +| `--noise` | random variations in brightness and color like film grain | +| `--emboss` | gives a raised, 3d appearance | +| `--box-blur` | smoothed appearance like frosted glass | +| `--identity` | no modifications, unchanged image | +| `--laplace` | enhances edges and boundaries in an image | +| `--noise-reduction` | reduces noise for a cleaner, clearer image | +| `--sharpen` | increases clarity and definition, making edges and details more distinct | +| `--cali` | cool blue tone with increased contrast | +| `--dramatic` | high contrast and vivid colors for a dramatic effect | +| `--firenze` | warm, earthy tones reminiscent of tuscan landscapes | +| `--golden` | warm, golden glow like sunset light | +| `--lix` | high-contrast black and white appearance with increased sharpness | +| `--lofi` | low-fidelity, retro appearance like old photographs or film | +| `--neue` | clean, modern appearance with neutral colors and simple design | +| `--obsidian` | dark, monochromatic appearance with black and gray shades | +| `--pastel-pink` | soft, delicate pink tint like pastel colors | +| `--ryo` | bright, high-contrast appearance with vivid colors and sharp details | +| `--invert` | colors are inverted, opposite on the color wheel | +| `--frosted-glass` | blurred, frosted appearance as if viewed through semi-transparent surface | +| `--solarize` | strange, otherworldly appearance with inverted colors and surreal atmosphere | +| `--edge-detection` | highlights edges and boundaries in an image | \ No newline at end of file diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..d7791a6 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,161 @@ +use clap::Parser; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Args { + /// image url or file path + #[arg(index = 1)] + pub image: String, + + /// render type (irc, ansi) + #[arg(short, long)] + pub render: Option, + + /// image width to resize to + #[arg(short, long, default_value_t = 50)] + pub width: u32, + + /// brightness (-255 to 255) + #[arg(short, long, require_equals = true, default_value_t = 0)] + pub brightness: i16, + + /// hue (-180 to 180) + #[arg(short = 'H', long, require_equals = true, default_value_t = 0)] + pub hue: i16, + + /// contrast (-255 to 255) + #[arg(short, long, require_equals = true, default_value_t = 0)] + pub contrast: i16, + + /// saturation (-255 to 255) + #[arg(short, long, require_equals = true, default_value_t = 0)] + pub saturation: i16, + + /// opacity (-255 to 255) + #[arg(short, long, require_equals = true, default_value_t = 0)] + pub opacity: i16, + + /// gamma (-255 to 255) + #[arg(short, long, require_equals = true, default_value_t = 0)] + pub gamma: i16, + + /// dither (1 to 8) + #[arg(long, default_value_t = 0)] + pub dither: u32, + + /// pixelize size + #[arg(long, default_value_t = 0)] + pub pixelize: i32, + + /// gaussian blur radius + #[arg(long, default_value_t = 0)] + pub gaussian_blur: i32, + + /// oil (",") + #[arg(long)] + pub oil: Option, + + /// grayscale + #[arg(long, default_value_t = false)] + pub grayscale: bool, + + /// halftone + #[arg(long, default_value_t = false)] + pub halftone: bool, + + /// sepia + #[arg(long, default_value_t = false)] + pub sepia: bool, + + /// normalize + #[arg(long, default_value_t = false)] + pub normalize: bool, + + /// noise + #[arg(long, default_value_t = false)] + pub noise: bool, + + /// emboss + #[arg(long, default_value_t = false)] + pub emboss: bool, + + /// box_blur + #[arg(long, default_value_t = false)] + pub box_blur: bool, + + /// identity + #[arg(long, default_value_t = false)] + pub identity: bool, + + /// laplace + #[arg(long, default_value_t = false)] + pub laplace: bool, + + /// noise reduction + #[arg(long, default_value_t = false)] + pub noise_reduction: bool, + + /// sharpen + #[arg(long, default_value_t = false)] + pub sharpen: bool, + + /// cali + #[arg(long, default_value_t = false)] + pub cali: bool, + + /// dramatic + #[arg(long, default_value_t = false)] + pub dramatic: bool, + + /// firenze + #[arg(long, default_value_t = false)] + pub firenze: bool, + + /// golden + #[arg(long, default_value_t = false)] + pub golden: bool, + + /// lix + #[arg(long, default_value_t = false)] + pub lix: bool, + + /// lofi + #[arg(long, default_value_t = false)] + pub lofi: bool, + + /// neue + #[arg(long, default_value_t = false)] + pub neue: bool, + + /// obsidian + #[arg(long, default_value_t = false)] + pub obsidian: bool, + + /// pastel_pink + #[arg(long, default_value_t = false)] + pub pastel_pink: bool, + + /// ryo + #[arg(long, default_value_t = false)] + pub ryo: bool, + + /// invert + #[arg(long, default_value_t = false)] + pub invert: bool, + + /// frosted glass + #[arg(long, default_value_t = false)] + pub frosted_glass: bool, + + /// solarize + #[arg(long, default_value_t = false)] + pub solarize: bool, + + /// edge detection + #[arg(long, default_value_t = false)] + pub edge_detection: bool, +} + +pub fn parse_args() -> Args { + Args::parse() +} \ No newline at end of file diff --git a/src/draw.rs b/src/draw.rs new file mode 100644 index 0000000..70ebcdb --- /dev/null +++ b/src/draw.rs @@ -0,0 +1,145 @@ +use crate::palette::{RGB99, ANSI256, nearest_hex_color}; + +use photon_rs::PhotonImage; + +const CHAR: &str ="\u{2580}"; + +#[derive(Debug, Clone)] +pub struct AnsiImage { + pub image: PhotonImage, + pub bitmap: Vec>, + pub halfblock: Vec>, +} + +#[derive(Debug, Clone, Copy)] +pub struct AnsiPixelPair { + pub top: AnsiPixel, + pub bottom: AnsiPixel, +} + +#[derive(Debug, Clone, Copy)] +pub struct AnsiPixel { + pub orig: u32, + pub ansi: u8, + pub irc: u8, +} + +impl AnsiPixel { + pub fn new(pixel: &u32) -> AnsiPixel { + let irc = nearest_hex_color(*pixel, RGB99.to_vec()); + let ansi = nearest_hex_color(*pixel, ANSI256.to_vec()); + AnsiPixel { + orig: *pixel, + ansi: ansi, + irc: irc, + } + } +} + +impl AnsiImage { + pub fn new(image: PhotonImage) -> AnsiImage { + let mut bitmap = image.get_raw_pixels() + .chunks(4) + .map(|x| make_rgb(x.to_vec())) + .collect::>() + .chunks(image.get_width() as usize) + .map(|x| x.to_vec()) + .collect::>>(); + + if bitmap.len() % 2 != 0 { + bitmap.push(vec![0; image.get_width() as usize]); + } + + let halfblock = halfblock_bitmap(&bitmap); + + return AnsiImage { + image: image, + bitmap: bitmap, + halfblock: halfblock, + } + } +} + +pub fn make_rgb(rgb: Vec) -> u32 { + let r = *rgb.get(0).unwrap() as u32; + let g = *rgb.get(1).unwrap() as u32; + let b = *rgb.get(2).unwrap() as u32; + + let rgb = (r << 16) + (g << 8) + b; + + return rgb +} + +pub fn halfblock_bitmap(bitmap: &Vec>) -> Vec> { + let ansi_bitmap = bitmap + .iter() + .map(|x| { + x.iter().map(|y| AnsiPixel::new(y)).collect::>() + }) + .collect::>>(); + + let mut ansi_canvas: Vec> = Vec::new(); + + for two_rows in ansi_bitmap.chunks(2) { + let rows = two_rows.to_vec(); + let top_row = rows.get(0).unwrap(); + let bottom_row = rows.get(1).unwrap(); + + let mut ansi_row: Vec = Vec::new(); + + for i in 0..bitmap.get(0).unwrap().len() { + let top_pixel = top_row.get(i as usize).unwrap(); + let bottom_pixel = bottom_row.get(i as usize).unwrap(); + + let pixel_pair = AnsiPixelPair { + top: *top_pixel, + bottom: *bottom_pixel, + }; + + ansi_row.push(pixel_pair); + } + + ansi_canvas.push(ansi_row); + } + + ansi_canvas +} + +pub fn ansi_draw(image: AnsiImage) -> String { + let mut out: String = String::new(); + for row in image.halfblock { + for pixel_pair in row.iter() { + let fg = pixel_pair.top.ansi; + let bg = pixel_pair.bottom.ansi; + out.push_str(format!("\x1b[38;5;{}m\x1b[48;5;{}m{}", fg, bg, CHAR).as_str()); + } + out.push_str("\x1b[0m"); + out.push_str("\n"); + } + return out +} + +pub fn irc_draw(image: AnsiImage) -> String { + let mut out: String = String::new(); + for row in image.halfblock { + let mut last_fg: u8 = 0; + let mut last_bg: u8 = 0; + for pixel_pair in row.iter() { + let fg = pixel_pair.top.irc; + let bg = pixel_pair.bottom.irc; + + if fg == last_fg && bg == last_bg { + out.push_str(format!("{}", CHAR).as_str()); + } else if bg == last_bg { + out.push_str(format!("\x03{}{}", fg, CHAR).as_str()); + } else { + out.push_str(format!("\x03{},{}{}", fg, bg, CHAR).as_str()); + } + + last_fg = fg; + last_bg = bg; + } + out.push_str("\n"); + } + return out +} \ No newline at end of file diff --git a/src/effects.rs b/src/effects.rs new file mode 100644 index 0000000..0a9b20d --- /dev/null +++ b/src/effects.rs @@ -0,0 +1,202 @@ +use crate::args; +use photon_rs::{channels, channels::alter_channels, conv, effects, filters, monochrome, noise}; +use photon_rs::transform::{resize, SamplingFilter}; +use photon_rs::PhotonImage; + +pub fn apply_effects( + args: &args::Args, + mut photon_image: PhotonImage, +) -> PhotonImage { + + // Resize to width + let height = + (args.width as f32 / photon_image.get_width() as f32 * photon_image.get_height() as f32) as u32; + + photon_image = resize(&mut photon_image, args.width, height, SamplingFilter::Lanczos3); + + // Adjust brightness + if args.brightness != 0 { + alter_channels(&mut photon_image, args.brightness, args.brightness, args.brightness); + } + + // Adjust hue + if args.hue != 0 { + alter_channels(&mut photon_image, args.hue, args.hue, args.hue); + } + + // Adjust contrast + if args.contrast != 0 { + alter_channels(&mut photon_image, args.contrast, args.contrast, args.contrast); + } + + // Adjust saturation + if args.saturation != 0 { + alter_channels(&mut photon_image, args.saturation, args.saturation, args.saturation); + } + + // Adjust opacity + if args.opacity != 0 { + alter_channels(&mut photon_image, args.opacity, args.opacity, args.opacity); + } + + // Adjust gamma + if args.gamma != 0 { + alter_channels(&mut photon_image, args.gamma, args.gamma, args.gamma); + } + + // Adjust dither + if args.dither > 0 { + effects::dither(&mut photon_image, args.dither); + } + + // Adjust gaussian_blur + if args.gaussian_blur > 0 { + + conv::gaussian_blur(&mut photon_image, args.gaussian_blur); + } + + // Adjust pixelize + if args.pixelize > 0 { + effects::pixelize(&mut photon_image, args.pixelize); + } + + // Adjust halftone + if args.halftone { + effects::halftone(&mut photon_image); + } + + // Adjust invert + if args.invert { + channels::invert(&mut photon_image); + } + + // Adjust sepia + if args.sepia { + monochrome::sepia(&mut photon_image); + } + + // Adjust solarize + if args.solarize { + effects::solarize(&mut photon_image); + } + + // Adjust normalize + if args.normalize { + effects::normalize(&mut photon_image); + } + + // Adjust noise + if args.noise { + noise::add_noise_rand(photon_image.clone()); + } + + // Adjust sharpen + if args.sharpen { + conv::sharpen(&mut photon_image); + } + + // Adjust edge_detection + if args.edge_detection { + conv::edge_detection(&mut photon_image); + } + + // Adjust emboss + if args.emboss { + conv::emboss(&mut photon_image); + } + + // Adjust frosted_glass + if args.frosted_glass { + effects::frosted_glass(&mut photon_image); + } + + // Adjust box_blur + if args.box_blur { + conv::box_blur(&mut photon_image); + } + + // Adjust grayscale + if args.grayscale { + monochrome::grayscale(&mut photon_image); + } + + // Adjust identity + if args.identity { + conv::identity(&mut photon_image); + } + + // Adjust laplace + if args.laplace { + conv::laplace(&mut photon_image); + } + + // Adjust cali + if args.cali { + filters::cali(&mut photon_image); + } + + // Adjust dramatic + if args.dramatic { + filters::dramatic(&mut photon_image); + } + + // Adjust firenze + if args.firenze { + filters::firenze(&mut photon_image); + } + + // Adjust golden + if args.golden { + filters::golden(&mut photon_image); + } + + // Adjust lix + if args.lix { + filters::lix(&mut photon_image); + } + + // Adjust lofi + if args.lofi { + filters::lofi(&mut photon_image); + } + + // Adjust neue + if args.neue { + filters::neue(&mut photon_image); + } + + // Adjust obsidian + if args.obsidian { + filters::obsidian(&mut photon_image); + } + + // Adjust pastel_pink + if args.pastel_pink { + filters::pastel_pink(&mut photon_image); + } + + // Adjust ryo + if args.ryo { + filters::ryo(&mut photon_image); + } + + // Adjust oil + match &args.oil { + Some(oil) => { + // split oil at comma + let vals: Vec<&str> = oil.split(",").collect(); + + // check if args.oil has 2 values + if vals.len() == 2 { + // convert oil values to i32 and f64 + let radius: i32 = vals.get(0).unwrap().parse::().unwrap(); + let intensity: f64 = vals.get(1).unwrap().parse::().unwrap(); + + effects::oil(&mut photon_image, radius, intensity); + } + } + None => {} + } + + photon_image +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c3acb95 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,64 @@ +mod args; +mod draw; +mod palette; +mod effects; + +use reqwest; +use url::Url; + +use photon_rs::PhotonImage; +use std::{error::Error, io::Cursor, process::exit}; + + +#[tokio::main] +async fn main() { + let args = args::parse_args(); + + match load_image_from_url_or_path(args.image.as_str()).await { + Ok(mut image) => { + image = effects::apply_effects( + &args, + image, + ); + + let canvas = draw::AnsiImage::new(image); + match &args.render { + None => println!("{}", draw::irc_draw(canvas).as_str()), + Some(ref render) => match render.as_str() { + "irc" => println!("{}", draw::irc_draw(canvas).as_str()), + "ansi" => println!("{}", draw::ansi_draw(canvas).as_str()), + _ => { + eprintln!("Error: invalid render type"); + exit(1); + } + }, + } + } + Err(e) => { + eprintln!("Error: {}", e); + exit(1); + } + } +} + +async fn load_image_from_url_or_path(image: &str) -> Result> { + match Url::parse(image) { + Ok(url) => { + let response = reqwest::get(url).await?; + let bytes = response.bytes().await?; + + let image_data = Cursor::new(bytes); + match photon_rs::native::open_image_from_bytes(image_data.into_inner().as_ref()) { + Ok(image) => Ok(image), + Err(e) => Err(Box::new(e)), + } + } + Err(_) => { + match photon_rs::native::open_image(image) { + Ok(image) => Ok(image), + Err(e) => Err(Box::new(e)), + } + } + } +} + diff --git a/src/palette.rs b/src/palette.rs new file mode 100644 index 0000000..91371f4 --- /dev/null +++ b/src/palette.rs @@ -0,0 +1,78 @@ +pub const RGB99: [u32; 99] = [ + 0xffffff, 0x000000, 0x00007f, 0x009300, 0xff0000, 0x7f0000, 0x9c009c, 0xfc7f00, + 0xffff00, 0x00fc00, 0x009393, 0x00ffff, 0x0000fc, 0xff00ff, 0x7f7f7f, 0xd2d2d2, + 0x470000, 0x472100, 0x474700, 0x324700, 0x004700, 0x00472c, 0x004747, 0x002747, + 0x000047, 0x2e0047, 0x470047, 0x47002a, 0x740000, 0x743a00, 0x747400, 0x517400, + 0x007400, 0x007449, 0x007474, 0x004074, 0x000074, 0x4b0074, 0x740074, 0x740045, + 0xb50000, 0xb56300, 0xb5b500, 0x7db500, 0x00b500, 0x00b571, 0x00b5b5, 0x0063b5, + 0x0000b5, 0x7500b5, 0xb500b5, 0xb5006b, 0xff0000, 0xff8c00, 0xffff00, 0xb2ff00, + 0x00ff00, 0x00ffa0, 0x00ffff, 0x008cff, 0x0000ff, 0xa500ff, 0xff00ff, 0xff0098, + 0xff5959, 0xffb459, 0xffff71, 0xcfff60, 0x6fff6f, 0x65ffc9, 0x6dffff, 0x59b4ff, + 0x5959ff, 0xc459ff, 0xff66ff, 0xff59bc, 0xff9c9c, 0xffd39c, 0xffff9c, 0xe2ff9c, + 0x9cff9c, 0x9cffdb, 0x9cffff, 0x9cd3ff, 0x9c9cff, 0xdc9cff, 0xff9cff, 0xff94d3, + 0x000000, 0x131313, 0x282828, 0x363636, 0x4d4d4d, 0x656565, 0x818181, 0x9f9f9f, + 0xbcbcbc, 0xe2e2e2, 0xffffff, +]; + +pub const ANSI256: [u32; 256] = [ + 0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, 0x008080, 0xc0c0c0, + 0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff, + 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, + 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af, + 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, + 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f, + 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, + 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff, + 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f, + 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, + 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff, + 0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, + 0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af, + 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, + 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f, + 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, + 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff, + 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f, + 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af, + 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, + 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, + 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af, + 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, + 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f, + 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af, + 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff, + 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f, + 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, + 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff, + 0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e, + 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, + 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee +]; + +fn hex_to_rgb(hex: u32) -> (u8, u8, u8) { + let r = ((hex >> 16) & 0xFF) as u8; + let g = ((hex >> 8) & 0xFF) as u8; + let b = (hex & 0xFF) as u8; + + (r, g, b) +} + +fn color_distance_squared(c1: (u8, u8, u8), c2: (u8, u8, u8)) -> u32 { + let dr = c1.0 as i32 - c2.0 as i32; + let dg = c1.1 as i32 - c2.1 as i32; + let db = c1.2 as i32 - c2.2 as i32; + + (dr * dr + dg * dg + db * db) as u32 +} + +pub fn nearest_hex_color(input_color: u32, hex_colors: Vec) -> u8 { + let hex = hex_colors + .iter() + .map(|&hex| (hex, hex_to_rgb(hex))) + .min_by_key(|(_, rgb)| color_distance_squared(hex_to_rgb(input_color), *rgb)) + .map(|(hex, _)| hex).unwrap(); + + let index = hex_colors.iter().position(|&x| x == hex).unwrap(); + return index as u8; + +} \ No newline at end of file