embedded_graphics_core/draw_target/mod.rs
1//! A target for embedded-graphics drawing operations.
2
3use crate::{
4 geometry::Dimensions,
5 pixelcolor::PixelColor,
6 primitives::{PointsIter, Rectangle},
7 Pixel,
8};
9
10/// A target for embedded-graphics drawing operations.
11///
12/// The `DrawTarget` trait is used to add embedded-graphics support to a display
13/// driver or similar targets like framebuffers or image files.
14/// Targets are required to at least implement the [`draw_iter`] method and the [`Dimensions`]
15/// trait. All other methods provide default implementations which use these methods internally.
16///
17/// Because the default implementations cannot use features specific to the target hardware they
18/// can be overridden to improve performance. These target specific implementations might, for
19/// example, use hardware accelerated drawing operations provided by a display controller or
20/// specialized hardware modules in a microcontroller.
21///
22/// Note that some displays require a "flush" operation to write changes from a framebuffer to the
23/// display. See docs associated with the chosen display driver for details on how to update the
24/// display.
25///
26/// # Examples
27///
28/// ## Minimum implementation
29///
30/// In this example `DrawTarget` is implemented for an an imaginary 64px x 64px 8-bit grayscale display
31/// that is connected using a simplified SPI interface. Because the hardware doesn't support any
32/// acceleration only the [`draw_iter`] method and [`OriginDimensions`] trait need to be implemented.
33///
34/// To reduce the overhead caused by communicating with the display for each drawing operation
35/// the display driver uses and framebuffer to store the pixel data in memory. This way all drawing
36/// operations can be executed in local memory and the actual display is only updated on demand
37/// by calling the `flush` method.
38///
39/// Because all drawing operations are using a local framebuffer no communication error can occur
40/// while they are executed and the [`Error` type] can be set to `core::convert::Infallible`.
41///
42/// ```rust
43/// use core::convert::TryInto;
44/// use embedded_graphics::{
45/// pixelcolor::{Gray8, GrayColor},
46/// prelude::*,
47/// primitives::{Circle, PrimitiveStyle},
48/// };
49/// #
50/// # struct SPI1;
51/// #
52/// # impl SPI1 {
53/// # pub fn send_bytes(&self, buf: &[u8]) -> Result<(), CommError> {
54/// # Ok(())
55/// # }
56/// # }
57/// #
58///
59/// /// SPI communication error
60/// #[derive(Debug)]
61/// struct CommError;
62///
63/// /// A fake 64px x 64px display.
64/// struct ExampleDisplay {
65/// /// The framebuffer with one `u8` value per pixel.
66/// framebuffer: [u8; 64 * 64],
67///
68/// /// The interface to the display controller.
69/// iface: SPI1,
70/// }
71///
72/// impl ExampleDisplay {
73/// /// Updates the display from the framebuffer.
74/// pub fn flush(&self) -> Result<(), CommError> {
75/// self.iface.send_bytes(&self.framebuffer)
76/// }
77/// }
78///
79/// impl DrawTarget for ExampleDisplay {
80/// type Color = Gray8;
81/// // `ExampleDisplay` uses a framebuffer and doesn't need to communicate with the display
82/// // controller to draw pixel, which means that drawing operations can never fail. To reflect
83/// // this the type `Infallible` was chosen as the `Error` type.
84/// type Error = core::convert::Infallible;
85///
86/// fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
87/// where
88/// I: IntoIterator<Item = Pixel<Self::Color>>,
89/// {
90/// for Pixel(coord, color) in pixels.into_iter() {
91/// // Check if the pixel coordinates are out of bounds (negative or greater than
92/// // (63,63)). `DrawTarget` implementation are required to discard any out of bounds
93/// // pixels without returning an error or causing a panic.
94/// if let Ok((x @ 0..=63, y @ 0..=63)) = coord.try_into() {
95/// // Calculate the index in the framebuffer.
96/// let index: u32 = x + y * 64;
97/// self.framebuffer[index as usize] = color.luma();
98/// }
99/// }
100///
101/// Ok(())
102/// }
103/// }
104///
105/// impl OriginDimensions for ExampleDisplay {
106/// fn size(&self) -> Size {
107/// Size::new(64, 64)
108/// }
109/// }
110///
111/// let mut display = ExampleDisplay {
112/// framebuffer: [0; 4096],
113/// iface: SPI1,
114/// };
115///
116/// // Draw a circle with top-left at `(22, 22)` with a diameter of `20` and a white stroke
117/// let circle = Circle::new(Point::new(22, 22), 20)
118/// .into_styled(PrimitiveStyle::with_stroke(Gray8::WHITE, 1));
119///
120/// circle.draw(&mut display)?;
121///
122/// // Update the display
123/// display.flush().unwrap();
124/// # Ok::<(), core::convert::Infallible>(())
125/// ```
126///
127/// # Hardware acceleration - solid rectangular fill
128///
129/// This example uses an imaginary display with 16bpp RGB565 colors and hardware support for
130/// filling of rectangular areas with a solid color. A real display controller that supports this
131/// operation is the SSD1331 with it's "Draw Rectangle" (`22h`) command which this example
132/// is loosely based on.
133///
134/// To leverage this feature in a `DrawTarget`, the default implementation of [`fill_solid`] can be
135/// overridden by a custom implementation. Instead of drawing individual pixels, this target
136/// specific version will only send a single command to the display controller in one transaction.
137/// Because the command size is independent of the filled area, all [`fill_solid`] calls will only
138/// transmit 8 bytes to the display, which is far less then what is required to transmit each pixel
139/// color inside the filled area.
140/// ```rust
141/// use core::convert::TryInto;
142/// use embedded_graphics::{
143/// pixelcolor::{raw::RawU16, Rgb565, RgbColor},
144/// prelude::*,
145/// primitives::{Circle, Rectangle, PrimitiveStyle, PrimitiveStyleBuilder},
146/// };
147/// #
148/// # struct SPI1;
149/// #
150/// # impl SPI1 {
151/// # pub fn send_bytes(&self, buf: &[u16]) -> Result<(), ()> {
152/// # Ok(())
153/// # }
154/// # }
155/// #
156///
157/// /// SPI communication error
158/// #[derive(Debug)]
159/// struct CommError;
160///
161/// /// An example display connected over SPI.
162/// struct ExampleDisplay {
163/// iface: SPI1,
164/// }
165///
166/// impl ExampleDisplay {
167/// /// Send a single pixel to the display
168/// pub fn set_pixel(&self, x: u32, y: u32, color: u16) -> Result<(), CommError> {
169/// // ...
170///
171/// Ok(())
172/// }
173///
174/// /// Send commands to the display
175/// pub fn send_commands(&self, commands: &[u8]) -> Result<(), CommError> {
176/// // Send data marked as commands to the display.
177///
178/// Ok(())
179/// }
180/// }
181///
182/// impl DrawTarget for ExampleDisplay {
183/// type Color = Rgb565;
184/// type Error = CommError;
185///
186/// fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
187/// where
188/// I: IntoIterator<Item = Pixel<Self::Color>>,
189/// {
190/// for Pixel(coord, color) in pixels.into_iter() {
191/// // Check if the pixel coordinates are out of bounds (negative or greater than
192/// // (63,63)). `DrawTarget` implementation are required to discard any out of bounds
193/// // pixels without returning an error or causing a panic.
194/// if let Ok((x @ 0..=63, y @ 0..=63)) = coord.try_into() {
195/// self.set_pixel(x, y, RawU16::from(color).into_inner())?;
196/// }
197/// }
198///
199/// Ok(())
200/// }
201///
202/// fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
203/// // Clamp the rectangle coordinates to the valid range by determining
204/// // the intersection of the fill area and the visible display area
205/// // by using Rectangle::intersection.
206/// let area = area.intersection(&self.bounding_box());
207///
208/// // Do not send a draw rectangle command if the intersection size if zero.
209/// // The size is checked by using `Rectangle::bottom_right`, which returns `None`
210/// // if the size is zero.
211/// let bottom_right = if let Some(bottom_right) = area.bottom_right() {
212/// bottom_right
213/// } else {
214/// return Ok(());
215/// };
216///
217/// self.send_commands(&[
218/// // Draw rectangle command
219/// 0x22,
220/// // Top left X coordinate
221/// area.top_left.x as u8,
222/// // Top left Y coordinate
223/// area.top_left.y as u8,
224/// // Bottom right X coordinate
225/// bottom_right.x as u8,
226/// // Bottom right Y coordinate
227/// bottom_right.y as u8,
228/// // Fill color red channel
229/// color.r(),
230/// // Fill color green channel
231/// color.g(),
232/// // Fill color blue channel
233/// color.b(),
234/// ])
235/// }
236/// }
237///
238/// impl OriginDimensions for ExampleDisplay {
239/// fn size(&self) -> Size {
240/// Size::new(64, 64)
241/// }
242/// }
243///
244/// let mut display = ExampleDisplay { iface: SPI1 };
245///
246/// // Draw a rectangle with 5px red stroke and green fill.
247/// // The stroke and fill can be broken down into multiple individual rectangles,
248/// // so this uses `fill_solid` internally.
249/// Rectangle::new(Point::new(20, 20), Size::new(50, 40))
250/// .into_styled(
251/// PrimitiveStyleBuilder::new()
252/// .stroke_color(Rgb565::RED)
253/// .stroke_width(5)
254/// .fill_color(Rgb565::GREEN)
255/// .build(),
256/// )
257/// .draw(&mut display)?;
258///
259/// // Draw a circle with top-left at `(5, 5)` with a diameter of `10` and a magenta stroke with
260/// // cyan fill. This shape cannot be optimized by calls to `fill_solid` as it contains transparent
261/// // pixels as well as pixels of different colors. It will instead delegate to `draw_iter`
262/// // internally.
263/// Circle::new(Point::new(5, 5), 10)
264/// .into_styled(
265/// PrimitiveStyleBuilder::new()
266/// .stroke_color(Rgb565::MAGENTA)
267/// .stroke_width(1)
268/// .fill_color(Rgb565::CYAN)
269/// .build(),
270/// )
271/// .draw(&mut display)?;
272///
273/// # Ok::<(), CommError>(())
274/// ```
275///
276/// [`fill_solid`]: DrawTarget::fill_solid()
277/// [`draw_iter`]: DrawTarget::draw_iter
278/// [`Dimensions`]: super::geometry::Dimensions
279/// [`OriginDimensions`]: super::geometry::OriginDimensions
280/// [`Error` type]: DrawTarget::Error
281pub trait DrawTarget: Dimensions {
282 /// The pixel color type the targetted display supports.
283 type Color: PixelColor;
284
285 /// Error type to return when a drawing operation fails.
286 ///
287 /// This error is returned if an error occurred during a drawing operation. This mainly applies
288 /// to drivers that need to communicate with the display for each drawing operation, where a
289 /// communication error can occur. For drivers that use an internal framebuffer where drawing
290 /// operations can never fail, [`core::convert::Infallible`] can instead be used as the `Error`
291 /// type.
292 ///
293 /// [`core::convert::Infallible`]: https://doc.rust-lang.org/stable/core/convert/enum.Infallible.html
294 type Error;
295
296 /// Draw individual pixels to the display without a defined order.
297 ///
298 /// Due to the unordered nature of the pixel iterator, this method is likely to be the slowest
299 /// drawing method for a display that writes data to the hardware immediately. If possible, the
300 /// other methods in this trait should be implemented to improve performance when rendering
301 /// more contiguous pixel patterns.
302 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
303 where
304 I: IntoIterator<Item = Pixel<Self::Color>>;
305
306 /// Fill a given area with an iterator providing a contiguous stream of pixel colors.
307 ///
308 /// Use this method to fill an area with contiguous, non-transparent pixel colors. Pixel
309 /// coordinates are iterated over from the top left to the bottom right corner of the area in
310 /// row-first order. The provided iterator must provide pixel color values based on this
311 /// ordering to produce correct output.
312 ///
313 /// As seen in the example below, the [`PointsIter::points`] method can be used to get an
314 /// iterator over all points in the provided area.
315 ///
316 /// The provided iterator is not required to provide `width * height` pixels to completely fill
317 /// the area. In this case, `fill_contiguous` should return without error.
318 ///
319 /// This method should not attempt to draw any pixels that fall outside the drawable area of the
320 /// target display. The `area` argument can be clipped to the drawable area using the
321 /// [`Rectangle::intersection`] method.
322 ///
323 /// The default implementation of this method delegates to [`draw_iter`](DrawTarget::draw_iter).
324 ///
325 /// # Examples
326 ///
327 /// This is an example implementation of `fill_contiguous` that delegates to [`draw_iter`]. This
328 /// delegation behaviour is undesirable in a real application as it will be as slow as the
329 /// default trait implementation, however is shown here for demonstration purposes.
330 ///
331 /// The example demonstrates the usage of [`Rectangle::intersection`] on the passed `area`
332 /// argument to only draw visible pixels. If there is no intersection between `area` and the
333 /// display area, no pixels will be drawn.
334 ///
335 /// ```rust
336 /// use embedded_graphics::{
337 /// pixelcolor::{Gray8, GrayColor},
338 /// prelude::*,
339 /// primitives::{ContainsPoint, Rectangle},
340 /// };
341 ///
342 /// struct ExampleDisplay;
343 ///
344 /// impl DrawTarget for ExampleDisplay {
345 /// type Color = Gray8;
346 /// type Error = core::convert::Infallible;
347 ///
348 /// fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
349 /// where
350 /// I: IntoIterator<Item = Pixel<Self::Color>>,
351 /// {
352 /// // Draw pixels to the display
353 ///
354 /// Ok(())
355 /// }
356 ///
357 /// fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
358 /// where
359 /// I: IntoIterator<Item = Self::Color>,
360 /// {
361 /// // Clamp area to drawable part of the display target
362 /// let drawable_area = area.intersection(&self.bounding_box());
363 ///
364 /// // Check that there are visible pixels to be drawn
365 /// if drawable_area.size != Size::zero() {
366 /// self.draw_iter(
367 /// area.points()
368 /// .zip(colors)
369 /// .filter(|(pos, _color)| drawable_area.contains(*pos))
370 /// .map(|(pos, color)| Pixel(pos, color)),
371 /// )
372 /// } else {
373 /// Ok(())
374 /// }
375 /// }
376 /// }
377 ///
378 /// impl OriginDimensions for ExampleDisplay {
379 /// fn size(&self) -> Size {
380 /// Size::new(64, 64)
381 /// }
382 /// }
383 /// ```
384 ///
385 /// [`draw_iter`]: DrawTarget::draw_iter
386 /// [`Rectangle::intersection`]: super::primitives::rectangle::Rectangle::intersection()
387 /// [`PointsIter::points`]: super::primitives::PointsIter::points
388 fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
389 where
390 I: IntoIterator<Item = Self::Color>,
391 {
392 self.draw_iter(
393 area.points()
394 .zip(colors)
395 .map(|(pos, color)| Pixel(pos, color)),
396 )
397 }
398
399 /// Fill a given area with a solid color.
400 ///
401 /// If the target display provides optimized hardware commands for filling a rectangular area of
402 /// the display with a solid color, this method should be overridden to use those commands to
403 /// improve performance.
404 ///
405 /// The default implementation of this method calls [`fill_contiguous`](DrawTarget::fill_contiguous())
406 /// with an iterator that repeats the given `color` for every point in `area`.
407 fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
408 self.fill_contiguous(area, core::iter::repeat(color))
409 }
410
411 /// Fill the entire display with a solid color.
412 ///
413 /// If the target hardware supports a more optimized way of filling the entire display with a
414 /// solid color, this method should be overridden to use those commands.
415 ///
416 /// The default implementation of this method delegates to [`fill_solid`] to fill the
417 /// [`bounding_box`] returned by the [`Dimensions`] implementation.
418 ///
419 /// [`Dimensions`]: super::geometry::Dimensions
420 /// [`bounding_box`]: super::geometry::Dimensions::bounding_box
421 /// [`fill_solid`]: DrawTarget::fill_solid()
422 fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
423 self.fill_solid(&self.bounding_box(), color)
424 }
425}