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> { fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error>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 get_size(&self) -> (u32, u32)111 fn get_size(&self) -> (u32, u32); 112 113 /// Ensure the backend is ready to draw ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>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. present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>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 draw_pixel( &mut self, point: BackendCoord, color: BackendColor, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>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 draw_line<S: BackendStyle>( &mut self, from: BackendCoord, to: BackendCoord, style: &S, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>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 draw_rect<S: BackendStyle>( &mut self, upper_left: BackendCoord, bottom_right: BackendCoord, style: &S, fill: bool, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>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 draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>( &mut self, path: I, style: &S, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>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 draw_circle<S: BackendStyle>( &mut self, center: BackendCoord, radius: u32, style: &S, fill: bool, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>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 fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>( &mut self, vert: I, style: &S, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>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 draw_text<TStyle: BackendTextStyle>( &mut self, text: &str, style: &TStyle, pos: BackendCoord, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>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 estimate_text_size<TStyle: BackendTextStyle>( &self, text: &str, style: &TStyle, ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>>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. blit_bitmap( &mut self, pos: BackendCoord, (iw, ih): (u32, u32), src: &[u8], ) -> Result<(), DrawingErrorKind<Self::ErrorType>>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