From 703000e68af6f94af386ff7cfbb3684db6eb7d33 Mon Sep 17 00:00:00 2001 From: Waveplate Date: Sun, 15 Dec 2024 07:33:48 -0800 Subject: [PATCH] add new CLI options for crop, rotate, flip, filter, scale, and aspect ratio --- src/args.rs | 275 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 201 insertions(+), 74 deletions(-) diff --git a/src/args.rs b/src/args.rs index 29effbf..ce70593 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,173 +1,300 @@ use clap::Parser; -#[derive(Parser, Debug)] +#[derive(clap::ValueEnum, Clone, Debug)] +pub enum SamplingFilter { + Nearest, + Triangle, + CatmullRom, + Gaussian, + Lanczos3, +} + +#[derive(clap::ValueEnum, Clone, Debug)] +pub enum ColourSpace { + HSL, + HSV, + HSLUV, + LCH, +} + +#[derive(Parser, Clone, Debug)] #[command(author, version, about, long_about = None)] pub struct Args { /// image url or file path #[arg(index = 1)] pub image: String, - /// irc + /// output image width in columns + #[arg(short, long)] + pub width: Option, + + /// output image height in rows + #[arg(short, long)] + pub height: Option, + + /// scaling factors (x:y, e.g., "2:2") + #[arg(long, value_parser = parse_xy_pair)] + pub scale: Option<(f32, f32)>, + + /// final aspect ratio (x:y, e.g., "2:1") + #[arg(long, value_parser = parse_xy_pair)] + pub aspect: Option<(f32, f32)>, + + /// crop image (x1,y1,x2,y2) + #[arg(long, value_parser = parse_crop_coordinates)] + pub crop: Option<(u32, u32, u32, u32)>, + + /// sampling filter + #[arg(long, value_enum, default_value_t = SamplingFilter::Nearest)] + pub filter: SamplingFilter, + + /// rotate degrees + #[arg(long, default_value_t = 0)] + pub rotate: i32, + + /// flip horizontal #[arg(long, default_value_t = false)] + pub fliph: bool, + + /// flip vertical + #[arg(long, default_value_t = false)] + pub flipv: bool, + + /// use IRC99 colours + #[arg(long, default_value_t = false, group = "colour", required_unless_present_any = ["ansi", "ansi24"])] pub irc: bool, - /// 8-bit ansi - #[arg(long, default_value_t = false)] + /// use 8-bit ANSI colours + #[arg(long, default_value_t = false, group = "colour", required_unless_present_any = ["irc", "ansi24"])] pub ansi: bool, - /// 24-bit ansi - #[arg(long, default_value_t = false)] + /// use 24-bit ANSI colours + #[arg(long, default_value_t = false, group = "colour", required_unless_present_any = ["irc", "ansi"])] pub ansi24: bool, - /// quarterblock - #[arg(long, default_value_t = false)] - pub qb: bool, + /// use braille pixels + #[arg(long, default_value_t = false, group = "pixel")] + pub braille: bool, - /// image width to resize to - #[arg(short, long, default_value_t = 50)] - pub width: u32, + /// use halfblock pixels + #[arg(long, alias = "halfblock", default_value_t = true, group = "pixel")] + pub hb: bool, - /// brightness (-255 to 255) - #[arg(short, long, require_equals = true, default_value_t = 0.0)] + /// use quarterblocks pixels + #[arg(long, alias = "quarterblock", default_value_t = false, group = "pixel")] + pub qb: bool, + + /// adjust brightness (0 = no change) + #[arg(short = 'b', long, default_value_t = 0.0, allow_hyphen_values = true)] pub brightness: f32, - /// contrast (-255 to 255) - #[arg(short, long, require_equals = true, default_value_t = 0.0)] + /// adjust contrast (0 = no change) + #[arg(short = 'c', long, default_value_t = 0.0, allow_hyphen_values = true)] pub contrast: f32, - /// saturation (-255 to 255) - #[arg(short, long, require_equals = true, default_value_t = 0.0)] - pub saturation: f32, - - /// hue (0 to 360) - #[arg(short = 'H', long, default_value_t = 0.0)] - pub hue: f32, - - /// gamma (0 to 255) - #[arg(short, long, default_value_t = 0.0)] + /// adjust gamma (0 to 255) + #[arg(short = 'g', long, default_value_t = 0.0, allow_hyphen_values = true)] pub gamma: f32, - /// dither (1 to 8) - #[arg(long, default_value_t = 0)] + /// adjust saturation (0 = no change) + #[arg(short = 's', long, default_value_t = 0.0, allow_hyphen_values = true)] + pub saturation: f32, + + /// rotate hue (0 to 360) + #[arg(short = 'u', long, default_value_t = 0.0)] + pub hue: f32, + + /// colors are inverted, opposite on the color wheel + #[arg(short = 'i', long, default_value_t = false)] + pub invert: bool, + + /// dithering (1 to 8) + #[arg(long, short = 'd', long, default_value_t = 0)] pub dither: u32, - /// pixelize size + /// adjust luma brightness (braille only) + #[arg(short = 'B', long, default_value_t = 0.0, allow_hyphen_values = true, requires = "braille")] + pub luma_brightness: f32, + + /// adjust luma contrast (braille only) + #[arg(short = 'C', long, default_value_t = 0.0, allow_hyphen_values = true, requires = "braille")] + pub luma_contrast: f32, + + /// adjust luma gamma (braille only) + #[arg(short = 'G', long, default_value_t = 0.0, allow_hyphen_values = true, requires = "braille")] + pub luma_gamma: f32, + + /// adjust luma saturation (braille only) + #[arg(short = 'S', long, default_value_t = 0.0, allow_hyphen_values = true, requires = "braille")] + pub luma_saturation: f32, + + /// luminance is inverted + #[arg(short = 'I', long, default_value_t = false, requires = "braille")] + pub luma_invert: bool, + + /// colour space + #[arg(long, value_enum, default_value_t = ColourSpace::HSV)] + pub colorspace: ColourSpace, + + /// converts image to black and white + #[arg(long, default_value_t = false, group = "grayscale_opts")] + pub grayscale: bool, + + /// exclude grayscale colours from the palette + #[arg(long, default_value_t = false, group = "grayscale_opts")] + pub nograyscale: bool, + + /// pixelize pixel size #[arg(long, default_value_t = 0)] pub pixelize: i32, + /// simple average of all the neighboring pixels surrounding a given pixel + #[arg(long="boxblur", default_value_t = false)] + pub box_blur: bool, + /// gaussian blur radius - #[arg(long, default_value_t = 0)] + #[arg(long="gaussianblur", default_value_t = 0)] pub gaussian_blur: i32, - /// oil (",") + /// oil filter ("[radius],[intensity]") #[arg(long)] pub oil: Option, - /// grayscale - #[arg(long, default_value_t = false)] - pub grayscale: bool, - - /// no grayscale - #[arg(long, default_value_t = false)] - pub nograyscale: bool, - - /// halftone + /// made up of small dots creating a continuous-tone illusion #[arg(long, default_value_t = false)] pub halftone: bool, - /// sepia + /// brownish, aged appearance like old photographs #[arg(long, default_value_t = false)] pub sepia: bool, - /// normalize + /// adjusts brightness and contrast for better image quality #[arg(long, default_value_t = false)] pub normalize: bool, - /// noise + /// random variations in brightness and color like film grain #[arg(long, default_value_t = false)] pub noise: bool, - /// emboss + /// gives a raised, 3d appearance #[arg(long, default_value_t = false)] pub emboss: bool, - /// box_blur - #[arg(long, default_value_t = false)] - pub box_blur: bool, - - /// identity + /// no modifications, unchanged image #[arg(long, default_value_t = false)] pub identity: bool, - /// laplace + /// enhances edges and boundaries in an image #[arg(long, default_value_t = false)] pub laplace: bool, - /// noise reduction - #[arg(long, default_value_t = false)] + /// reduces noise for a cleaner, clearer image + #[arg(long="denoise", default_value_t = false)] pub noise_reduction: bool, - /// sharpen + /// increases clarity and definition, making edges and details more distinct #[arg(long, default_value_t = false)] pub sharpen: bool, - /// cali + /// cool blue tone with increased contrast #[arg(long, default_value_t = false)] pub cali: bool, - /// dramatic + /// high contrast and vivid colors for a dramatic effect #[arg(long, default_value_t = false)] pub dramatic: bool, - /// firenze + /// warm, earthy tones reminiscent of tuscan landscapes #[arg(long, default_value_t = false)] pub firenze: bool, - /// golden + /// warm, golden glow like sunset light #[arg(long, default_value_t = false)] pub golden: bool, - /// lix + /// high-contrast black and white appearance with increased sharpness #[arg(long, default_value_t = false)] pub lix: bool, - /// lofi + /// low-fidelity, retro appearance like old photographs or film #[arg(long, default_value_t = false)] pub lofi: bool, - /// neue + /// clean, modern appearance with neutral colors and simple design #[arg(long, default_value_t = false)] pub neue: bool, - /// obsidian + /// dark, monochromatic appearance with black and gray shades #[arg(long, default_value_t = false)] pub obsidian: bool, - /// pastel_pink - #[arg(long, default_value_t = false)] + /// soft, delicate pink tint like pastel colors + #[arg(long="pastelpink", default_value_t = false)] pub pastel_pink: bool, - /// ryo + /// bright, high-contrast appearance with vivid colors and sharp details #[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)] + /// blurred, frosted appearance as if viewed through semi-transparent surface + #[arg(long="frostedglass", default_value_t = false)] pub frosted_glass: bool, - /// solarize + /// strange, otherworldly appearance with inverted colors and surreal atmosphere #[arg(long, default_value_t = false)] pub solarize: bool, - /// edge detection - #[arg(long, default_value_t = false)] + /// highlights edges and boundaries in an image + #[arg(long="edgedetection", default_value_t = false)] pub edge_detection: bool, } pub fn parse_args() -> Args { Args::parse() -} \ No newline at end of file +} + +fn parse_xy_pair(s: &str) -> Result<(f32, f32), String> { + let parts: Vec<&str> = s.split(':').collect(); + if parts.len() != 2 { + return Err(format!( + "Invalid format. Expected 'value1:value2', got '{}'", + s + )); + } + + let first = parts[0].parse::().map_err(|e| { + format!( + "Failed to parse the first value ('{}') as f32: {}", + parts[0], e + ) + })?; + let second = parts[1].parse::().map_err(|e| { + format!( + "Failed to parse the second value ('{}') as f32: {}", + parts[1], e + ) + })?; + + if first <= 0.0 || second <= 0.0 { + return Err("Both values must be positive numbers.".to_string()); + } + + Ok((first, second)) +} + +fn parse_crop_coordinates(s: &str) -> Result<(u32, u32, u32, u32), String> { + let coords: Vec<&str> = s.split(',').collect(); + if coords.len() != 4 { + return Err(format!( + "Invalid crop format '{}'. Expected 'x1,y1,x2,y2' (e.g., '50,50,200,200').", + s + )); + } + + let x1 = coords[0].parse::().map_err(|_| format!("Invalid x1 value '{}'.", coords[0]))?; + let y1 = coords[1].parse::().map_err(|_| format!("Invalid y1 value '{}'.", coords[1]))?; + let x2 = coords[2].parse::().map_err(|_| format!("Invalid x2 value '{}'.", coords[2]))?; + let y2 = coords[3].parse::().map_err(|_| format!("Invalid y2 value '{}'.", coords[3]))?; + + Ok((x1, y1, x2, y2)) +}