ZShell settings add
This commit is contained in:
Generated
+20
-76
@@ -260,12 +260,6 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "equivalent"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "exr"
|
name = "exr"
|
||||||
version = "1.74.0"
|
version = "1.74.0"
|
||||||
@@ -345,12 +339,6 @@ dependencies = [
|
|||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.17.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.25.10"
|
version = "0.25.10"
|
||||||
@@ -391,16 +379,6 @@ version = "1.12.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2"
|
checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "indexmap"
|
|
||||||
version = "2.14.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
|
||||||
dependencies = [
|
|
||||||
"equivalent",
|
|
||||||
"hashbrown",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "interpolate_name"
|
name = "interpolate_name"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
@@ -421,6 +399,12 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.34"
|
version = "0.1.34"
|
||||||
@@ -813,8 +797,8 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"image",
|
"image",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tiny-skia",
|
"tiny-skia",
|
||||||
"toml",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -854,12 +838,16 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_json"
|
||||||
version = "0.6.9"
|
version = "1.0.149"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -972,47 +960,6 @@ dependencies = [
|
|||||||
"strict-num",
|
"strict-num",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml"
|
|
||||||
version = "0.8.23"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde_spanned",
|
|
||||||
"toml_datetime",
|
|
||||||
"toml_edit",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_datetime"
|
|
||||||
version = "0.6.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_edit"
|
|
||||||
version = "0.22.27"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
|
||||||
dependencies = [
|
|
||||||
"indexmap",
|
|
||||||
"serde",
|
|
||||||
"serde_spanned",
|
|
||||||
"toml_datetime",
|
|
||||||
"toml_write",
|
|
||||||
"winnow",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_write"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.24"
|
version = "1.0.24"
|
||||||
@@ -1090,15 +1037,6 @@ version = "0.1.12"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winnow"
|
|
||||||
version = "0.7.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
version = "0.57.1"
|
version = "0.57.1"
|
||||||
@@ -1131,6 +1069,12 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-core"
|
name = "zune-core"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|||||||
+1
-1
@@ -16,10 +16,10 @@ tiny-skia = "0.11"
|
|||||||
|
|
||||||
# Config serialization
|
# Config serialization
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
toml = "0.8"
|
|
||||||
|
|
||||||
# Error handling
|
# Error handling
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
serde_json = "1.0.149"
|
||||||
|
|
||||||
# ── Build profiles ────────────────────────────────────────────────────────────
|
# ── Build profiles ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
+6
-50
@@ -2,35 +2,20 @@ use anyhow::{Context, Result};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// Top-level application configuration.
|
|
||||||
/// Serialized to/from ~/.config/rs-pictures/config.toml
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Visual effects applied to images.
|
#[serde(rename = "screenshot")]
|
||||||
pub effects: EffectsConfig,
|
pub effects: EffectsConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct EffectsConfig {
|
pub struct EffectsConfig {
|
||||||
/// Apply rounded corners to the image.
|
|
||||||
pub rounded_corners: bool,
|
pub rounded_corners: bool,
|
||||||
|
|
||||||
/// Radius in pixels for rounded corners.
|
|
||||||
pub corner_radius: f32,
|
pub corner_radius: f32,
|
||||||
|
|
||||||
/// Apply a drop shadow beneath the image.
|
|
||||||
pub drop_shadow: bool,
|
pub drop_shadow: bool,
|
||||||
|
|
||||||
/// Blur radius for the drop shadow (higher = softer shadow).
|
|
||||||
pub shadow_blur_radius: f32,
|
pub shadow_blur_radius: f32,
|
||||||
|
|
||||||
/// Horizontal offset of the shadow in pixels.
|
|
||||||
pub shadow_offset_x: f32,
|
pub shadow_offset_x: f32,
|
||||||
|
|
||||||
/// Vertical offset of the shadow in pixels.
|
|
||||||
pub shadow_offset_y: f32,
|
pub shadow_offset_y: f32,
|
||||||
|
|
||||||
/// Shadow color as [R, G, B, A] in 0..=255.
|
|
||||||
pub shadow_color: [u8; 4],
|
pub shadow_color: [u8; 4],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,52 +42,23 @@ impl Default for Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
/// Returns the path to the config file: ~/.config/rs-pictures/config.toml
|
|
||||||
pub fn config_path() -> Option<PathBuf> {
|
pub fn config_path() -> Option<PathBuf> {
|
||||||
let home = std::env::var("HOME").ok()?;
|
let home = std::env::var("HOME").ok()?;
|
||||||
Some(
|
Some(
|
||||||
PathBuf::from(home)
|
PathBuf::from(home)
|
||||||
.join(".config")
|
.join(".config")
|
||||||
.join("rs-pictures")
|
.join("zshell")
|
||||||
.join("config.toml"),
|
.join("config.json"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load config from disk, or write and return the default config if the file doesn't exist.
|
|
||||||
pub fn load() -> Result<Self> {
|
pub fn load() -> Result<Self> {
|
||||||
let path = match Self::config_path() {
|
let path = Self::config_path().context("Could not determine HOME directory")?;
|
||||||
Some(p) => p,
|
|
||||||
None => return Ok(Self::default()),
|
|
||||||
};
|
|
||||||
|
|
||||||
if !path.exists() {
|
|
||||||
let config = Self::default();
|
|
||||||
config.save()?;
|
|
||||||
return Ok(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
let raw = std::fs::read_to_string(&path)
|
let raw = std::fs::read_to_string(&path)
|
||||||
.with_context(|| format!("Failed to read config at {}", path.display()))?;
|
.with_context(|| format!("Failed to read config at {}", path.display()))?;
|
||||||
|
|
||||||
toml::from_str(&raw)
|
serde_json::from_str(&raw)
|
||||||
.with_context(|| format!("Failed to parse config at {}", path.display()))
|
.with_context(|| format!("Failed to parse JSON config at {}", path.display()))
|
||||||
}
|
|
||||||
|
|
||||||
/// Persist the current config to disk.
|
|
||||||
pub fn save(&self) -> Result<()> {
|
|
||||||
let path =
|
|
||||||
Self::config_path().context("Could not determine config directory (HOME not set)")?;
|
|
||||||
|
|
||||||
if let Some(parent) = path.parent() {
|
|
||||||
std::fs::create_dir_all(parent)
|
|
||||||
.with_context(|| format!("Failed to create config dir {}", parent.display()))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let serialized = toml::to_string_pretty(self).context("Failed to serialize config")?;
|
|
||||||
|
|
||||||
std::fs::write(&path, serialized)
|
|
||||||
.with_context(|| format!("Failed to write config to {}", path.display()))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,9 @@
|
|||||||
//! Post-capture image effects: rounded corners and drop shadow.
|
|
||||||
//!
|
|
||||||
//! Pipeline:
|
|
||||||
//! RgbaImage (captured)
|
|
||||||
//! → apply_rounded_corners() – clips corners to transparent via tiny-skia mask
|
|
||||||
//! → apply_drop_shadow() – composites a blurred shadow beneath the image
|
|
||||||
//! → final RgbaImage (may be larger when shadow is added)
|
|
||||||
//!
|
|
||||||
//! Performance notes:
|
|
||||||
//! - Box blur uses a sliding-window algorithm: O(W*H) regardless of radius.
|
|
||||||
//! Three passes of the box filter approximate a Gaussian.
|
|
||||||
//! - Pixel format conversions between RgbaImage and tiny-skia Pixmap are done
|
|
||||||
//! with a single pass each way.
|
|
||||||
//! - This module is called from a background thread in review.rs so the UI
|
|
||||||
//! never blocks.
|
|
||||||
|
|
||||||
use crate::config::EffectsConfig;
|
use crate::config::EffectsConfig;
|
||||||
use image::RgbaImage;
|
use image::RgbaImage;
|
||||||
use tiny_skia::{
|
use tiny_skia::{
|
||||||
BlendMode, Color, FillRule, Paint, Path, PathBuilder, Pixmap, PixmapPaint, Transform,
|
BlendMode, Color, FillRule, Paint, Path, PathBuilder, Pixmap, PixmapPaint, Transform,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Apply all configured effects in order. Returns a new image.
|
|
||||||
pub fn apply_effects(img: RgbaImage, cfg: &EffectsConfig) -> RgbaImage {
|
pub fn apply_effects(img: RgbaImage, cfg: &EffectsConfig) -> RgbaImage {
|
||||||
let img = if cfg.rounded_corners {
|
let img = if cfg.rounded_corners {
|
||||||
apply_rounded_corners(img, cfg.corner_radius)
|
apply_rounded_corners(img, cfg.corner_radius)
|
||||||
@@ -40,8 +23,6 @@ pub fn apply_effects(img: RgbaImage, cfg: &EffectsConfig) -> RgbaImage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Rounded corners ─────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
pub fn apply_rounded_corners(img: RgbaImage, radius: f32) -> RgbaImage {
|
pub fn apply_rounded_corners(img: RgbaImage, radius: f32) -> RgbaImage {
|
||||||
let (w, h) = img.dimensions();
|
let (w, h) = img.dimensions();
|
||||||
let mut mask = Pixmap::new(w, h).expect("mask pixmap");
|
let mut mask = Pixmap::new(w, h).expect("mask pixmap");
|
||||||
@@ -64,8 +45,6 @@ pub fn apply_rounded_corners(img: RgbaImage, radius: f32) -> RgbaImage {
|
|||||||
pixmap_to_rgba_image(pixmap)
|
pixmap_to_rgba_image(pixmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Drop shadow ─────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
pub fn apply_drop_shadow(
|
pub fn apply_drop_shadow(
|
||||||
img: RgbaImage,
|
img: RgbaImage,
|
||||||
blur_radius: f32,
|
blur_radius: f32,
|
||||||
@@ -84,7 +63,6 @@ pub fn apply_drop_shadow(
|
|||||||
let canvas_w = iw + extra_left + extra_right;
|
let canvas_w = iw + extra_left + extra_right;
|
||||||
let canvas_h = ih + extra_top + extra_bottom;
|
let canvas_h = ih + extra_top + extra_bottom;
|
||||||
|
|
||||||
// 1. Place the image silhouette at the shadow position.
|
|
||||||
let mut shadow_pixmap = Pixmap::new(canvas_w, canvas_h).expect("shadow pixmap");
|
let mut shadow_pixmap = Pixmap::new(canvas_w, canvas_h).expect("shadow pixmap");
|
||||||
let img_pixmap = rgba_image_to_pixmap(&img);
|
let img_pixmap = rgba_image_to_pixmap(&img);
|
||||||
let shadow_x = (extra_left as f32 + offset_x) as i32;
|
let shadow_x = (extra_left as f32 + offset_x) as i32;
|
||||||
@@ -101,15 +79,12 @@ pub fn apply_drop_shadow(
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. Tint the silhouette with the shadow colour.
|
|
||||||
tint_pixmap_as_shadow(&mut shadow_pixmap, shadow_color);
|
tint_pixmap_as_shadow(&mut shadow_pixmap, shadow_color);
|
||||||
|
|
||||||
// 3. Blur the shadow (sliding-window box blur, 3 passes).
|
|
||||||
let shadow_img = pixmap_to_rgba_image(shadow_pixmap);
|
let shadow_img = pixmap_to_rgba_image(shadow_pixmap);
|
||||||
let blurred = box_blur_rgba(&shadow_img, br);
|
let blurred = box_blur_rgba(&shadow_img, br);
|
||||||
let blurred_pixmap = rgba_image_to_pixmap(&blurred);
|
let blurred_pixmap = rgba_image_to_pixmap(&blurred);
|
||||||
|
|
||||||
// 4. Composite: shadow first, image on top.
|
|
||||||
let mut canvas = Pixmap::new(canvas_w, canvas_h).expect("canvas pixmap");
|
let mut canvas = Pixmap::new(canvas_w, canvas_h).expect("canvas pixmap");
|
||||||
let mut p = PixmapPaint::default();
|
let mut p = PixmapPaint::default();
|
||||||
p.blend_mode = BlendMode::Source;
|
p.blend_mode = BlendMode::Source;
|
||||||
@@ -136,8 +111,6 @@ pub fn apply_drop_shadow(
|
|||||||
pixmap_to_rgba_image(canvas)
|
pixmap_to_rgba_image(canvas)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
fn rounded_rect_path(x: f32, y: f32, w: f32, h: f32, r: f32) -> Path {
|
fn rounded_rect_path(x: f32, y: f32, w: f32, h: f32, r: f32) -> Path {
|
||||||
let r = r.min(w / 2.0).min(h / 2.0);
|
let r = r.min(w / 2.0).min(h / 2.0);
|
||||||
let mut pb = PathBuilder::new();
|
let mut pb = PathBuilder::new();
|
||||||
@@ -211,18 +184,10 @@ fn tint_pixmap_as_shadow(pixmap: &mut Pixmap, color: [u8; 4]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Sliding-window box blur (O(W*H) regardless of radius) ───────────────────
|
|
||||||
//
|
|
||||||
// Classic algorithm: maintain a running sum over a window of (2r+1) pixels.
|
|
||||||
// When the window slides by one pixel, subtract the pixel leaving the window
|
|
||||||
// and add the pixel entering it. Three passes (H→V→H or H→V→H) approximate
|
|
||||||
// a Gaussian kernel.
|
|
||||||
|
|
||||||
fn box_blur_rgba(img: &RgbaImage, radius: u32) -> RgbaImage {
|
fn box_blur_rgba(img: &RgbaImage, radius: u32) -> RgbaImage {
|
||||||
if radius == 0 {
|
if radius == 0 {
|
||||||
return img.clone();
|
return img.clone();
|
||||||
}
|
}
|
||||||
// Three passes of H+V to approximate a Gaussian.
|
|
||||||
let mut buf = sliding_horizontal(img, radius);
|
let mut buf = sliding_horizontal(img, radius);
|
||||||
buf = sliding_vertical(&buf, radius);
|
buf = sliding_vertical(&buf, radius);
|
||||||
buf = sliding_horizontal(&buf, radius);
|
buf = sliding_horizontal(&buf, radius);
|
||||||
@@ -230,7 +195,6 @@ fn box_blur_rgba(img: &RgbaImage, radius: u32) -> RgbaImage {
|
|||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Horizontal sliding-window box blur, single pass.
|
|
||||||
fn sliding_horizontal(img: &RgbaImage, radius: u32) -> RgbaImage {
|
fn sliding_horizontal(img: &RgbaImage, radius: u32) -> RgbaImage {
|
||||||
let (w, h) = img.dimensions();
|
let (w, h) = img.dimensions();
|
||||||
let r = radius as i32;
|
let r = radius as i32;
|
||||||
@@ -238,13 +202,11 @@ fn sliding_horizontal(img: &RgbaImage, radius: u32) -> RgbaImage {
|
|||||||
let mut out = RgbaImage::new(w, h);
|
let mut out = RgbaImage::new(w, h);
|
||||||
|
|
||||||
for y in 0..h {
|
for y in 0..h {
|
||||||
// Accumulator for the current window.
|
|
||||||
let mut sr = 0u32;
|
let mut sr = 0u32;
|
||||||
let mut sg = 0u32;
|
let mut sg = 0u32;
|
||||||
let mut sb = 0u32;
|
let mut sb = 0u32;
|
||||||
let mut sa = 0u32;
|
let mut sa = 0u32;
|
||||||
|
|
||||||
// Seed the window around x=0.
|
|
||||||
for dx in -r..=r {
|
for dx in -r..=r {
|
||||||
let sx = dx.clamp(0, w as i32 - 1) as u32;
|
let sx = dx.clamp(0, w as i32 - 1) as u32;
|
||||||
let p = img.get_pixel(sx, y).0;
|
let p = img.get_pixel(sx, y).0;
|
||||||
@@ -266,7 +228,6 @@ fn sliding_horizontal(img: &RgbaImage, radius: u32) -> RgbaImage {
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Slide: remove left edge, add right edge.
|
|
||||||
let remove_x = (x as i32 - r).clamp(0, w as i32 - 1) as u32;
|
let remove_x = (x as i32 - r).clamp(0, w as i32 - 1) as u32;
|
||||||
let add_x = (x as i32 + r + 1).clamp(0, w as i32 - 1) as u32;
|
let add_x = (x as i32 + r + 1).clamp(0, w as i32 - 1) as u32;
|
||||||
let rp = img.get_pixel(remove_x, y).0;
|
let rp = img.get_pixel(remove_x, y).0;
|
||||||
@@ -280,7 +241,6 @@ fn sliding_horizontal(img: &RgbaImage, radius: u32) -> RgbaImage {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Vertical sliding-window box blur, single pass.
|
|
||||||
fn sliding_vertical(img: &RgbaImage, radius: u32) -> RgbaImage {
|
fn sliding_vertical(img: &RgbaImage, radius: u32) -> RgbaImage {
|
||||||
let (w, h) = img.dimensions();
|
let (w, h) = img.dimensions();
|
||||||
let r = radius as i32;
|
let r = radius as i32;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use std::io::Write as _;
|
|||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
// ── 1. Collect image paths from CLI arguments ─────────────────────────────
|
|
||||||
let paths: Vec<String> = std::env::args().skip(1).collect();
|
let paths: Vec<String> = std::env::args().skip(1).collect();
|
||||||
|
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
@@ -15,10 +14,8 @@ fn main() -> Result<()> {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 2. Load config (generates default if missing) ─────────────────────────
|
|
||||||
let config = config::Config::load().context("Failed to load config")?;
|
let config = config::Config::load().context("Failed to load config")?;
|
||||||
|
|
||||||
// ── 3. Process each image ─────────────────────────────────────────────────
|
|
||||||
for path in &paths {
|
for path in &paths {
|
||||||
if let Err(e) = process_image(path, &config) {
|
if let Err(e) = process_image(path, &config) {
|
||||||
eprintln!("Error processing '{}': {e:#}", path);
|
eprintln!("Error processing '{}': {e:#}", path);
|
||||||
@@ -29,15 +26,12 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn process_image(path: &str, config: &config::Config) -> Result<()> {
|
fn process_image(path: &str, config: &config::Config) -> Result<()> {
|
||||||
// ── a. Load image ─────────────────────────────────────────────────────────
|
|
||||||
let img = image::open(path)
|
let img = image::open(path)
|
||||||
.with_context(|| format!("Failed to open image '{path}'"))?
|
.with_context(|| format!("Failed to open image '{path}'"))?
|
||||||
.into_rgba8();
|
.into_rgba8();
|
||||||
|
|
||||||
// ── b. Apply effects (rounded corners + drop shadow) ─────────────────────
|
|
||||||
let processed = effects::apply_effects(img, &config.effects);
|
let processed = effects::apply_effects(img, &config.effects);
|
||||||
|
|
||||||
// ── c. Encode as PNG into memory ──────────────────────────────────────────
|
|
||||||
let mut png_bytes: Vec<u8> = Vec::new();
|
let mut png_bytes: Vec<u8> = Vec::new();
|
||||||
image::DynamicImage::ImageRgba8(processed)
|
image::DynamicImage::ImageRgba8(processed)
|
||||||
.write_to(
|
.write_to(
|
||||||
@@ -45,9 +39,7 @@ fn process_image(path: &str, config: &config::Config) -> Result<()> {
|
|||||||
image::ImageFormat::Png,
|
image::ImageFormat::Png,
|
||||||
)
|
)
|
||||||
.context("Failed to encode processed image as PNG")?;
|
.context("Failed to encode processed image as PNG")?;
|
||||||
// Re-unwrap into RgbaImage — not needed further, png_bytes is ready
|
|
||||||
|
|
||||||
// ── d. Pipe PNG bytes to `swappy -f -` ────────────────────────────────────
|
|
||||||
let mut child = Command::new("swappy")
|
let mut child = Command::new("swappy")
|
||||||
.args(["-f", "-"])
|
.args(["-f", "-"])
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
|
|||||||
Reference in New Issue
Block a user