gardesk/garshot / 68248f5

Browse files

annotate: add canvas with layered drawing surface

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
68248f524a35e2dcaa5365327e69468e1fcf8f76
Parents
1d9cd07
Tree
08108fc

1 changed file

StatusFile+-
A garshot/src/annotate/canvas.rs 178 0
garshot/src/annotate/canvas.rsadded
@@ -0,0 +1,178 @@
1
+//! Annotation canvas with layered surfaces.
2
+//!
3
+//! The canvas manages three layers:
4
+//! 1. Background: Original screenshot (immutable)
5
+//! 2. Annotations: Committed drawings
6
+//! 3. Preview: Current tool preview (uncommitted)
7
+
8
+use anyhow::{Context, Result};
9
+use gartk_core::Size;
10
+use gartk_render::Surface;
11
+
12
+/// Annotation canvas with layered rendering.
13
+pub struct AnnotationCanvas {
14
+    /// Original screenshot (immutable background).
15
+    background: Surface,
16
+    /// Committed annotations layer.
17
+    annotations: Surface,
18
+    /// Live preview of current tool.
19
+    preview: Surface,
20
+    /// Composited output for display.
21
+    composite: Surface,
22
+    /// Canvas dimensions.
23
+    size: Size,
24
+}
25
+
26
+impl AnnotationCanvas {
27
+    /// Create a new annotation canvas from image data.
28
+    ///
29
+    /// # Arguments
30
+    /// * `image_data` - RGBA pixel data
31
+    /// * `width` - Image width in pixels
32
+    /// * `height` - Image height in pixels
33
+    pub fn new(image_data: &[u8], width: u32, height: u32) -> Result<Self> {
34
+        let size = Size::new(width, height);
35
+
36
+        // Create background surface from image data
37
+        let background = Surface::from_rgba(image_data, width, height)
38
+            .context("Failed to create background surface")?;
39
+
40
+        // Create transparent annotation layer
41
+        let annotations = Surface::new(width, height)
42
+            .context("Failed to create annotation surface")?;
43
+
44
+        // Create transparent preview layer
45
+        let preview = Surface::new(width, height)
46
+            .context("Failed to create preview surface")?;
47
+
48
+        // Create composite surface for display
49
+        let composite = Surface::new(width, height)
50
+            .context("Failed to create composite surface")?;
51
+
52
+        Ok(Self {
53
+            background,
54
+            annotations,
55
+            preview,
56
+            composite,
57
+            size,
58
+        })
59
+    }
60
+
61
+    /// Get canvas dimensions.
62
+    pub fn size(&self) -> Size {
63
+        self.size
64
+    }
65
+
66
+    /// Get width.
67
+    pub fn width(&self) -> u32 {
68
+        self.size.width
69
+    }
70
+
71
+    /// Get height.
72
+    pub fn height(&self) -> u32 {
73
+        self.size.height
74
+    }
75
+
76
+    /// Get the annotations surface for drawing.
77
+    pub fn annotations_surface(&self) -> &Surface {
78
+        &self.annotations
79
+    }
80
+
81
+    /// Get the preview surface for drawing.
82
+    pub fn preview_surface(&self) -> &Surface {
83
+        &self.preview
84
+    }
85
+
86
+    /// Get the composite surface for display.
87
+    pub fn composite_surface(&self) -> &Surface {
88
+        &self.composite
89
+    }
90
+
91
+    /// Get mutable reference to composite surface.
92
+    pub fn composite_surface_mut(&mut self) -> &mut Surface {
93
+        &mut self.composite
94
+    }
95
+
96
+    /// Clear the preview layer.
97
+    pub fn clear_preview(&self) -> Result<()> {
98
+        let ctx = self.preview.context()?;
99
+        ctx.set_operator(cairo::Operator::Clear);
100
+        ctx.paint()?;
101
+        ctx.set_operator(cairo::Operator::Over);
102
+        Ok(())
103
+    }
104
+
105
+    /// Commit preview to annotations layer.
106
+    pub fn commit_preview(&self) -> Result<()> {
107
+        // Paint preview onto annotations
108
+        let ctx = self.annotations.context()?;
109
+        ctx.set_source_surface(self.preview.cairo_surface(), 0.0, 0.0)?;
110
+        ctx.paint()?;
111
+
112
+        // Clear preview
113
+        self.clear_preview()?;
114
+
115
+        Ok(())
116
+    }
117
+
118
+    /// Render all layers to composite surface.
119
+    pub fn render(&self) -> Result<()> {
120
+        let ctx = self.composite.context()?;
121
+
122
+        // Clear composite
123
+        ctx.set_operator(cairo::Operator::Source);
124
+
125
+        // Draw background
126
+        ctx.set_source_surface(self.background.cairo_surface(), 0.0, 0.0)?;
127
+        ctx.paint()?;
128
+
129
+        // Draw annotations with alpha blending
130
+        ctx.set_operator(cairo::Operator::Over);
131
+        ctx.set_source_surface(self.annotations.cairo_surface(), 0.0, 0.0)?;
132
+        ctx.paint()?;
133
+
134
+        // Draw preview
135
+        ctx.set_source_surface(self.preview.cairo_surface(), 0.0, 0.0)?;
136
+        ctx.paint()?;
137
+
138
+        Ok(())
139
+    }
140
+
141
+    /// Export the final composited image as RGBA data.
142
+    pub fn export(&mut self) -> Result<Vec<u8>> {
143
+        // Render final composite
144
+        self.render()?;
145
+
146
+        // Get pixel data from composite surface
147
+        self.composite
148
+            .to_rgba()
149
+            .context("Failed to export canvas data")
150
+    }
151
+
152
+    /// Get a snapshot of the annotations layer for undo.
153
+    pub fn snapshot_annotations(&mut self) -> Result<Vec<u8>> {
154
+        self.annotations
155
+            .to_rgba()
156
+            .context("Failed to snapshot annotations")
157
+    }
158
+
159
+    /// Restore annotations layer from snapshot.
160
+    pub fn restore_annotations(&self, data: &[u8]) -> Result<()> {
161
+        let ctx = self.annotations.context()?;
162
+
163
+        // Clear current annotations
164
+        ctx.set_operator(cairo::Operator::Clear);
165
+        ctx.paint()?;
166
+
167
+        // Create temporary surface from snapshot data
168
+        let temp = Surface::from_rgba(data, self.size.width, self.size.height)?;
169
+
170
+        // Paint snapshot onto annotations
171
+        ctx.set_operator(cairo::Operator::Source);
172
+        ctx.set_source_surface(temp.cairo_surface(), 0.0, 0.0)?;
173
+        ctx.paint()?;
174
+
175
+        ctx.set_operator(cairo::Operator::Over);
176
+        Ok(())
177
+    }
178
+}