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;
#[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<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)]
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)]
/// use braille pixels
#[arg(long, default_value_t = false, group = "pixel")]
pub braille: bool,
/// use halfblock pixels
#[arg(long, alias = "halfblock", default_value_t = true, group = "pixel")]
pub hb: bool,
/// use quarterblocks pixels
#[arg(long, alias = "quarterblock", default_value_t = false, group = "pixel")]
pub qb: bool,
/// 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.0)]
/// 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 ("<radius>,<intensity>")
/// oil filter ("[radius],[intensity]")
#[arg(long)]
pub oil: Option<String>,
/// 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()
}
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))
}