use crate::pixel_target::{
PixelTarget, PixelTargetAdapter, fill_ellipse_pixels, pixel_put, pixel_put_565,
};
use embedded_graphics::{
Drawable,
pixelcolor::{Rgb565, Rgb888, raw::RawU16},
prelude::{IntoStorage, Point, Size},
primitives::Rectangle,
primitives::{Circle, Line, Primitive, PrimitiveStyle},
};
#[cfg_attr(
feature = "doc-images",
doc = ::embed_doc_image::embed_image!(
"image565_view",
"docs/assets/image565_view.png"
)
)]
#[cfg_attr(
feature = "host",
doc = r#"
```rust
use device_envoy_core::cyd::{
Cyd, CydDisplay,
display::{CydFrame, DrawItem, Image565Fixed, tga},
};
use embedded_graphics::{
prelude::{Point, Size},
primitives::Rectangle,
};
const IMAGE: Image565Fixed<45, 73, { 45 * 73 }> = tga!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/assets/cyd_fill_contiguous.tga"
))
.to_565();
async fn draw<C: Cyd>(cyd: &mut C) -> Result<(), C::Error> {
let full_view = IMAGE.view();
let source = Rectangle::new(Point::new(2, 35), Size::new(41, 36));
let cropped_view = IMAGE.view_rect(source);
assert_eq!(cropped_view.size(), source.size);
assert_eq!(
cropped_view.pixel_at(Point::zero()),
full_view.pixel_at(source.top_left),
);
assert_eq!(
cropped_view.rgb565_iter().count(),
source.size.width as usize * source.size.height as usize,
);
let display = cyd.display();
let mut frame = display.full_frame_mut();
DrawItem::Bitmap {
view: full_view,
top_left: Point::new(80, 84),
}
.draw(&mut frame);
DrawItem::Bitmap {
view: cropped_view,
top_left: Point::new(200, 102),
}
.draw(&mut frame);
frame.flush().await
}
# use device_envoy_core::memory::{CydMemory, assert_framebuffer_matches_expected_png};
# use embedded_graphics::{
# mono_font::ascii::FONT_9X15_BOLD,
# pixelcolor::Rgb888,
# prelude::RgbColor,
# };
# let mut cyd_memory = CydMemory::new(
# Size::new(320, 240),
# Rgb888::BLACK,
# Rgb888::WHITE,
# &FONT_9X15_BOLD,
# );
# futures_executor::block_on(draw(&mut cyd_memory))?;
# let golden_result = assert_framebuffer_matches_expected_png(
# &cyd_memory,
# env!("CARGO_MANIFEST_DIR"),
# "image565_view.png",
# );
# assert!(golden_result.is_ok(), "{golden_result:?}");
# Ok::<(), device_envoy_core::memory::Error>(())
```
The complete image is shown on the left and its cropped view on the right:
![A complete RGB565 image beside a cropped view of the same image][image565_view]
"#
)]
#[derive(Clone, Copy, Debug)]
pub struct Image565View {
pixels: &'static [u16],
stride: u32,
source: Rectangle,
}
impl Image565View {
#[must_use]
pub const fn new(pixels: &'static [u16], size: Size) -> Self {
assert!(
pixels.len() == size.width as usize * size.height as usize,
"Image565View pixels must match width * height"
);
Self {
pixels,
stride: size.width,
source: Rectangle::new(Point::zero(), size),
}
}
#[must_use]
pub(crate) const fn new_cropped(
pixels: &'static [u16],
stride: u32,
source: Rectangle,
) -> Self {
Self {
pixels,
stride,
source,
}
}
#[must_use]
pub const fn size(&self) -> Size {
self.source.size
}
#[must_use]
pub fn pixel_at(&self, point: Point) -> Rgb565 {
assert!(
point.x >= 0 && point.y >= 0,
"Image565View pixel coordinate must be non-negative"
);
let vx = point.x as usize;
let vy = point.y as usize;
assert!(
vx < self.source.size.width as usize && vy < self.source.size.height as usize,
"Image565View pixel coordinate must be inside the view"
);
let source_x = self.source.top_left.x as usize + vx;
let source_y = self.source.top_left.y as usize + vy;
let index = source_y * self.stride as usize + source_x;
Rgb565::from(RawU16::new(self.pixels[index]))
}
pub fn rgb565_iter(&self) -> impl Iterator<Item = Rgb565> + '_ {
Image565ViewPixels {
view: *self,
index: 0,
}
}
}
struct Image565ViewPixels {
view: Image565View,
index: usize,
}
impl Iterator for Image565ViewPixels {
type Item = Rgb565;
fn next(&mut self) -> Option<Self::Item> {
let width = self.view.source.size.width as usize;
let height = self.view.source.size.height as usize;
if self.index >= width * height {
return None;
}
let view_x = self.index % width;
let view_y = self.index / width;
let source_x = self.view.source.top_left.x as usize + view_x;
let source_y = self.view.source.top_left.y as usize + view_y;
let source_index = source_y * self.view.stride as usize + source_x;
self.index += 1;
Some(Rgb565::from(RawU16::new(self.view.pixels[source_index])))
}
}
#[cfg_attr(
feature = "doc-images",
doc = ::embed_doc_image::embed_image!(
"draw_item_bitmap",
"docs/assets/draw_item_bitmap.png"
)
)]
#[cfg_attr(
feature = "host",
doc = r#"
```rust
use device_envoy_core::cyd::{
Cyd, CydDisplay,
display::{CydFrame, DrawItem, Image565Fixed, tga},
};
use embedded_graphics::{
pixelcolor::Rgb888,
prelude::{Point, RgbColor},
};
const BITMAP: Image565Fixed<45, 73, { 45 * 73 }> = tga!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/assets/cyd_fill_contiguous.tga"
))
.to_565();
async fn draw<C: Cyd>(cyd: &mut C) -> Result<(), C::Error> {
let display = cyd.display();
let mut frame = display.full_frame_mut();
let draw_items = [
DrawItem::Bitmap {
view: BITMAP.view(),
top_left: Point::new(35, 84),
},
DrawItem::Circle {
center: (125.0, 120.0),
pixel_radius: 32.0,
color: Rgb888::CYAN,
},
DrawItem::Ellipse {
center: (215.0, 120.0),
axis_a: (38.0, 0.0),
axis_b: (12.0, 24.0),
color: Rgb888::GREEN,
},
DrawItem::Stroke {
start: (280.0, 80.0),
end: (300.0, 160.0),
color: Rgb888::YELLOW,
pixel_width: 8.0,
},
];
for draw_item in draw_items {
draw_item.draw(&mut frame);
}
frame.flush().await
}
# use device_envoy_core::memory::{CydMemory, assert_framebuffer_matches_expected_png};
# use embedded_graphics::{mono_font::ascii::FONT_9X15_BOLD, prelude::Size};
# let mut cyd_memory = CydMemory::new(
# Size::new(320, 240),
# Rgb888::BLACK,
# Rgb888::WHITE,
# &FONT_9X15_BOLD,
# );
# futures_executor::block_on(draw(&mut cyd_memory))?;
# let golden_result = assert_framebuffer_matches_expected_png(
# &cyd_memory,
# env!("CARGO_MANIFEST_DIR"),
# "draw_item_bitmap.png",
# );
# assert!(golden_result.is_ok(), "{golden_result:?}");
# Ok::<(), device_envoy_core::memory::Error>(())
```
![Examples of all four DrawItem variants][draw_item_bitmap]
"#
)]
#[derive(Clone, Copy, Debug)]
pub enum DrawItem {
Stroke {
start: (f32, f32),
end: (f32, f32),
color: Rgb888,
pixel_width: f32,
},
Ellipse {
center: (f32, f32),
axis_a: (f32, f32),
axis_b: (f32, f32),
color: Rgb888,
},
Circle {
center: (f32, f32),
pixel_radius: f32,
color: Rgb888,
},
Bitmap {
view: Image565View,
top_left: Point,
},
}
impl DrawItem {
pub fn draw<T: PixelTarget>(&self, target: &mut T) {
match *self {
DrawItem::Stroke {
start,
end,
color,
pixel_width,
} => {
let width = ((pixel_width + 0.5) as u32).max(1);
Line::new(
embedded_graphics::prelude::Point::new(start.0 as i32, start.1 as i32),
embedded_graphics::prelude::Point::new(end.0 as i32, end.1 as i32),
)
.into_styled(PrimitiveStyle::with_stroke(color, width))
.draw(&mut PixelTargetAdapter(target))
.expect("drawing onto a PixelTargetAdapter is Infallible");
}
DrawItem::Ellipse {
center,
axis_a,
axis_b,
color,
} => {
fill_ellipse_pixels(center, axis_a, axis_b, |position_x, position_y| {
pixel_put(target, position_x, position_y, color);
});
}
DrawItem::Circle {
center,
pixel_radius,
color,
} => {
let diameter = (((pixel_radius * 2.0) + 0.5) as u32).max(1);
Circle::with_center(
embedded_graphics::prelude::Point::new(center.0 as i32, center.1 as i32),
diameter,
)
.into_styled(PrimitiveStyle::with_fill(color))
.draw(&mut PixelTargetAdapter(target))
.expect("drawing onto a PixelTargetAdapter is Infallible");
}
DrawItem::Bitmap { view, top_left } => {
let size = view.size();
for dy in 0..size.height as i32 {
for dx in 0..size.width as i32 {
let view_point = Point::new(dx, dy);
let target_point = top_left + view_point;
pixel_put_565(
target,
target_point.x,
target_point.y,
view.pixel_at(view_point).into_storage(),
);
}
}
}
}
}
}