aboutsummaryrefslogtreecommitdiff
path: root/src/drawing/backend_impl/bitmap.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/drawing/backend_impl/bitmap.rs')
-rw-r--r--src/drawing/backend_impl/bitmap.rs1607
1 files changed, 0 insertions, 1607 deletions
diff --git a/src/drawing/backend_impl/bitmap.rs b/src/drawing/backend_impl/bitmap.rs
deleted file mode 100644
index a4d776a..0000000
--- a/src/drawing/backend_impl/bitmap.rs
+++ /dev/null
@@ -1,1607 +0,0 @@
-use crate::drawing::backend::{BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind};
-use crate::style::{Color, RGBAColor};
-use std::marker::PhantomData;
-
-#[cfg(all(not(target_arch = "wasm32"), feature = "image"))]
-mod image_encoding_support {
- pub(super) use image::{ImageBuffer, ImageError, Rgb};
- pub(super) use std::path::Path;
- pub(super) type BorrowedImage<'a> = ImageBuffer<Rgb<u8>, &'a mut [u8]>;
-}
-
-#[cfg(all(not(target_arch = "wasm32"), feature = "image"))]
-use image_encoding_support::*;
-
-#[derive(Debug)]
-/// Indicates some error occurs within the bitmap backend
-pub enum BitMapBackendError {
- /// The buffer provided is invalid, for example, wrong pixel buffer size
- InvalidBuffer,
- /// Some IO error occurs while the bitmap maniuplation
- IOError(std::io::Error),
- #[cfg(all(not(target_arch = "wasm32"), feature = "image"))]
- /// Image encoding error
- ImageError(ImageError),
-}
-
-impl std::fmt::Display for BitMapBackendError {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(f, "{:?}", self)
- }
-}
-
-impl std::error::Error for BitMapBackendError {}
-
-#[inline(always)]
-fn blend(prev: &mut u8, new: u8, a: u64) {
- if new > *prev {
- *prev += (u64::from(new - *prev) * a / 256) as u8
- } else {
- *prev -= (u64::from(*prev - new) * a / 256) as u8
- }
-}
-
-#[cfg(all(feature = "gif", not(target_arch = "wasm32"), feature = "image"))]
-mod gif_support {
- use super::*;
- use gif::{Encoder as GifEncoder, Frame as GifFrame, Repeat, SetParameter};
- use std::fs::File;
-
- pub(super) struct GifFile {
- encoder: GifEncoder<File>,
- height: u32,
- width: u32,
- delay: u32,
- }
-
- impl GifFile {
- pub(super) fn new<T: AsRef<Path>>(
- path: T,
- dim: (u32, u32),
- delay: u32,
- ) -> Result<Self, BitMapBackendError> {
- let mut encoder = GifEncoder::new(
- File::create(path.as_ref()).map_err(BitMapBackendError::IOError)?,
- dim.0 as u16,
- dim.1 as u16,
- &[],
- )
- .map_err(BitMapBackendError::IOError)?;
-
- encoder
- .set(Repeat::Infinite)
- .map_err(BitMapBackendError::IOError)?;
-
- Ok(Self {
- encoder,
- width: dim.0,
- height: dim.1,
- delay: (delay + 5) / 10,
- })
- }
-
- pub(super) fn flush_frame(&mut self, buffer: &[u8]) -> Result<(), BitMapBackendError> {
- let mut frame =
- GifFrame::from_rgb_speed(self.width as u16, self.height as u16, buffer, 10);
-
- frame.delay = self.delay as u16;
-
- self.encoder
- .write_frame(&frame)
- .map_err(BitMapBackendError::IOError)?;
-
- Ok(())
- }
- }
-}
-
-enum Target<'a> {
- #[cfg(all(not(target_arch = "wasm32"), feature = "image"))]
- File(&'a Path),
- Buffer(PhantomData<&'a u32>),
- #[cfg(all(feature = "gif", not(target_arch = "wasm32"), feature = "image"))]
- Gif(Box<gif_support::GifFile>),
-}
-
-enum Buffer<'a> {
- #[cfg(all(not(target_arch = "wasm32"), feature = "image"))]
- Owned(Vec<u8>),
- Borrowed(&'a mut [u8]),
-}
-
-impl<'a> Buffer<'a> {
- #[inline(always)]
- fn borrow_buffer(&mut self) -> &mut [u8] {
- match self {
- #[cfg(all(not(target_arch = "wasm32"), feature = "image"))]
- Buffer::Owned(buf) => &mut buf[..],
- Buffer::Borrowed(buf) => *buf,
- }
- }
-}
-
-/// The trait that describes some details about a particular pixel format
-pub trait PixelFormat: Sized {
- /// Number of bytes per pixel
- const PIXEL_SIZE: usize;
-
- /// Number of effective bytes per pixel, e.g. for BGRX pixel format, the size of pixel
- /// is 4 but the effective size is 3, since the 4th byte isn't used
- const EFFECTIVE_PIXEL_SIZE: usize;
-
- /// Encoding a pixel and returns the idx-th byte for the pixel
- fn byte_at(r: u8, g: u8, b: u8, a: u64, idx: usize) -> u8;
-
- /// Decode a pixel at the given location
- fn decode_pixel(data: &[u8]) -> (u8, u8, u8, u64);
-
- /// The fast alpha blending algorithm for this pixel format
- ///
- /// - `target`: The target bitmap backend
- /// - `upper_left`: The upper-left coord for the rect
- /// - `bottom_right`: The bottom-right coord for the rect
- /// - `r`, `g`, `b`, `a`: The blending color and alpha value
- fn blend_rect_fast(
- target: &mut BitMapBackend<'_, Self>,
- upper_left: (i32, i32),
- bottom_right: (i32, i32),
- r: u8,
- g: u8,
- b: u8,
- a: f64,
- );
-
- /// The fast vertical line filling algorithm
- ///
- /// - `target`: The target bitmap backend
- /// - `x`: the X coordinate for the entire line
- /// - `ys`: The range of y coord
- /// - `r`, `g`, `b`: The blending color and alpha value
- fn fill_vertical_line_fast(
- target: &mut BitMapBackend<'_, Self>,
- x: i32,
- ys: (i32, i32),
- r: u8,
- g: u8,
- b: u8,
- ) {
- let (w, h) = target.get_size();
- let w = w as i32;
- let h = h as i32;
-
- // Make sure we are in the range
- if x < 0 || x >= w {
- return;
- }
-
- let dst = target.get_raw_pixel_buffer();
- let (mut y0, mut y1) = ys;
- if y0 > y1 {
- std::mem::swap(&mut y0, &mut y1);
- }
- // And check the y axis isn't out of bound
- y0 = y0.max(0);
- y1 = y1.min(h - 1);
- // This is ok because once y0 > y1, there won't be any iteration anymore
- for y in y0..=y1 {
- for idx in 0..Self::EFFECTIVE_PIXEL_SIZE {
- dst[(y * w + x) as usize * Self::PIXEL_SIZE + idx] = Self::byte_at(r, g, b, 0, idx);
- }
- }
- }
-
- /// The fast rectangle filling algorithm
- ///
- /// - `target`: The target bitmap backend
- /// - `upper_left`: The upper-left coord for the rect
- /// - `bottom_right`: The bottom-right coord for the rect
- /// - `r`, `g`, `b`: The filling color
- fn fill_rect_fast(
- target: &mut BitMapBackend<'_, Self>,
- upper_left: (i32, i32),
- bottom_right: (i32, i32),
- r: u8,
- g: u8,
- b: u8,
- );
-
- #[inline(always)]
- /// Drawing a single pixel in this format
- ///
- /// - `target`: The target bitmap backend
- /// - `point`: The coord of the point
- /// - `r`, `g`, `b`: The filling color
- /// - `alpha`: The alpha value
- fn draw_pixel(
- target: &mut BitMapBackend<'_, Self>,
- point: (i32, i32),
- (r, g, b): (u8, u8, u8),
- alpha: f64,
- ) {
- let (x, y) = (point.0 as usize, point.1 as usize);
- let (w, _) = target.get_size();
- let buf = target.get_raw_pixel_buffer();
- let w = w as usize;
- let base = (y * w + x) * Self::PIXEL_SIZE;
-
- if base < buf.len() {
- unsafe {
- if alpha >= 1.0 - 1.0 / 256.0 {
- for idx in 0..Self::EFFECTIVE_PIXEL_SIZE {
- *buf.get_unchecked_mut(base + idx) = Self::byte_at(r, g, b, 0, idx);
- }
- } else {
- if alpha <= 0.0 {
- return;
- }
-
- let alpha = (alpha * 256.0).floor() as u64;
- for idx in 0..Self::EFFECTIVE_PIXEL_SIZE {
- blend(
- buf.get_unchecked_mut(base + idx),
- Self::byte_at(r, g, b, 0, idx),
- alpha,
- );
- }
- }
- }
- }
- }
-
- /// Indicates if this pixel format can be saved as image.
- /// Note: Currently we only using RGB pixel format in the image crate, but later we may lift
- /// this restriction
- ///
- /// - `returns`: If the image can be saved as image file
- fn can_be_saved() -> bool {
- false
- }
-}
-
-/// The marker type that indicates we are currently using a RGB888 pixel format
-pub struct RGBPixel;
-
-/// The marker type that indicates we are currently using a BGRX8888 pixel format
-pub struct BGRXPixel;
-
-impl PixelFormat for RGBPixel {
- const PIXEL_SIZE: usize = 3;
- const EFFECTIVE_PIXEL_SIZE: usize = 3;
-
- #[inline(always)]
- fn byte_at(r: u8, g: u8, b: u8, _a: u64, idx: usize) -> u8 {
- match idx {
- 0 => r,
- 1 => g,
- 2 => b,
- _ => 0xff,
- }
- }
-
- #[inline(always)]
- fn decode_pixel(data: &[u8]) -> (u8, u8, u8, u64) {
- (data[0], data[1], data[2], 0x255)
- }
-
- fn can_be_saved() -> bool {
- true
- }
-
- #[allow(clippy::many_single_char_names, clippy::cast_ptr_alignment)]
- fn blend_rect_fast(
- target: &mut BitMapBackend<'_, Self>,
- upper_left: (i32, i32),
- bottom_right: (i32, i32),
- r: u8,
- g: u8,
- b: u8,
- a: f64,
- ) {
- let (w, h) = target.get_size();
- let a = a.min(1.0).max(0.0);
- if a == 0.0 {
- return;
- }
-
- let (x0, y0) = (
- upper_left.0.min(bottom_right.0).max(0),
- upper_left.1.min(bottom_right.1).max(0),
- );
- let (x1, y1) = (
- upper_left.0.max(bottom_right.0).min(w as i32 - 1),
- upper_left.1.max(bottom_right.1).min(h as i32 - 1),
- );
-
- // This may happen when the minimal value is larger than the limit.
- // Thus we just have something that is completely out-of-range
- if x0 > x1 || y0 > y1 {
- return;
- }
-
- let dst = target.get_raw_pixel_buffer();
-
- let a = (256.0 * a).floor() as u64;
-
- // Since we should always make sure the RGB payload occupies the logic lower bits
- // thus, this type purning should work for both LE and BE CPUs
- #[rustfmt::skip]
- let (p1, p2, p3): (u64, u64, u64) = unsafe {
- std::mem::transmute([
- u16::from(r), u16::from(b), u16::from(g), u16::from(r), // QW1
- u16::from(b), u16::from(g), u16::from(r), u16::from(b), // QW2
- u16::from(g), u16::from(r), u16::from(b), u16::from(g), // QW3
- ])
- };
-
- #[rustfmt::skip]
- let (q1, q2, q3): (u64, u64, u64) = unsafe {
- std::mem::transmute([
- u16::from(g), u16::from(r), u16::from(b), u16::from(g), // QW1
- u16::from(r), u16::from(b), u16::from(g), u16::from(r), // QW2
- u16::from(b), u16::from(g), u16::from(r), u16::from(b), // QW3
- ])
- };
-
- const N: u64 = 0xff00_ff00_ff00_ff00;
- const M: u64 = 0x00ff_00ff_00ff_00ff;
-
- for y in y0..=y1 {
- let start = (y * w as i32 + x0) as usize;
- let count = (x1 - x0 + 1) as usize;
-
- let start_ptr = &mut dst[start * Self::PIXEL_SIZE] as *mut u8 as *mut [u8; 24];
- let slice = unsafe { std::slice::from_raw_parts_mut(start_ptr, (count - 1) / 8) };
- for p in slice.iter_mut() {
- let ptr = p as *mut [u8; 24] as *mut (u64, u64, u64);
- let (d1, d2, d3) = unsafe { *ptr };
- let (mut h1, mut h2, mut h3) = ((d1 >> 8) & M, (d2 >> 8) & M, (d3 >> 8) & M);
- let (mut l1, mut l2, mut l3) = (d1 & M, d2 & M, d3 & M);
-
- #[cfg(target_endian = "little")]
- {
- h1 = (h1 * (256 - a) + q1 * a) & N;
- h2 = (h2 * (256 - a) + q2 * a) & N;
- h3 = (h3 * (256 - a) + q3 * a) & N;
- l1 = ((l1 * (256 - a) + p1 * a) & N) >> 8;
- l2 = ((l2 * (256 - a) + p2 * a) & N) >> 8;
- l3 = ((l3 * (256 - a) + p3 * a) & N) >> 8;
- }
-
- #[cfg(target_endian = "big")]
- {
- h1 = (h1 * (256 - a) + p1 * a) & N;
- h2 = (h2 * (256 - a) + p2 * a) & N;
- h3 = (h3 * (256 - a) + p3 * a) & N;
- l1 = ((l1 * (256 - a) + q1 * a) & N) >> 8;
- l2 = ((l2 * (256 - a) + q2 * a) & N) >> 8;
- l3 = ((l3 * (256 - a) + q3 * a) & N) >> 8;
- }
-
- unsafe {
- *ptr = (h1 | l1, h2 | l2, h3 | l3);
- }
- }
-
- let mut iter = dst[((start + slice.len() * 8) * Self::PIXEL_SIZE)
- ..((start + count) * Self::PIXEL_SIZE)]
- .iter_mut();
- for _ in (slice.len() * 8)..count {
- blend(iter.next().unwrap(), r, a);
- blend(iter.next().unwrap(), g, a);
- blend(iter.next().unwrap(), b, a);
- }
- }
- }
-
- #[allow(clippy::many_single_char_names, clippy::cast_ptr_alignment)]
- fn fill_rect_fast(
- target: &mut BitMapBackend<'_, Self>,
- upper_left: (i32, i32),
- bottom_right: (i32, i32),
- r: u8,
- g: u8,
- b: u8,
- ) {
- let (w, h) = target.get_size();
- let (x0, y0) = (
- upper_left.0.min(bottom_right.0).max(0),
- upper_left.1.min(bottom_right.1).max(0),
- );
- let (x1, y1) = (
- upper_left.0.max(bottom_right.0).min(w as i32 - 1),
- upper_left.1.max(bottom_right.1).min(h as i32 - 1),
- );
-
- // This may happen when the minimal value is larger than the limit.
- // Thus we just have something that is completely out-of-range
- if x0 > x1 || y0 > y1 {
- return;
- }
-
- let dst = target.get_raw_pixel_buffer();
-
- if r == g && g == b {
- // If r == g == b, then we can use memset
- if x0 != 0 || x1 != w as i32 - 1 {
- // If it's not the entire row is filled, we can only do
- // memset per row
- for y in y0..=y1 {
- let start = (y * w as i32 + x0) as usize;
- let count = (x1 - x0 + 1) as usize;
- dst[(start * Self::PIXEL_SIZE)..((start + count) * Self::PIXEL_SIZE)]
- .iter_mut()
- .for_each(|e| *e = r);
- }
- } else {
- // If the entire memory block is going to be filled, just use single memset
- dst[Self::PIXEL_SIZE * (y0 * w as i32) as usize
- ..((y1 + 1) * w as i32) as usize * Self::PIXEL_SIZE]
- .iter_mut()
- .for_each(|e| *e = r);
- }
- } else {
- let count = (x1 - x0 + 1) as usize;
- if count < 8 {
- for y in y0..=y1 {
- let start = (y * w as i32 + x0) as usize;
- let mut iter = dst
- [(start * Self::PIXEL_SIZE)..((start + count) * Self::PIXEL_SIZE)]
- .iter_mut();
- for _ in 0..=(x1 - x0) {
- *iter.next().unwrap() = r;
- *iter.next().unwrap() = g;
- *iter.next().unwrap() = b;
- }
- }
- } else {
- for y in y0..=y1 {
- let start = (y * w as i32 + x0) as usize;
- let start_ptr = &mut dst[start * Self::PIXEL_SIZE] as *mut u8 as *mut [u8; 24];
- let slice =
- unsafe { std::slice::from_raw_parts_mut(start_ptr, (count - 1) / 8) };
- for p in slice.iter_mut() {
- // In this case, we can actually fill 8 pixels in one iteration with
- // only 3 movq instructions.
- // TODO: Consider using AVX instructions when possible
- let ptr = p as *mut [u8; 24] as *mut u64;
- unsafe {
- let (d1, d2, d3): (u64, u64, u64) = std::mem::transmute([
- r, g, b, r, g, b, r, g, // QW1
- b, r, g, b, r, g, b, r, // QW2
- g, b, r, g, b, r, g, b, // QW3
- ]);
- *ptr = d1;
- *ptr.offset(1) = d2;
- *ptr.offset(2) = d3;
- }
- }
-
- for idx in (slice.len() * 8)..count {
- dst[start * Self::PIXEL_SIZE + idx * Self::PIXEL_SIZE] = r;
- dst[start * Self::PIXEL_SIZE + idx * Self::PIXEL_SIZE + 1] = g;
- dst[start * Self::PIXEL_SIZE + idx * Self::PIXEL_SIZE + 2] = b;
- }
- }
- }
- }
- }
-}
-
-impl PixelFormat for BGRXPixel {
- const PIXEL_SIZE: usize = 4;
- const EFFECTIVE_PIXEL_SIZE: usize = 3;
-
- #[inline(always)]
- fn byte_at(r: u8, g: u8, b: u8, _a: u64, idx: usize) -> u8 {
- match idx {
- 0 => b,
- 1 => g,
- 2 => r,
- _ => 0xff,
- }
- }
-
- #[inline(always)]
- fn decode_pixel(data: &[u8]) -> (u8, u8, u8, u64) {
- (data[2], data[1], data[0], 0x255)
- }
-
- #[allow(clippy::many_single_char_names, clippy::cast_ptr_alignment)]
- fn blend_rect_fast(
- target: &mut BitMapBackend<'_, Self>,
- upper_left: (i32, i32),
- bottom_right: (i32, i32),
- r: u8,
- g: u8,
- b: u8,
- a: f64,
- ) {
- let (w, h) = target.get_size();
- let a = a.min(1.0).max(0.0);
- if a == 0.0 {
- return;
- }
-
- let (x0, y0) = (
- upper_left.0.min(bottom_right.0).max(0),
- upper_left.1.min(bottom_right.1).max(0),
- );
- let (x1, y1) = (
- upper_left.0.max(bottom_right.0).min(w as i32 - 1),
- upper_left.1.max(bottom_right.1).min(h as i32 - 1),
- );
-
- // This may happen when the minimal value is larger than the limit.
- // Thus we just have something that is completely out-of-range
- if x0 > x1 || y0 > y1 {
- return;
- }
-
- let dst = target.get_raw_pixel_buffer();
-
- let a = (256.0 * a).floor() as u64;
-
- // Since we should always make sure the RGB payload occupies the logic lower bits
- // thus, this type purning should work for both LE and BE CPUs
- #[rustfmt::skip]
- let p: u64 = unsafe {
- std::mem::transmute([
- u16::from(b), u16::from(r), u16::from(b), u16::from(r), // QW1
- ])
- };
-
- #[rustfmt::skip]
- let q: u64 = unsafe {
- std::mem::transmute([
- u16::from(g), 0u16, u16::from(g), 0u16, // QW1
- ])
- };
-
- const N: u64 = 0xff00_ff00_ff00_ff00;
- const M: u64 = 0x00ff_00ff_00ff_00ff;
-
- for y in y0..=y1 {
- let start = (y * w as i32 + x0) as usize;
- let count = (x1 - x0 + 1) as usize;
-
- let start_ptr = &mut dst[start * Self::PIXEL_SIZE] as *mut u8 as *mut [u8; 8];
- let slice = unsafe { std::slice::from_raw_parts_mut(start_ptr, (count - 1) / 2) };
- for rp in slice.iter_mut() {
- let ptr = rp as *mut [u8; 8] as *mut u64;
- let d1 = unsafe { *ptr };
- let mut h = (d1 >> 8) & M;
- let mut l = d1 & M;
-
- #[cfg(target_endian = "little")]
- {
- h = (h * (256 - a) + q * a) & N;
- l = ((l * (256 - a) + p * a) & N) >> 8;
- }
-
- #[cfg(target_endian = "big")]
- {
- h = (h * (256 - a) + p * a) & N;
- l = ((l * (256 - a) + q * a) & N) >> 8;
- }
-
- unsafe {
- *ptr = h | l;
- }
- }
-
- let mut iter = dst[((start + slice.len() * 2) * Self::PIXEL_SIZE)
- ..((start + count) * Self::PIXEL_SIZE)]
- .iter_mut();
- for _ in (slice.len() * 2)..count {
- blend(iter.next().unwrap(), b, a);
- blend(iter.next().unwrap(), g, a);
- blend(iter.next().unwrap(), r, a);
- iter.next();
- }
- }
- }
-
- #[allow(clippy::many_single_char_names, clippy::cast_ptr_alignment)]
- fn fill_rect_fast(
- target: &mut BitMapBackend<'_, Self>,
- upper_left: (i32, i32),
- bottom_right: (i32, i32),
- r: u8,
- g: u8,
- b: u8,
- ) {
- let (w, h) = target.get_size();
- let (x0, y0) = (
- upper_left.0.min(bottom_right.0).max(0),
- upper_left.1.min(bottom_right.1).max(0),
- );
- let (x1, y1) = (
- upper_left.0.max(bottom_right.0).min(w as i32 - 1),
- upper_left.1.max(bottom_right.1).min(h as i32 - 1),
- );
-
- // This may happen when the minimal value is larger than the limit.
- // Thus we just have something that is completely out-of-range
- if x0 > x1 || y0 > y1 {
- return;
- }
-
- let dst = target.get_raw_pixel_buffer();
-
- if r == g && g == b {
- // If r == g == b, then we can use memset
- if x0 != 0 || x1 != w as i32 - 1 {
- // If it's not the entire row is filled, we can only do
- // memset per row
- for y in y0..=y1 {
- let start = (y * w as i32 + x0) as usize;
- let count = (x1 - x0 + 1) as usize;
- dst[(start * Self::PIXEL_SIZE)..((start + count) * Self::PIXEL_SIZE)]
- .iter_mut()
- .for_each(|e| *e = r);
- }
- } else {
- // If the entire memory block is going to be filled, just use single memset
- dst[Self::PIXEL_SIZE * (y0 * w as i32) as usize
- ..((y1 + 1) * w as i32) as usize * Self::PIXEL_SIZE]
- .iter_mut()
- .for_each(|e| *e = r);
- }
- } else {
- let count = (x1 - x0 + 1) as usize;
- if count < 8 {
- for y in y0..=y1 {
- let start = (y * w as i32 + x0) as usize;
- let mut iter = dst
- [(start * Self::PIXEL_SIZE)..((start + count) * Self::PIXEL_SIZE)]
- .iter_mut();
- for _ in 0..=(x1 - x0) {
- *iter.next().unwrap() = b;
- *iter.next().unwrap() = g;
- *iter.next().unwrap() = r;
- iter.next();
- }
- }
- } else {
- for y in y0..=y1 {
- let start = (y * w as i32 + x0) as usize;
- let start_ptr = &mut dst[start * Self::PIXEL_SIZE] as *mut u8 as *mut [u8; 8];
- let slice =
- unsafe { std::slice::from_raw_parts_mut(start_ptr, (count - 1) / 2) };
- for p in slice.iter_mut() {
- // In this case, we can actually fill 8 pixels in one iteration with
- // only 3 movq instructions.
- // TODO: Consider using AVX instructions when possible
- let ptr = p as *mut [u8; 8] as *mut u64;
- unsafe {
- let d: u64 = std::mem::transmute([
- b, g, r, 0, b, g, r, 0, // QW1
- ]);
- *ptr = d;
- }
- }
-
- for idx in (slice.len() * 2)..count {
- dst[start * Self::PIXEL_SIZE + idx * Self::PIXEL_SIZE] = b;
- dst[start * Self::PIXEL_SIZE + idx * Self::PIXEL_SIZE + 1] = g;
- dst[start * Self::PIXEL_SIZE + idx * Self::PIXEL_SIZE + 2] = r;
- }
- }
- }
- }
- }
-}
-
-/// The backend that drawing a bitmap
-pub struct BitMapBackend<'a, P: PixelFormat = RGBPixel> {
- /// The path to the image
- #[allow(dead_code)]
- target: Target<'a>,
- /// The size of the image
- size: (u32, u32),
- /// The data buffer of the image
- buffer: Buffer<'a>,
- /// Flag indicates if the bitmap has been saved
- saved: bool,
- _pantomdata: PhantomData<P>,
-}
-
-impl<'a, P: PixelFormat> BitMapBackend<'a, P> {
- /// The number of bytes per pixel
- const PIXEL_SIZE: usize = P::PIXEL_SIZE;
-}
-
-impl<'a> BitMapBackend<'a, RGBPixel> {
- /// Create a new bitmap backend
- #[cfg(all(not(target_arch = "wasm32"), feature = "image"))]
- pub fn new<T: AsRef<Path> + ?Sized>(path: &'a T, (w, h): (u32, u32)) -> Self {
- Self {
- target: Target::File(path.as_ref()),
- size: (w, h),
- buffer: Buffer::Owned(vec![0; Self::PIXEL_SIZE * (w * h) as usize]),
- saved: false,
- _pantomdata: PhantomData,
- }
- }
-
- /// Create a new bitmap backend that generate GIF animation
- ///
- /// When this is used, the bitmap backend acts similar to a real-time rendering backend.
- /// When the program finished drawing one frame, use `present` function to flush the frame
- /// into the GIF file.
- ///
- /// - `path`: The path to the GIF file to create
- /// - `dimension`: The size of the GIF image
- /// - `speed`: The amount of time for each frame to display
- #[cfg(all(feature = "gif", not(target_arch = "wasm32"), feature = "image"))]
- pub fn gif<T: AsRef<Path>>(
- path: T,
- (w, h): (u32, u32),
- frame_delay: u32,
- ) -> Result<Self, BitMapBackendError> {
- Ok(Self {
- target: Target::Gif(Box::new(gif_support::GifFile::new(
- path,
- (w, h),
- frame_delay,
- )?)),
- size: (w, h),
- buffer: Buffer::Owned(vec![0; Self::PIXEL_SIZE * (w * h) as usize]),
- saved: false,
- _pantomdata: PhantomData,
- })
- }
-
- /// Create a new bitmap backend which only lives in-memory
- ///
- /// When this is used, the bitmap backend will write to a user provided [u8] array (or Vec<u8>)
- /// in RGB pixel format.
- ///
- /// Note: This function provides backward compatibility for those code that assumes Plotters
- /// uses RGB pixel format and maniuplates the in-memory framebuffer.
- /// For more pixel format option, use `with_buffer_and_format` instead.
- ///
- /// - `buf`: The buffer to operate
- /// - `dimension`: The size of the image in pixels
- /// - **returns**: The newly created bitmap backend
- pub fn with_buffer(buf: &'a mut [u8], (w, h): (u32, u32)) -> Self {
- Self::with_buffer_and_format(buf, (w, h)).expect("Wrong buffer size")
- }
-}
-
-impl<'a, P: PixelFormat> BitMapBackend<'a, P> {
- /// Create a new bitmap backend with a in-memory buffer with specific pixel format.
- ///
- /// Note: This can be used as a way to manipulate framebuffer, `mmap` can be used on the top of this
- /// as well.
- ///
- /// - `buf`: The buffer to operate
- /// - `dimension`: The size of the image in pixels
- /// - **returns**: The newly created bitmap backend
- pub fn with_buffer_and_format(
- buf: &'a mut [u8],
- (w, h): (u32, u32),
- ) -> Result<Self, BitMapBackendError> {
- if (w * h) as usize * Self::PIXEL_SIZE > buf.len() {
- return Err(BitMapBackendError::InvalidBuffer);
- }
-
- Ok(Self {
- target: Target::Buffer(PhantomData),
- size: (w, h),
- buffer: Buffer::Borrowed(buf),
- saved: false,
- _pantomdata: PhantomData,
- })
- }
-
- #[inline(always)]
- fn get_raw_pixel_buffer(&mut self) -> &mut [u8] {
- self.buffer.borrow_buffer()
- }
-
- /// Split a bitmap backend vertically into several sub drawing area which allows
- /// multi-threading rendering.
- ///
- /// - `area_size`: The size of the area
- /// - **returns**: The splitted backends that can be rendered in parallel
- pub fn split(&mut self, area_size: &[u32]) -> Vec<BitMapBackend<P>> {
- let (w, h) = self.get_size();
- let buf = self.get_raw_pixel_buffer();
-
- let base_addr = &mut buf[0] as *mut u8;
- let mut split_points = vec![0];
- for size in area_size {
- let next = split_points.last().unwrap() + size;
- if next >= h {
- break;
- }
- split_points.push(next);
- }
- split_points.push(h);
-
- split_points
- .iter()
- .zip(split_points.iter().skip(1))
- .map(|(begin, end)| {
- let actual_buf = unsafe {
- std::slice::from_raw_parts_mut(
- base_addr.offset((begin * w) as isize * Self::PIXEL_SIZE as isize),
- ((end - begin) * w) as usize * Self::PIXEL_SIZE,
- )
- };
- Self::with_buffer_and_format(actual_buf, (w, end - begin)).unwrap()
- })
- .collect()
- }
-}
-
-impl<'a, P: PixelFormat> DrawingBackend for BitMapBackend<'a, P> {
- type ErrorType = BitMapBackendError;
-
- fn get_size(&self) -> (u32, u32) {
- self.size
- }
-
- fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<BitMapBackendError>> {
- self.saved = false;
- Ok(())
- }
-
- #[cfg(any(target_arch = "wasm32", not(feature = "image")))]
- fn present(&mut self) -> Result<(), DrawingErrorKind<BitMapBackendError>> {
- Ok(())
- }
-
- #[cfg(all(not(target_arch = "wasm32"), feature = "image"))]
- fn present(&mut self) -> Result<(), DrawingErrorKind<BitMapBackendError>> {
- if !P::can_be_saved() {
- return Ok(());
- }
- let (w, h) = self.get_size();
- match &mut self.target {
- Target::File(path) => {
- if let Some(img) = BorrowedImage::from_raw(w, h, self.buffer.borrow_buffer()) {
- img.save(&path).map_err(|x| {
- DrawingErrorKind::DrawingError(match x {
- ImageError::IoError(x) => BitMapBackendError::IOError(x),
- whatever => BitMapBackendError::IOError(std::io::Error::new(
- std::io::ErrorKind::Other,
- format!("{}", whatever),
- )),
- })
- })?;
- self.saved = true;
- Ok(())
- } else {
- Err(DrawingErrorKind::DrawingError(
- BitMapBackendError::InvalidBuffer,
- ))
- }
- }
- Target::Buffer(_) => Ok(()),
-
- #[cfg(all(feature = "gif", not(target_arch = "wasm32"), feature = "image"))]
- Target::Gif(target) => {
- target
- .flush_frame(self.buffer.borrow_buffer())
- .map_err(DrawingErrorKind::DrawingError)?;
- self.saved = true;
- Ok(())
- }
- }
- }
-
- fn draw_pixel(
- &mut self,
- point: BackendCoord,
- color: &RGBAColor,
- ) -> Result<(), DrawingErrorKind<BitMapBackendError>> {
- if point.0 < 0 || point.1 < 0 {
- return Ok(());
- }
-
- let alpha = color.alpha();
- let rgb = color.rgb();
-
- P::draw_pixel(self, point, rgb, alpha);
-
- Ok(())
- }
-
- fn draw_line<S: BackendStyle>(
- &mut self,
- from: (i32, i32),
- to: (i32, i32),
- style: &S,
- ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
- let alpha = style.as_color().alpha();
- let (r, g, b) = style.as_color().rgb();
-
- if (from.0 == to.0 || from.1 == to.1) && style.stroke_width() == 1 {
- if alpha >= 1.0 {
- if from.1 == to.1 {
- P::fill_rect_fast(self, from, to, r, g, b);
- } else {
- P::fill_vertical_line_fast(self, from.0, (from.1, to.1), r, g, b);
- }
- } else {
- P::blend_rect_fast(self, from, to, r, g, b, alpha);
- }
- return Ok(());
- }
-
- crate::drawing::rasterizer::draw_line(self, from, to, style)
- }
-
- fn draw_rect<S: BackendStyle>(
- &mut self,
- upper_left: (i32, i32),
- bottom_right: (i32, i32),
- style: &S,
- fill: bool,
- ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
- let alpha = style.as_color().alpha();
- let (r, g, b) = style.as_color().rgb();
- if fill {
- if alpha >= 1.0 {
- P::fill_rect_fast(self, upper_left, bottom_right, r, g, b);
- } else {
- P::blend_rect_fast(self, upper_left, bottom_right, r, g, b, alpha);
- }
- return Ok(());
- }
- crate::drawing::rasterizer::draw_rect(self, upper_left, bottom_right, style, fill)
- }
-
- fn blit_bitmap<'b>(
- &mut self,
- pos: BackendCoord,
- (sw, sh): (u32, u32),
- src: &'b [u8],
- ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
- let (dw, dh) = self.get_size();
-
- let (x0, y0) = pos;
- let (x1, y1) = (x0 + sw as i32, y0 + sh as i32);
-
- let (x0, y0, x1, y1) = (x0.max(0), y0.max(0), x1.min(dw as i32), y1.min(dh as i32));
-
- if x0 == x1 || y0 == y1 {
- return Ok(());
- }
-
- let mut chunk_size = (x1 - x0) as usize;
- let mut num_chunks = (y1 - y0) as usize;
- let dst_gap = dw as usize - chunk_size;
- let src_gap = sw as usize - chunk_size;
-
- let dst_start = Self::PIXEL_SIZE * (y0 as usize * dw as usize + x0 as usize);
-
- let mut dst = &mut self.get_raw_pixel_buffer()[dst_start..];
-
- let src_start =
- Self::PIXEL_SIZE * ((sh as i32 + y0 - y1) * sw as i32 + (sw as i32 + x0 - x1)) as usize;
- let mut src = &src[src_start..];
-
- if src_gap == 0 && dst_gap == 0 {
- chunk_size *= num_chunks;
- num_chunks = 1;
- }
- for i in 0..num_chunks {
- dst[0..(chunk_size * Self::PIXEL_SIZE)]
- .copy_from_slice(&src[0..(chunk_size * Self::PIXEL_SIZE)]);
- if i != num_chunks - 1 {
- dst = &mut dst[((chunk_size + dst_gap) * Self::PIXEL_SIZE)..];
- src = &src[((chunk_size + src_gap) * Self::PIXEL_SIZE)..];
- }
- }
-
- Ok(())
- }
-}
-
-impl<P: PixelFormat> Drop for BitMapBackend<'_, P> {
- fn drop(&mut self) {
- if !self.saved {
- // drop should not panic, so we ignore a failed present
- let _ = self.present();
- }
- }
-}
-
-#[cfg(test)]
-#[test]
-fn test_bitmap_backend() {
- use crate::prelude::*;
- let mut buffer = vec![0; 10 * 10 * 3];
-
- {
- let back = BitMapBackend::with_buffer(&mut buffer, (10, 10));
-
- let area = back.into_drawing_area();
- area.fill(&WHITE).unwrap();
- area.draw(&PathElement::new(vec![(0, 0), (10, 10)], RED.filled()))
- .unwrap();
- area.present().unwrap();
- }
-
- for i in 0..10 {
- assert_eq!(buffer[i * 33], 255);
- assert_eq!(buffer[i * 33 + 1], 0);
- assert_eq!(buffer[i * 33 + 2], 0);
- buffer[i * 33] = 255;
- buffer[i * 33 + 1] = 255;
- buffer[i * 33 + 2] = 255;
- }
-
- assert!(buffer.into_iter().all(|x| x == 255));
-}
-
-#[cfg(test)]
-#[test]
-fn test_bitmap_backend_fill_half() {
- use crate::prelude::*;
- let mut buffer = vec![0; 10 * 10 * 3];
-
- {
- let back = BitMapBackend::with_buffer(&mut buffer, (10, 10));
-
- let area = back.into_drawing_area();
- area.draw(&Rectangle::new([(0, 0), (5, 10)], RED.filled()))
- .unwrap();
- area.present().unwrap();
- }
- for x in 0..10 {
- for y in 0..10 {
- assert_eq!(
- buffer[(y * 10 + x) as usize * 3 + 0],
- if x <= 5 { 255 } else { 0 }
- );
- assert_eq!(buffer[(y * 10 + x) as usize * 3 + 1], 0);
- assert_eq!(buffer[(y * 10 + x) as usize * 3 + 2], 0);
- }
- }
-
- let mut buffer = vec![0; 10 * 10 * 3];
-
- {
- let back = BitMapBackend::with_buffer(&mut buffer, (10, 10));
-
- let area = back.into_drawing_area();
- area.draw(&Rectangle::new([(0, 0), (10, 5)], RED.filled()))
- .unwrap();
- area.present().unwrap();
- }
- for x in 0..10 {
- for y in 0..10 {
- assert_eq!(
- buffer[(y * 10 + x) as usize * 3 + 0],
- if y <= 5 { 255 } else { 0 }
- );
- assert_eq!(buffer[(y * 10 + x) as usize * 3 + 1], 0);
- assert_eq!(buffer[(y * 10 + x) as usize * 3 + 2], 0);
- }
- }
-}
-
-#[cfg(test)]
-#[test]
-fn test_bitmap_backend_blend() {
- use crate::prelude::*;
- let mut buffer = vec![255; 10 * 10 * 3];
-
- {
- let back = BitMapBackend::with_buffer(&mut buffer, (10, 10));
-
- let area = back.into_drawing_area();
- area.draw(&Rectangle::new(
- [(0, 0), (5, 10)],
- RGBColor(0, 100, 200).mix(0.2).filled(),
- ))
- .unwrap();
- area.present().unwrap();
- }
-
- for x in 0..10 {
- for y in 0..10 {
- let (r, g, b) = if x <= 5 {
- (205, 225, 245)
- } else {
- (255, 255, 255)
- };
- assert_eq!(buffer[(y * 10 + x) as usize * 3 + 0], r);
- assert_eq!(buffer[(y * 10 + x) as usize * 3 + 1], g);
- assert_eq!(buffer[(y * 10 + x) as usize * 3 + 2], b);
- }
- }
-}
-
-#[cfg(test)]
-#[test]
-fn test_bitmap_backend_split_and_fill() {
- use crate::prelude::*;
- let mut buffer = vec![255; 10 * 10 * 3];
-
- {
- let mut back = BitMapBackend::with_buffer(&mut buffer, (10, 10));
-
- for (sub_backend, color) in back.split(&[5]).into_iter().zip([&RED, &GREEN].iter()) {
- sub_backend.into_drawing_area().fill(*color).unwrap();
- }
- }
-
- for x in 0..10 {
- for y in 0..10 {
- let (r, g, b) = if y < 5 { (255, 0, 0) } else { (0, 255, 0) };
- assert_eq!(buffer[(y * 10 + x) as usize * 3 + 0], r);
- assert_eq!(buffer[(y * 10 + x) as usize * 3 + 1], g);
- assert_eq!(buffer[(y * 10 + x) as usize * 3 + 2], b);
- }
- }
-}
-
-#[cfg(test)]
-#[test]
-fn test_draw_rect_out_of_range() {
- use crate::prelude::*;
- let mut buffer = vec![0; 1099 * 1000 * 3];
-
- {
- let mut back = BitMapBackend::with_buffer(&mut buffer, (1000, 1000));
-
- back.draw_line((1100, 0), (1100, 999), &RED.to_rgba())
- .unwrap();
- back.draw_line((0, 1100), (999, 1100), &RED.to_rgba())
- .unwrap();
- back.draw_rect((1100, 0), (1100, 999), &RED.to_rgba(), true)
- .unwrap();
- }
-
- for x in 0..1000 {
- for y in 0..1000 {
- assert_eq!(buffer[(y * 1000 + x) as usize * 3 + 0], 0);
- assert_eq!(buffer[(y * 1000 + x) as usize * 3 + 1], 0);
- assert_eq!(buffer[(y * 1000 + x) as usize * 3 + 2], 0);
- }
- }
-}
-
-#[cfg(test)]
-#[test]
-fn test_draw_line_out_of_range() {
- use crate::prelude::*;
- let mut buffer = vec![0; 1000 * 1000 * 3];
-
- {
- let mut back = BitMapBackend::with_buffer(&mut buffer, (1000, 1000));
-
- back.draw_line((-1000, -1000), (2000, 2000), &WHITE.to_rgba())
- .unwrap();
-
- back.draw_line((999, -1000), (999, 2000), &WHITE.to_rgba())
- .unwrap();
- }
-
- for x in 0..1000 {
- for y in 0..1000 {
- let expected_value = if x == y || x == 999 { 255 } else { 0 };
- assert_eq!(buffer[(y * 1000 + x) as usize * 3 + 0], expected_value);
- assert_eq!(buffer[(y * 1000 + x) as usize * 3 + 1], expected_value);
- assert_eq!(buffer[(y * 1000 + x) as usize * 3 + 2], expected_value);
- }
- }
-}
-
-#[cfg(test)]
-#[test]
-fn test_bitmap_blend_large() {
- use crate::prelude::*;
- let mut buffer = vec![0; 1000 * 1000 * 3];
-
- for fill_color in [RED, GREEN, BLUE].iter() {
- buffer.iter_mut().for_each(|x| *x = 0);
-
- {
- let mut back = BitMapBackend::with_buffer(&mut buffer, (1000, 1000));
-
- back.draw_rect((0, 0), (1000, 1000), &WHITE.mix(0.1), true)
- .unwrap(); // should be (24, 24, 24)
- back.draw_rect((0, 0), (100, 100), &fill_color.mix(0.5), true)
- .unwrap(); // should be (139, 24, 24)
- }
-
- for x in 0..1000 {
- for y in 0..1000 {
- let expected_value = if x <= 100 && y <= 100 {
- let (r, g, b) = fill_color.to_rgba().rgb();
- (
- if r > 0 { 139 } else { 12 },
- if g > 0 { 139 } else { 12 },
- if b > 0 { 139 } else { 12 },
- )
- } else {
- (24, 24, 24)
- };
- assert_eq!(buffer[(y * 1000 + x) as usize * 3 + 0], expected_value.0);
- assert_eq!(buffer[(y * 1000 + x) as usize * 3 + 1], expected_value.1);
- assert_eq!(buffer[(y * 1000 + x) as usize * 3 + 2], expected_value.2);
- }
- }
- }
-}
-
-#[cfg(test)]
-#[test]
-fn test_bitmap_bgrx_pixel_format() {
- use crate::drawing::bitmap_pixel::BGRXPixel;
- use crate::prelude::*;
- let mut rgb_buffer = vec![0; 1000 * 1000 * 3];
- let mut bgrx_buffer = vec![0; 1000 * 1000 * 4];
-
- {
- let mut rgb_back = BitMapBackend::with_buffer(&mut rgb_buffer, (1000, 1000));
- let mut bgrx_back =
- BitMapBackend::<BGRXPixel>::with_buffer_and_format(&mut bgrx_buffer, (1000, 1000))
- .unwrap();
-
- rgb_back
- .draw_rect((0, 0), (1000, 1000), &BLACK, true)
- .unwrap();
- bgrx_back
- .draw_rect((0, 0), (1000, 1000), &BLACK, true)
- .unwrap();
-
- rgb_back
- .draw_rect(
- (0, 0),
- (1000, 1000),
- &RGBColor(0xaa, 0xbb, 0xcc).mix(0.85),
- true,
- )
- .unwrap();
- bgrx_back
- .draw_rect(
- (0, 0),
- (1000, 1000),
- &RGBColor(0xaa, 0xbb, 0xcc).mix(0.85),
- true,
- )
- .unwrap();
-
- rgb_back
- .draw_rect((0, 0), (1000, 1000), &RED.mix(0.85), true)
- .unwrap();
- bgrx_back
- .draw_rect((0, 0), (1000, 1000), &RED.mix(0.85), true)
- .unwrap();
-
- rgb_back.draw_circle((300, 300), 100, &GREEN, true).unwrap();
- bgrx_back
- .draw_circle((300, 300), 100, &GREEN, true)
- .unwrap();
-
- rgb_back.draw_rect((10, 10), (50, 50), &BLUE, true).unwrap();
- bgrx_back
- .draw_rect((10, 10), (50, 50), &BLUE, true)
- .unwrap();
-
- rgb_back
- .draw_rect((10, 10), (50, 50), &WHITE, true)
- .unwrap();
- bgrx_back
- .draw_rect((10, 10), (50, 50), &WHITE, true)
- .unwrap();
-
- rgb_back
- .draw_rect((10, 10), (15, 50), &YELLOW, true)
- .unwrap();
- bgrx_back
- .draw_rect((10, 10), (15, 50), &YELLOW, true)
- .unwrap();
- }
-
- for x in 0..1000 {
- for y in 0..1000 {
- assert!(
- (rgb_buffer[y * 3000 + x * 3 + 0] as i32
- - bgrx_buffer[y * 4000 + x * 4 + 2] as i32)
- .abs()
- <= 1
- );
- assert!(
- (rgb_buffer[y * 3000 + x * 3 + 1] as i32
- - bgrx_buffer[y * 4000 + x * 4 + 1] as i32)
- .abs()
- <= 1
- );
- assert!(
- (rgb_buffer[y * 3000 + x * 3 + 2] as i32
- - bgrx_buffer[y * 4000 + x * 4 + 0] as i32)
- .abs()
- <= 1
- );
- }
- }
-}
-#[cfg(test)]
-#[test]
-fn test_draw_simple_lines() {
- use crate::prelude::*;
- let mut buffer = vec![0; 1000 * 1000 * 3];
-
- {
- let mut back = BitMapBackend::with_buffer(&mut buffer, (1000, 1000));
- back.draw_line((500, 0), (500, 1000), &WHITE.filled().stroke_width(5))
- .unwrap();
- }
-
- let nz_count = buffer.into_iter().filter(|x| *x != 0).count();
-
- assert_eq!(nz_count, 6 * 1000 * 3);
-}
-
-#[cfg(test)]
-#[test]
-fn test_bitmap_blit() {
- let src_bitmap: Vec<u8> = (0..100)
- .map(|y| (0..300).map(move |x| ((x * y) % 253) as u8))
- .flatten()
- .collect();
-
- use crate::prelude::*;
- let mut buffer = vec![0; 1000 * 1000 * 3];
-
- {
- let mut back = BitMapBackend::with_buffer(&mut buffer, (1000, 1000));
- back.blit_bitmap((500, 500), (100, 100), &src_bitmap[..])
- .unwrap();
- }
-
- for y in 0..1000 {
- for x in 0..1000 {
- if x >= 500 && x < 600 && y >= 500 && y < 600 {
- let lx = x - 500;
- let ly = y - 500;
- assert_eq!(buffer[y * 3000 + x * 3 + 0] as usize, (ly * lx * 3) % 253);
- assert_eq!(
- buffer[y * 3000 + x * 3 + 1] as usize,
- (ly * (lx * 3 + 1)) % 253
- );
- assert_eq!(
- buffer[y * 3000 + x * 3 + 2] as usize,
- (ly * (lx * 3 + 2)) % 253
- );
- } else {
- assert_eq!(buffer[y * 3000 + x * 3 + 0], 0);
- assert_eq!(buffer[y * 3000 + x * 3 + 1], 0);
- assert_eq!(buffer[y * 3000 + x * 3 + 2], 0);
- }
- }
- }
-}
-
-#[cfg(all(not(target_arch = "wasm32"), feature = "image"))]
-#[cfg(test)]
-mod test {
- use crate::prelude::*;
- use crate::style::text_anchor::{HPos, Pos, VPos};
- use image::{ImageBuffer, Rgb};
- use std::fs;
- use std::path::Path;
-
- static DST_DIR: &str = "target/test/bitmap";
-
- fn checked_save_file(name: &str, content: &[u8], w: u32, h: u32) {
- /*
- Please use the PNG file to manually verify the results.
- */
- assert!(content.iter().any(|x| *x != 0));
- fs::create_dir_all(DST_DIR).unwrap();
- let file_name = format!("{}.png", name);
- let file_path = Path::new(DST_DIR).join(file_name);
- println!("{:?} created", file_path);
- let img = ImageBuffer::<Rgb<u8>, &[u8]>::from_raw(w, h, content).unwrap();
- img.save(&file_path).unwrap();
- }
-
- fn draw_mesh_with_custom_ticks(tick_size: i32, test_name: &str) {
- let (width, height) = (500, 500);
- let mut buffer = vec![0; (width * height * 3) as usize];
- {
- let root = BitMapBackend::with_buffer(&mut buffer, (width, height)).into_drawing_area();
- root.fill(&WHITE).unwrap();
-
- let mut chart = ChartBuilder::on(&root)
- .caption("This is a test", ("sans-serif", 20))
- .set_all_label_area_size(40)
- .build_ranged(0..10, 0..10)
- .unwrap();
-
- chart
- .configure_mesh()
- .set_all_tick_mark_size(tick_size)
- .draw()
- .unwrap();
- }
- checked_save_file(test_name, &buffer, width, height);
- }
-
- #[test]
- fn test_draw_mesh_no_ticks() {
- draw_mesh_with_custom_ticks(0, "test_draw_mesh_no_ticks");
- }
-
- #[test]
- fn test_draw_mesh_negative_ticks() {
- draw_mesh_with_custom_ticks(-10, "test_draw_mesh_negative_ticks");
- }
-
- #[test]
- fn test_text_draw() {
- let (width, height) = (1500, 800);
- let mut buffer = vec![0; (width * height * 3) as usize];
- {
- let root = BitMapBackend::with_buffer(&mut buffer, (width, height)).into_drawing_area();
- root.fill(&WHITE).unwrap();
- let root = root
- .titled("Image Title", ("sans-serif", 60).into_font())
- .unwrap();
-
- let mut chart = ChartBuilder::on(&root)
- .caption("All anchor point positions", ("sans-serif", 20))
- .set_all_label_area_size(40)
- .build_ranged(0..100, 0..50)
- .unwrap();
-
- chart
- .configure_mesh()
- .disable_x_mesh()
- .disable_y_mesh()
- .x_desc("X Axis")
- .y_desc("Y Axis")
- .draw()
- .unwrap();
-
- let ((x1, y1), (x2, y2), (x3, y3)) = ((-30, 30), (0, -30), (30, 30));
-
- for (dy, trans) in [
- FontTransform::None,
- FontTransform::Rotate90,
- FontTransform::Rotate180,
- FontTransform::Rotate270,
- ]
- .iter()
- .enumerate()
- {
- for (dx1, h_pos) in [HPos::Left, HPos::Right, HPos::Center].iter().enumerate() {
- for (dx2, v_pos) in [VPos::Top, VPos::Center, VPos::Bottom].iter().enumerate() {
- let x = 150_i32 + (dx1 as i32 * 3 + dx2 as i32) * 150;
- let y = 120 + dy as i32 * 150;
- let draw = |x, y, text| {
- root.draw(&Circle::new((x, y), 3, &BLACK.mix(0.5))).unwrap();
- let style = TextStyle::from(("sans-serif", 20).into_font())
- .pos(Pos::new(*h_pos, *v_pos))
- .transform(trans.clone());
- root.draw_text(text, &style, (x, y)).unwrap();
- };
- draw(x + x1, y + y1, "dood");
- draw(x + x2, y + y2, "dog");
- draw(x + x3, y + y3, "goog");
- }
- }
- }
- }
- checked_save_file("test_text_draw", &buffer, width, height);
- }
-
- #[test]
- fn test_text_clipping() {
- let (width, height) = (500_i32, 500_i32);
- let mut buffer = vec![0; (width * height * 3) as usize];
- {
- let root = BitMapBackend::with_buffer(&mut buffer, (width as u32, height as u32))
- .into_drawing_area();
- root.fill(&WHITE).unwrap();
-
- let style = TextStyle::from(("sans-serif", 20).into_font())
- .pos(Pos::new(HPos::Center, VPos::Center));
- root.draw_text("TOP LEFT", &style, (0, 0)).unwrap();
- root.draw_text("TOP CENTER", &style, (width / 2, 0))
- .unwrap();
- root.draw_text("TOP RIGHT", &style, (width, 0)).unwrap();
-
- root.draw_text("MIDDLE LEFT", &style, (0, height / 2))
- .unwrap();
- root.draw_text("MIDDLE RIGHT", &style, (width, height / 2))
- .unwrap();
-
- root.draw_text("BOTTOM LEFT", &style, (0, height)).unwrap();
- root.draw_text("BOTTOM CENTER", &style, (width / 2, height))
- .unwrap();
- root.draw_text("BOTTOM RIGHT", &style, (width, height))
- .unwrap();
- }
- checked_save_file("test_text_clipping", &buffer, width as u32, height as u32);
- }
-
- #[test]
- fn test_series_labels() {
- let (width, height) = (500, 500);
- let mut buffer = vec![0; (width * height * 3) as usize];
- {
- let root = BitMapBackend::with_buffer(&mut buffer, (width, height)).into_drawing_area();
- root.fill(&WHITE).unwrap();
-
- let mut chart = ChartBuilder::on(&root)
- .caption("All series label positions", ("sans-serif", 20))
- .set_all_label_area_size(40)
- .build_ranged(0..50, 0..50)
- .unwrap();
-
- chart
- .configure_mesh()
- .disable_x_mesh()
- .disable_y_mesh()
- .draw()
- .unwrap();
-
- chart
- .draw_series(std::iter::once(Circle::new((5, 15), 5, &RED)))
- .expect("Drawing error")
- .label("Series 1")
- .legend(|(x, y)| Circle::new((x, y), 3, RED.filled()));
-
- chart
- .draw_series(std::iter::once(Circle::new((5, 15), 10, &BLUE)))
- .expect("Drawing error")
- .label("Series 2")
- .legend(|(x, y)| Circle::new((x, y), 3, BLUE.filled()));
-
- for pos in vec![
- SeriesLabelPosition::UpperLeft,
- SeriesLabelPosition::MiddleLeft,
- SeriesLabelPosition::LowerLeft,
- SeriesLabelPosition::UpperMiddle,
- SeriesLabelPosition::MiddleMiddle,
- SeriesLabelPosition::LowerMiddle,
- SeriesLabelPosition::UpperRight,
- SeriesLabelPosition::MiddleRight,
- SeriesLabelPosition::LowerRight,
- SeriesLabelPosition::Coordinate(70, 70),
- ]
- .into_iter()
- {
- chart
- .configure_series_labels()
- .border_style(&BLACK.mix(0.5))
- .position(pos)
- .draw()
- .expect("Drawing error");
- }
- }
- checked_save_file("test_series_labels", &buffer, width, height);
- }
-
- #[test]
- fn test_draw_pixel_alphas() {
- let (width, height) = (100_i32, 100_i32);
- let mut buffer = vec![0; (width * height * 3) as usize];
- {
- let root = BitMapBackend::with_buffer(&mut buffer, (width as u32, height as u32))
- .into_drawing_area();
- root.fill(&WHITE).unwrap();
- for i in -20..20 {
- let alpha = i as f64 * 0.1;
- root.draw_pixel((50 + i, 50 + i), &BLACK.mix(alpha))
- .unwrap();
- }
- }
- checked_save_file(
- "test_draw_pixel_alphas",
- &buffer,
- width as u32,
- height as u32,
- );
- }
-}