Skip to main content

buffer_graphics_lib/
clipping.rs

1use crate::clipping::ClipMode::*;
2use graphics_shapes::coord::Coord;
3use graphics_shapes::prelude::{Circle, Rect};
4use graphics_shapes::Shape;
5use log::error;
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10#[derive(Clone, Debug, Eq, PartialEq)]
11enum ClipShape {
12    Box(Rect),
13    Round(Circle),
14}
15
16impl ClipShape {
17    pub fn contains(&self, xy: (isize, isize)) -> bool {
18        match self {
19            ClipShape::Box(rect) => rect.contains(Coord::from(xy)),
20            ClipShape::Round(circle) => circle.contains(Coord::from(xy)),
21        }
22    }
23}
24
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26#[derive(Clone, Debug, Eq, PartialEq)]
27enum ClipElement {
28    Add(ClipShape),
29    Remove(ClipShape),
30}
31
32#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
33#[derive(Clone, Debug, Eq, PartialEq)]
34enum ClipMode {
35    Nothing,
36    Simple(ClipShape),
37    Complex(Vec<ClipElement>),
38    Custom(Vec<bool>),
39}
40
41/// Clip has four modes:
42/// * Nothing - All pixels are valid
43/// * Simple - Only pixels in the shape (rect or circle) are valid
44/// * Custom - User provides a list of which pixels are valid
45/// * Complex - A series of shapes adding and removing clip area
46///
47/// Complex starts with all pixels being valid
48/// * use `add_*` to decrease the valid area
49/// * use `remove_*` to increase the valid area
50///
51/// the last shape to touch a pixel determines it's validity
52///
53/// With complex mode a list of valid pixels is stored internally and each time the complex clip is updated the valid list is updated as well, if you're making a bulk edit call `set_auto_build_map(false)` first
54#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
55#[derive(Debug, Clone, Eq, PartialEq)]
56pub struct Clip {
57    width: usize,
58    height: usize,
59    mode: ClipMode,
60    valid_pixel_map: Option<Vec<bool>>,
61    auto_build_map: bool,
62}
63
64impl Clip {
65    pub fn new(width: usize, height: usize) -> Self {
66        Self {
67            width,
68            height,
69            mode: Nothing,
70            valid_pixel_map: None,
71            auto_build_map: true,
72        }
73    }
74}
75
76impl Clip {
77    pub fn is_nothing(&self) -> bool {
78        matches!(&self.mode, Nothing)
79    }
80
81    pub fn is_simple(&self) -> bool {
82        matches!(&self.mode, Simple(_))
83    }
84
85    pub fn is_complex(&self) -> bool {
86        matches!(&self.mode, Complex(_))
87    }
88
89    pub fn is_custom(&self) -> bool {
90        matches!(&self.mode, Custom(_))
91    }
92
93    pub fn set_auto_build_map(&mut self, auto_build_map: bool) {
94        self.auto_build_map = auto_build_map;
95    }
96}
97
98impl Clip {
99    pub fn is_valid(&self, xy: (isize, isize)) -> bool {
100        let i = xy.0 + xy.1 * (self.width as isize);
101        let u = i.max(0) as usize;
102        match &self.mode {
103            Nothing => true,
104            Simple(shape) => shape.contains(xy),
105            Complex(_) => {
106                if let Some(map) = &self.valid_pixel_map {
107                    map[u]
108                } else {
109                    error!("Using complex clip but pixel map hasn't been built");
110                    true
111                }
112            }
113            Custom(map) => map[u],
114        }
115    }
116}
117
118impl Clip {
119    /// Clears the clip so all pixels can be drawn to
120    pub fn set_all_valid(&mut self) {
121        self.mode = Nothing;
122    }
123
124    /// Set the valid pixels to `rect`
125    pub fn set_valid_rect(&mut self, rect: Rect) {
126        self.mode = Simple(ClipShape::Box(rect));
127    }
128
129    /// Set the valid pixels to `circle`
130    pub fn set_valid_circle(&mut self, circle: Circle) {
131        self.mode = Simple(ClipShape::Round(circle));
132    }
133
134    /// Set the valid pixels to `pixel_map`
135    pub fn custom(&mut self, pixel_map: Vec<bool>) {
136        self.mode = Custom(pixel_map);
137    }
138
139    pub fn get_pixel_map(&mut self) -> Vec<bool> {
140        match &self.mode {
141            Simple(_) | Nothing => self.build_pixel_map(),
142            Complex(_) => {
143                if let Some(map) = &self.valid_pixel_map {
144                    map.clone()
145                } else {
146                    self.update_pixel_map();
147                    self.valid_pixel_map.as_ref().unwrap().clone()
148                }
149            }
150            Custom(map) => map.clone(),
151        }
152    }
153}
154
155impl Clip {
156    fn swap_to_complex(&mut self) {
157        if !self.is_complex() {
158            self.mode = Complex(vec![]);
159        }
160    }
161
162    fn add(&mut self, element: ClipElement) {
163        self.swap_to_complex();
164        if let Complex(list) = &mut self.mode {
165            list.push(element);
166        }
167        if self.auto_build_map {
168            self.update_pixel_map();
169        }
170    }
171
172    /// Set the mode to `complex` (clearing any other mode)
173    /// Set any pixels in `rect` to valid
174    pub fn add_rect(&mut self, rect: Rect) {
175        self.add(ClipElement::Add(ClipShape::Box(rect)));
176    }
177
178    /// Set the mode to `complex` (clearing any other mode)
179    /// Set any pixels in `rect` to invalid
180    pub fn remove_rect(&mut self, rect: Rect) {
181        self.add(ClipElement::Remove(ClipShape::Box(rect)));
182    }
183
184    /// Set the mode to `complex` (clearing any other mode)
185    /// Set any pixels in `circle` to valid
186    pub fn add_circle(&mut self, circle: Circle) {
187        self.add(ClipElement::Add(ClipShape::Round(circle)));
188    }
189
190    /// Set the mode to `complex` (clearing any other mode)
191    /// Set any pixels in `circle` to invalid
192    pub fn remove_circle(&mut self, circle: Circle) {
193        self.add(ClipElement::Remove(ClipShape::Round(circle)));
194    }
195}
196
197impl Clip {
198    pub fn update_pixel_map(&mut self) {
199        if self.is_complex() {
200            self.valid_pixel_map = Some(self.build_pixel_map())
201        } else {
202            self.valid_pixel_map = None;
203        }
204    }
205
206    fn build_pixel_map(&self) -> Vec<bool> {
207        match &self.mode {
208            Nothing => vec![true; self.width * self.height],
209            Simple(shape) => self.build_simple_map(shape),
210            Complex(elements) => self.build_complex_map(elements),
211            Custom(map) => map.clone(),
212        }
213    }
214
215    fn build_simple_map(&self, shape: &ClipShape) -> Vec<bool> {
216        let mut output = vec![false; self.width * self.height];
217        for x in 0..self.width {
218            for y in 0..self.height {
219                let i = x + y * self.width;
220                let contains = shape.contains((x as isize, y as isize));
221                if contains {
222                    output[i] = true;
223                }
224            }
225        }
226        output
227    }
228
229    fn build_complex_map(&self, elements: &[ClipElement]) -> Vec<bool> {
230        let mut output = vec![true; self.width * self.height];
231        for x in 0..self.width {
232            for y in 0..self.height {
233                let mut valid = true;
234                for element in elements {
235                    match element {
236                        ClipElement::Add(shape) => {
237                            if shape.contains((x as isize, y as isize)) {
238                                valid = true;
239                            }
240                        }
241                        ClipElement::Remove(shape) => {
242                            if shape.contains((x as isize, y as isize)) {
243                                valid = false;
244                            }
245                        }
246                    }
247                }
248                let i = x + y * self.width;
249                if i >= output.len() {
250                    break;
251                }
252                output[x + y * self.width] = valid;
253            }
254        }
255        output
256    }
257}
258
259#[cfg(test)]
260mod test {
261    use crate::clipping::Clip;
262    use graphics_shapes::rect::Rect;
263
264    #[test]
265    fn check_all_pixels_valid_for_none() {
266        let mut clip = Clip::new(4, 4);
267        assert_eq!(clip.get_pixel_map(), vec![true; 16]);
268    }
269
270    #[test]
271    fn check_pixels_valid_for_square() {
272        let mut clip = Clip::new(4, 4);
273        clip.set_valid_rect(Rect::new((1, 1), (2, 2)));
274        let expected = vec![
275            false, false, false, false, false, true, true, false, false, true, true, false, false,
276            false, false, false,
277        ];
278        assert_eq!(clip.get_pixel_map(), expected);
279    }
280
281    #[test]
282    fn check_pixels_split_horz() {
283        let mut clip = Clip::new(4, 4);
284        clip.set_valid_rect(Rect::new((2, 0), (3, 3)));
285        let expected = vec![
286            false, false, true, true, false, false, true, true, false, false, true, true, false,
287            false, true, true,
288        ];
289        assert_eq!(clip.get_pixel_map(), expected);
290    }
291
292    #[test]
293    fn complex() {
294        let mut clip = Clip::new(4, 4);
295
296        clip.add_rect(Rect::new((0, 0), (3, 3))); //set all pixels to valid
297        let expected = vec![true; 16];
298        assert_eq!(clip.get_pixel_map(), expected);
299
300        clip.remove_rect(Rect::new((2, 0), (3, 3))); //set right hand side to invalid
301        let expected = vec![
302            true, true, false, false, true, true, false, false, true, true, false, false, true,
303            true, false, false,
304        ];
305        assert_eq!(clip.get_pixel_map(), expected);
306
307        clip.add_rect(Rect::new((3, 0), (3, 3))); //set last column to valid
308        let expected = vec![
309            true, true, false, true, true, true, false, true, true, true, false, true, true, true,
310            false, true,
311        ];
312        assert_eq!(clip.get_pixel_map(), expected);
313    }
314}