diff --git a/.gitignore b/.gitignore index 0b188bc..9fd00ce 100644 --- a/.gitignore +++ b/.gitignore @@ -9,10 +9,3 @@ target/ # MSVC Windows builds of rustc generate these, which store debugging information *.pdb - -# RustRover -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ diff --git a/Cargo.lock b/Cargo.lock index 9943e14..c5e7fe8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -405,6 +405,17 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "iwaku" +version = "0.1.0" +dependencies = [ + "anyhow", + "image", + "serde", + "serde_json", + "tiny-skia", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -790,17 +801,6 @@ version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" -[[package]] -name = "rs-pictures" -version = "0.1.0" -dependencies = [ - "anyhow", - "image", - "serde", - "serde_json", - "tiny-skia", -] - [[package]] name = "rustversion" version = "1.0.22" diff --git a/Cargo.toml b/Cargo.toml index 1d8f6a8..d74c3eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,39 +1,27 @@ [package] -name = "rs-pictures" +name = "iwaku" version = "0.1.0" edition = "2024" [[bin]] -name = "rs-pictures" +name = "iwaku" path = "src/main.rs" [dependencies] -# Image loading and encoding -image = { version = "0.25", features = ["png", "jpeg"] } - -# 2D rendering for effects (rounded corners, drop shadow) +image = { version = "0.25", features = ["png"] } tiny-skia = "0.11" - -# Config serialization serde = { version = "1", features = ["derive"] } - -# Error handling anyhow = "1" serde_json = "1.0.149" -# ── Build profiles ──────────────────────────────────────────────────────────── - [profile.release] opt-level = 3 -lto = "thin" # link-time optimisation across crates -codegen-units = 1 # better inlining at the cost of compile time -strip = true # strip debug symbols → smaller binary +lto = "thin" +codegen-units = 1 +strip = true -# Dev builds are slow for pixel-processing code. This gives opt-level 2 -# to our own crate only while keeping dependencies at their default (opt=3 -# they already compiled with), so incremental rebuilds stay fast. [profile.dev] opt-level = 0 [profile.dev.package."*"] -opt-level = 3 # all deps at full optimisation even in dev mode +opt-level = 3 diff --git a/src/config.rs b/src/config.rs index c997238..3b502f1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,11 +5,12 @@ use std::path::PathBuf; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { #[serde(rename = "screenshot")] - pub effects: EffectsConfig, + pub screenshot: EffectsConfig, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EffectsConfig { + pub mode: String, pub rounded_corners: bool, pub corner_radius: f32, pub drop_shadow: bool, @@ -19,28 +20,6 @@ pub struct EffectsConfig { pub shadow_color: [u8; 4], } -impl Default for EffectsConfig { - fn default() -> Self { - Self { - rounded_corners: false, - corner_radius: 12.0, - drop_shadow: false, - shadow_blur_radius: 20.0, - shadow_offset_x: 5.0, - shadow_offset_y: 8.0, - shadow_color: [0, 0, 0, 160], - } - } -} - -impl Default for Config { - fn default() -> Self { - Self { - effects: EffectsConfig::default(), - } - } -} - impl Config { pub fn config_path() -> Option { let home = std::env::var("HOME").ok()?; @@ -54,10 +33,12 @@ impl Config { pub fn load() -> Result { let path = Self::config_path().context("Could not determine HOME directory")?; + Self::load_from(&path) + } - let raw = std::fs::read_to_string(&path) + pub fn load_from(path: &PathBuf) -> Result { + let raw = std::fs::read_to_string(path) .with_context(|| format!("Failed to read config at {}", path.display()))?; - serde_json::from_str(&raw) .with_context(|| format!("Failed to parse JSON config at {}", path.display())) } diff --git a/src/main.rs b/src/main.rs index d332e53..ab61e5d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,36 +1,70 @@ mod config; mod effects; -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use std::io::Write as _; +use std::path::PathBuf; use std::process::{Command, Stdio}; fn main() -> Result<()> { - let paths: Vec = std::env::args().skip(1).collect(); + let args: Vec = std::env::args().skip(1).collect(); - if paths.is_empty() { - eprintln!("Usage: rs-pictures [image2 ...]"); - eprintln!("No image paths provided."); - std::process::exit(1); + let mut image_path: Option = None; + let mut hypr_path: Option = None; + + let mut i = 0; + while i < args.len() { + match args[i].as_str() { + "-image" => { + i += 1; + image_path = Some( + args.get(i) + .cloned() + .context("Expected a path after -image")?, + ); + } + "-hypr" => { + i += 1; + hypr_path = Some( + args.get(i) + .cloned() + .context("Expected a path after -hypr")?, + ); + } + unknown => bail!("Unknown argument: {unknown}"), + } + i += 1; } + let image_path = + image_path.context("Usage: iwaku -image [-hypr ]\nMissing -image")?; + let config = config::Config::load().context("Failed to load config")?; - for path in &paths { - if let Err(e) = process_image(path, &config) { - eprintln!("Error processing '{}': {e:#}", path); - } + let effects = if config.screenshot.mode == "auto" { + let hypr_config_path = hypr_path + .map(PathBuf::from) + .context("Mode is 'auto' but -hypr was not provided")?; + let hypr_config = + config::Config::load_from(&hypr_config_path).context("Failed to load hypr config")?; + hypr_config.screenshot + } else { + config.screenshot + }; + + if let Err(e) = process_image(&image_path, &effects) { + eprintln!("Error processing '{}': {e:#}", image_path); } Ok(()) } -fn process_image(path: &str, config: &config::Config) -> Result<()> { +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, &config.effects); + let processed = effects::apply_effects(img, effects); let mut png_bytes: Vec = Vec::new(); image::DynamicImage::ImageRgba8(processed)