use wasm_bindgen::{prelude::*, Clamped};
use embedded_graphics::{
drawable::Pixel,
geometry::{Dimensions, Size},
image::{Image, ImageDimensions},
pixelcolor::{Rgb565, Rgb888, RgbColor},
prelude::*,
primitives,
style::{PrimitiveStyle, Styled},
DrawTarget,
};
pub mod prelude;
pub mod utils;
#[derive(Clone, Debug, PartialEq)]
pub struct CanvasDisplay {
canvas: web_sys::HtmlCanvasElement,
context: web_sys::CanvasRenderingContext2d,
#[cfg(feature = "offscreen")]
offscreen_canvas: web_sys::HtmlCanvasElement,
#[cfg(feature = "offscreen")]
offscreen_context: web_sys::CanvasRenderingContext2d,
}
impl CanvasDisplay {
pub fn new(width: u32, height: u32, canvas: &str) -> Result<Self, String> {
let canvas = utils::canvas(canvas)?;
canvas.set_width(width);
canvas.set_height(height);
let context = utils::context_2d(&canvas)?;
#[cfg(feature = "offscreen")]
let offscreen_canvas = utils::create_canvas(width, height)?;
#[cfg(feature = "offscreen")]
let offscreen_context = utils::context_2d(&offscreen_canvas)?;
Ok(CanvasDisplay {
canvas,
context,
#[cfg(feature = "offscreen")]
offscreen_canvas,
#[cfg(feature = "offscreen")]
offscreen_context,
})
}
#[cfg(feature = "offscreen")]
pub fn flip(&self) -> Result<(), String> {
self.context
.draw_image_with_html_canvas_element(&self.offscreen_canvas, 0.0, 0.0)
.map_err(|_| "CanvasDisplay::flip: Could not draw offscreen canvas.".to_string())
}
#[cfg(not(feature = "offscreen"))]
pub fn context(&self) -> &web_sys::CanvasRenderingContext2d {
&self.context
}
#[cfg(feature = "offscreen")]
pub fn context(&self) -> &web_sys::CanvasRenderingContext2d {
&self.offscreen_context
}
}
impl DrawTarget<Rgb565> for CanvasDisplay {
type Error = String;
fn draw_pixel(&mut self, pixel: Pixel<Rgb565>) -> Result<(), Self::Error> {
let color: Rgb888 = Rgb888::from(pixel.1);
let data = web_sys::ImageData::new_with_u8_clamped_array(
Clamped(&mut vec![color.r(), color.g(), color.b(), 255]),
1,
)
.map_err(|_| "CanvasDisplay.draw_pixel: Could not create image data.".to_string())?;
self.context()
.put_image_data(&data, pixel.0.x as f64, pixel.0.y as f64)
.map_err(|_| {
"CanvasDisplay.draw_pixel: Could not put image data to context.".to_string()
})?;
Ok(())
}
fn draw_line(
&mut self,
item: &Styled<primitives::Line, PrimitiveStyle<Rgb565>>,
) -> Result<(), Self::Error> {
let line = item.primitive;
let style = item.style;
self.context().begin_path();
self.context().set_line_width(style.stroke_width as f64);
if let Some(fill_color) = style.fill_color {
let fill_color = color_to_js(fill_color);
self.context().set_fill_style(&fill_color);
}
if let Some(stroke_color) = style.stroke_color {
let stroke_color = color_to_js(stroke_color);
self.context().set_stroke_style(&stroke_color);
}
self.context()
.move_to(line.start.x as f64, line.start.y as f64);
self.context().line_to(line.end.x as f64, line.end.y as f64);
self.context().close_path();
Ok(())
}
fn draw_rectangle(
&mut self,
item: &Styled<primitives::Rectangle, PrimitiveStyle<Rgb565>>,
) -> Result<(), Self::Error> {
let rectangle = item.primitive;
let style = item.style;
let width = (rectangle.bottom_right.x - rectangle.top_left.x) as f64;
let height = (rectangle.bottom_right.y - rectangle.top_left.y) as f64;
if let Some(fill_color) = style.fill_color {
let fill_color = color_to_js(fill_color);
self.context().set_fill_style(&fill_color);
self.context().fill_rect(
rectangle.top_left.x as f64,
rectangle.top_left.y as f64,
width as f64,
height as f64,
);
}
if let Some(stroke_color) = style.stroke_color {
self.context().set_line_width(style.stroke_width as f64);
let stroke_color = color_to_js(stroke_color);
self.context().set_stroke_style(&stroke_color);
self.context().stroke_rect(
rectangle.top_left.x as f64,
rectangle.top_left.y as f64,
width as f64,
height as f64,
);
}
Ok(())
}
fn draw_circle(
&mut self,
item: &Styled<primitives::Circle, PrimitiveStyle<Rgb565>>,
) -> Result<(), Self::Error> {
let circle = item.primitive;
let style = item.style;
self.context().begin_path();
self.context()
.arc(
circle.center.x as f64,
circle.center.y as f64,
circle.radius as f64,
0.0,
2.0 * std::f64::consts::PI,
)
.map_err(|_| "CanvasDisplay::draw_circle: Could not call context.arc.".to_string())?;
self.context().set_line_width(style.stroke_width as f64);
if let Some(fill_color) = style.fill_color {
let fill_color = color_to_js(fill_color);
self.context().set_fill_style(&fill_color);
self.context().fill();
}
if let Some(stroke_color) = style.stroke_color {
let stroke_color = color_to_js(stroke_color);
self.context().set_stroke_style(&stroke_color);
self.context().stroke();
}
self.context().close_path();
Ok(())
}
fn draw_image<'a, 'b, I>(&mut self, item: &'a Image<'b, I, Rgb565>) -> Result<(), Self::Error>
where
&'b I: IntoPixelIter<Rgb565>,
I: ImageDimensions,
{
let width = item.size().width;
let mut pixels = vec![];
for pixel in item {
let color: Rgb888 = Rgb888::from(pixel.1);
pixels.push(color.r());
pixels.push(color.g());
pixels.push(color.b());
pixels.push(255);
}
let data = web_sys::ImageData::new_with_u8_clamped_array(Clamped(&mut pixels), width)
.map_err(|_| "CanvasDisplay.draw_image: Could not create image data.".to_string())?;
self.context()
.put_image_data(&data, item.top_left().x as f64, item.top_left().y as f64)
.map_err(|_| {
"CanvasDisplay.draw_image: Could not put image data on context.".to_string()
})?;
Ok(())
}
fn size(&self) -> Size {
Size::new(self.canvas.width(), self.canvas.height())
}
}
fn color_to_js(color: Rgb565) -> JsValue {
let color: Rgb888 = Rgb888::from(color);
let data =
0xFF00_0000 | ((color.r() as u32) << 16) | ((color.g() as u32) << 8) | (color.b() as u32);
let mut color = format!("#{:x}", data);
color.remove(1);
color.remove(1);
JsValue::from_str(color.as_str())
}