mod config; mod effects; use anyhow::{Context, Result, bail}; use std::io::Write as _; use std::process::{Command, Stdio}; #[derive(Default)] struct CliOverrides { rounding: Option, radius: Option, shadow: Option, shadow_blur: Option, shadow_offset_x: Option, shadow_offset_y: Option, shadow_color: Option<[u8; 4]>, } fn parse_bool(s: &str) -> Result { match s.to_lowercase().as_str() { "true" | "1" | "yes" => Ok(true), "false" | "0" | "no" => Ok(false), other => bail!("Expected a boolean (true/false), got '{other}'"), } } fn parse_shadow_color(s: &str) -> Result<[u8; 4]> { let parts: Vec<&str> = s.split(',').collect(); if parts.len() != 4 { bail!("--shadow-color expects four comma-separated u8 values, e.g. 255,0,0,200"); } let r = parts[0] .trim() .parse::() .context("shadow-color red channel")?; let g = parts[1] .trim() .parse::() .context("shadow-color green channel")?; let b = parts[2] .trim() .parse::() .context("shadow-color blue channel")?; let a = parts[3] .trim() .parse::() .context("shadow-color alpha channel")?; Ok([r, g, b, a]) } fn extract_image_path() -> Option { let args: Vec = std::env::args().skip(1).collect(); args.windows(2) .find(|w| w[0] == "--image") .map(|w| w[1].clone()) } fn main() { // Fundamental issue when supplying args it won't give output unless --image is used. // Will have to be fixed in a later patch upcoming week if let Some(path) = extract_image_path() && let Err(e) = run() { eprintln!("Error: {}", e); push_image(&path).ok(); } } fn run() -> Result<()> { let args: Vec = std::env::args().skip(1).collect(); let mut image_path: Option = None; let mut overrides = CliOverrides::default(); let mut scale: Option = None; let mut i = 0; while i < args.len() { match args[i].as_str() { "--image" => { image_path = Some(next_arg(&args, &mut i, "--image")?); } "--rounding" => { let val = next_arg(&args, &mut i, "--rounding")?; overrides.rounding = Some(parse_bool(&val)?); } "--radius" => { let val = next_arg(&args, &mut i, "--radius")?; overrides.radius = Some(val.parse::().context("--radius must be a number")?); } "--shadow" => { let val = next_arg(&args, &mut i, "--shadow")?; overrides.shadow = Some(parse_bool(&val)?); } "--shadow-blur" => { let val = next_arg(&args, &mut i, "--shadow-blur")?; overrides.shadow_blur = Some( val.parse::() .context("--shadow-blur must be a number")?, ); } "--shadow-offset-x" => { let val = next_arg(&args, &mut i, "--shadow-offset-x")?; overrides.shadow_offset_x = Some( val.parse::() .context("--shadow-offset-x must be a number")?, ); } "--shadow-offset-y" => { let val = next_arg(&args, &mut i, "--shadow-offset-y")?; overrides.shadow_offset_y = Some( val.parse::() .context("--shadow-offset-y must be a number")?, ); } "--shadow-color" => { let val = next_arg(&args, &mut i, "--shadow-color")?; overrides.shadow_color = Some(parse_shadow_color(&val)?); } "--scale" => { let val = next_arg(&args, &mut i, "--scale")?; scale = Some(val.parse::().context("--scale must be a number")?); } unknown => { let unknown_args = unknown.to_string(); println!("Warning: Unknown argument '{}'", unknown); next_arg(&args, &mut i, &unknown_args)?; } } i += 1; } let image_path = image_path.context("Missing --image ")?; let cli_args_provided = overrides.rounding.is_some() || overrides.radius.is_some() || overrides.shadow.is_some() || overrides.shadow_blur.is_some() || overrides.shadow_offset_x.is_some() || overrides.shadow_offset_y.is_some() || overrides.shadow_color.is_some(); let mut effects = if cli_args_provided { let rounding = overrides.rounding.context("Missing --rounding")?; let radius = overrides.radius.context("Missing --radius")?; let shadow = overrides.shadow.context("Missing --shadow")?; let shadow_blur = overrides.shadow_blur.context("Missing --shadow-blur")?; let shadow_offset_x = overrides .shadow_offset_x .context("Missing --shadow-offset-x")?; let shadow_offset_y = overrides .shadow_offset_y .context("Missing --shadow-offset-y")?; let shadow_color = overrides.shadow_color.context("Missing --shadow-color")?; config::EffectsConfig { rounding, radius, shadow, shadow_blur, shadow_offset_x, shadow_offset_y, shadow_color, } } else { let config = config::Config::load()?; config.screenshot }; if let Some(scale) = scale.filter(|&s| s != 1.0) { effects.radius *= scale; effects.shadow_blur *= scale; effects.shadow_offset_x *= scale; effects.shadow_offset_y *= scale; } if let Err(e) = process_image(&image_path, &effects) { eprintln!("Error processing '{}': {e:#}", image_path); } Ok(()) } fn next_arg(args: &[String], i: &mut usize, flag: &str) -> Result { *i += 1; let val = args .get(*i) .context(format!("Expected value after {}", flag))?; if val.starts_with('-') { bail!("Expected value after {}, found flag {}", flag, val); } Ok(val.clone()) } fn push_image(path: &str) -> Result<()> { let img = image::open(path) .with_context(|| format!("Failed to open image '{path}'"))? .into_rgba8(); let mut png_bytes: Vec = Vec::new(); image::DynamicImage::ImageRgba8(img) .write_to( &mut std::io::Cursor::new(&mut png_bytes), image::ImageFormat::Png, ) .context("Failed to encode processed image as PNG")?; let mut child = Command::new("swappy") .args(["-f", "-"]) .stdin(Stdio::piped()) .spawn() .context("Failed to spawn swappy. Is it installed and in PATH?")?; // Writes the PNG bytes to swappy's stdin and then closes if let Some(mut stdin) = child.stdin.take() { stdin .write_all(&png_bytes) .context("Failed to write image data to swappy")?; } Ok(()) } fn process_image(path: &str, effects: &config::EffectsConfig) -> Result<()> { let img = image::open(path) .with_context(|| format!("Failed to open image '{path}'"))? .into_rgba8(); let processed = effects::apply_effects(img, effects); let mut png_bytes: Vec = Vec::new(); image::DynamicImage::ImageRgba8(processed) .write_to( &mut std::io::Cursor::new(&mut png_bytes), image::ImageFormat::Png, ) .context("Failed to encode processed image as PNG")?; let mut child = Command::new("swappy") .args(["-f", "-"]) .stdin(Stdio::piped()) .spawn() .context("Failed to spawn swappy. Is it installed and in PATH?")?; // Writes the PNG bytes to swappy's stdin and then closes if let Some(mut stdin) = child.stdin.take() { stdin .write_all(&png_bytes) .context("Failed to write image data to swappy")?; } Ok(()) }