embedded_graphics/draw_target/
clipped.rs

1use crate::{
2    draw_target::DrawTarget, geometry::Dimensions, iterator::contiguous::Cropped,
3    primitives::Rectangle, transform::Transform, Pixel,
4};
5
6/// Clipped draw target.
7///
8/// Created by calling [`clipped`] on any [`DrawTarget`].
9/// See the [`clipped`] method documentation for more.
10///
11/// [`clipped`]: crate::draw_target::DrawTargetExt::clipped
12#[derive(Debug)]
13pub struct Clipped<'a, T>
14where
15    T: DrawTarget,
16{
17    parent: &'a mut T,
18    clip_area: Rectangle,
19}
20
21impl<'a, T> Clipped<'a, T>
22where
23    T: DrawTarget,
24{
25    pub(super) fn new(parent: &'a mut T, clip_area: &Rectangle) -> Self {
26        let clip_area = clip_area.intersection(&parent.bounding_box());
27
28        Self { parent, clip_area }
29    }
30}
31
32impl<T> DrawTarget for Clipped<'_, T>
33where
34    T: DrawTarget,
35{
36    type Color = T::Color;
37    type Error = T::Error;
38
39    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
40    where
41        I: IntoIterator<Item = Pixel<Self::Color>>,
42    {
43        let pixels = pixels
44            .into_iter()
45            .filter(|Pixel(p, _)| self.clip_area.contains(*p));
46
47        self.parent.draw_iter(pixels)
48    }
49
50    fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
51    where
52        I: IntoIterator<Item = Self::Color>,
53    {
54        let intersection = self.bounding_box().intersection(area);
55
56        if &intersection == area {
57            self.parent.fill_contiguous(area, colors)
58        } else {
59            let crop_area = intersection.translate(-area.top_left);
60            let cropped = Cropped::new(colors.into_iter(), area.size, &crop_area);
61            self.parent.fill_contiguous(&intersection, cropped)
62        }
63    }
64
65    fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
66        let area = area.intersection(&self.clip_area);
67
68        self.parent.fill_solid(&area, color)
69    }
70}
71
72impl<T> Dimensions for Clipped<'_, T>
73where
74    T: DrawTarget,
75{
76    fn bounding_box(&self) -> Rectangle {
77        self.clip_area
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use crate::{
84        draw_target::{DrawTarget, DrawTargetExt},
85        geometry::Dimensions,
86        geometry::{Point, Size},
87        mock_display::MockDisplay,
88        pixelcolor::BinaryColor,
89        primitives::{Primitive, PrimitiveStyle, Rectangle},
90        Drawable, Pixel,
91    };
92
93    #[test]
94    fn draw_iter() {
95        let mut display = MockDisplay::new();
96
97        let area = Rectangle::new(Point::new(2, 1), Size::new(2, 4));
98        let mut clipped = display.clipped(&area);
99
100        let pixels = [
101            Pixel(Point::new(0, 1), BinaryColor::On),
102            Pixel(Point::new(1, 1), BinaryColor::On),
103            Pixel(Point::new(2, 1), BinaryColor::On),
104            Pixel(Point::new(3, 1), BinaryColor::On),
105            Pixel(Point::new(4, 1), BinaryColor::On),
106            Pixel(Point::new(2, 0), BinaryColor::Off),
107            Pixel(Point::new(2, 2), BinaryColor::Off),
108            Pixel(Point::new(2, 3), BinaryColor::Off),
109            Pixel(Point::new(2, 4), BinaryColor::Off),
110            Pixel(Point::new(2, 5), BinaryColor::Off),
111        ];
112        clipped.draw_iter(pixels.iter().copied()).unwrap();
113
114        display.assert_pattern(&[
115            "    ", //
116            "  ##", //
117            "  . ", //
118            "  . ", //
119            "  . ", //
120        ]);
121    }
122
123    #[test]
124    fn fill_contiguous() {
125        let mut display = MockDisplay::new();
126
127        let area = Rectangle::new(Point::new(3, 2), Size::new(2, 3));
128        let mut clipped = display.clipped(&area);
129
130        let colors = [
131            1, 1, 1, 1, 1, //
132            0, 0, 0, 0, 1, //
133            0, 1, 0, 1, 1, //
134            1, 0, 1, 0, 1, //
135        ];
136        let area = Rectangle::new(Point::new(1, 2), Size::new(5, 4));
137        clipped
138            .fill_contiguous(&area, colors.iter().map(|c| BinaryColor::from(*c != 0)))
139            .unwrap();
140
141        display.assert_pattern(&[
142            "     ", //
143            "     ", //
144            "   ##", //
145            "   ..", //
146            "   .#", //
147        ]);
148    }
149
150    #[test]
151    fn fill_solid() {
152        let mut display = MockDisplay::new();
153
154        let area = Rectangle::new(Point::new(3, 2), Size::new(4, 2));
155        let mut clipped = display.clipped(&area);
156
157        let area = Rectangle::new(Point::new(2, 1), Size::new(6, 4));
158        clipped.fill_solid(&area, BinaryColor::On).unwrap();
159
160        display.assert_pattern(&[
161            "       ", //
162            "       ", //
163            "   ####", //
164            "   ####", //
165        ]);
166    }
167
168    #[test]
169    fn clear() {
170        let mut display = MockDisplay::new();
171
172        let area = Rectangle::new(Point::new(1, 3), Size::new(3, 4));
173        let mut clipped = display.clipped(&area);
174        clipped.clear(BinaryColor::On).unwrap();
175
176        let mut expected = MockDisplay::new();
177        area.into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
178            .draw(&mut expected)
179            .unwrap();
180
181        display.assert_eq(&expected);
182    }
183
184    #[test]
185    fn bounding_box() {
186        let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
187
188        let area = Rectangle::new(Point::new(1, 3), Size::new(2, 4));
189        let clipped = display.clipped(&area);
190
191        assert_eq!(clipped.bounding_box(), area);
192    }
193
194    #[test]
195    fn bounding_box_is_clipped() {
196        let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
197        let display_bb = display.bounding_box();
198
199        let top_left = Point::new(10, 20);
200        let size = Size::new(1000, 1000);
201        let area = Rectangle::new(top_left, size);
202        let clipped = display.clipped(&area);
203
204        let expected_size = display_bb.size - Size::new(top_left.x as u32, top_left.y as u32);
205
206        assert_eq!(
207            clipped.bounding_box(),
208            Rectangle::new(top_left, expected_size),
209        );
210    }
211}