Skip to main content

embedded_gui/render/
stroke.rs

1use embedded_graphics_core::pixelcolor::Rgb565;
2
3#[cfg(not(feature = "std"))]
4use crate::math::F32Ext as _;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum RenderQuality {
8    Low,
9    Medium,
10    High,
11}
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum AntiAliasMode {
15    None,
16    Coverage,
17    Subpixel,
18}
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum StrokeCap {
22    Butt,
23    Round,
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub enum StrokeJoin {
28    Miter,
29    Round,
30}
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub struct StrokeStyle {
34    pub color: Rgb565,
35    pub width: u8,
36    pub antialias: bool,
37    pub antialias_mode: AntiAliasMode,
38    pub cap: StrokeCap,
39    pub join: StrokeJoin,
40}
41
42impl StrokeStyle {
43    pub const fn new(color: Rgb565) -> Self {
44        Self {
45            color,
46            width: 1,
47            antialias: false,
48            antialias_mode: AntiAliasMode::None,
49            cap: StrokeCap::Butt,
50            join: StrokeJoin::Miter,
51        }
52    }
53
54    pub const fn with_width(mut self, width: u8) -> Self {
55        self.width = if width == 0 { 1 } else { width };
56        self
57    }
58
59    pub const fn with_antialias(mut self, antialias: bool) -> Self {
60        self.antialias = antialias;
61        if antialias {
62            if let AntiAliasMode::None = self.antialias_mode {
63                self.antialias_mode = AntiAliasMode::Coverage;
64            }
65        }
66        if !antialias {
67            self.antialias_mode = AntiAliasMode::None;
68        }
69        self
70    }
71
72    pub const fn with_antialias_mode(mut self, mode: AntiAliasMode) -> Self {
73        self.antialias_mode = mode;
74        self.antialias = !matches!(mode, AntiAliasMode::None);
75        self
76    }
77
78    pub const fn with_cap(mut self, cap: StrokeCap) -> Self {
79        self.cap = cap;
80        self
81    }
82
83    pub const fn with_join(mut self, join: StrokeJoin) -> Self {
84        self.join = join;
85        self
86    }
87}
88
89#[derive(Clone, Copy, Debug, PartialEq)]
90pub struct Transform2D {
91    pub m11: f32,
92    pub m12: f32,
93    pub m21: f32,
94    pub m22: f32,
95    pub tx: f32,
96    pub ty: f32,
97}
98
99impl Transform2D {
100    pub const IDENTITY: Self = Self {
101        m11: 1.0,
102        m12: 0.0,
103        m21: 0.0,
104        m22: 1.0,
105        tx: 0.0,
106        ty: 0.0,
107    };
108
109    pub const fn translation(x: f32, y: f32) -> Self {
110        Self {
111            tx: x,
112            ty: y,
113            ..Self::IDENTITY
114        }
115    }
116
117    pub const fn scale(x: f32, y: f32) -> Self {
118        Self {
119            m11: x,
120            m22: y,
121            ..Self::IDENTITY
122        }
123    }
124
125    pub fn rotation(deg: f32) -> Self {
126        let r = deg.to_radians();
127        Self {
128            m11: r.cos(),
129            m12: -r.sin(),
130            m21: r.sin(),
131            m22: r.cos(),
132            ..Self::IDENTITY
133        }
134    }
135
136    pub fn skew(x_deg: f32, y_deg: f32) -> Self {
137        Self {
138            m12: x_deg.to_radians().tan(),
139            m21: y_deg.to_radians().tan(),
140            ..Self::IDENTITY
141        }
142    }
143
144    pub fn then(self, rhs: Self) -> Self {
145        Self {
146            m11: self.m11 * rhs.m11 + self.m12 * rhs.m21,
147            m12: self.m11 * rhs.m12 + self.m12 * rhs.m22,
148            m21: self.m21 * rhs.m11 + self.m22 * rhs.m21,
149            m22: self.m21 * rhs.m12 + self.m22 * rhs.m22,
150            tx: self.m11 * rhs.tx + self.m12 * rhs.ty + self.tx,
151            ty: self.m21 * rhs.tx + self.m22 * rhs.ty + self.ty,
152        }
153    }
154
155    #[inline(always)]
156    pub fn is_identity(self) -> bool {
157        self.m11 == 1.0
158            && self.m12 == 0.0
159            && self.m21 == 0.0
160            && self.m22 == 1.0
161            && self.tx == 0.0
162            && self.ty == 0.0
163    }
164
165    #[inline(always)]
166    pub fn apply(self, x: i32, y: i32) -> (i32, i32) {
167        if self.is_identity() {
168            (x, y)
169        } else {
170            let xf = x as f32;
171            let yf = y as f32;
172            (
173                (self.m11 * xf + self.m12 * yf + self.tx).round() as i32,
174                (self.m21 * xf + self.m22 * yf + self.ty).round() as i32,
175            )
176        }
177    }
178
179    #[inline(always)]
180    pub fn apply_f32(self, x: f32, y: f32) -> (f32, f32) {
181        if self.is_identity() {
182            (x, y)
183        } else {
184            (
185                self.m11 * x + self.m12 * y + self.tx,
186                self.m21 * x + self.m22 * y + self.ty,
187            )
188        }
189    }
190
191    pub fn inverse(self) -> Option<Self> {
192        let det = self.m11 * self.m22 - self.m12 * self.m21;
193        if det.abs() < 1e-7 {
194            return None;
195        }
196        let inv_det = 1.0 / det;
197        let m11 = self.m22 * inv_det;
198        let m12 = -self.m12 * inv_det;
199        let m21 = -self.m21 * inv_det;
200        let m22 = self.m11 * inv_det;
201        let tx = (self.m12 * self.ty - self.m22 * self.tx) * inv_det;
202        let ty = (self.m21 * self.tx - self.m11 * self.ty) * inv_det;
203        Some(Self {
204            m11,
205            m12,
206            m21,
207            m22,
208            tx,
209            ty,
210        })
211    }
212}
213
214/// Path command for 2D vector paths.
215#[derive(Clone, Copy, Debug, PartialEq)]
216pub enum PathVerb {
217    MoveTo(embedded_graphics_core::geometry::Point),
218    LineTo(embedded_graphics_core::geometry::Point),
219    QuadTo {
220        control: embedded_graphics_core::geometry::Point,
221        to: embedded_graphics_core::geometry::Point,
222    },
223    CubicTo {
224        control1: embedded_graphics_core::geometry::Point,
225        control2: embedded_graphics_core::geometry::Point,
226        to: embedded_graphics_core::geometry::Point,
227    },
228    Close,
229}
230
231/// Fixed-capacity vector path (`#![no_std]` zero-allocation).
232#[derive(Clone, Copy, Debug, PartialEq)]
233pub struct VectorPath<const N: usize> {
234    pub verbs: [PathVerb; N],
235    pub len: usize,
236}
237
238impl<const N: usize> Default for VectorPath<N> {
239    fn default() -> Self {
240        Self::new()
241    }
242}
243
244impl<const N: usize> VectorPath<N> {
245    pub const fn new() -> Self {
246        Self {
247            verbs: [PathVerb::Close; N],
248            len: 0,
249        }
250    }
251
252    pub fn is_empty(&self) -> bool {
253        self.len == 0
254    }
255
256    pub fn len(&self) -> usize {
257        self.len
258    }
259
260    pub fn push(&mut self, verb: PathVerb) -> bool {
261        if self.len < N {
262            self.verbs[self.len] = verb;
263            self.len += 1;
264            true
265        } else {
266            false
267        }
268    }
269
270    pub fn move_to(&mut self, pt: embedded_graphics_core::geometry::Point) -> &mut Self {
271        self.push(PathVerb::MoveTo(pt));
272        self
273    }
274
275    pub fn line_to(&mut self, pt: embedded_graphics_core::geometry::Point) -> &mut Self {
276        self.push(PathVerb::LineTo(pt));
277        self
278    }
279
280    pub fn quad_to(
281        &mut self,
282        control: embedded_graphics_core::geometry::Point,
283        to: embedded_graphics_core::geometry::Point,
284    ) -> &mut Self {
285        self.push(PathVerb::QuadTo { control, to });
286        self
287    }
288
289    pub fn cubic_to(
290        &mut self,
291        control1: embedded_graphics_core::geometry::Point,
292        control2: embedded_graphics_core::geometry::Point,
293        to: embedded_graphics_core::geometry::Point,
294    ) -> &mut Self {
295        self.push(PathVerb::CubicTo {
296            control1,
297            control2,
298            to,
299        });
300        self
301    }
302
303    pub fn close(&mut self) -> &mut Self {
304        self.push(PathVerb::Close);
305        self
306    }
307
308    pub fn verbs(&self) -> &[PathVerb] {
309        &self.verbs[..self.len]
310    }
311}