From e3a1ffb13d52ed81cccc7de4c767fe920ff512aa Mon Sep 17 00:00:00 2001 From: Taylor Beeston Date: Mon, 5 Aug 2024 21:38:33 -0700 Subject: [PATCH] :sparkles: Update definition of colorschemes to simpler format --- src/config.rs | 74 ++++++++++++++++++++++++++++++++++++--------------- src/utils.rs | 38 +++++++++++++++++++++----- 2 files changed, 85 insertions(+), 27 deletions(-) diff --git a/src/config.rs b/src/config.rs index 255d54c..f361d49 100644 --- a/src/config.rs +++ b/src/config.rs @@ -76,7 +76,13 @@ struct SerializedAppConfig { spatial_averaging_radius: String, } -fn load_config(config_path: Option<&str>) -> Result { +#[derive(Debug)] +pub struct ConfigInfo { + config: SerializedAppConfig, + config_dir: PathBuf, +} + +fn load_config(config_path: Option<&str>) -> Result { let mut builder = ConfigBuilder::default(); builder = builder @@ -86,32 +92,62 @@ fn load_config(config_path: Option<&str>) -> Result::add_source( builder, - File::from(default_config_path).required(false), + File::from(config_path).required(false), ); } - if let Some(path) = config_path { - builder = builder.add_source(File::with_name(path).required(true)); - } - let config = builder.build()?; - config.try_deserialize().map_err(AppError::from) + Ok(ConfigInfo { + config: config.try_deserialize()?, + config_dir, + }) +} + +fn parse_colorscheme(content: &str) -> Vec { + content + .lines() + .filter_map(|line| { + let trimmed = line.split("//").next().unwrap_or("").trim(); + if trimmed.is_empty() { + None + } else { + Some(trimmed.to_string()) + } + }) + .collect() } fn load_colorscheme(name: &str, config_dir: &Path) -> Result, AppError> { - let colorscheme_path = config_dir.join(format!("{}.toml", name)); + let colorscheme_path = config_dir.join(format!("{}.txt", name)); if colorscheme_path.exists() { let colorscheme_str = fs::read_to_string(colorscheme_path)?; - let colorscheme: Vec = toml::from_str(&colorscheme_str)?; - Ok(colorscheme) + let colorscheme = parse_colorscheme(&colorscheme_str); + + if colorscheme.is_empty() { + Err(AppError::Other(format!("Colorscheme '{}' is empty", name))) + } else { + Ok(colorscheme) + } } else if name == "kanagawa" { Ok(KANAGAWA.iter().map(|&s| s.to_string()).collect()) } else { @@ -149,7 +185,7 @@ pub fn init() -> Result, AppError> { .version(VERSION) .author("Taylor Beeston") .about("Applies color schemes to images") - .after_help("Config should be a TOML that contains a colorscheme and a Blend Factor.\n\nBlend Factor is a [0.0-1.0] float. Higher values will make the image adhere more strictly to the colorscheme. Lower values will make artifacting less visible. Colorscheme is a string that should be the name of a TOML file (minus the extension) in the same directory as the config file. For example if 'kanagawa' is used as the name of the colorscheme string, there should be a 'kanagawa.toml' file in the same directory as the config file.") + .after_help("Config should be a TOML that contains a colorscheme and a Blend Factor.\n\nBlend Factor is a [0.0-1.0] float. Higher values will make the image adhere more strictly to the colorscheme. Lower values will make artifacting less visible. Colorscheme is a string that should be the name of a colorscheme txt file (minus the extension) in the same directory as the config file. For example if 'kanagawa' is used as the name of the colorscheme string, there should be a 'kanagawa.txt' file in the same directory as the config file.\n\nColorscheme files are simple files with one hex code per line and may optionally have comments using double slashes, e.g.\n\n// Grayscale\n#fff\n#000") .arg( Arg::with_name("Blend Factor") .short('b') @@ -184,7 +220,7 @@ pub fn init() -> Result, AppError> { Arg::with_name("config") .short('c') .long("config") - .value_name("/path/to/image.png") + .value_name("/path/to/config.toml") .help("Sets a custom config file") .takes_value(true), ) @@ -205,7 +241,7 @@ pub fn init() -> Result, AppError> { ) .get_matches(); - let config = load_config(matches.value_of("config"))?; + let ConfigInfo { config, config_dir } = load_config(matches.value_of("config"))?; let input_paths: Vec<&str> = matches.values_of("Image Paths").unwrap().collect(); let output_dir = matches.value_of("output").map(PathBuf::from); @@ -213,12 +249,8 @@ pub fn init() -> Result, AppError> { let input_output_pairs = generate_input_output_pairs(&input_paths, output_dir, &config.colorscheme)?; - let config_dir = dirs::home_dir() - .unwrap_or_else(|| PathBuf::from("")) - .join(".config/colorizer"); - let colors = load_colorscheme(&config.colorscheme, &config_dir)?; - let colors: Vec> = colors.iter().map(|hex| hex_to_rgb(hex)).collect(); + let colors: Vec> = colors.iter().map(|hex| hex_to_rgb(hex).unwrap()).collect(); let blend_factor = matches .value_of("Blend Factor") diff --git a/src/utils.rs b/src/utils.rs index 030b460..6a9a22b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,12 +2,38 @@ use image::RgbImage; use indicatif::ProgressBar; use palette::{IntoColor, Lab, Srgb}; -pub fn hex_to_rgb(hex: &str) -> Srgb { - let hex = hex.trim_start_matches('#'); - let r = u8::from_str_radix(&hex[0..2], 16).unwrap() as f32 / 255.0; - let g = u8::from_str_radix(&hex[2..4], 16).unwrap() as f32 / 255.0; - let b = u8::from_str_radix(&hex[4..6], 16).unwrap() as f32 / 255.0; - Srgb::new(r, g, b) +pub fn hex_to_rgb(input: &str) -> Result, String> { + let cleaned = input.trim_start_matches('#'); + + match cleaned.len() { + 3 => { + // Three-character hex code + let r = u8::from_str_radix(&cleaned[0..1].repeat(2), 16).map_err(|e| e.to_string())? + as f32 + / 255.0; + let g = u8::from_str_radix(&cleaned[1..2].repeat(2), 16).map_err(|e| e.to_string())? + as f32 + / 255.0; + let b = u8::from_str_radix(&cleaned[2..3].repeat(2), 16).map_err(|e| e.to_string())? + as f32 + / 255.0; + Ok(Srgb::new(r, g, b)) + } + 6 => { + // Six-character hex code + let r = + u8::from_str_radix(&cleaned[0..2], 16).map_err(|e| e.to_string())? as f32 / 255.0; + let g = + u8::from_str_radix(&cleaned[2..4], 16).map_err(|e| e.to_string())? as f32 / 255.0; + let b = + u8::from_str_radix(&cleaned[4..6], 16).map_err(|e| e.to_string())? as f32 / 255.0; + Ok(Srgb::new(r, g, b)) + } + _ => Err(format!( + "Invalid input: '{}'. Expected a 3 or 6-digit hex code.", + input + )), + } } pub fn interpolate_color(color1: &Lab, color2: &Lab, t: f32) -> Lab {