diff options
author | Jakub Kotur <qtr@google.com> | 2021-03-16 20:22:32 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-03-16 20:22:32 +0000 |
commit | 68f08f00ec023303e7ada7c19b6695b3c1f3f052 (patch) | |
tree | ba83cdbdb209dec2b72e56e01982599900f5bb89 /src/lib.rs | |
parent | dbc5f3e479629cfd71c8d8e8f0043a8a6752b809 (diff) | |
parent | d07d046b12e1d675c426076bba0c3efe1f4fff7b (diff) | |
download | plotters-backend-68f08f00ec023303e7ada7c19b6695b3c1f3f052.tar.gz |
Initial import of plotters-backend-0.2.1. am: 251cc39ae9 am: e01bccf43b am: d07d046b12
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/plotters-backend/+/1621409
Change-Id: I2b15c0f071d2e5c57819a326e41e78f2fb39f9e0
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f4a92b0 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,326 @@ +/*! + The Plotters backend API crate. This is a part of Plotters, the Rust drawing and plotting library, for more details regarding the entire + Plotters project, please check the [main crate](https://crates.io/crates/plotters). + + This is the crate that used as the connector between Plotters and different backend crates. Since Plotters 0.3, all the backends has been + hosted as seperate crates for the usability and maintainability reasons. + + At the same time, Plotters is now supporting third-party backends and all the backends are now supports "plug-and-play": + To use a external backend, just depends on both the Plotters main crate and the third-party backend crate. + + # Notes for implementing Backend for Plotters + + To create a new Plotters backend, this crate should be imported to the crate and the trait [DrawingBackend](trait.DrawingBackend.html) should + be implemented. It's highly recommended that the third-party backend uses `plotters-backend` by version specification `^x.y.*`. + For more details, see the [compatibility note](#compatibility-note). + + If the backend only implements [DrawingBackend::draw_pixel](trait.DrawingBackend.html#tymethod.draw_pixel), the default CPU rasterizer will be + used to give your backend ability of drawing different shapes. For those backend that supports advanced drawing instructions, such as, GPU + acelerated shape drawing, all the provided trait method can be overriden from the specific backend code. + + If your backend have text rendering ability, you may want to override the [DrawingBackend::estimate_text_size](trait.DrawingBackend.html#tymethod.estimate_text_size) + to avoid wrong spacing, since the Plotters default text handling code may behaves differently from the backend in terms of text rendering. + + ## Animated or Realtime Rendering + Backend might render the image realtimely/animated, for example, a GTK backend for realtime display or a GIF image rendering. To support these + features, you need to play with `ensure_prepared` and `present` method. The following figure illustrates how Plotters operates a drawing backend. + + - `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 + for a frame, this function should simply do nothing. + - `present` - Called when plotters want to finish current frame drawing + + + ```text + .ensure_prepared() && + +-------------+ +-------------+ .draw_pixels() +--------------+ drop + |Start drwaing|--->|Ready to draw| ------------------------+---->|Finish 1 frame| ---------> + +-------------+ +-------------+ | +--------------+ + ^ ^ | | + | +------------------------------- + | + | continue drawing | + +----------------------------------------------------------------+ + start render the next frame + .present() + ``` + - For both animated and static drawing, `DrawingBackend::present` indicates current frame should be flushed. + - For both animated and static drawing, `DrawingBackend::ensure_prepared` is called every time when plotters need to draw. + - For static drawing, the `DrawingBackend::present` is only called once manually, or from the Drop impl for the backend. + - For dynamic drawing, frames are defined by invocation of `DrawingBackend::present`, everything prior the invocation should belongs to previous frame + + # Compatibility Note + 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 + same version of `plotters-backend` crate. This crate (`plotters-backend`) will enforce that any revision (means the last number in a version number) + won't contains breaking change - both on the Plotters side and backend side. + + Plotters main crate is always importing the backend crate with version specification `plotters-backend = "^<major>.<minor>*"`. + It's highly recommended that all the external crates follows the same rule to import `plotters-backend` depdendency, to avoid protential breaking + caused by `plotters-backend` crates gets a revision update. + + We also impose a versioning rule with `plotters` and some backends: + The compatible main crate (`plotters`) and this crate (`plotters-backend`) are always use the same major and minor version number. + 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.*` + +*/ +use std::error::Error; + +pub mod rasterizer; +mod style; +mod text; + +pub use style::{BackendColor, BackendStyle}; +pub use text::{text_anchor, BackendTextStyle, FontFamily, FontStyle, FontTransform}; + +use text_anchor::{HPos, VPos}; + +/// A coordinate in the pixel-based backend. The coordinate follows the framebuffer's convention, +/// which defines the top-left point as (0, 0). +pub type BackendCoord = (i32, i32); + +/// The error produced by a drawing backend. +#[derive(Debug)] +pub enum DrawingErrorKind<E: Error + Send + Sync> { + /// A drawing backend error + DrawingError(E), + /// A font rendering error + FontError(Box<dyn Error + Send + Sync + 'static>), +} + +impl<E: Error + Send + Sync> std::fmt::Display for DrawingErrorKind<E> { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + match self { + DrawingErrorKind::DrawingError(e) => write!(fmt, "Drawing backend error: {}", e), + DrawingErrorKind::FontError(e) => write!(fmt, "Font loading error: {}", e), + } + } +} + +impl<E: Error + Send + Sync> Error for DrawingErrorKind<E> {} + +/// The drawing backend trait, which implements the low-level drawing APIs. +/// This trait has a set of default implementation. And the minimal requirement of +/// implementing a drawing backend is implementing the `draw_pixel` function. +/// +/// If the drawing backend supports vector graphics, the other drawing APIs should be +/// override by the backend specific implementation. Otherwise, the default implementation +/// will use the pixel-based approach to draw other types of low-level shapes. +pub trait DrawingBackend: Sized { + /// The error type reported by the backend + type ErrorType: Error + Send + Sync; + + /// Get the dimension of the drawing backend in pixels + fn get_size(&self) -> (u32, u32); + + /// Ensure the backend is ready to draw + fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>; + + /// Finalize the drawing step and present all the changes. + /// This is used as the real-time rendering support. + /// The backend may implement in the following way, when `ensure_prepared` is called + /// it checks if it needs a fresh buffer and `present` is called rendering all the + /// pending changes on the screen. + fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>; + + /// Draw a pixel on the drawing backend + /// - `point`: The backend pixel-based coordinate to draw + /// - `color`: The color of the pixel + fn draw_pixel( + &mut self, + point: BackendCoord, + color: BackendColor, + ) -> Result<(), DrawingErrorKind<Self::ErrorType>>; + + /// Draw a line on the drawing backend + /// - `from`: The start point of the line + /// - `to`: The end point of the line + /// - `style`: The style of the line + fn draw_line<S: BackendStyle>( + &mut self, + from: BackendCoord, + to: BackendCoord, + style: &S, + ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { + rasterizer::draw_line(self, from, to, style) + } + + /// Draw a rectangle on the drawing backend + /// - `upper_left`: The coordinate of the upper-left corner of the rect + /// - `bottom_right`: The coordinate of the bottom-right corner of the rect + /// - `style`: The style + /// - `fill`: If the rectangle should be filled + fn draw_rect<S: BackendStyle>( + &mut self, + upper_left: BackendCoord, + bottom_right: BackendCoord, + style: &S, + fill: bool, + ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { + rasterizer::draw_rect(self, upper_left, bottom_right, style, fill) + } + + /// Draw a path on the drawing backend + /// - `path`: The iterator of key points of the path + /// - `style`: The style of the path + fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>( + &mut self, + path: I, + style: &S, + ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { + if style.color().alpha == 0.0 { + return Ok(()); + } + + if style.stroke_width() == 1 { + let mut begin: Option<BackendCoord> = None; + for end in path.into_iter() { + if let Some(begin) = begin { + let result = self.draw_line(begin, end, style); + if result.is_err() { + return result; + } + } + begin = Some(end); + } + } else { + let p: Vec<_> = path.into_iter().collect(); + let v = rasterizer::polygonize(&p[..], style.stroke_width()); + return self.fill_polygon(v, &style.color()); + } + Ok(()) + } + + /// Draw a circle on the drawing backend + /// - `center`: The center coordinate of the circle + /// - `radius`: The radius of the circle + /// - `style`: The style of the shape + /// - `fill`: If the circle should be filled + fn draw_circle<S: BackendStyle>( + &mut self, + center: BackendCoord, + radius: u32, + style: &S, + fill: bool, + ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { + rasterizer::draw_circle(self, center, radius, style, fill) + } + + fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>( + &mut self, + vert: I, + style: &S, + ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { + let vert_buf: Vec<_> = vert.into_iter().collect(); + + rasterizer::fill_polygon(self, &vert_buf[..], style) + } + + /// Draw a text on the drawing backend + /// - `text`: The text to draw + /// - `style`: The text style + /// - `pos` : The text anchor point + fn draw_text<TStyle: BackendTextStyle>( + &mut self, + text: &str, + style: &TStyle, + pos: BackendCoord, + ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { + let color = style.color(); + if color.alpha == 0.0 { + return Ok(()); + } + + let layout = style + .layout_box(text) + .map_err(|e| DrawingErrorKind::FontError(Box::new(e)))?; + let ((min_x, min_y), (max_x, max_y)) = layout; + let width = (max_x - min_x) as i32; + let height = (max_y - min_y) as i32; + let dx = match style.anchor().h_pos { + HPos::Left => 0, + HPos::Right => -width, + HPos::Center => -width / 2, + }; + let dy = match style.anchor().v_pos { + VPos::Top => 0, + VPos::Center => -height / 2, + VPos::Bottom => -height, + }; + let trans = style.transform(); + let (w, h) = self.get_size(); + match style.draw(text, (0, 0), |x, y, color| { + let (x, y) = trans.transform(x + dx - min_x, y + dy - min_y); + let (x, y) = (pos.0 + x, pos.1 + y); + if x >= 0 && x < w as i32 && y >= 0 && y < h as i32 { + self.draw_pixel((x, y), color) + } else { + Ok(()) + } + }) { + Ok(drawing_result) => drawing_result, + Err(font_error) => Err(DrawingErrorKind::FontError(Box::new(font_error))), + } + } + + /// Estimate the size of the horizontal text if rendered on this backend. + /// This is important because some of the backend may not have font ability. + /// Thus this allows those backend reports proper value rather than ask the + /// font rasterizer for that. + /// + /// - `text`: The text to estimate + /// - `font`: The font to estimate + /// - *Returns* The estimated text size + fn estimate_text_size<TStyle: BackendTextStyle>( + &self, + text: &str, + style: &TStyle, + ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> { + let layout = style + .layout_box(text) + .map_err(|e| DrawingErrorKind::FontError(Box::new(e)))?; + Ok(( + ((layout.1).0 - (layout.0).0) as u32, + ((layout.1).1 - (layout.0).1) as u32, + )) + } + + /// Blit a bitmap on to the backend. + /// + /// - `text`: pos the left upper conner of the bitmap to blit + /// - `src`: The source of the image + /// + /// TODO: The default implementation of bitmap blitting assumes that the bitmap is RGB, but + /// this may not be the case. But for bitmap backend it's actually ok if we use the bitmap + /// element that matches the pixel format, but we need to fix this. + fn blit_bitmap<'a>( + &mut self, + pos: BackendCoord, + (iw, ih): (u32, u32), + src: &'a [u8], + ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { + let (w, h) = self.get_size(); + + for dx in 0..iw { + if pos.0 + dx as i32 >= w as i32 { + break; + } + for dy in 0..ih { + if pos.1 + dy as i32 >= h as i32 { + break; + } + // FIXME: This assume we have RGB image buffer + let r = src[(dx + dy * w) as usize * 3]; + let g = src[(dx + dy * w) as usize * 3 + 1]; + let b = src[(dx + dy * w) as usize * 3 + 2]; + let color = BackendColor { + alpha: 1.0, + rgb: (r, g, b), + }; + let result = self.draw_pixel((pos.0 + dx as i32, pos.1 + dy as i32), color); + if result.is_err() { + return result; + } + } + } + + Ok(()) + } +} |