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}