gardesk/garshot / 044925d

Browse files

cli: add --annotate flag and annotate subcommand

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
044925d3f741d05dbd29e2d1e311febea7ad4318
Parents
fd064e5
Tree
a842a5d

1 changed file

StatusFile+-
M garshot/src/main.rs 156 4
garshot/src/main.rsmodified
@@ -11,6 +11,7 @@ use anyhow::Context;
1111
 use clap::{Parser, Subcommand};
1212
 use tracing_subscriber::EnvFilter;
1313
 
14
+use garshot::annotate::{AnnotationOverlay, AnnotationResult};
1415
 use garshot::capture::{
1516
     blend_cursor, capture_full_screen, capture_region, get_cursor_image, Region,
1617
 };
@@ -61,6 +62,10 @@ enum Command {
6162
         /// Show desktop notification on save.
6263
         #[arg(long)]
6364
         notify: bool,
65
+
66
+        /// Open annotation editor after capture.
67
+        #[arg(short, long)]
68
+        annotate: bool,
6469
     },
6570
 
6671
     /// Capture a region by geometry.
@@ -92,6 +97,10 @@ enum Command {
9297
         /// Show desktop notification on save.
9398
         #[arg(long)]
9499
         notify: bool,
100
+
101
+        /// Open annotation editor after capture.
102
+        #[arg(short, long)]
103
+        annotate: bool,
95104
     },
96105
 
97106
     /// Capture a window.
@@ -127,6 +136,10 @@ enum Command {
127136
         /// Show desktop notification on save.
128137
         #[arg(long)]
129138
         notify: bool,
139
+
140
+        /// Open annotation editor after capture.
141
+        #[arg(short, long)]
142
+        annotate: bool,
130143
     },
131144
 
132145
     /// Interactive region selection with blur overlay.
@@ -154,6 +167,24 @@ enum Command {
154167
         /// Show desktop notification on save.
155168
         #[arg(long)]
156169
         notify: bool,
170
+
171
+        /// Open annotation editor after capture.
172
+        #[arg(short, long)]
173
+        annotate: bool,
174
+    },
175
+
176
+    /// Annotate an existing image file.
177
+    Annotate {
178
+        /// Input image file to annotate.
179
+        file: PathBuf,
180
+
181
+        /// Output file path (default: overwrites input, use "-" for stdout).
182
+        #[arg(short, long)]
183
+        output: Option<PathBuf>,
184
+
185
+        /// Output format (defaults to input format).
186
+        #[arg(short, long)]
187
+        format: Option<String>,
157188
     },
158189
 
159190
     /// List available monitors.
