| 1 | //! A target for embedded-graphics drawing operations. |
| 2 | |
| 3 | use 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 |
| 281 | pub 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 | } |
| 426 | |