1 | /*! |
2 | The Plotters backend API crate. This is a part of Plotters, the Rust drawing and plotting library, for more details regarding the entire |
3 | Plotters project, please check the [main crate](https://crates.io/crates/plotters). |
4 | |
5 | This is the crate that used as the connector between Plotters and different backend crates. Since Plotters 0.3, all the backends has been |
6 | hosted as seperate crates for the usability and maintainability reasons. |
7 | |
8 | At the same time, Plotters is now supporting third-party backends and all the backends are now supports "plug-and-play": |
9 | To use a external backend, just depends on both the Plotters main crate and the third-party backend crate. |
10 | |
11 | # Notes for implementing Backend for Plotters |
12 | |
13 | To create a new Plotters backend, this crate should be imported to the crate and the trait [DrawingBackend](trait.DrawingBackend.html) should |
14 | be implemented. It's highly recommended that the third-party backend uses `plotters-backend` by version specification `^x.y.*`. |
15 | For more details, see the [compatibility note](#compatibility-note). |
16 | |
17 | If the backend only implements [DrawingBackend::draw_pixel](trait.DrawingBackend.html#tymethod.draw_pixel), the default CPU rasterizer will be |
18 | used to give your backend ability of drawing different shapes. For those backend that supports advanced drawing instructions, such as, GPU |
19 | acelerated shape drawing, all the provided trait method can be overriden from the specific backend code. |
20 | |
21 | If your backend have text rendering ability, you may want to override the [DrawingBackend::estimate_text_size](trait.DrawingBackend.html#tymethod.estimate_text_size) |
22 | to avoid wrong spacing, since the Plotters default text handling code may behaves differently from the backend in terms of text rendering. |
23 | |
24 | ## Animated or Realtime Rendering |
25 | Backend might render the image realtimely/animated, for example, a GTK backend for realtime display or a GIF image rendering. To support these |
26 | features, you need to play with `ensure_prepared` and `present` method. The following figure illustrates how Plotters operates a drawing backend. |
27 | |
28 | - `ensure_prepared` - Called before each time when plotters want to draw. This function should initialize the backend for current frame, if the backend is already prepared |
29 | for a frame, this function should simply do nothing. |
30 | - `present` - Called when plotters want to finish current frame drawing |
31 | |
32 | |
33 | ```text |
34 | .ensure_prepared() && |
35 | +-------------+ +-------------+ .draw_pixels() +--------------+ drop |
36 | |Start drwaing|--->|Ready to draw| ------------------------+---->|Finish 1 frame| ---------> |
37 | +-------------+ +-------------+ | +--------------+ |
38 | ^ ^ | | |
39 | | +------------------------------- + | |
40 | | continue drawing | |
41 | +----------------------------------------------------------------+ |
42 | start render the next frame |
43 | .present() |
44 | ``` |
45 | - For both animated and static drawing, `DrawingBackend::present` indicates current frame should be flushed. |
46 | - For both animated and static drawing, `DrawingBackend::ensure_prepared` is called every time when plotters need to draw. |
47 | - For static drawing, the `DrawingBackend::present` is only called once manually, or from the Drop impl for the backend. |
48 | - For dynamic drawing, frames are defined by invocation of `DrawingBackend::present`, everything prior the invocation should belongs to previous frame |
49 | |
50 | # Compatibility Note |
51 | Since Plotters v0.3, plotters use the "plug-and-play" schema to import backends, this requires both Plotters and the backend crates depdens on a |
52 | same version of `plotters-backend` crate. This crate (`plotters-backend`) will enforce that any revision (means the last number in a version number) |
53 | won't contains breaking change - both on the Plotters side and backend side. |
54 | |
55 | Plotters main crate is always importing the backend crate with version specification `plotters-backend = "^<major>.<minor>*"`. |
56 | It's highly recommended that all the external crates follows the same rule to import `plotters-backend` depdendency, to avoid protential breaking |
57 | caused by `plotters-backend` crates gets a revision update. |
58 | |
59 | We also impose a versioning rule with `plotters` and some backends: |
60 | The compatible main crate (`plotters`) and this crate (`plotters-backend`) are always use the same major and minor version number. |
61 | All the plotters main crate and second-party backends with version "x.y.*" should be compatible, and they should depens on the latest version of `plotters-backend x.y.*` |
62 | |
63 | */ |
64 | use std::error::Error; |
65 | |
66 | pub mod rasterizer; |
67 | mod style; |
68 | mod text; |
69 | |
70 | pub use style::{BackendColor, BackendStyle}; |
71 | pub use text::{text_anchor, BackendTextStyle, FontFamily, FontStyle, FontTransform}; |
72 | |
73 | use text_anchor::{HPos, VPos}; |
74 | |
75 | /// A coordinate in the pixel-based backend. The coordinate follows the framebuffer's convention, |
76 | /// which defines the top-left point as (0, 0). |
77 | pub type BackendCoord = (i32, i32); |
78 | |
79 | /// The error produced by a drawing backend. |
80 | #[derive(Debug)] |
81 | pub enum DrawingErrorKind<E: Error + Send + Sync> { |
82 | /// A drawing backend error |
83 | DrawingError(E), |
84 | /// A font rendering error |
85 | FontError(Box<dyn Error + Send + Sync + 'static>), |
86 | } |
87 | |
88 | impl<E: Error + Send + Sync> std::fmt::Display for DrawingErrorKind<E> { |
89 | fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { |
90 | match self { |
91 | DrawingErrorKind::DrawingError(e) => write!(fmt, "Drawing backend error: {}" , e), |
92 | DrawingErrorKind::FontError(e) => write!(fmt, "Font loading error: {}" , e), |
93 | } |
94 | } |
95 | } |
96 | |
97 | impl<E: Error + Send + Sync> Error for DrawingErrorKind<E> {} |
98 | |
99 | /// The drawing backend trait, which implements the low-level drawing APIs. |
100 | /// This trait has a set of default implementation. And the minimal requirement of |
101 | /// implementing a drawing backend is implementing the `draw_pixel` function. |
102 | /// |
103 | /// If the drawing backend supports vector graphics, the other drawing APIs should be |
104 | /// override by the backend specific implementation. Otherwise, the default implementation |
105 | /// will use the pixel-based approach to draw other types of low-level shapes. |
106 | pub trait DrawingBackend: Sized { |
107 | /// The error type reported by the backend |
108 | type ErrorType: Error + Send + Sync; |
109 | |
110 | /// Get the dimension of the drawing backend in pixels |
111 | fn get_size(&self) -> (u32, u32); |
112 | |
113 | /// Ensure the backend is ready to draw |
114 | fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>; |
115 | |
116 | /// Finalize the drawing step and present all the changes. |
117 | /// This is used as the real-time rendering support. |
118 | /// The backend may implement in the following way, when `ensure_prepared` is called |
119 | /// it checks if it needs a fresh buffer and `present` is called rendering all the |
120 | /// pending changes on the screen. |
121 | fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>; |
122 | |
123 | /// Draw a pixel on the drawing backend |
124 | /// - `point`: The backend pixel-based coordinate to draw |
125 | /// - `color`: The color of the pixel |
126 | fn draw_pixel( |
127 | &mut self, |
128 | point: BackendCoord, |
129 | color: BackendColor, |
130 | ) -> Result<(), DrawingErrorKind<Self::ErrorType>>; |
131 | |
132 | /// Draw a line on the drawing backend |
133 | /// - `from`: The start point of the line |
134 | /// - `to`: The end point of the line |
135 | /// - `style`: The style of the line |
136 | fn draw_line<S: BackendStyle>( |
137 | &mut self, |
138 | from: BackendCoord, |
139 | to: BackendCoord, |
140 | style: &S, |
141 | ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { |
142 | rasterizer::draw_line(self, from, to, style) |
143 | } |
144 | |
145 | /// Draw a rectangle on the drawing backend |
146 | /// - `upper_left`: The coordinate of the upper-left corner of the rect |
147 | /// - `bottom_right`: The coordinate of the bottom-right corner of the rect |
148 | /// - `style`: The style |
149 | /// - `fill`: If the rectangle should be filled |
150 | fn draw_rect<S: BackendStyle>( |
151 | &mut self, |
152 | upper_left: BackendCoord, |
153 | bottom_right: BackendCoord, |
154 | style: &S, |
155 | fill: bool, |
156 | ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { |
157 | rasterizer::draw_rect(self, upper_left, bottom_right, style, fill) |
158 | } |
159 | |
160 | /// Draw a path on the drawing backend |
161 | /// - `path`: The iterator of key points of the path |
162 | /// - `style`: The style of the path |
163 | fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>( |
164 | &mut self, |
165 | path: I, |
166 | style: &S, |
167 | ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { |
168 | if style.color().alpha == 0.0 { |
169 | return Ok(()); |
170 | } |
171 | |
172 | if style.stroke_width() == 1 { |
173 | let mut begin: Option<BackendCoord> = None; |
174 | for end in path.into_iter() { |
175 | if let Some(begin) = begin { |
176 | let result = self.draw_line(begin, end, style); |
177 | #[allow (clippy::question_mark)] |
178 | if result.is_err() { |
179 | return result; |
180 | } |
181 | } |
182 | begin = Some(end); |
183 | } |
184 | } else { |
185 | let p: Vec<_> = path.into_iter().collect(); |
186 | let v = rasterizer::polygonize(&p[..], style.stroke_width()); |
187 | return self.fill_polygon(v, &style.color()); |
188 | } |
189 | Ok(()) |
190 | } |
191 | |
192 | /// Draw a circle on the drawing backend |
193 | /// - `center`: The center coordinate of the circle |
194 | /// - `radius`: The radius of the circle |
195 | /// - `style`: The style of the shape |
196 | /// - `fill`: If the circle should be filled |
197 | fn draw_circle<S: BackendStyle>( |
198 | &mut self, |
199 | center: BackendCoord, |
200 | radius: u32, |
201 | style: &S, |
202 | fill: bool, |
203 | ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { |
204 | rasterizer::draw_circle(self, center, radius, style, fill) |
205 | } |
206 | |
207 | fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>( |
208 | &mut self, |
209 | vert: I, |
210 | style: &S, |
211 | ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { |
212 | let vert_buf: Vec<_> = vert.into_iter().collect(); |
213 | |
214 | rasterizer::fill_polygon(self, &vert_buf[..], style) |
215 | } |
216 | |
217 | /// Draw a text on the drawing backend |
218 | /// - `text`: The text to draw |
219 | /// - `style`: The text style |
220 | /// - `pos` : The text anchor point |
221 | fn draw_text<TStyle: BackendTextStyle>( |
222 | &mut self, |
223 | text: &str, |
224 | style: &TStyle, |
225 | pos: BackendCoord, |
226 | ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { |
227 | let color = style.color(); |
228 | if color.alpha == 0.0 { |
229 | return Ok(()); |
230 | } |
231 | |
232 | let layout = style |
233 | .layout_box(text) |
234 | .map_err(|e| DrawingErrorKind::FontError(Box::new(e)))?; |
235 | let ((min_x, min_y), (max_x, max_y)) = layout; |
236 | let width = (max_x - min_x) as i32; |
237 | let height = (max_y - min_y) as i32; |
238 | let dx = match style.anchor().h_pos { |
239 | HPos::Left => 0, |
240 | HPos::Right => -width, |
241 | HPos::Center => -width / 2, |
242 | }; |
243 | let dy = match style.anchor().v_pos { |
244 | VPos::Top => 0, |
245 | VPos::Center => -height / 2, |
246 | VPos::Bottom => -height, |
247 | }; |
248 | let trans = style.transform(); |
249 | let (w, h) = self.get_size(); |
250 | match style.draw(text, (0, 0), |x, y, color| { |
251 | let (x, y) = trans.transform(x + dx - min_x, y + dy - min_y); |
252 | let (x, y) = (pos.0 + x, pos.1 + y); |
253 | if x >= 0 && x < w as i32 && y >= 0 && y < h as i32 { |
254 | self.draw_pixel((x, y), color) |
255 | } else { |
256 | Ok(()) |
257 | } |
258 | }) { |
259 | Ok(drawing_result) => drawing_result, |
260 | Err(font_error) => Err(DrawingErrorKind::FontError(Box::new(font_error))), |
261 | } |
262 | } |
263 | |
264 | /// Estimate the size of the horizontal text if rendered on this backend. |
265 | /// This is important because some of the backend may not have font ability. |
266 | /// Thus this allows those backend reports proper value rather than ask the |
267 | /// font rasterizer for that. |
268 | /// |
269 | /// - `text`: The text to estimate |
270 | /// - `font`: The font to estimate |
271 | /// - *Returns* The estimated text size |
272 | fn estimate_text_size<TStyle: BackendTextStyle>( |
273 | &self, |
274 | text: &str, |
275 | style: &TStyle, |
276 | ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> { |
277 | let layout = style |
278 | .layout_box(text) |
279 | .map_err(|e| DrawingErrorKind::FontError(Box::new(e)))?; |
280 | Ok(( |
281 | ((layout.1).0 - (layout.0).0) as u32, |
282 | ((layout.1).1 - (layout.0).1) as u32, |
283 | )) |
284 | } |
285 | |
286 | /// Blit a bitmap on to the backend. |
287 | /// |
288 | /// - `text`: pos the left upper conner of the bitmap to blit |
289 | /// - `src`: The source of the image |
290 | /// |
291 | /// TODO: The default implementation of bitmap blitting assumes that the bitmap is RGB, but |
292 | /// this may not be the case. But for bitmap backend it's actually ok if we use the bitmap |
293 | /// element that matches the pixel format, but we need to fix this. |
294 | fn blit_bitmap( |
295 | &mut self, |
296 | pos: BackendCoord, |
297 | (iw, ih): (u32, u32), |
298 | src: &[u8], |
299 | ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { |
300 | let (w, h) = self.get_size(); |
301 | |
302 | for dx in 0..iw { |
303 | if pos.0 + dx as i32 >= w as i32 { |
304 | break; |
305 | } |
306 | for dy in 0..ih { |
307 | if pos.1 + dy as i32 >= h as i32 { |
308 | break; |
309 | } |
310 | // FIXME: This assume we have RGB image buffer |
311 | let r = src[(dx + dy * w) as usize * 3]; |
312 | let g = src[(dx + dy * w) as usize * 3 + 1]; |
313 | let b = src[(dx + dy * w) as usize * 3 + 2]; |
314 | let color = BackendColor { |
315 | alpha: 1.0, |
316 | rgb: (r, g, b), |
317 | }; |
318 | let result = self.draw_pixel((pos.0 + dx as i32, pos.1 + dy as i32), color); |
319 | #[allow (clippy::question_mark)] |
320 | if result.is_err() { |
321 | return result; |
322 | } |
323 | } |
324 | } |
325 | |
326 | Ok(()) |
327 | } |
328 | } |
329 | |