plotters_unsable/drawing/
backend.rs

1use crate::style::{Color, FontDesc, FontError, Mixable};
2use std::error::Error;
3
4/// A coordiante in the image
5pub type BackendCoord = (i32, i32);
6
7/// The error produced by a drawing backend
8#[derive(Debug)]
9pub enum DrawingErrorKind<E: Error> {
10    /// A drawing backend error
11    DrawingError(E),
12    /// A font rendering error
13    FontError(FontError),
14}
15
16impl<E: Error> std::fmt::Display for DrawingErrorKind<E> {
17    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
18        match self {
19            DrawingErrorKind::DrawingError(e) => write!(fmt, "Drawing backend error: {}", e),
20            DrawingErrorKind::FontError(e) => write!(fmt, "Font loading error: {}", e),
21        }
22    }
23}
24
25impl<E: Error> Error for DrawingErrorKind<E> {}
26
27/// The style data for the backend drawing API
28pub trait BackendStyle {
29    /// The underlying type reprsents the color for this style
30    type ColorType: Color;
31
32    /// Convert the style into the underlying color
33    fn as_color(&self) -> &Self::ColorType;
34    // TODO: In the future we should support stroke width, line shape, etc....
35}
36
37impl<T: Color> BackendStyle for T {
38    type ColorType = T;
39    fn as_color(&self) -> &T {
40        self
41    }
42}
43
44///  The drawing backend trait, which implemenets the low-level drawing APIs.
45///  This trait has a set of default implementation. And the minimal requirement of
46///  implementing a drawing backend is implementing the `draw_pixel` function.
47///
48///  If the drawing backend supports vector graphics, the other drawing APIs should be
49///  overrided by the backend specific implementation. Otherwise, the default implementation
50///  will use the pixel-based approach to draw other types of low-level shapes.
51pub trait DrawingBackend {
52    /// The error type reported by the backend
53    type ErrorType: Error;
54
55    /// Get the dimension of the drawing backend in pixel
56    fn get_size(&self) -> (u32, u32);
57
58    /// Ensure the backend is ready to draw
59    fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>;
60
61    /// Finialize the drawing step and present all the changes.
62    /// This is used as the real-time rendering support.
63    /// The backend may implement in the following way, when `ensure_prepared` is called
64    /// it checks if it needs a fresh buffer and `present` is called rendering all the
65    /// pending changes on the screen.
66    fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>;
67
68    /// Draw a pixel on the drawing backend
69    /// - `point`: The backend pixel-based coordinate to draw
70    /// - `color`: The color of the pixel
71    fn draw_pixel<S: Color>(
72        &mut self,
73        point: BackendCoord,
74        color: &S,
75    ) -> Result<(), DrawingErrorKind<Self::ErrorType>>;
76
77    /// Draw a line on the drawing backend
78    /// - `from`: The start point of the line
79    /// - `to`: The end point of the line
80    /// - `style`: The style of the line
81    fn draw_line<S: BackendStyle>(
82        &mut self,
83        mut from: BackendCoord,
84        mut to: BackendCoord,
85        style: &S,
86    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
87        if style.as_color().alpha() == 0.0 {
88            return Ok(());
89        }
90
91        let steep = (from.0 - to.0).abs() < (from.1 - to.1).abs();
92
93        if steep {
94            from = (from.1, from.0);
95            to = (to.1, to.0);
96        }
97
98        let (from, to) = if from.0 > to.0 {
99            (to, from)
100        } else {
101            (from, to)
102        };
103
104        let grad = f64::from(to.1 - from.1) / f64::from(to.0 - from.0);
105
106        let mut put_pixel = |(x, y): BackendCoord, b: f64| {
107            if steep {
108                self.draw_pixel((y, x), &style.as_color().mix(b))
109            } else {
110                self.draw_pixel((x, y), &style.as_color().mix(b))
111            }
112        };
113
114        let mut y = f64::from(from.1);
115
116        for x in from.0..=to.0 {
117            put_pixel((x, y as i32), 1.0 + y.floor() - y)?;
118            put_pixel((x, y as i32 + 1), y - y.floor())?;
119
120            y += grad;
121        }
122
123        Ok(())
124    }
125
126    /// Draw a rectangle on the drawing backend
127    /// - `upper_left`: The coordinate of the upper-left corner of the rect
128    /// - `bottom_right`: The coordinate of the bottom-right corner of the rect
129    /// - `style`: The style
130    /// - `fill`: If the rectangle should be filled
131    fn draw_rect<S: BackendStyle>(
132        &mut self,
133        upper_left: BackendCoord,
134        bottom_right: BackendCoord,
135        style: &S,
136        fill: bool,
137    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
138        if style.as_color().alpha() == 0.0 {
139            return Ok(());
140        }
141        let (upper_left, bottom_right) = (
142            (
143                upper_left.0.min(bottom_right.0),
144                upper_left.1.min(bottom_right.1),
145            ),
146            (
147                upper_left.0.max(bottom_right.0),
148                upper_left.1.max(bottom_right.1),
149            ),
150        );
151
152        if fill {
153            if bottom_right.0 - upper_left.0 < bottom_right.1 - upper_left.1 {
154                for x in upper_left.0..=bottom_right.0 {
155                    self.draw_line((x, upper_left.1), (x, bottom_right.1), style)?;
156                }
157            } else {
158                for y in upper_left.1..=bottom_right.1 {
159                    self.draw_line((upper_left.0, y), (bottom_right.0, y), style)?;
160                }
161            }
162        } else {
163            self.draw_line(
164                (upper_left.0, upper_left.1),
165                (upper_left.0, bottom_right.1),
166                style,
167            )?;
168            self.draw_line(
169                (upper_left.0, upper_left.1),
170                (bottom_right.0, upper_left.1),
171                style,
172            )?;
173            self.draw_line(
174                (bottom_right.0, bottom_right.1),
175                (upper_left.0, bottom_right.1),
176                style,
177            )?;
178            self.draw_line(
179                (bottom_right.0, bottom_right.1),
180                (bottom_right.0, upper_left.1),
181                style,
182            )?;
183        }
184        Ok(())
185    }
186
187    /// Draw a path on the drawing backend
188    /// - `path`: The iterator of key points of the path
189    /// - `style`: The style of the path
190    fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
191        &mut self,
192        path: I,
193        style: &S,
194    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
195        if style.as_color().alpha() == 0.0 {
196            return Ok(());
197        }
198
199        let mut begin: Option<BackendCoord> = None;
200        for end in path.into_iter() {
201            if let Some(begin) = begin {
202                self.draw_line(begin, end, style)?;
203            }
204            begin = Some(end);
205        }
206        Ok(())
207    }
208
209    /// Draw a circle on the drawing backend
210    /// - `center`: The center coordinate of the circle
211    /// - `radius`: The radius of the circle
212    /// - `style`: The style of the shape
213    /// - `fill`: If the circle should be filled
214    fn draw_circle<S: BackendStyle>(
215        &mut self,
216        center: BackendCoord,
217        radius: u32,
218        style: &S,
219        fill: bool,
220    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
221        if style.as_color().alpha() == 0.0 {
222            return Ok(());
223        }
224
225        let min = (f64::from(radius) * (1.0 - (2f64).sqrt() / 2.0)).ceil() as i32;
226        let max = (f64::from(radius) * (1.0 + (2f64).sqrt() / 2.0)).floor() as i32;
227
228        let range = min..=max;
229
230        let (up, down) = (
231            range.start() + center.1 - radius as i32,
232            range.end() + center.1 - radius as i32,
233        );
234
235        for dy in range {
236            let dy = dy - radius as i32;
237            let y = center.1 + dy;
238
239            let lx = (f64::from(radius) * f64::from(radius)
240                - (f64::from(dy) * f64::from(dy)).max(1e-5))
241            .sqrt();
242
243            let left = center.0 - lx.floor() as i32;
244            let right = center.0 + lx.floor() as i32;
245
246            let v = lx - lx.floor();
247
248            let x = center.0 + dy;
249            let top = center.1 - lx.floor() as i32;
250            let bottom = center.1 + lx.floor() as i32;
251
252            if fill {
253                self.draw_line((left, y), (right, y), style)?;
254                self.draw_line((x, top), (x, up), style)?;
255                self.draw_line((x, down), (x, bottom), style)?;
256            } else {
257                self.draw_pixel((left, y), &style.as_color().mix(1.0 - v))?;
258                self.draw_pixel((right, y), &style.as_color().mix(1.0 - v))?;
259
260                self.draw_pixel((x, top), &style.as_color().mix(1.0 - v))?;
261                self.draw_pixel((x, bottom), &style.as_color().mix(1.0 - v))?;
262            }
263
264            self.draw_pixel((left - 1, y), &style.as_color().mix(v))?;
265            self.draw_pixel((right + 1, y), &style.as_color().mix(v))?;
266            self.draw_pixel((x, top - 1), &style.as_color().mix(v))?;
267            self.draw_pixel((x, bottom + 1), &style.as_color().mix(v))?;
268        }
269
270        Ok(())
271    }
272
273    /// Draw a text on the drawing backend
274    /// - `text`: The text to draw
275    /// - `font`: The description of the font
276    /// - `pos` : The position backend
277    /// - `color`: The color of the text
278    fn draw_text<'a, C: Color>(
279        &mut self,
280        text: &str,
281        font: &FontDesc<'a>,
282        pos: BackendCoord,
283        color: &C,
284    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
285        if color.alpha() == 0.0 {
286            return Ok(());
287        }
288
289        match font.draw(text, (pos.0, pos.1), |x, y, v| {
290            self.draw_pixel((x as i32, y as i32), &color.mix(f64::from(v)))
291        }) {
292            Ok(drawing_result) => drawing_result,
293            Err(font_error) => Err(DrawingErrorKind::FontError(font_error)),
294        }
295    }
296}