embedded_graphics/draw_target/
mod.rs

1//! A target for embedded-graphics drawing operations.
2
3mod clipped;
4mod color_converted;
5mod cropped;
6mod translated;
7
8use crate::{geometry::Point, pixelcolor::PixelColor, primitives::Rectangle};
9
10pub use clipped::Clipped;
11pub use color_converted::ColorConverted;
12pub use cropped::Cropped;
13pub use translated::Translated;
14
15pub use embedded_graphics_core::draw_target::DrawTarget;
16
17/// Extension trait for `DrawTarget`s.
18pub trait DrawTargetExt: DrawTarget + Sized {
19    /// Creates a translated draw target based on this draw target.
20    ///
21    /// All drawing operations are translated by `offset` pixels, before being passed to the parent
22    /// draw target.
23    ///
24    /// # Examples
25    ///
26    /// ```
27    /// use embedded_graphics::{
28    ///     mock_display::MockDisplay,
29    ///     mono_font::{ascii::FONT_6X9, MonoTextStyle},
30    ///     pixelcolor::BinaryColor,
31    ///     prelude::*,
32    ///     text::Text,
33    /// };
34    ///
35    /// let mut display = MockDisplay::new();
36    /// let mut translated_display = display.translated(Point::new(5, 10));
37    ///
38    /// let style = MonoTextStyle::new(&FONT_6X9, BinaryColor::On);
39    ///
40    /// // Draws text at position (5, 10) in the display coordinate system
41    /// Text::new("Text", Point::zero(), style).draw(&mut translated_display)?;
42    /// #
43    /// # let mut expected = MockDisplay::new();
44    /// #
45    /// # Text::new("Text", Point::new(5, 10), style).draw(&mut expected)?;
46    /// #
47    /// # display.assert_eq(&expected);
48    /// #
49    /// # Ok::<(), core::convert::Infallible>(())
50    /// ```
51    fn translated(&mut self, offset: Point) -> Translated<'_, Self>;
52
53    /// Creates a cropped draw target based on this draw target.
54    ///
55    /// A cropped draw target is a draw target for a rectangular subregion of the parent draw target.
56    /// Its coordinate system is shifted so that the origin coincides with `area.top_left` in the
57    /// parent draw target's coordinate system.
58    ///
59    /// The bounding box of the returned target will always be contained inside the bounding box
60    /// of the parent target. If any of the requested `area` lies outside the parent target's bounding
61    /// box the intersection of the parent target's bounding box and `area` will be used.
62    ///
63    /// Drawing operations outside the bounding box will not be clipped.
64    ///
65    /// # Examples
66    ///
67    /// ```
68    /// use embedded_graphics::{
69    ///     mock_display::MockDisplay,
70    ///     mono_font::{ascii::FONT_6X9, MonoTextStyle},
71    ///     pixelcolor::Rgb565,
72    ///     prelude::*,
73    ///     primitives::Rectangle,
74    ///     text::{Text, Alignment, Baseline, TextStyleBuilder},
75    /// };
76    ///
77    /// /// Fills a draw target with a blue background and prints centered yellow text.
78    /// fn draw_text<T>(target: &mut T, text: &str) -> Result<(), T::Error>
79    /// where
80    ///     T: DrawTarget<Color = Rgb565>,
81    /// {
82    ///     target.clear(Rgb565::BLUE)?;
83    ///
84    ///     let text_position = target.bounding_box().center();
85    ///
86    ///     let character_style = MonoTextStyle::new(&FONT_6X9, Rgb565::YELLOW);
87    ///     let text_style = TextStyleBuilder::new()
88    ///         .alignment(Alignment::Center)
89    ///         .baseline(Baseline::Middle)
90    ///         .build();
91    ///
92    ///     Text::with_text_style(text, text_position, character_style, text_style).draw(target)?;
93    ///
94    ///     Ok(())
95    /// }
96    ///
97    /// let mut display = MockDisplay::new();
98    /// display.set_allow_overdraw(true);
99    ///
100    /// let area = Rectangle::new(Point::new(5, 10), Size::new(40, 15));
101    /// let mut cropped_display = display.cropped(&area);
102    ///
103    /// draw_text(&mut cropped_display, "Text")?;
104    /// #
105    /// # Ok::<(), core::convert::Infallible>(())
106    /// ```
107    fn cropped(&mut self, area: &Rectangle) -> Cropped<'_, Self>;
108
109    /// Creates a clipped draw target based on this draw target.
110    ///
111    /// A clipped draw target is a draw target for a rectangular subregion of the parent draw target.
112    /// The coordinate system of the created draw target is equal to the parent target's coordinate
113    /// system. All drawing operations outside the bounding box will be clipped.
114    ///
115    /// The bounding box of the returned target will always be contained inside the bounding box
116    /// of the parent target. If any of the requested `area` lies outside the parent target's bounding
117    /// box the intersection of the parent target's bounding box and `area` will be used.
118    ///
119    /// # Examples
120    ///
121    /// ```
122    /// use embedded_graphics::{
123    ///     mock_display::MockDisplay,
124    ///     mono_font::{ascii::FONT_10X20, MonoTextStyle},
125    ///     pixelcolor::BinaryColor,
126    ///     prelude::*,
127    ///     primitives::Rectangle,
128    ///     text::Text,
129    /// };
130    ///
131    /// let mut display = MockDisplay::new();
132    ///
133    /// let area = Rectangle::new(Point::zero(), Size::new(4 * 10, 20));
134    /// let mut clipped_display = display.clipped(&area);
135    ///
136    /// let style = MonoTextStyle::new(&FONT_10X20, BinaryColor::On);
137    ///
138    /// // Only the first 4 characters will be drawn, because the others are outside
139    /// // the clipping area
140    /// Text::new("Clipped", Point::new(0, 15), style).draw(&mut clipped_display)?;
141    /// #
142    /// # let mut expected = MockDisplay::new();
143    /// #
144    /// # Text::new("Clip", Point::new(0, 15), style).draw(&mut expected)?;
145    /// #
146    /// # display.assert_eq(&expected);
147    /// #
148    /// # Ok::<(), core::convert::Infallible>(())
149    /// ```
150    fn clipped(&mut self, area: &Rectangle) -> Clipped<'_, Self>;
151
152    /// Creates a color conversion draw target.
153    ///
154    /// A color conversion draw target is used to draw drawables with a different color type to a
155    /// draw target. The drawable color type must implement `Into<C>`, where `C` is the draw
156    /// target color type.
157    ///
158    /// # Performance
159    ///
160    /// Color conversion can be expensive on embedded hardware and should be avoided if possible.
161    /// Using the same color type for drawables and the draw target makes sure that no unnecessary
162    /// color conversion is used. But in some cases color conversion will be required, for example,
163    /// to draw images with a color format only known at runtime.
164    ///
165    /// # Examples
166    ///
167    /// This example draws a `BinaryColor` image to an `Rgb888` display.
168    ///
169    /// ```
170    /// use embedded_graphics::{
171    ///     image::{Image, ImageRaw},
172    ///     mock_display::MockDisplay,
173    ///     pixelcolor::{BinaryColor, Rgb888},
174    ///     prelude::*,
175    /// };
176    ///
177    /// /// The image data.
178    /// const DATA: &[u8] = &[
179    ///     0b11110000, //
180    ///     0b10010000, //
181    ///     0b10010000, //
182    ///     0b11110000, //
183    /// ];
184    ///
185    /// // Create a `BinaryColor` image from the image data.
186    /// let raw_image = ImageRaw::<BinaryColor>::new(DATA, 4);
187    /// let image = Image::new(&raw_image, Point::zero());
188    ///
189    /// // Create a `Rgb888` display.
190    /// let mut display = MockDisplay::<Rgb888>::new();
191    ///
192    /// // The image can't directly be drawn to the draw target because they use different
193    /// // color type. This will fail to compile:
194    /// // image.draw(&mut display)?;
195    ///
196    /// // To fix this `color_converted` is added to enable color conversion.
197    /// image.draw(&mut display.color_converted())?;
198    /// #
199    /// # let mut expected = MockDisplay::from_pattern(&[
200    /// #     "WWWW", //
201    /// #     "WKKW", //
202    /// #     "WKKW", //
203    /// #     "WWWW", //
204    /// # ]);
205    /// #
206    /// # display.assert_eq(&expected);
207    /// #
208    /// # Ok::<(), core::convert::Infallible>(())
209    /// ```
210    fn color_converted<C>(&mut self) -> ColorConverted<'_, Self, C>
211    where
212        C: PixelColor + Into<Self::Color>;
213}
214
215impl<T> DrawTargetExt for T
216where
217    T: DrawTarget,
218{
219    fn translated(&mut self, offset: Point) -> Translated<'_, Self> {
220        Translated::new(self, offset)
221    }
222
223    fn cropped(&mut self, area: &Rectangle) -> Cropped<'_, Self> {
224        Cropped::new(self, area)
225    }
226
227    fn clipped(&mut self, area: &Rectangle) -> Clipped<'_, Self> {
228        Clipped::new(self, area)
229    }
230
231    fn color_converted<C>(&mut self) -> ColorConverted<'_, Self, C>
232    where
233        C: PixelColor + Into<Self::Color>,
234    {
235        ColorConverted::new(self)
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use crate::{
242        draw_target::{DrawTarget, DrawTargetExt},
243        geometry::{Dimensions, Point, Size},
244        mock_display::MockDisplay,
245        pixelcolor::BinaryColor,
246        primitives::{Primitive, PrimitiveStyle, Rectangle},
247        Drawable, Pixel,
248    };
249
250    #[test]
251    fn draw_iter() {
252        let mut display = MockDisplay::new();
253
254        let area = Rectangle::new(Point::new(2, 1), Size::new(2, 4));
255        let mut clipped = display.clipped(&area);
256
257        let pixels = [
258            Pixel(Point::new(0, 1), BinaryColor::On),
259            Pixel(Point::new(1, 1), BinaryColor::On),
260            Pixel(Point::new(2, 1), BinaryColor::On),
261            Pixel(Point::new(3, 1), BinaryColor::On),
262            Pixel(Point::new(4, 1), BinaryColor::On),
263            Pixel(Point::new(2, 0), BinaryColor::Off),
264            Pixel(Point::new(2, 2), BinaryColor::Off),
265            Pixel(Point::new(2, 3), BinaryColor::Off),
266            Pixel(Point::new(2, 4), BinaryColor::Off),
267            Pixel(Point::new(2, 5), BinaryColor::Off),
268        ];
269        clipped.draw_iter(pixels.iter().copied()).unwrap();
270
271        display.assert_pattern(&[
272            "    ", //
273            "  ##", //
274            "  . ", //
275            "  . ", //
276            "  . ", //
277        ]);
278    }
279
280    #[test]
281    fn fill_contiguous() {
282        let mut display = MockDisplay::new();
283
284        let area = Rectangle::new(Point::new(3, 2), Size::new(2, 3));
285        let mut clipped = display.clipped(&area);
286
287        let colors = [
288            1, 1, 1, 1, 1, //
289            0, 0, 0, 0, 1, //
290            0, 1, 0, 1, 1, //
291            1, 0, 1, 0, 1, //
292        ];
293        let area = Rectangle::new(Point::new(1, 2), Size::new(5, 4));
294        clipped
295            .fill_contiguous(&area, colors.iter().map(|c| BinaryColor::from(*c != 0)))
296            .unwrap();
297
298        display.assert_pattern(&[
299            "     ", //
300            "     ", //
301            "   ##", //
302            "   ..", //
303            "   .#", //
304        ]);
305    }
306
307    #[test]
308    fn fill_solid() {
309        let mut display = MockDisplay::new();
310
311        let area = Rectangle::new(Point::new(3, 2), Size::new(4, 2));
312        let mut clipped = display.clipped(&area);
313
314        let area = Rectangle::new(Point::new(2, 1), Size::new(6, 4));
315        clipped.fill_solid(&area, BinaryColor::On).unwrap();
316
317        display.assert_pattern(&[
318            "       ", //
319            "       ", //
320            "   ####", //
321            "   ####", //
322        ]);
323    }
324
325    #[test]
326    fn clear() {
327        let mut display = MockDisplay::new();
328
329        let area = Rectangle::new(Point::new(1, 3), Size::new(3, 4));
330        let mut clipped = display.clipped(&area);
331        clipped.clear(BinaryColor::On).unwrap();
332
333        let mut expected = MockDisplay::new();
334        area.into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
335            .draw(&mut expected)
336            .unwrap();
337
338        display.assert_eq(&expected);
339    }
340
341    #[test]
342    fn bounding_box() {
343        let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
344
345        let area = Rectangle::new(Point::new(1, 3), Size::new(2, 4));
346        let clipped = display.clipped(&area);
347
348        assert_eq!(clipped.bounding_box(), area);
349    }
350
351    #[test]
352    fn bounding_box_is_clipped() {
353        let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
354        let display_bb = display.bounding_box();
355
356        let top_left = Point::new(10, 20);
357        let size = Size::new(1000, 1000);
358        let area = Rectangle::new(top_left, size);
359        let clipped = display.clipped(&area);
360
361        let expected_size = display_bb.size - Size::new(top_left.x as u32, top_left.y as u32);
362
363        assert_eq!(
364            clipped.bounding_box(),
365            Rectangle::new(top_left, expected_size),
366        );
367    }
368}