Skip to main content

halley_capit/
capture.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use image::{DynamicImage, GenericImageView};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct CaptureCrop {
9    pub x: i32,
10    pub y: i32,
11    pub w: i32,
12    pub h: i32,
13}
14
15pub fn crop_image(img: DynamicImage, crop: CaptureCrop) -> Result<DynamicImage, String> {
16    let (iw, ih) = img.dimensions();
17    let x0 = crop.x.max(0) as u32;
18    let y0 = crop.y.max(0) as u32;
19    let x1 = (crop.x.max(0) as u32)
20        .saturating_add(crop.w.max(0) as u32)
21        .min(iw);
22    let y1 = (crop.y.max(0) as u32)
23        .saturating_add(crop.h.max(0) as u32)
24        .min(ih);
25    let cw = x1.saturating_sub(x0);
26    let ch = y1.saturating_sub(y0);
27    if cw == 0 || ch == 0 {
28        return Err(format!(
29            "crop rect empty after clamping: ({},{}) {}x{} within {}x{}",
30            crop.x, crop.y, crop.w, crop.h, iw, ih
31        ));
32    }
33    Ok(img.crop_imm(x0, y0, cw, ch))
34}
35
36pub fn save_cropped_png(src_path: &Path, out_path: &Path, crop: CaptureCrop) -> Result<(), String> {
37    let img = image::open(src_path).map_err(|e| format!("open screenshot: {e}"))?;
38    let cropped = crop_image(img, crop)?;
39    ensure_parent_dir(out_path)?;
40    cropped
41        .save(out_path)
42        .map_err(|e| format!("save cropped screenshot: {e}"))
43}
44
45pub fn default_output_path(stem: &str) -> PathBuf {
46    default_output_path_in(
47        std::env::var_os("HOME")
48            .map(PathBuf::from)
49            .unwrap_or_else(|| PathBuf::from("."))
50            .join("Pictures")
51            .join("Screenshots"),
52        stem,
53    )
54}
55
56pub fn default_output_path_in(dir: PathBuf, stem: &str) -> PathBuf {
57    let nanos = SystemTime::now()
58        .duration_since(UNIX_EPOCH)
59        .unwrap_or_default()
60        .as_nanos();
61    dir.join(format!("{stem}-{nanos}.png"))
62}
63
64pub fn temp_output_path(final_out_path: &Path) -> PathBuf {
65    let nanos = SystemTime::now()
66        .duration_since(UNIX_EPOCH)
67        .unwrap_or_default()
68        .as_nanos();
69    let mut p = final_out_path.to_path_buf();
70    let stem = final_out_path
71        .file_stem()
72        .and_then(|s| s.to_str())
73        .unwrap_or("capit");
74    p.set_file_name(format!("{stem}.halley_capit_tmp_{nanos}.png"));
75    p
76}
77
78pub(crate) fn ensure_parent_dir(path: &Path) -> Result<(), String> {
79    if let Some(parent) = path.parent() {
80        fs::create_dir_all(parent).map_err(|e| format!("create dir {parent:?}: {e}"))?;
81    }
82    Ok(())
83}