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}