1use embedded_graphics_core::{draw_target::DrawTarget, geometry::Point, pixelcolor::Rgb565};
7use heapless::Vec;
8
9use crate::{
10 geometry::Rect,
11 render::{RenderCtx, StrokeStyle},
12};
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum PdcCommandType {
17 Path,
19 Circle,
21 PrecisePath,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub struct PdcError;
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
31pub struct PdcPrecisePoint {
32 pub x_fixed: i16,
33 pub y_fixed: i16,
34}
35
36impl PdcPrecisePoint {
37 pub const fn from_subpixels(x_fixed: i16, y_fixed: i16) -> Self {
38 Self { x_fixed, y_fixed }
39 }
40
41 pub const fn from_pixels(x: i16, y: i16) -> Self {
42 Self {
43 x_fixed: x << 3,
44 y_fixed: y << 3,
45 }
46 }
47
48 pub const fn to_pixel_point(self) -> Point {
49 Point::new((self.x_fixed >> 3) as i32, (self.y_fixed >> 3) as i32)
50 }
51
52 pub fn to_f32_point(self) -> (f32, f32) {
53 (self.x_fixed as f32 / 8.0, self.y_fixed as f32 / 8.0)
54 }
55}
56
57#[derive(Clone, Debug, PartialEq)]
59pub struct PdcCommand<const MAX_POINTS: usize = 16> {
60 pub command_type: PdcCommandType,
61 pub stroke_color: Option<Rgb565>,
62 pub fill_color: Option<Rgb565>,
63 pub stroke_width: u8,
64 pub radius: u16,
65 pub is_closed: bool,
66 pub points: Vec<PdcPrecisePoint, MAX_POINTS>,
67}
68
69impl<const MAX_POINTS: usize> PdcCommand<MAX_POINTS> {
70 pub const fn new(command_type: PdcCommandType) -> Self {
71 Self {
72 command_type,
73 stroke_color: None,
74 fill_color: None,
75 stroke_width: 1,
76 radius: 0,
77 is_closed: false,
78 points: Vec::new(),
79 }
80 }
81
82 pub fn circle(
83 center: Point,
84 radius: u16,
85 stroke: Option<Rgb565>,
86 fill: Option<Rgb565>,
87 stroke_width: u8,
88 ) -> Self {
89 let mut cmd = Self::new(PdcCommandType::Circle);
90 cmd.stroke_color = stroke;
91 cmd.fill_color = fill;
92 cmd.stroke_width = stroke_width;
93 cmd.radius = radius;
94 let p = PdcPrecisePoint::from_pixels(center.x as i16, center.y as i16);
95 let _ = cmd.points.push(p);
96 cmd
97 }
98
99 pub fn add_point(&mut self, pt: Point) -> Result<(), PdcError> {
100 self.points
101 .push(PdcPrecisePoint::from_pixels(pt.x as i16, pt.y as i16))
102 .map_err(|_| PdcError)
103 }
104
105 pub fn add_subpixel_point(&mut self, x_fixed: i16, y_fixed: i16) -> Result<(), PdcError> {
106 self.points
107 .push(PdcPrecisePoint::from_subpixels(x_fixed, y_fixed))
108 .map_err(|_| PdcError)
109 }
110
111 pub fn render<D, C>(&self, ctx: &mut RenderCtx<'_, D, C>, offset: Point) -> Result<(), D::Error>
113 where
114 D: DrawTarget<Color = Rgb565>,
115 C: crate::render::Compositor<D>,
116 {
117 match self.command_type {
118 PdcCommandType::Circle => {
119 if let Some(center_pt) = self.points.first() {
120 let p = center_pt.to_pixel_point();
121 let cx = p.x + offset.x;
122 let cy = p.y + offset.y;
123 let r = self.radius as u32;
124
125 if let Some(fill) = self.fill_color {
126 ctx.fill_circle(cx, cy, r, fill)?;
127 }
128 if let Some(stroke) = self.stroke_color {
129 if self.stroke_width > 0 {
130 ctx.stroke_circle(cx, cy, r, stroke)?;
131 }
132 }
133 }
134 }
135 PdcCommandType::Path | PdcCommandType::PrecisePath => {
136 if self.points.len() < 2 {
137 return Ok(());
138 }
139
140 if let Some(stroke) = self.stroke_color {
142 let mut prev = self.points[0].to_pixel_point();
143 prev.x += offset.x;
144 prev.y += offset.y;
145
146 let mut i = 1;
147 while i < self.points.len() {
148 let mut curr = self.points[i].to_pixel_point();
149 curr.x += offset.x;
150 curr.y += offset.y;
151
152 if self.stroke_width <= 1 {
153 ctx.draw_line(prev.x, prev.y, curr.x, curr.y, stroke)?;
154 } else {
155 ctx.draw_line_styled(
156 prev.x,
157 prev.y,
158 curr.x,
159 curr.y,
160 StrokeStyle::new(stroke).with_width(self.stroke_width),
161 )?;
162 }
163 prev = curr;
164 i += 1;
165 }
166
167 if self.is_closed && self.points.len() >= 3 {
168 let mut first = self.points[0].to_pixel_point();
169 first.x += offset.x;
170 first.y += offset.y;
171 if self.stroke_width <= 1 {
172 ctx.draw_line(prev.x, prev.y, first.x, first.y, stroke)?;
173 } else {
174 ctx.draw_line_styled(
175 prev.x,
176 prev.y,
177 first.x,
178 first.y,
179 StrokeStyle::new(stroke).with_width(self.stroke_width),
180 )?;
181 }
182 }
183 }
184 }
185 }
186 Ok(())
187 }
188}
189
190#[derive(Clone, Debug, PartialEq)]
192pub struct PdcImage<const MAX_COMMANDS: usize = 8, const MAX_POINTS_PER_CMD: usize = 16> {
193 pub viewbox: Rect,
194 pub commands: Vec<PdcCommand<MAX_POINTS_PER_CMD>, MAX_COMMANDS>,
195}
196
197impl<const MAX_COMMANDS: usize, const MAX_POINTS_PER_CMD: usize> Default
198 for PdcImage<MAX_COMMANDS, MAX_POINTS_PER_CMD>
199{
200 fn default() -> Self {
201 Self::new(Rect::new(0, 0, 0, 0))
202 }
203}
204
205impl<const MAX_COMMANDS: usize, const MAX_POINTS_PER_CMD: usize>
206 PdcImage<MAX_COMMANDS, MAX_POINTS_PER_CMD>
207{
208 pub const fn new(viewbox: Rect) -> Self {
209 Self {
210 viewbox,
211 commands: Vec::new(),
212 }
213 }
214
215 pub fn push_command(&mut self, cmd: PdcCommand<MAX_POINTS_PER_CMD>) -> Result<(), PdcError> {
216 self.commands.push(cmd).map_err(|_| PdcError)
217 }
218
219 pub fn draw<D, C>(&self, ctx: &mut RenderCtx<'_, D, C>, origin: Point) -> Result<(), D::Error>
221 where
222 D: DrawTarget<Color = Rgb565>,
223 C: crate::render::Compositor<D>,
224 {
225 for cmd in &self.commands {
226 cmd.render(ctx, origin)?;
227 }
228 Ok(())
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use crate::framebuffer::Framebuffer;
236 use embedded_graphics_core::pixelcolor::RgbColor;
237
238 #[test]
239 fn test_pdc_precise_points() {
240 let pt = PdcPrecisePoint::from_pixels(10, 20);
241 assert_eq!(pt.x_fixed, 80);
242 assert_eq!(pt.y_fixed, 160);
243 assert_eq!(pt.to_pixel_point(), Point::new(10, 20));
244
245 let sub_pt = PdcPrecisePoint::from_subpixels(84, 164);
246 assert_eq!(sub_pt.to_pixel_point(), Point::new(10, 20));
247 let (fx, fy) = sub_pt.to_f32_point();
248 assert!((fx - 10.5).abs() < 0.001);
249 assert!((fy - 20.5).abs() < 0.001);
250 }
251
252 #[test]
253 fn test_pdc_image_render() {
254 let mut img = PdcImage::<4, 8>::new(Rect::new(0, 0, 20, 20));
255 let circle = PdcCommand::circle(Point::new(10, 10), 4, Some(Rgb565::RED), None, 1);
256 assert!(img.push_command(circle).is_ok());
257
258 let mut fb = Framebuffer::<400>::new(20, 20);
259 let mut ctx = RenderCtx::new(&mut fb, Rect::new(0, 0, 20, 20));
260 img.draw(&mut ctx, Point::new(0, 0)).unwrap();
261 }
262}