plotters-bitmap-0.3.3/.cargo_vcs_info.json0000644000000001550000000000100141660ustar { "git": { "sha1": "ce83c0e66c793e47dc3e6ab7b167e6361ff36f8a" }, "path_in_vcs": "plotters-bitmap" }plotters-bitmap-0.3.3/Cargo.toml0000644000000024760000000000100121740ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "plotters-bitmap" version = "0.3.3" authors = ["Hao Hou "] description = "Plotters Bitmap Backend" homepage = "https://plotters-rs.github.io" readme = "README.md" license = "MIT" repository = "https://github.com/plotters-rs/plotters" [[bench]] name = "benchmark" path = "benches/main.rs" harness = false [dependencies.gif] version = "0.12.0" optional = true [dependencies.plotters-backend] version = "0.3.5" [dev-dependencies.criterion] version = "0.4.0" [dev-dependencies.rayon] version = "1.5.1" [features] default = [ "image_encoder", "gif_backend", ] gif_backend = [ "gif", "image_encoder", ] image_encoder = ["image"] [target."cfg(not(target_arch = \"wasm32\"))".dependencies.image] version = "0.24.3" features = [ "jpeg", "png", "bmp", ] optional = true default-features = false plotters-bitmap-0.3.3/Cargo.toml.orig000064400000000000000000000020451046102023000156450ustar 00000000000000[package] name = "plotters-bitmap" version = "0.3.3" authors = ["Hao Hou "] edition = "2018" license = "MIT" description = "Plotters Bitmap Backend" homepage = "https://plotters-rs.github.io" repository = "https://github.com/plotters-rs/plotters" readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] gif = { version = "0.12.0", optional = true } [dependencies.plotters-backend] version = "0.3.5" path = "../plotters-backend" [target.'cfg(not(target_arch = "wasm32"))'.dependencies.image] version = "0.24.3" optional = true default-features = false features = ["jpeg", "png", "bmp"] [features] default = ["image_encoder", "gif_backend"] image_encoder = ["image"] gif_backend = ["gif", "image_encoder"] [dev-dependencies.plotters] default_features = false features = ["ttf", "line_series", "bitmap_backend"] path = "../plotters" [dev-dependencies] criterion = "0.4.0" rayon = "1.5.1" [[bench]] name = "benchmark" harness = false path = "benches/main.rs" plotters-bitmap-0.3.3/LICENSE000064400000000000000000000000121046102023000137530ustar 00000000000000../LICENSEplotters-bitmap-0.3.3/README.md000064400000000000000000000010061046102023000142310ustar 00000000000000# plotters-bitmap - The bitmap backend for Plotters This is a part of plotters project. For more details, please check the following links: - For high-level intro of Plotters, see: [Plotters on crates.io](https://crates.io/crates/plotters) - Check the main repo at [Plotters repo](https://github.com/plotters-rs/plotters.git) - For detailed documentation about this crate, check [plotters-backend on docs.rs](https://docs.rs/plotters-backend/) - You can also visit Plotters [Homepage](https://plotters-rs.github.io) plotters-bitmap-0.3.3/benches/benches/mod.rs000064400000000000000000000000461046102023000171200ustar 00000000000000pub mod parallel; pub mod rasterizer; plotters-bitmap-0.3.3/benches/benches/parallel.rs000064400000000000000000000155361046102023000201470ustar 00000000000000use criterion::{criterion_group, BenchmarkId, Criterion}; use plotters::coord::Shift; use plotters::prelude::*; use plotters_bitmap::BitMapBackend; use rayon::prelude::*; const SIZES: &'static [u32] = &[100, 400, 800, 1000, 2000]; fn draw_plot(root: &DrawingArea, pow: f64) { let mut chart = ChartBuilder::on(root) .caption(format!("y = x^{}", pow), ("Arial", 30)) .build_cartesian_2d(-1.0..1.0, -1.0..1.0) .unwrap(); chart.configure_mesh().draw().unwrap(); chart .draw_series(LineSeries::new( (-50..=50) .map(|x| x as f64 / 50.0) .map(|x| (x, x.powf(pow))), &RED, )) .unwrap() .label(format!("y = x^{}", pow)) .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &RED)); chart .configure_series_labels() .background_style(&WHITE.mix(0.8)) .border_style(&BLACK) .draw() .unwrap(); } fn draw_func_1x1_seq(c: &mut Criterion) { let mut group = c.benchmark_group("draw_func_1x1"); for size in SIZES { group.bench_with_input(BenchmarkId::new("sequential", size), size, |b, &s| { let mut buffer = vec![0; (s * s * 3) as usize]; b.iter(|| { let root = BitMapBackend::with_buffer(&mut buffer, (s, s)).into_drawing_area(); root.fill(&WHITE).unwrap(); draw_plot(&root, 2.0); }) }); } } fn draw_func_4x4(c: &mut Criterion) { let mut group = c.benchmark_group("draw_func_4x4"); for size in SIZES { group .bench_with_input(BenchmarkId::new("sequential", size), size, |b, &s| { let mut buffer = vec![0; (s * s * 3) as usize]; b.iter(|| { let root = BitMapBackend::with_buffer(&mut buffer, (s, s)).into_drawing_area(); let areas = root.split_evenly((4, 4)); areas.iter().for_each(|area| draw_plot(&area, 2.0)); }) }) .bench_with_input(BenchmarkId::new("blit", size), size, |b, &s| { let mut buffer = vec![0; (s * s * 3) as usize]; let mut element_buffer = vec![vec![0; (s * s / 4 * 3) as usize]; 4]; b.iter(|| { let root = BitMapBackend::with_buffer(&mut buffer, (s, s)).into_drawing_area(); let areas = root.split_evenly((4, 4)); let elements: Vec<_> = element_buffer .par_iter_mut() .map(|b| { let mut e = BitMapElement::with_mut((0, 0), (s / 2, s / 2), b).unwrap(); draw_plot(&e.as_bitmap_backend().into_drawing_area(), 2.0); e }) .collect(); areas .into_iter() .zip(elements.into_iter()) .for_each(|(a, e)| a.draw(&e).unwrap()); }) }) .bench_with_input(BenchmarkId::new("inplace-blit", size), size, |b, &s| { let mut buffer = vec![0; (s * s * 3) as usize]; let mut element_buffer = vec![vec![vec![0; (s * s / 4 * 3) as usize]; 2]; 2]; b.iter(|| { let mut back = BitMapBackend::with_buffer(&mut buffer, (s, s)); back.split(&[s / 2]) .into_iter() .zip(element_buffer.iter_mut()) .collect::>() .into_par_iter() .for_each(|(back, buffer)| { let root = back.into_drawing_area(); let areas = root.split_evenly((1, 2)); let elements: Vec<_> = buffer .par_iter_mut() .map(|b| { let mut e = BitMapElement::with_mut((0, 0), (s / 2, s / 2), b).unwrap(); draw_plot(&e.as_bitmap_backend().into_drawing_area(), 2.0); e }) .collect(); areas .into_iter() .zip(elements.into_iter()) .for_each(|(a, e)| a.draw(&e).unwrap()) }); }) }); } } fn draw_func_2x1(c: &mut Criterion) { let mut group = c.benchmark_group("draw_func_2x1"); for size in SIZES { group .bench_with_input(BenchmarkId::new("blit", size), size, |b, &s| { let mut buffer = vec![0; (s * s * 3) as usize]; let mut element_buffer = vec![vec![0; (s * s / 2 * 3) as usize]; 2]; b.iter(|| { let root = BitMapBackend::with_buffer(&mut buffer, (s, s)).into_drawing_area(); let areas = root.split_evenly((2, 1)); let elements: Vec<_> = element_buffer .par_iter_mut() .map(|buf| { let mut element = BitMapElement::with_mut((0, 0), (s, s / 2), buf).unwrap(); draw_plot(&element.as_bitmap_backend().into_drawing_area(), 2.0); element }) .collect(); areas .into_iter() .zip(elements.into_iter()) .for_each(|(a, e)| a.draw(&e).unwrap()); }) }) .bench_with_input(BenchmarkId::new("inplace", size), size, |b, &s| { let mut buffer = vec![0; (s * s * 3) as usize]; b.iter(|| { let mut back = BitMapBackend::with_buffer(&mut buffer, (s, s)); back.split(&[s / 2]) .into_par_iter() .for_each(|b| draw_plot(&b.into_drawing_area(), 2.0)); }) }) .bench_with_input(BenchmarkId::new("sequential", size), size, |b, &s| { let mut buffer = vec![0; (s * s * 3) as usize]; b.iter(|| { let root = BitMapBackend::with_buffer(&mut buffer, (s, s)).into_drawing_area(); root.split_evenly((2, 1)) .iter_mut() .for_each(|area| draw_plot(area, 2.0)); }) }); } } criterion_group! { name = parallel_group; config = Criterion::default().sample_size(10); targets = draw_func_1x1_seq, draw_func_4x4, draw_func_2x1, } plotters-bitmap-0.3.3/benches/benches/rasterizer.rs000064400000000000000000000146071046102023000205430ustar 00000000000000use criterion::{criterion_group, Criterion}; use plotters::prelude::*; use plotters_backend::BackendStyle; use plotters_bitmap::{bitmap_pixel::BGRXPixel, BitMapBackend}; const W: u32 = 1000; const H: u32 = 1000; fn draw_pixel(c: &mut Criterion) { let mut group = c.benchmark_group("rasterizer::draw_pixel"); group.bench_function("rgb", |b| { let mut buffer = vec![0; (W * H * 3) as usize]; b.iter(|| { let mut root = BitMapBackend::with_buffer(&mut buffer, (W, H)); for x in 0..W / 10 { for y in 0..H / 10 { root.draw_pixel((x as i32, y as i32), RGBColor(255, 0, 234).color()) .unwrap(); } } }) }); group.bench_function("xbgr", |b| { let mut buffer = vec![0; (W * H * 4) as usize]; b.iter(|| { let mut root = BitMapBackend::::with_buffer_and_format(&mut buffer, (W, H)).unwrap(); for x in 0..W / 10 { for y in 0..H / 10 { root.draw_pixel((x as i32, y as i32), RGBColor(255, 0, 234).color()) .unwrap(); } } }) }); } fn draw_line(c: &mut Criterion) { let mut group = c.benchmark_group("rasterizer::draw_line"); group.bench_function("rgb", |b| { let mut buffer = vec![0; (W * H * 3) as usize]; b.iter(|| { let mut root = BitMapBackend::with_buffer(&mut buffer, (W, H)); for y in 0..10 { root.draw_line( (0, 0), ((W / 2) as i32, (y * 100) as i32), &RGBColor(255, 0, 234).to_rgba(), ) .unwrap(); } }) }); group.bench_function("bgrx", |b| { let mut buffer = vec![0; (W * H * 4) as usize]; b.iter(|| { let mut root = BitMapBackend::::with_buffer_and_format(&mut buffer, (W, H)).unwrap(); for y in 0..10 { root.draw_line( (0, 0), ((W / 2) as i32, (y * 100) as i32), &RGBColor(255, 0, 234).to_rgba(), ) .unwrap(); } }) }); } fn fill_background(c: &mut Criterion) { let mut group = c.benchmark_group("rasterizer::fill_background"); group.bench_function("rgb", |b| { let mut buffer = vec![0; (W * H * 3) as usize]; b.iter(|| { let root = BitMapBackend::with_buffer(&mut buffer, (W, H)).into_drawing_area(); root.fill(&WHITE).unwrap(); }) }); group.bench_function("bgrx", |b| { let mut buffer = vec![0; (W * H * 4) as usize]; b.iter(|| { let root = BitMapBackend::::with_buffer_and_format(&mut buffer, (W, H)) .unwrap() .into_drawing_area(); root.fill(&WHITE).unwrap(); }) }); } fn blend_background(c: &mut Criterion) { let mut g = c.benchmark_group("rasterizer::blend_background"); g.bench_function("rgb", |b| { let mut buffer = vec![0; (W * H * 3) as usize]; b.iter(|| { let root = BitMapBackend::with_buffer(&mut buffer, (W, H)).into_drawing_area(); root.fill(&WHITE.mix(0.1)).unwrap(); }) }); g.bench_function("bgrx", |b| { let mut buffer = vec![0; (W * H * 4) as usize]; b.iter(|| { let root = BitMapBackend::::with_buffer_and_format(&mut buffer, (W, H)) .unwrap() .into_drawing_area(); root.fill(&WHITE.mix(0.1)).unwrap(); }) }); } fn fill_circle(c: &mut Criterion) { let mut g = c.benchmark_group("rasterizer::fill_circle"); g.bench_function("rgb", |b| { let mut buffer = vec![0; (W * H * 3) as usize]; b.iter(|| { let mut root = BitMapBackend::with_buffer(&mut buffer, (W, H)); root.draw_circle((W as i32 / 2, H as i32 / 2), W / 2, &WHITE.to_rgba(), true) .unwrap(); }) }); g.bench_function("bgrx", |b| { let mut buffer = vec![0; (W * H * 4) as usize]; b.iter(|| { let mut root = BitMapBackend::::with_buffer_and_format(&mut buffer, (W, H)).unwrap(); root.draw_circle((W as i32 / 2, H as i32 / 2), W / 2, &WHITE.to_rgba(), true) .unwrap(); }) }); } fn fill_background_red(c: &mut Criterion) { let mut g = c.benchmark_group("rasterizer::fill_background_red"); g.bench_function("rgb", |b| { let mut buffer = vec![0; (W * H * 3) as usize]; b.iter(|| { let root = BitMapBackend::with_buffer(&mut buffer, (W, H)).into_drawing_area(); root.fill(&RED).unwrap(); }) }); g.bench_function("bgrx", |b| { let mut buffer = vec![0; (W * H * 4) as usize]; b.iter(|| { let root = BitMapBackend::::with_buffer_and_format(&mut buffer, (W, H)) .unwrap() .into_drawing_area(); root.fill(&RED).unwrap(); }) }); } fn fill_hexagon(c: &mut Criterion) { let mut g = c.benchmark_group("rasterizer::fill_hexagon"); let mut vert = vec![]; for i in 0..6 { let x = (W as f64 / 5.0 * (std::f64::consts::PI * i as f64 / 3.0).cos()).ceil() as i32 + W as i32 / 2; let y = (W as f64 / 5.0 * (std::f64::consts::PI * i as f64 / 3.0).sin()).ceil() as i32 + W as i32 / 2; vert.push((x, y)); } g.bench_function("rgb", |b| { let mut buffer = vec![0; (W * H * 3) as usize]; b.iter(|| { let mut root = BitMapBackend::with_buffer(&mut buffer, (W, H)); root.fill_polygon(vert.clone(), &RED).unwrap(); }) }); g.bench_function("bgrx", |b| { let mut buffer = vec![0; (W * H * 4) as usize]; b.iter(|| { let mut root = BitMapBackend::::with_buffer_and_format(&mut buffer, (W, H)).unwrap(); root.fill_polygon(vert.clone(), &RED).unwrap(); }) }); } criterion_group! { name = rasterizer_group; config = Criterion::default(); targets = blend_background, draw_pixel, draw_line, fill_background, fill_circle, fill_background_red, fill_hexagon, } plotters-bitmap-0.3.3/benches/main.rs000064400000000000000000000002241046102023000156540ustar 00000000000000use criterion::criterion_main; mod benches; criterion_main! { benches::parallel::parallel_group, benches::rasterizer::rasterizer_group, } plotters-bitmap-0.3.3/src/bitmap/target.rs000064400000000000000000000016401046102023000166550ustar 00000000000000#[cfg(all(feature = "gif", not(target_arch = "wasm32"), feature = "image"))] use crate::gif_support; use std::marker::PhantomData; #[cfg(all(not(target_arch = "wasm32"), feature = "image"))] use std::path::Path; pub(super) 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), } pub(super) enum Buffer<'a> { #[cfg(all(not(target_arch = "wasm32"), feature = "image"))] Owned(Vec), Borrowed(&'a mut [u8]), } impl<'a> Buffer<'a> { #[inline(always)] pub(super) 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, } } } plotters-bitmap-0.3.3/src/bitmap/test.rs000064400000000000000000000465221046102023000163560ustar 00000000000000use super::*; #[test] fn test_bitmap_backend() { use plotters::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 plotters::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 plotters::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 plotters::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 plotters::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_rect_exclude_bottom_right() { use plotters::prelude::*; let mut buffer = vec![0; 100 * 100 * 3]; { let mut back = BitMapBackend::with_buffer(&mut buffer, (100, 100)); back.draw_rect((0, 0), (0, 0), &RED.to_rgba(), true) .unwrap(); } assert_eq!(&buffer[0..3], &[0, 0, 0]); } #[cfg(test)] #[test] fn test_draw_line_out_of_range() { use plotters::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 plotters::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::{bitmap_pixel::BGRXPixel, BitMapBackend}; use plotters::prelude::{Color, RGBColor, BLACK, BLUE, GREEN, RED, WHITE, YELLOW}; 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::::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 plotters::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 = (0..100) .map(|y| (0..300).map(move |x| ((x * y) % 253) as u8)) .flatten() .collect(); use plotters::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::BitMapBackend; use image::{ImageBuffer, Rgb}; use plotters::prelude::*; use plotters::style::text_anchor::{HPos, Pos, VPos}; 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::, &[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_cartesian_2d(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_cartesian_2d(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_cartesian_2d(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, ); } } plotters-bitmap-0.3.3/src/bitmap.rs000064400000000000000000000266371046102023000154040ustar 00000000000000use plotters_backend::{ BackendColor, BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind, }; use std::marker::PhantomData; use crate::bitmap_pixel::{PixelFormat, RGBPixel}; use crate::error::BitMapBackendError; #[cfg(all(not(target_arch = "wasm32"), feature = "image"))] mod image_encoding_support { pub(super) use image::{ImageBuffer, Rgb}; pub(super) use std::path::Path; pub(super) type BorrowedImage<'a> = ImageBuffer, &'a mut [u8]>; } #[cfg(all(not(target_arch = "wasm32"), feature = "image"))] use image_encoding_support::*; mod target; use target::{Buffer, Target}; /// The backend that drawing a bitmap /// /// # Warning /// /// You should call [`.present()?`](plotters_backend::DrawingBackend::present) on a /// `BitMapBackend`, not just `drop` it or allow it to go out of scope. /// /// If the `BitMapBackend` is just dropped, it will make a best effort attempt to write the /// generated charts to the output file, but any errors that occcur (such as inability to /// create the output file) will be silently ignored. 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

