| 1 | //! A target for embedded-graphics drawing operations. |
| 2 | |
| 3 | mod clipped; |
| 4 | mod color_converted; |
| 5 | mod cropped; |
| 6 | mod translated; |
| 7 | |
| 8 | use crate::{geometry::Point, pixelcolor::PixelColor, primitives::Rectangle}; |
| 9 | |
| 10 | pub use clipped::Clipped; |
| 11 | pub use color_converted::ColorConverted; |
| 12 | pub use cropped::Cropped; |
| 13 | pub use translated::Translated; |
| 14 | |
| 15 | pub use embedded_graphics_core::draw_target::DrawTarget; |
| 16 | |
| 17 | /// Extension trait for `DrawTarget`s. |
| 18 | pub 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 | |
| 215 | impl<T> DrawTargetExt for T |
| 216 | where |
| 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)] |
| 240 | mod 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 | } |
| 369 | |