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)]
pub enum BitMapBackendError {
InvalidBuffer,
IOError(std::io::Error),
#[cfg(all(not(target_arch = "wasm32"), feature = "image"))]
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,
}
}
}
pub trait PixelFormat: Sized {
const PIXEL_SIZE: usize;
const EFFECTIVE_PIXEL_SIZE: usize;
fn byte_at(r: u8, g: u8, b: u8, a: u64, idx: usize) -> u8;
fn decode_pixel(data: &[u8]) -> (u8, u8, u8, u64);
fn blend_rect_fast(
target: &mut BitMapBackend<'_, Self>,
upper_left: (i32, i32),
bottom_right: (i32, i32),
r: u8,
g: u8,
b: u8,
a: f64,
);
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;
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);
}
y0 = y0.max(0);
y1 = y1.min(h - 1);
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);
}
}
}
fn fill_rect_fast(
target: &mut BitMapBackend<'_, Self>,
upper_left: (i32, i32),
bottom_right: (i32, i32),
r: u8,
g: u8,
b: u8,
);
#[inline(always)]
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,
);
}
}
}
}
}
fn can_be_saved() -> bool {
false
}
}
pub struct RGBPixel;
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),
);
if x0 > x1 || y0 > y1 {
return;
}
let dst = target.get_raw_pixel_buffer();
let a = (256.0 * a).floor() as u64;
#[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), u16::from(b), u16::from(g), u16::from(r), u16::from(b), u16::from(g), u16::from(r), u16::from(b), u16::from(g), ])
};
#[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), u16::from(r), u16::from(b), u16::from(g), u16::from(r), u16::from(b), u16::from(g), u16::from(r), u16::from(b), ])
};
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),
);
if x0 > x1 || y0 > y1 {
return;
}
let dst = target.get_raw_pixel_buffer();
if r == g && g == b {
if x0 != 0 || x1 != w as i32 - 1 {
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 {
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() {
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, b, r, g, b, r, g, b, r, g, b, r, g, b, r, g, b, ]);
*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),
);
if x0 > x1 || y0 > y1 {
return;
}
let dst = target.get_raw_pixel_buffer();
let a = (256.0 * a).floor() as u64;
#[rustfmt::skip]
let p: u64 = unsafe {
std::mem::transmute([
u16::from(b), u16::from(r), u16::from(b), u16::from(r), ])
};
#[rustfmt::skip]
let q: u64 = unsafe {
std::mem::transmute([
u16::from(g), 0u16, u16::from(g), 0u16, ])
};
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),
);
if x0 > x1 || y0 > y1 {
return;
}
let dst = target.get_raw_pixel_buffer();
if r == g && g == b {
if x0 != 0 || x1 != w as i32 - 1 {
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 {
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() {
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, ]);
*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;
}
}
}
}
}
}
pub struct BitMapBackend<'a, P: PixelFormat = RGBPixel> {
#[allow(dead_code)]
target: Target<'a>,
size: (u32, u32),
buffer: Buffer<'a>,
saved: bool,
_pantomdata: PhantomData<P>,
}
impl<'a, P: PixelFormat> BitMapBackend<'a, P> {
const PIXEL_SIZE: usize = P::PIXEL_SIZE;
}
impl<'a> BitMapBackend<'a, RGBPixel> {
#[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,
}
}
#[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,
})
}
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> {
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()
}
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(BitMapBackendError::IOError(x))
})?;
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 {
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 {
self.present().expect("Unable to save the bitmap");
}
}
}
#[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(); back.draw_rect((0, 0), (100, 100), &fill_color.mix(0.5), true)
.unwrap(); }
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_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) {
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,
);
}
}