1use crate::pixel_target::{
2 PixelTarget, PixelTargetAdapter, fill_ellipse_pixels, pixel_put, pixel_put_565,
3};
4use embedded_graphics::{
5 Drawable,
6 pixelcolor::{Rgb565, Rgb888, raw::RawU16},
7 prelude::{IntoStorage, Point, Size},
8 primitives::Rectangle,
9 primitives::{Circle, Line, Primitive, PrimitiveStyle},
10};
11
12#[cfg_attr(
30 feature = "doc-images",
31 doc = ::embed_doc_image::embed_image!(
32 "image565_view",
33 "docs/assets/image565_view.png"
34 )
35)]
36#[cfg_attr(
37 feature = "host",
38 doc = r#"
39
40```rust
41use device_envoy_core::cyd::{
42 Cyd, CydDisplay,
43 display::{CydFrame, DrawItem, Image565Fixed, tga},
44};
45use embedded_graphics::{
46 prelude::{Point, Size},
47 primitives::Rectangle,
48};
49
50const IMAGE: Image565Fixed<45, 73, { 45 * 73 }> = tga!(concat!(
51 env!("CARGO_MANIFEST_DIR"),
52 "/docs/assets/cyd_fill_contiguous.tga"
53))
54.to_565();
55
56async fn draw<C: Cyd>(cyd: &mut C) -> Result<(), C::Error> {
57 let full_view = IMAGE.view();
58 let source = Rectangle::new(Point::new(2, 35), Size::new(41, 36));
59 let cropped_view = IMAGE.view_rect(source);
60
61 assert_eq!(cropped_view.size(), source.size);
62 assert_eq!(
63 cropped_view.pixel_at(Point::zero()),
64 full_view.pixel_at(source.top_left),
65 );
66 assert_eq!(
67 cropped_view.rgb565_iter().count(),
68 source.size.width as usize * source.size.height as usize,
69 );
70
71 let display = cyd.display();
72 let mut frame = display.full_frame_mut();
73 DrawItem::Bitmap {
74 view: full_view,
75 top_left: Point::new(80, 84),
76 }
77 .draw(&mut frame);
78 DrawItem::Bitmap {
79 view: cropped_view,
80 top_left: Point::new(200, 102),
81 }
82 .draw(&mut frame);
83 frame.flush().await
84}
85
86# use device_envoy_core::memory::{CydMemory, assert_framebuffer_matches_expected_png};
87# use embedded_graphics::{
88# mono_font::ascii::FONT_9X15_BOLD,
89# pixelcolor::Rgb888,
90# prelude::RgbColor,
91# };
92# let mut cyd_memory = CydMemory::new(
93# Size::new(320, 240),
94# Rgb888::BLACK,
95# Rgb888::WHITE,
96# &FONT_9X15_BOLD,
97# );
98# futures_executor::block_on(draw(&mut cyd_memory))?;
99# let golden_result = assert_framebuffer_matches_expected_png(
100# &cyd_memory,
101# env!("CARGO_MANIFEST_DIR"),
102# "image565_view.png",
103# );
104# assert!(golden_result.is_ok(), "{golden_result:?}");
105# Ok::<(), device_envoy_core::memory::Error>(())
106```
107
108The complete image is shown on the left and its cropped view on the right:
109
110![A complete RGB565 image beside a cropped view of the same image][image565_view]
111"#
112)]
113#[derive(Clone, Copy, Debug)]
114pub struct Image565View {
115 pixels: &'static [u16],
116 stride: u32,
117 source: Rectangle,
118}
119
120impl Image565View {
121 #[must_use]
129 pub const fn new(pixels: &'static [u16], size: Size) -> Self {
130 assert!(
131 pixels.len() == size.width as usize * size.height as usize,
132 "Image565View pixels must match width * height"
133 );
134 Self {
135 pixels,
136 stride: size.width,
137 source: Rectangle::new(Point::zero(), size),
138 }
139 }
140
141 #[must_use]
144 pub(crate) const fn new_cropped(
145 pixels: &'static [u16],
146 stride: u32,
147 source: Rectangle,
148 ) -> Self {
149 Self {
150 pixels,
151 stride,
152 source,
153 }
154 }
155
156 #[must_use]
161 pub const fn size(&self) -> Size {
162 self.source.size
163 }
164
165 #[must_use]
172 pub fn pixel_at(&self, point: Point) -> Rgb565 {
173 assert!(
174 point.x >= 0 && point.y >= 0,
175 "Image565View pixel coordinate must be non-negative"
176 );
177 let vx = point.x as usize;
178 let vy = point.y as usize;
179 assert!(
180 vx < self.source.size.width as usize && vy < self.source.size.height as usize,
181 "Image565View pixel coordinate must be inside the view"
182 );
183 let source_x = self.source.top_left.x as usize + vx;
184 let source_y = self.source.top_left.y as usize + vy;
185 let index = source_y * self.stride as usize + source_x;
186 Rgb565::from(RawU16::new(self.pixels[index]))
187 }
188
189 pub fn rgb565_iter(&self) -> impl Iterator<Item = Rgb565> + '_ {
196 Image565ViewPixels {
197 view: *self,
198 index: 0,
199 }
200 }
201}
202
203struct Image565ViewPixels {
204 view: Image565View,
205 index: usize,
206}
207
208impl Iterator for Image565ViewPixels {
209 type Item = Rgb565;
210
211 fn next(&mut self) -> Option<Self::Item> {
212 let width = self.view.source.size.width as usize;
213 let height = self.view.source.size.height as usize;
214 if self.index >= width * height {
215 return None;
216 }
217
218 let view_x = self.index % width;
219 let view_y = self.index / width;
220 let source_x = self.view.source.top_left.x as usize + view_x;
221 let source_y = self.view.source.top_left.y as usize + view_y;
222 let source_index = source_y * self.view.stride as usize + source_x;
223 self.index += 1;
224 Some(Rgb565::from(RawU16::new(self.view.pixels[source_index])))
225 }
226}
227
228#[cfg_attr(
253 feature = "doc-images",
254 doc = ::embed_doc_image::embed_image!(
255 "draw_item_bitmap",
256 "docs/assets/draw_item_bitmap.png"
257 )
258)]
259#[cfg_attr(
260 feature = "host",
261 doc = r#"
262
263```rust
264use device_envoy_core::cyd::{
265 Cyd, CydDisplay,
266 display::{CydFrame, DrawItem, Image565Fixed, tga},
267};
268use embedded_graphics::{
269 pixelcolor::Rgb888,
270 prelude::{Point, RgbColor},
271};
272
273const BITMAP: Image565Fixed<45, 73, { 45 * 73 }> = tga!(concat!(
274 env!("CARGO_MANIFEST_DIR"),
275 "/docs/assets/cyd_fill_contiguous.tga"
276))
277.to_565();
278
279async fn draw<C: Cyd>(cyd: &mut C) -> Result<(), C::Error> {
280 let display = cyd.display();
281 let mut frame = display.full_frame_mut();
282 let draw_items = [
283 DrawItem::Bitmap {
284 view: BITMAP.view(),
285 top_left: Point::new(35, 84),
286 },
287 DrawItem::Circle {
288 center: (125.0, 120.0),
289 pixel_radius: 32.0,
290 color: Rgb888::CYAN,
291 },
292 DrawItem::Ellipse {
293 center: (215.0, 120.0),
294 axis_a: (38.0, 0.0),
295 axis_b: (12.0, 24.0),
296 color: Rgb888::GREEN,
297 },
298 DrawItem::Stroke {
299 start: (280.0, 80.0),
300 end: (300.0, 160.0),
301 color: Rgb888::YELLOW,
302 pixel_width: 8.0,
303 },
304 ];
305 for draw_item in draw_items {
306 draw_item.draw(&mut frame);
307 }
308 frame.flush().await
309}
310
311# use device_envoy_core::memory::{CydMemory, assert_framebuffer_matches_expected_png};
312# use embedded_graphics::{mono_font::ascii::FONT_9X15_BOLD, prelude::Size};
313# let mut cyd_memory = CydMemory::new(
314# Size::new(320, 240),
315# Rgb888::BLACK,
316# Rgb888::WHITE,
317# &FONT_9X15_BOLD,
318# );
319# futures_executor::block_on(draw(&mut cyd_memory))?;
320# let golden_result = assert_framebuffer_matches_expected_png(
321# &cyd_memory,
322# env!("CARGO_MANIFEST_DIR"),
323# "draw_item_bitmap.png",
324# );
325# assert!(golden_result.is_ok(), "{golden_result:?}");
326# Ok::<(), device_envoy_core::memory::Error>(())
327```
328
329![Examples of all four DrawItem variants][draw_item_bitmap]
330"#
331)]
332#[derive(Clone, Copy, Debug)]
333pub enum DrawItem {
334 Stroke {
336 start: (f32, f32),
338 end: (f32, f32),
340 color: Rgb888,
342 pixel_width: f32,
344 },
345 Ellipse {
349 center: (f32, f32),
351 axis_a: (f32, f32),
353 axis_b: (f32, f32),
355 color: Rgb888,
357 },
358 Circle {
360 center: (f32, f32),
362 pixel_radius: f32,
364 color: Rgb888,
366 },
367 Bitmap {
369 view: Image565View,
371 top_left: Point,
373 },
374}
375
376impl DrawItem {
377 pub fn draw<T: PixelTarget>(&self, target: &mut T) {
388 match *self {
389 DrawItem::Stroke {
390 start,
391 end,
392 color,
393 pixel_width,
394 } => {
395 let width = ((pixel_width + 0.5) as u32).max(1);
396 Line::new(
397 embedded_graphics::prelude::Point::new(start.0 as i32, start.1 as i32),
398 embedded_graphics::prelude::Point::new(end.0 as i32, end.1 as i32),
399 )
400 .into_styled(PrimitiveStyle::with_stroke(color, width))
401 .draw(&mut PixelTargetAdapter(target))
402 .expect("drawing onto a PixelTargetAdapter is Infallible");
403 }
404 DrawItem::Ellipse {
405 center,
406 axis_a,
407 axis_b,
408 color,
409 } => {
410 fill_ellipse_pixels(center, axis_a, axis_b, |position_x, position_y| {
411 pixel_put(target, position_x, position_y, color);
412 });
413 }
414 DrawItem::Circle {
415 center,
416 pixel_radius,
417 color,
418 } => {
419 let diameter = (((pixel_radius * 2.0) + 0.5) as u32).max(1);
420 Circle::with_center(
421 embedded_graphics::prelude::Point::new(center.0 as i32, center.1 as i32),
422 diameter,
423 )
424 .into_styled(PrimitiveStyle::with_fill(color))
425 .draw(&mut PixelTargetAdapter(target))
426 .expect("drawing onto a PixelTargetAdapter is Infallible");
427 }
428 DrawItem::Bitmap { view, top_left } => {
429 let size = view.size();
430 for dy in 0..size.height as i32 {
431 for dx in 0..size.width as i32 {
432 let view_point = Point::new(dx, dy);
433 let target_point = top_left + view_point;
434 pixel_put_565(
435 target,
436 target_point.x,
437 target_point.y,
438 view.pixel_at(view_point).into_storage(),
439 );
440 }
441 }
442 }
443 }
444 }
445}