| 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 | |