diff --git a/src/cli.rs b/src/cli.rs index e969a212..62a725d2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -137,6 +137,13 @@ impl CliPosition { } } +#[derive(Clone)] +pub enum CliImage { + Path(PathBuf), + /// Single rgb color + Color([u8; 3]), +} + #[derive(Parser)] #[command(version, name = "swww")] ///A Solution to your Wayland Wallpaper Woes @@ -246,8 +253,9 @@ pub struct Restore { #[derive(Parser)] pub struct Img { - /// Path to the image to display - pub path: PathBuf, + /// Path of image or hexcode (starting with 0x) to display + #[arg(value_parser = parse_image)] + pub image: CliImage, /// Comma separated list of outputs to display the image at. /// @@ -423,6 +431,19 @@ fn parse_bezier(raw: &str) -> Result<(f32, f32, f32, f32), String> { Ok(parsed) } +pub fn parse_image(raw: &str) -> Result { + let path = PathBuf::from(raw); + if path.exists() { + return Ok(CliImage::Path(path)); + } + if let Some(color) = raw.strip_prefix("0x") { + if let Ok(color) = from_hex(color) { + return Ok(CliImage::Color(color)); + } + } + Err(format!("Path '{}' does not exist", raw)) +} + // parses Percents and numbers in format of "," fn parse_coords(raw: &str) -> Result { let coords = raw.split(',').map(|s| s.trim()).collect::>(); diff --git a/src/main.rs b/src/main.rs index 1d713d63..5a0246a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use clap::Parser; -use std::{path::PathBuf, process::Stdio, time::Duration}; +use std::{process::Stdio, time::Duration}; use utils::{ cache, @@ -10,7 +10,7 @@ mod imgproc; use imgproc::*; mod cli; -use cli::{ResizeStrategy, Swww}; +use cli::{CliImage, ResizeStrategy, Swww}; fn main() -> Result<(), String> { let swww = Swww::parse(); @@ -137,8 +137,10 @@ fn make_request(args: &Swww) -> Result, String> { Swww::Img(img) => { let requested_outputs = split_cmdline_outputs(&img.outputs); let (format, dims, outputs) = get_format_dims_and_outputs(&requested_outputs)?; - let imgbuf = ImgBuf::new(&img.path)?; - let img_request = make_img_request(img, &imgbuf, &dims, format, &outputs)?; + // let imgbuf = ImgBuf::new(&img.path)?; + + let img_request = make_img_request(img, &dims, format, &outputs)?; + Ok(Some(RequestSend::Img(img_request))) } Swww::Init { no_cache, .. } => { @@ -154,73 +156,97 @@ fn make_request(args: &Swww) -> Result, String> { fn make_img_request( img: &cli::Img, - imgbuf: &ImgBuf, dims: &[(u32, u32)], pixel_format: ipc::PixelFormat, outputs: &[Vec], ) -> Result { - let img_raw = imgbuf.decode(pixel_format)?; let transition = make_transition(img); let mut img_req_builder = ipc::ImageRequestBuilder::new(transition); - for (&dim, outputs) in dims.iter().zip(outputs) { - let path = match img.path.canonicalize() { - Ok(p) => p.to_string_lossy().to_string(), - Err(e) => { - if let Some("-") = img.path.to_str() { - "STDIN".to_string() - } else { - return Err(format!("failed no canonicalize image path: {e}")); - } + + match &img.image { + CliImage::Color(color) => { + for (&dim, outputs) in dims.iter().zip(outputs) { + img_req_builder.push( + ipc::ImgSend { + img: image::RgbImage::from_pixel(dim.0, dim.1, image::Rgb(*color)) + .to_vec() + .into_boxed_slice(), + path: format!("0x{:02x}{:02x}{:02x}", color[0], color[1], color[2]), + dim, + format: pixel_format, + }, + outputs, + None, + ); } - }; + } + CliImage::Path(img_path) => { + let imgbuf = ImgBuf::new(img_path)?; + let img_raw = imgbuf.decode(pixel_format)?; - let animation = if !imgbuf.is_animated() { - None - } else if img.resize == ResizeStrategy::Crop { - match cache::load_animation_frames(&img.path, dim, pixel_format) { - Ok(Some(animation)) => Some(animation), - otherwise => { - if let Err(e) = otherwise { - eprintln!("Error loading cache for {:?}: {e}", img.path); + for (&dim, outputs) in dims.iter().zip(outputs) { + let path = match img_path.canonicalize() { + Ok(p) => p.to_string_lossy().to_string(), + Err(e) => { + if let Some("-") = img_path.to_str() { + "STDIN".to_string() + } else { + return Err(format!("failed no canonicalize image path: {e}")); + } } + }; - Some({ - ipc::Animation { - animation: compress_frames( - imgbuf.as_frames()?, - dim, - pixel_format, - make_filter(&img.filter), - img.resize, - &img.fill_color, - )? - .into_boxed_slice(), + let animation = if !imgbuf.is_animated() { + None + } else if img.resize == ResizeStrategy::Crop { + match cache::load_animation_frames(img_path, dim, pixel_format) { + Ok(Some(animation)) => Some(animation), + otherwise => { + if let Err(e) = otherwise { + eprintln!("Error loading cache for {:?}: {e}", img_path); + } + + Some({ + ipc::Animation { + animation: compress_frames( + imgbuf.as_frames()?, + dim, + pixel_format, + make_filter(&img.filter), + img.resize, + &img.fill_color, + )? + .into_boxed_slice(), + } + }) } - }) - } - } - } else { - None - }; + } + } else { + None + }; - let img = match img.resize { - ResizeStrategy::No => img_pad(&img_raw, dim, &img.fill_color)?, - ResizeStrategy::Crop => img_resize_crop(&img_raw, dim, make_filter(&img.filter))?, - ResizeStrategy::Fit => { - img_resize_fit(&img_raw, dim, make_filter(&img.filter), &img.fill_color)? - } - }; + let img = match img.resize { + ResizeStrategy::No => img_pad(&img_raw, dim, &img.fill_color)?, + ResizeStrategy::Crop => { + img_resize_crop(&img_raw, dim, make_filter(&img.filter))? + } + ResizeStrategy::Fit => { + img_resize_fit(&img_raw, dim, make_filter(&img.filter), &img.fill_color)? + } + }; - img_req_builder.push( - ipc::ImgSend { - img, - path, - dim, - format: pixel_format, - }, - outputs, - animation, - ); + img_req_builder.push( + ipc::ImgSend { + img, + path, + dim, + format: pixel_format, + }, + outputs, + animation, + ); + } + } } Ok(img_req_builder.build()) @@ -332,7 +358,7 @@ fn restore_from_cache(requested_outputs: &[String]) -> Result<(), String> { .map_err(|e| format!("failed to get previous image path: {e}"))?; #[allow(deprecated)] if let Err(e) = process_swww_args(&Swww::Img(cli::Img { - path: PathBuf::from(img_path), + image: cli::parse_image(&img_path)?, outputs: output.to_string(), no_resize: false, resize: ResizeStrategy::Crop,