, } 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 + ?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>( path: T, (w, h): (u32, u32), frame_delay: u32, ) -> Result { Ok(Self { target: Target::Gif(Box::new(crate::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) /// 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 { 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)] pub(crate) 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> { 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> { self.saved = false; Ok(()) } #[cfg(any(target_arch = "wasm32", not(feature = "image")))] fn present(&mut self) -> Result<(), DrawingErrorKind> { Ok(()) } #[cfg(all(not(target_arch = "wasm32"), feature = "image"))] fn present(&mut self) -> Result<(), DrawingErrorKind> { 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::ImageError(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: BackendColor, ) -> Result<(), DrawingErrorKind> { if point.0 < 0 || point.1 < 0 || point.0 as u32 >= self.size.0 || point.1 as u32 >= self.size.1 { return Ok(()); } let alpha = color.alpha; let rgb = color.rgb; P::draw_pixel(self, point, rgb, alpha); Ok(()) } fn draw_line( &mut self, from: (i32, i32), to: (i32, i32), style: &S, ) -> Result<(), DrawingErrorKind> { let alpha = style.color().alpha; let (r, g, b) = style.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.0 + 1, to.1 + 1), 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.0 + 1, to.1 + 1), r, g, b, alpha); } return Ok(()); } plotters_backend::rasterizer::draw_line(self, from, to, style) } fn draw_rect( &mut self, upper_left: (i32, i32), bottom_right: (i32, i32), style: &S, fill: bool, ) -> Result<(), DrawingErrorKind> { let alpha = style.color().alpha; let (r, g, b) = style.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(()); } plotters_backend::rasterizer::draw_rect(self, upper_left, bottom_right, style, fill) } fn blit_bitmap( &mut self, pos: BackendCoord, (sw, sh): (u32, u32), src: &[u8], ) -> Result<(), DrawingErrorKind> { 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 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)] mod test; plotters-bitmap-0.3.3/src/bitmap_pixel/bgrx.rs000064400000000000000000000165611046102023000175420ustar 00000000000000use super::pixel_format::blend; use super::PixelFormat; use crate::BitMapBackend; use plotters_backend::DrawingBackend; /// The marker type that indicates we are currently using a BGRX8888 pixel format pub struct BGRXPixel; 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), upper_left.1.max(bottom_right.1).min(h as i32), ); // 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) 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.read_unaligned() }; 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.write_unaligned(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), upper_left.1.max(bottom_right.1).min(h as i32), ); // 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 { // 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) 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 * w as i32) as usize * Self::PIXEL_SIZE] .iter_mut() .for_each(|e| *e = r); } } else { let count = (x1 - x0) 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..count { *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.write_unaligned(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; } } } } } } plotters-bitmap-0.3.3/src/bitmap_pixel/mod.rs000064400000000000000000000001711046102023000173450ustar 00000000000000mod bgrx; mod pixel_format; mod rgb; pub use bgrx::BGRXPixel; pub use pixel_format::PixelFormat; pub use rgb::RGBPixel; plotters-bitmap-0.3.3/src/bitmap_pixel/pixel_format.rs000064400000000000000000000112451046102023000212630ustar 00000000000000use crate::BitMapBackend; use plotters_backend::DrawingBackend; #[inline(always)] pub(super) 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 } } /// 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 } } plotters-bitmap-0.3.3/src/bitmap_pixel/rgb.rs000064400000000000000000000210771046102023000173500ustar 00000000000000use super::pixel_format::blend; use super::PixelFormat; use crate::BitMapBackend; use plotters_backend::DrawingBackend; /// The marker type that indicates we are currently using a RGB888 pixel format pub struct RGBPixel; 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), upper_left.1.max(bottom_right.1).min(h as i32), ); // 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; 3] = 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; 3] = 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) 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; 3]; let [d1, d2, d3] = unsafe { ptr.read_unaligned() }; 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.write_unaligned([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), upper_left.1.max(bottom_right.1).min(h as i32), ); // 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 { // 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) 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 * w as i32) as usize * Self::PIXEL_SIZE] .iter_mut() .for_each(|e| *e = r); } } else { let count = (x1 - x0) 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..count { *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; 3] = 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.write_unaligned(d1); ptr.offset(1).write_unaligned(d2); ptr.offset(2).write_unaligned(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; } } } } } } plotters-bitmap-0.3.3/src/error.rs000064400000000000000000000014731046102023000152500ustar 00000000000000#[cfg(all(not(target_arch = "wasm32"), feature = "image"))] use image::ImageError; #[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(feature = "gif", not(target_arch = "wasm32"), feature = "image"))] GifEncodingError(gif::EncodingError), #[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 {} plotters-bitmap-0.3.3/src/gif_support.rs000064400000000000000000000024401046102023000164530ustar 00000000000000use crate::error::BitMapBackendError; use gif::{Encoder as GifEncoder, Frame as GifFrame, Repeat}; use std::fs::File; use std::path::Path; pub(super) struct GifFile { encoder: GifEncoder, height: u32, width: u32, delay: u32, } impl GifFile { pub(super) fn new>( path: T, dim: (u32, u32), delay: u32, ) -> Result { 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::GifEncodingError)?; encoder .set_repeat(Repeat::Infinite) .map_err(BitMapBackendError::GifEncodingError)?; 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::GifEncodingError)?; Ok(()) } } plotters-bitmap-0.3.3/src/lib.rs000064400000000000000000000012041046102023000146550ustar 00000000000000/*! The Plotters bitmap backend. The plotters bitmap backend allows you to render images by Plotters into bitmap. You can either generate image file(PNG, JPG, GIF, etc) or rendering the bitmap within internal buffer (for example for framebuffer, etc). See the documentation for [BitMapBackend](struct.BitMapBackend.html) for more details. */ #[cfg(all(feature = "gif", not(target_arch = "wasm32"), feature = "image"))] mod gif_support; pub mod bitmap_pixel; mod error; mod bitmap; pub use bitmap::BitMapBackend; pub use error::BitMapBackendError; /*pub mod bitmap_pixel { pub use super::bitmap::{BGRXPixel, RGBPixel}; }*/