@@ -192,6 +223,7 @@ fn main() -> anyhow::Result<()> {
192223
             no_clipboard,
193224
             delay,
194225
             notify,
226
+            annotate,
195227
         }) => {
196228
             if let Some(secs) = delay {
197229
                 sleep_with_countdown(secs);
@@ -202,7 +234,23 @@ fn main() -> anyhow::Result<()> {
202234
             } else {
203235
                 Some(output.unwrap_or_else(|| default_output(&config, &format)))
204236
             };
205
-            let (data, width, height) = capture_screen_raw(&format, monitor.as_deref(), cursor)?;
237
+            let (mut data, mut width, mut height) = capture_screen_raw(&format, monitor.as_deref(), cursor)?;
238
+
239
+            // Open annotation editor if requested
240
+            if annotate {
241
+                match run_annotation(&data, width, height)? {
242
+                    Some((new_data, new_w, new_h)) => {
243
+                        data = new_data;
244
+                        width = new_w;
245
+                        height = new_h;
246
+                    }
247
+                    None => {
248
+                        tracing::info!("Annotation cancelled");
249
+                        return Ok(());
250
+                    }
251
+                }
252
+            }
253
+
206254
             if let Some(ref path) = output_path {
207255
                 save_image(&data, width, height, path, &format)?;
208256
                 if !no_clipboard {
@@ -225,6 +273,7 @@ fn main() -> anyhow::Result<()> {
225273
             no_clipboard,
226274
             delay,
227275
             notify,
276
+            annotate,
228277
         }) => {
229278
             if let Some(secs) = delay {
230279
                 sleep_with_countdown(secs);
@@ -235,7 +284,23 @@ fn main() -> anyhow::Result<()> {
235284
             } else {
236285
                 Some(output.unwrap_or_else(|| default_output(&config, &format)))
237286
             };
238
-            let (data, width, height) = capture_region_raw(&geometry, cursor)?;
287
+            let (mut data, mut width, mut height) = capture_region_raw(&geometry, cursor)?;
288
+
289
+            // Open annotation editor if requested
290
+            if annotate {
291
+                match run_annotation(&data, width, height)? {
292
+                    Some((new_data, new_w, new_h)) => {
293
+                        data = new_data;
294
+                        width = new_w;
295
+                        height = new_h;
296
+                    }
297
+                    None => {
298
+                        tracing::info!("Annotation cancelled");
299
+                        return Ok(());
300
+                    }
301
+                }
302
+            }
303
+
239304
             if let Some(ref path) = output_path {
240305
                 save_image(&data, width, height, path, &format)?;
241306
                 if !no_clipboard {
@@ -259,6 +324,7 @@ fn main() -> anyhow::Result<()> {
259324
             no_clipboard,
260325
             delay,
261326
             notify,
327
+            annotate,
262328
         }) => {
263329
             if let Some(secs) = delay {
264330
                 sleep_with_countdown(secs);
@@ -269,7 +335,23 @@ fn main() -> anyhow::Result<()> {
269335
             } else {
270336
                 Some(output.unwrap_or_else(|| default_output(&config, &format)))
271337
             };
272
-            let (data, width, height) = capture_window_raw(id.as_deref(), decorations, cursor)?;
338
+            let (mut data, mut width, mut height) = capture_window_raw(id.as_deref(), decorations, cursor)?;
339
+
340
+            // Open annotation editor if requested
341
+            if annotate {
342
+                match run_annotation(&data, width, height)? {
343
+                    Some((new_data, new_w, new_h)) => {
344
+                        data = new_data;
345
+                        width = new_w;
346
+                        height = new_h;
347
+                    }
348
+                    None => {
349
+                        tracing::info!("Annotation cancelled");
350
+                        return Ok(());
351
+                    }
352
+                }
353
+            }
354
+
273355
             if let Some(ref path) = output_path {
274356
                 save_image(&data, width, height, path, &format)?;
275357
                 if !no_clipboard {
@@ -291,6 +373,7 @@ fn main() -> anyhow::Result<()> {
291373
             blur,
292374
             no_clipboard,
293375
             notify,
376
+            annotate,
294377
         }) => {
295378
             let is_stdout = output.as_ref().map(|p| p.as_os_str() == "-").unwrap_or(false);
296379
             let output_path = if is_stdout {
@@ -298,7 +381,22 @@ fn main() -> anyhow::Result<()> {
298381
             } else {
299382
                 Some(output.unwrap_or_else(|| default_output(&config, &format)))
300383
             };
301
-            if let Some((data, width, height)) = capture_select_raw(cursor, blur)? {
384
+            if let Some((mut data, mut width, mut height)) = capture_select_raw(cursor, blur)? {
385
+                // Open annotation editor if requested
386
+                if annotate {
387
+                    match run_annotation(&data, width, height)? {
388
+                        Some((new_data, new_w, new_h)) => {
389
+                            data = new_data;
390
+                            width = new_w;
391
+                            height = new_h;
392
+                        }
393
+                        None => {
394
+                            tracing::info!("Annotation cancelled");
395
+                            return Ok(());
396
+                        }
397
+                    }
398
+                }
399
+
302400
                 if let Some(ref path) = output_path {
303401
                     save_image(&data, width, height, path, &format)?;
304402
                     if !no_clipboard {
@@ -330,6 +428,42 @@ fn main() -> anyhow::Result<()> {
330428
             }
331429
         }
332430
 
431
+        Some(Command::Annotate { file, output, format }) => {
432
+            // Load image file
433
+            let img = image::open(&file).context("Failed to open image file")?;
434
+            let rgba = img.to_rgba8();
435
+            let width = rgba.width();
436
+            let height = rgba.height();
437
+            let data = rgba.into_raw();
438
+
439
+            tracing::info!("Opening annotation editor for {}", file.display());
440
+
441
+            // Run annotation
442
+            match run_annotation(&data, width, height)? {
443
+                Some((annotated_data, w, h)) => {
444
+                    // Determine output path and format
445
+                    let out_path = output.unwrap_or_else(|| file.clone());
446
+                    let is_stdout = out_path.as_os_str() == "-";
447
+                    let out_format = format.unwrap_or_else(|| {
448
+                        file.extension()
449
+                            .and_then(|e| e.to_str())
450
+                            .unwrap_or("png")
451
+                            .to_string()
452
+                    });
453
+
454
+                    if is_stdout {
455
+                        write_stdout(&annotated_data, w, h, &out_format)?;
456
+                    } else {
457
+                        save_image(&annotated_data, w, h, &out_path, &out_format)?;
458
+                        println!("{}", out_path.display());
459
+                    }
460
+                }
461
+                None => {
462
+                    tracing::info!("Annotation cancelled");
463
+                }
464
+            }
465
+        }
466
+
333467
         Some(Command::Daemon) => {
334468
             run_daemon()?;
335469
         }
@@ -670,3 +804,21 @@ fn run_daemon() -> anyhow::Result<()> {
670804
     tracing::info!("Daemon stopped");
671805
     Ok(())
672806
 }
807
+
808
+/// Run the annotation overlay and return the annotated image data.
809
+/// Returns None if the user cancelled.
810
+fn run_annotation(
811
+    data: &[u8],
812
+    width: u32,
813
+    height: u32,
814
+) -> anyhow::Result<Option<(Vec<u8>, u32, u32)>> {
815
+    let overlay = AnnotationOverlay::new(data, width, height)
816
+        .context("Failed to create annotation overlay")?;
817
+
818
+    match overlay.run().context("Annotation overlay failed")? {
819
+        AnnotationResult::Save { data, width, height } => {
820
+            Ok(Some((data, width, height)))
821
+        }
822
+        AnnotationResult::Cancel => Ok(None),
823
+    }
824
+}