Signed-off-by: hmz007 <hmz007@gmail.com> Change-Id: Ib87fdbe7a0be7dc961af3500fe9ea4589a127f9f
329 lines
14 KiB
Rust
329 lines
14 KiB
Rust
/*!
|
|
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);
|
|
#[allow(clippy::question_mark)]
|
|
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(
|
|
&mut self,
|
|
pos: BackendCoord,
|
|
(iw, ih): (u32, u32),
|
|
src: &[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);
|
|
#[allow(clippy::question_mark)]
|
|
if result.is_err() {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|