glance_core/drawing/
shapes.rs

1use super::traits::Drawable;
2use crate::Result;
3
4/// A circle shape that can be drawn onto an image.
5/// Can be either filled or drawn as an outline with a specified thickness.
6/// The color is specified in RGBA8 format.
7pub struct Circle {
8    /// Center of the circle (x, y)
9    pub position: [u32; 2],
10    /// Color in RGBA8 format
11    pub color: [u8; 4],
12    /// Radius in pixels
13    pub radius: u32,
14    /// Fill the shape (true) or draw outline (false)
15    pub filled: bool,
16    /// Outline thickness (only used when `filled = false`)
17    pub thickness: u32,
18}
19
20impl Drawable for Circle {
21    fn draw_on(&self, image: &mut crate::img::Image) -> Result<()> {
22        let (cx, cy) = (self.position[0] as i32, self.position[1] as i32);
23        let radius = self.radius as i32;
24        let thickness = self.thickness as i32;
25        let dims = image.dimensions();
26
27        let outer_radius = radius + thickness;
28        let outer_radius_sq = outer_radius.pow(2);
29        let inner_radius_sq = radius.pow(2);
30
31        for dy in -outer_radius..outer_radius {
32            for dx in -outer_radius..outer_radius {
33                let nx = cx + dx;
34                let ny = cy + dy;
35
36                // Early skip for out of bounds pixels
37                if nx < 0 || ny < 0 {
38                    continue;
39                }
40
41                let (nx, ny) = (nx as u32, ny as u32);
42                if nx >= dims[0] || ny >= dims[1] {
43                    continue;
44                }
45
46                let distance_sq = dx * dx + dy * dy;
47                // Check if (nx, ny) is within bounds
48                let draw_pixel = match self.filled {
49                    true => distance_sq <= inner_radius_sq,
50                    false => distance_sq <= outer_radius_sq && distance_sq >= inner_radius_sq,
51                };
52
53                if draw_pixel {
54                    image.alpha_blend_pixel([nx, ny], self.color)?;
55                }
56            }
57        }
58        Ok(())
59    }
60}
61
62/// An axis aligned bounding box that can be drawn onto an image.
63/// Can be either filled or drawn as an outline with a specified thickness.
64/// The color is specified in RGBA8 format.
65pub struct AABB {
66    /// Top-left corner (x, y)
67    pub position: [u32; 2],
68    /// Size as [width, height]
69    pub size: [u32; 2],
70    /// Color in RGBA8 format
71    pub color: [u8; 4],
72    /// Fill the shape (true) or draw outline (false)
73    pub filled: bool,
74    /// Outline thickness (only used when `filled = false`)
75    pub thickness: u32,
76}
77
78impl Drawable for AABB {
79    fn draw_on(&self, image: &mut crate::img::Image) -> Result<()> {
80        let (cx, cy) = (self.position[0] as i32, self.position[1] as i32);
81        let dims = image.dimensions();
82        let width = self.size[0] as i32;
83        let height = self.size[1] as i32;
84        let thickness = self.thickness as i32;
85
86        let left_x = cx - thickness;
87        let right_x = cx + width + thickness;
88        let top_y = cy - thickness;
89        let bottom_y = cy + height + thickness;
90
91        // Go through all pixels inside the rectangle
92        for x in left_x..right_x {
93            for y in top_y..bottom_y {
94                // Early return for out of bounds pixels
95                if x < 0 || y < 0 {
96                    continue;
97                }
98
99                let nx = x as u32;
100                let ny = y as u32;
101
102                if nx >= dims[0] || ny >= dims[1] {
103                    continue;
104                }
105
106                // If not filled, only draw outline
107                let on_left_edge = x - left_x <= thickness;
108                let on_right_edge = right_x - x <= thickness;
109                let on_top_edge = y - top_y < thickness;
110                let on_bottom_edge = bottom_y - y <= thickness;
111                let draw_pixel =
112                    self.filled || on_left_edge || on_right_edge || on_top_edge || on_bottom_edge;
113
114                if draw_pixel {
115                    image.alpha_blend_pixel([nx, ny], self.color)?;
116                }
117            }
118        }
119
120        Ok(())
121    }
122}
123
124/// A line that can be drawn onto an image. Uses a square brush (Bresenham's algorithm).
125/// The color is specified in RGBA8 format.
126pub struct Line {
127    /// Start point (x, y)
128    pub start: [u32; 2],
129    /// End point (x, y)
130    pub end: [u32; 2],
131    /// Color in RGBA8 format
132    pub color: [u8; 4],
133    /// Line segment thickness (only used when `filled = false`)
134    pub thickness: u32,
135}
136
137impl Drawable for Line {
138    fn draw_on(&self, image: &mut crate::img::Image) -> Result<()> {
139        let [x0, y0] = self.start;
140        let [x1, y1] = self.end;
141
142        let dims = image.dimensions();
143
144        let x0 = x0 as i32;
145        let y0 = y0 as i32;
146        let x1 = x1 as i32;
147        let y1 = y1 as i32;
148        let thickness = self.thickness as i32;
149        let half_thickness = thickness / 2;
150
151        // Difference in each direction
152        let dx = (x1 - x0).abs();
153        let dy = -(y1 - y0).abs();
154        // Step in each direcion
155        let sx = if x0 < x1 { 1 } else { -1 };
156        let sy = if y0 < y1 { 1 } else { -1 };
157        let mut err = dx + dy;
158        let mut x = x0;
159        let mut y = y0;
160
161        loop {
162            for tx in -half_thickness..=half_thickness {
163                for ty in -half_thickness..=half_thickness {
164                    let nx = x + tx;
165                    let ny = y + ty;
166
167                    // Early return for out of bounds pixels
168                    if nx < 0 || ny < 0 {
169                        continue;
170                    }
171
172                    let (nx, ny) = (nx as u32, ny as u32);
173                    if nx >= dims[0] || ny >= dims[1] {
174                        continue;
175                    }
176
177                    image.alpha_blend_pixel([nx, ny], self.color)?;
178                }
179            }
180
181            // Done if reached end point
182            if x == x1 && y == y1 {
183                break;
184            }
185
186            let err_twice = 2 * err;
187            if err_twice >= dy {
188                err += dy;
189                x += sx;
190            }
191            if err_twice <= dx {
192                err += dx;
193                y += sy;
194            }
195        }
196
197        Ok(())
198    }
199}