add new CLI options for crop, rotate, flip, filter, scale, and aspect ratio

This commit is contained in:
Waveplate 2024-12-15 07:33:48 -08:00
parent 492079af6c
commit 703000e68a

View File

@ -1,173 +1,300 @@
use clap::Parser; 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)] #[command(author, version, about, long_about = None)]
pub struct Args { pub struct Args {
/// image url or file path /// image url or file path
#[arg(index = 1)] #[arg(index = 1)]
pub image: String, pub image: String,
/// irc /// output image width in columns
#[arg(short, long)]
pub width: Option<u32>,
/// output image height in rows
#[arg(short, long)]
pub height: Option<u32>,
/// 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)] #[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, pub irc: bool,
/// 8-bit ansi /// use 8-bit ANSI colours
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false, group = "colour", required_unless_present_any = ["irc", "ansi24"])]
pub ansi: bool, pub ansi: bool,
/// 24-bit ansi /// use 24-bit ANSI colours
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false, group = "colour", required_unless_present_any = ["irc", "ansi"])]
pub ansi24: bool, pub ansi24: bool,
/// quarterblock /// use braille pixels
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false, group = "pixel")]
pub qb: bool, pub braille: bool,
/// image width to resize to /// use halfblock pixels
#[arg(short, long, default_value_t = 50)] #[arg(long, alias = "halfblock", default_value_t = true, group = "pixel")]
pub width: u32, pub hb: bool,
/// brightness (-255 to 255) /// use quarterblocks pixels
#[arg(short, long, require_equals = true, default_value_t = 0.0)] #[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, pub brightness: f32,
/// contrast (-255 to 255) /// adjust contrast (0 = no change)
#[arg(short, long, require_equals = true, default_value_t = 0.0)] #[arg(short = 'c', long, default_value_t = 0.0, allow_hyphen_values = true)]
pub contrast: f32, pub contrast: f32,
/// saturation (-255 to 255) /// adjust gamma (0 to 255)
#[arg(short, long, require_equals = true, default_value_t = 0.0)] #[arg(short = 'g', long, default_value_t = 0.0, allow_hyphen_values = true)]
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)]
pub gamma: f32, pub gamma: f32,
/// dither (1 to 8) /// adjust saturation (0 = no change)
#[arg(long, default_value_t = 0)] #[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, 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)] #[arg(long, default_value_t = 0)]
pub pixelize: i32, 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 /// gaussian blur radius
#[arg(long, default_value_t = 0)] #[arg(long="gaussianblur", default_value_t = 0)]
pub gaussian_blur: i32, pub gaussian_blur: i32,
/// oil ("<radius>,<intensity>") /// oil filter ("[radius],[intensity]")
#[arg(long)] #[arg(long)]
pub oil: Option<String>, pub oil: Option<String>,
/// grayscale /// made up of small dots creating a continuous-tone illusion
#[arg(long, default_value_t = false)]
pub grayscale: bool,
/// no grayscale
#[arg(long, default_value_t = false)]
pub nograyscale: bool,
/// halftone
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub halftone: bool, pub halftone: bool,
/// sepia /// brownish, aged appearance like old photographs
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub sepia: bool, pub sepia: bool,
/// normalize /// adjusts brightness and contrast for better image quality
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub normalize: bool, pub normalize: bool,
/// noise /// random variations in brightness and color like film grain
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub noise: bool, pub noise: bool,
/// emboss /// gives a raised, 3d appearance
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub emboss: bool, pub emboss: bool,
/// box_blur /// no modifications, unchanged image
#[arg(long, default_value_t = false)]
pub box_blur: bool,
/// identity
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub identity: bool, pub identity: bool,
/// laplace /// enhances edges and boundaries in an image
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub laplace: bool, pub laplace: bool,
/// noise reduction /// reduces noise for a cleaner, clearer image
#[arg(long, default_value_t = false)] #[arg(long="denoise", default_value_t = false)]
pub noise_reduction: bool, pub noise_reduction: bool,
/// sharpen /// increases clarity and definition, making edges and details more distinct
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub sharpen: bool, pub sharpen: bool,
/// cali /// cool blue tone with increased contrast
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub cali: bool, pub cali: bool,
/// dramatic /// high contrast and vivid colors for a dramatic effect
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub dramatic: bool, pub dramatic: bool,
/// firenze /// warm, earthy tones reminiscent of tuscan landscapes
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub firenze: bool, pub firenze: bool,
/// golden /// warm, golden glow like sunset light
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub golden: bool, pub golden: bool,
/// lix /// high-contrast black and white appearance with increased sharpness
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub lix: bool, pub lix: bool,
/// lofi /// low-fidelity, retro appearance like old photographs or film
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub lofi: bool, pub lofi: bool,
/// neue /// clean, modern appearance with neutral colors and simple design
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub neue: bool, pub neue: bool,
/// obsidian /// dark, monochromatic appearance with black and gray shades
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub obsidian: bool, pub obsidian: bool,
/// pastel_pink /// soft, delicate pink tint like pastel colors
#[arg(long, default_value_t = false)] #[arg(long="pastelpink", default_value_t = false)]
pub pastel_pink: bool, pub pastel_pink: bool,
/// ryo /// bright, high-contrast appearance with vivid colors and sharp details
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub ryo: bool, pub ryo: bool,
/// invert /// blurred, frosted appearance as if viewed through semi-transparent surface
#[arg(long, default_value_t = false)] #[arg(long="frostedglass", default_value_t = false)]
pub invert: bool,
/// frosted glass
#[arg(long, default_value_t = false)]
pub frosted_glass: bool, pub frosted_glass: bool,
/// solarize /// strange, otherworldly appearance with inverted colors and surreal atmosphere
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
pub solarize: bool, pub solarize: bool,
/// edge detection /// highlights edges and boundaries in an image
#[arg(long, default_value_t = false)] #[arg(long="edgedetection", default_value_t = false)]
pub edge_detection: bool, pub edge_detection: bool,
} }
pub fn parse_args() -> Args { pub fn parse_args() -> Args {
Args::parse() Args::parse()
} }
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::<f32>().map_err(|e| {
format!(
"Failed to parse the first value ('{}') as f32: {}",
parts[0], e
)
})?;
let second = parts[1].parse::<f32>().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::<u32>().map_err(|_| format!("Invalid x1 value '{}'.", coords[0]))?;
let y1 = coords[1].parse::<u32>().map_err(|_| format!("Invalid y1 value '{}'.", coords[1]))?;
let x2 = coords[2].parse::<u32>().map_err(|_| format!("Invalid x2 value '{}'.", coords[2]))?;
let y2 = coords[3].parse::<u32>().map_err(|_| format!("Invalid y2 value '{}'.", coords[3]))?;
Ok((x1, y1, x2, y2))
}