embedded_graphics_web_simulator/
display.rs1use core::marker::PhantomData;
2use std::error::Error;
3
4use embedded_graphics::{
5 geometry::Size,
6 pixelcolor::{PixelColor, Rgb888},
7 prelude::*,
8 primitives::Rectangle,
9};
10use wasm_bindgen::{Clamped, JsCast, JsValue};
11use web_sys::{CanvasRenderingContext2d, Element, ImageData};
12
13use crate::output_settings::OutputSettings;
14
15pub struct WebSimulatorDisplay<C> {
17 size: Size,
18 canvas_size: Size,
19 output_settings: OutputSettings,
20 backing: Vec<u8>,
21 context: CanvasRenderingContext2d,
22 _color_type: PhantomData<C>,
23}
24
25impl<C> WebSimulatorDisplay<C>
26where
27 C: PixelColor + Into<Rgb888>,
28{
29 pub fn new(
34 size: (u32, u32),
35 output_settings: &OutputSettings,
36 parent: Option<&Element>,
37 ) -> Self {
38 let canvas_width =
40 size.0 * output_settings.scale + (size.0 - 1) * output_settings.pixel_spacing;
41 let canvas_height =
43 size.1 * output_settings.scale + (size.1 - 1) * output_settings.pixel_spacing;
44
45 let document = web_sys::window().unwrap().document().unwrap();
46 let canvas = document.create_element("canvas").unwrap();
47 let canvas: web_sys::HtmlCanvasElement = canvas
48 .dyn_into::<web_sys::HtmlCanvasElement>()
49 .map_err(|_| ())
50 .unwrap();
51 canvas.set_width(canvas_width);
52 canvas.set_height(canvas_height);
53 parent
54 .unwrap_or(
55 &document
56 .body()
57 .expect("document doesn't have a body and no alternative parent was supplied")
58 .dyn_into::<web_sys::Element>()
59 .map_err(|_| ())
60 .unwrap(),
61 )
62 .append_child(&canvas)
63 .expect("couldn't append canvas to parent");
64 let context = canvas
65 .get_context("2d")
66 .unwrap()
67 .unwrap()
68 .dyn_into::<web_sys::CanvasRenderingContext2d>()
69 .unwrap();
70
71 WebSimulatorDisplay {
74 size: Size::new(size.0, size.1),
75 canvas_size: Size::new(canvas_width, canvas_height),
76 backing: vec![0; (4 * canvas_width * canvas_height) as usize],
77 context,
78 output_settings: output_settings.clone(),
79 _color_type: PhantomData,
80 }
81 }
82
83 pub fn flush(&mut self) -> Result<(), JsValue> {
84 let backing = self.backing.as_mut_slice();
85 let image_data =
86 ImageData::new_with_u8_clamped_array(Clamped(backing), self.canvas_size.width)
87 .expect("could not create ImageData");
88 self.context.put_image_data(&image_data, 0., 0.)?;
89 Ok(())
90 }
91}
92impl<C> OriginDimensions for WebSimulatorDisplay<C>
93where
94 C: PixelColor + Into<Rgb888>,
95{
96 fn size(&self) -> Size {
97 self.size
98 }
99}
100
101impl<C> DrawTarget for WebSimulatorDisplay<C>
102where
103 C: PixelColor + Into<Rgb888>,
104{
105 type Color = C;
106 type Error = Box<dyn Error>;
107
108 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
109 where
110 I: IntoIterator<Item = Pixel<Self::Color>>,
111 {
112 let canvas_width = self.canvas_size.width as usize;
113 let backing = self.backing.as_mut_slice();
114
115 let scale = self.output_settings.scale as usize;
116
117 let pitch = scale + self.output_settings.pixel_spacing as usize;
119
120 let bounding_box = Rectangle::new(Point::new(0, 0), self.size);
121 for pixel in pixels.into_iter() {
122 let point = pixel.0;
123 if bounding_box.contains(point) {
124 let rgb: Rgb888 = pixel.1.into();
125 let rgb_slice = &[rgb.r(), rgb.g(), rgb.b(), 255];
126 let py = point.y as usize;
127 let px = point.x as usize;
128
129 let x_offset = px * 4 * pitch;
130 for y in 0..scale {
131 let y_offset = py * 4 * canvas_width * pitch + y * 4 * canvas_width;
132 for x in 0..scale {
133 let pixel_offset = y_offset + x_offset + x * 4;
134 backing[pixel_offset..pixel_offset + 4].copy_from_slice(rgb_slice);
135 }
136 }
137 }
138 }
139
140 Ok(())
141 }
142}