1use wasm_bindgen::{prelude::*, Clamped};
2
3use embedded_graphics::{
4 drawable::Pixel,
5 geometry::{Dimensions, Size},
6 image::{Image, ImageDimensions},
7 pixelcolor::{Rgb565, Rgb888, RgbColor},
8 prelude::*,
9 primitives,
10 style::{PrimitiveStyle, Styled},
11 DrawTarget,
12};
13
14pub mod prelude;
15pub mod utils;
16
17#[derive(Clone, Debug, PartialEq)]
43pub struct CanvasDisplay {
44 canvas: web_sys::HtmlCanvasElement,
45 context: web_sys::CanvasRenderingContext2d,
46 #[cfg(feature = "offscreen")]
47 offscreen_canvas: web_sys::HtmlCanvasElement,
48 #[cfg(feature = "offscreen")]
49 offscreen_context: web_sys::CanvasRenderingContext2d,
50}
51
52impl CanvasDisplay {
53 pub fn new(width: u32, height: u32, canvas: &str) -> Result<Self, String> {
56 let canvas = utils::canvas(canvas)?;
57 canvas.set_width(width);
58 canvas.set_height(height);
59
60 let context = utils::context_2d(&canvas)?;
61
62 #[cfg(feature = "offscreen")]
63 let offscreen_canvas = utils::create_canvas(width, height)?;
64
65 #[cfg(feature = "offscreen")]
66 let offscreen_context = utils::context_2d(&offscreen_canvas)?;
67
68 Ok(CanvasDisplay {
69 canvas,
70 context,
71 #[cfg(feature = "offscreen")]
72 offscreen_canvas,
73 #[cfg(feature = "offscreen")]
74 offscreen_context,
75 })
76 }
77
78 #[cfg(feature = "offscreen")]
79 pub fn flip(&self) -> Result<(), String> {
81 self.context
82 .draw_image_with_html_canvas_element(&self.offscreen_canvas, 0.0, 0.0)
83 .map_err(|_| "CanvasDisplay::flip: Could not draw offscreen canvas.".to_string())
84 }
85
86 #[cfg(not(feature = "offscreen"))]
87 pub fn context(&self) -> &web_sys::CanvasRenderingContext2d {
89 &self.context
90 }
91
92 #[cfg(feature = "offscreen")]
94 pub fn context(&self) -> &web_sys::CanvasRenderingContext2d {
95 &self.offscreen_context
96 }
97}
98
99impl DrawTarget<Rgb565> for CanvasDisplay {
100 type Error = String;
101
102 fn draw_pixel(&mut self, pixel: Pixel<Rgb565>) -> Result<(), Self::Error> {
103 let color: Rgb888 = Rgb888::from(pixel.1);
104
105 let data = web_sys::ImageData::new_with_u8_clamped_array(
106 Clamped(&mut vec![color.r(), color.g(), color.b(), 255]),
107 1,
108 )
109 .map_err(|_| "CanvasDisplay.draw_pixel: Could not create image data.".to_string())?;
110
111 self.context()
112 .put_image_data(&data, pixel.0.x as f64, pixel.0.y as f64)
113 .map_err(|_| {
114 "CanvasDisplay.draw_pixel: Could not put image data to context.".to_string()
115 })?;
116
117 Ok(())
118 }
119
120 fn draw_line(
121 &mut self,
122 item: &Styled<primitives::Line, PrimitiveStyle<Rgb565>>,
123 ) -> Result<(), Self::Error> {
124 let line = item.primitive;
125 let style = item.style;
126
127 self.context().begin_path();
128 self.context().set_line_width(style.stroke_width as f64);
129 if let Some(fill_color) = style.fill_color {
130 let fill_color = color_to_js(fill_color);
131 self.context().set_fill_style(&fill_color);
132 }
133 if let Some(stroke_color) = style.stroke_color {
134 let stroke_color = color_to_js(stroke_color);
135 self.context().set_stroke_style(&stroke_color);
136 }
137 self.context()
138 .move_to(line.start.x as f64, line.start.y as f64);
139 self.context().line_to(line.end.x as f64, line.end.y as f64);
140
141 self.context().close_path();
142 Ok(())
143 }
144
145 fn draw_rectangle(
146 &mut self,
147 item: &Styled<primitives::Rectangle, PrimitiveStyle<Rgb565>>,
148 ) -> Result<(), Self::Error> {
149 let rectangle = item.primitive;
150 let style = item.style;
151
152 let width = (rectangle.bottom_right.x - rectangle.top_left.x) as f64;
153 let height = (rectangle.bottom_right.y - rectangle.top_left.y) as f64;
154
155 if let Some(fill_color) = style.fill_color {
156 let fill_color = color_to_js(fill_color);
157 self.context().set_fill_style(&fill_color);
158 self.context().fill_rect(
159 rectangle.top_left.x as f64,
160 rectangle.top_left.y as f64,
161 width as f64,
162 height as f64,
163 );
164 }
165
166 if let Some(stroke_color) = style.stroke_color {
167 self.context().set_line_width(style.stroke_width as f64);
168 let stroke_color = color_to_js(stroke_color);
169 self.context().set_stroke_style(&stroke_color);
170 self.context().stroke_rect(
171 rectangle.top_left.x as f64,
172 rectangle.top_left.y as f64,
173 width as f64,
174 height as f64,
175 );
176 }
177
178 Ok(())
179 }
180
181 fn draw_circle(
182 &mut self,
183 item: &Styled<primitives::Circle, PrimitiveStyle<Rgb565>>,
184 ) -> Result<(), Self::Error> {
185 let circle = item.primitive;
186 let style = item.style;
187
188 self.context().begin_path();
189 self.context()
190 .arc(
191 circle.center.x as f64,
192 circle.center.y as f64,
193 circle.radius as f64,
194 0.0,
195 2.0 * std::f64::consts::PI,
196 )
197 .map_err(|_| "CanvasDisplay::draw_circle: Could not call context.arc.".to_string())?;
198
199 self.context().set_line_width(style.stroke_width as f64);
200 if let Some(fill_color) = style.fill_color {
201 let fill_color = color_to_js(fill_color);
202 self.context().set_fill_style(&fill_color);
203 self.context().fill();
204 }
205 if let Some(stroke_color) = style.stroke_color {
206 let stroke_color = color_to_js(stroke_color);
207 self.context().set_stroke_style(&stroke_color);
208 self.context().stroke();
209 }
210
211 self.context().close_path();
212
213 Ok(())
214 }
215
216 fn draw_image<'a, 'b, I>(&mut self, item: &'a Image<'b, I, Rgb565>) -> Result<(), Self::Error>
217 where
218 &'b I: IntoPixelIter<Rgb565>,
219 I: ImageDimensions,
220 {
221 let width = item.size().width;
222 let mut pixels = vec![];
223
224 for pixel in item {
225 let color: Rgb888 = Rgb888::from(pixel.1);
226 pixels.push(color.r());
227 pixels.push(color.g());
228 pixels.push(color.b());
229 pixels.push(255);
230 }
231
232 let data = web_sys::ImageData::new_with_u8_clamped_array(Clamped(&mut pixels), width)
233 .map_err(|_| "CanvasDisplay.draw_image: Could not create image data.".to_string())?;
234
235 self.context()
236 .put_image_data(&data, item.top_left().x as f64, item.top_left().y as f64)
237 .map_err(|_| {
238 "CanvasDisplay.draw_image: Could not put image data on context.".to_string()
239 })?;
240
241 Ok(())
242 }
243
244 fn size(&self) -> Size {
245 Size::new(self.canvas.width(), self.canvas.height())
246 }
247}
248
249fn color_to_js(color: Rgb565) -> JsValue {
250 let color: Rgb888 = Rgb888::from(color);
251 let data =
252 0xFF00_0000 | ((color.r() as u32) << 16) | ((color.g() as u32) << 8) | (color.b() as u32);
253
254 let mut color = format!("#{:x}", data);
255 color.remove(1);
256 color.remove(1);
257
258 JsValue::from_str(color.as_str())
259}