| 1 | use plotters_backend::{ |
| 2 | BackendColor, BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind, |
| 3 | }; |
| 4 | use std::marker::PhantomData; |
| 5 | |
| 6 | use crate::bitmap_pixel::{PixelFormat, RGBPixel}; |
| 7 | use crate::error::BitMapBackendError; |
| 8 | |
| 9 | #[cfg (all(not(target_arch = "wasm32" ), feature = "image" ))] |
| 10 | mod image_encoding_support { |
| 11 | pub(super) use image::{ImageBuffer, Rgb}; |
| 12 | pub(super) use std::path::Path; |
| 13 | pub(super) type BorrowedImage<'a> = ImageBuffer<Rgb<u8>, &'a mut [u8]>; |
| 14 | } |
| 15 | |
| 16 | #[cfg (all(not(target_arch = "wasm32" ), feature = "image" ))] |
| 17 | use image_encoding_support::*; |
| 18 | |
| 19 | mod target; |
| 20 | |
| 21 | use target::{Buffer, Target}; |
| 22 | |
| 23 | /// The backend that drawing a bitmap |
| 24 | /// |
| 25 | /// # Warning |
| 26 | /// |
| 27 | /// You should call [`.present()?`](plotters_backend::DrawingBackend::present) on a |
| 28 | /// `BitMapBackend`, not just `drop` it or allow it to go out of scope. |
| 29 | /// |
| 30 | /// If the `BitMapBackend` is just dropped, it will make a best effort attempt to write the |
| 31 | /// generated charts to the output file, but any errors that occur (such as inability to |
| 32 | /// create the output file) will be silently ignored. |
| 33 | pub struct BitMapBackend<'a, P: PixelFormat = RGBPixel> { |
| 34 | /// The path to the image |
| 35 | #[allow (dead_code)] |
| 36 | target: Target<'a>, |
| 37 | /// The size of the image |
| 38 | size: (u32, u32), |
| 39 | /// The data buffer of the image |
| 40 | buffer: Buffer<'a>, |
| 41 | /// Flag indicates if the bitmap has been saved |
| 42 | saved: bool, |
| 43 | _pantomdata: PhantomData<P>, |
| 44 | } |
| 45 | |
| 46 | impl<'a, P: PixelFormat> BitMapBackend<'a, P> { |
| 47 | /// The number of bytes per pixel |
| 48 | const PIXEL_SIZE: usize = P::PIXEL_SIZE; |
| 49 | } |
| 50 | |
| 51 | impl<'a> BitMapBackend<'a, RGBPixel> { |
| 52 | /// Create a new bitmap backend |
| 53 | #[cfg (all(not(target_arch = "wasm32" ), feature = "image" ))] |
| 54 | pub fn new<T: AsRef<Path> + ?Sized>(path: &'a T, (w, h): (u32, u32)) -> Self { |
| 55 | Self { |
| 56 | target: Target::File(path.as_ref()), |
| 57 | size: (w, h), |
| 58 | buffer: Buffer::Owned(vec![0; Self::PIXEL_SIZE * (w * h) as usize]), |
| 59 | saved: false, |
| 60 | _pantomdata: PhantomData, |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | /// Create a new bitmap backend that generate GIF animation |
| 65 | /// |
| 66 | /// When this is used, the bitmap backend acts similar to a real-time rendering backend. |
| 67 | /// When the program finished drawing one frame, use `present` function to flush the frame |
| 68 | /// into the GIF file. |
| 69 | /// |
| 70 | /// - `path`: The path to the GIF file to create |
| 71 | /// - `dimension`: The size of the GIF image |
| 72 | /// - `speed`: The amount of time for each frame to display |
| 73 | #[cfg (all(feature = "gif" , not(target_arch = "wasm32" ), feature = "image" ))] |
| 74 | pub fn gif<T: AsRef<Path>>( |
| 75 | path: T, |
| 76 | (w, h): (u32, u32), |
| 77 | frame_delay: u32, |
| 78 | ) -> Result<Self, BitMapBackendError> { |
| 79 | Ok(Self { |
| 80 | target: Target::Gif(Box::new(crate::gif_support::GifFile::new( |
| 81 | path, |
| 82 | (w, h), |
| 83 | frame_delay, |
| 84 | )?)), |
| 85 | size: (w, h), |
| 86 | buffer: Buffer::Owned(vec![0; Self::PIXEL_SIZE * (w * h) as usize]), |
| 87 | saved: false, |
| 88 | _pantomdata: PhantomData, |
| 89 | }) |
| 90 | } |
| 91 | |
| 92 | /// Create a new bitmap backend which only lives in-memory |
| 93 | /// |
| 94 | /// When this is used, the bitmap backend will write to a user provided [u8] array (or `Vec<u8>`) |
| 95 | /// in RGB pixel format. |
| 96 | /// |
| 97 | /// Note: This function provides backward compatibility for those code that assumes Plotters |
| 98 | /// uses RGB pixel format and manipulates the in-memory framebuffer. |
| 99 | /// For more pixel format option, use `with_buffer_and_format` instead. |
| 100 | /// |
| 101 | /// - `buf`: The buffer to operate |
| 102 | /// - `dimension`: The size of the image in pixels |
| 103 | /// - **returns**: The newly created bitmap backend |
| 104 | pub fn with_buffer(buf: &'a mut [u8], (w, h): (u32, u32)) -> Self { |
| 105 | Self::with_buffer_and_format(buf, (w, h)).expect("Wrong buffer size" ) |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | impl<'a, P: PixelFormat> BitMapBackend<'a, P> { |
| 110 | /// Create a new bitmap backend with a in-memory buffer with specific pixel format. |
| 111 | /// |
| 112 | /// Note: This can be used as a way to manipulate framebuffer, `mmap` can be used on the top of this |
| 113 | /// as well. |
| 114 | /// |
| 115 | /// - `buf`: The buffer to operate |
| 116 | /// - `dimension`: The size of the image in pixels |
| 117 | /// - **returns**: The newly created bitmap backend |
| 118 | pub fn with_buffer_and_format( |
| 119 | buf: &'a mut [u8], |
| 120 | (w, h): (u32, u32), |
| 121 | ) -> Result<Self, BitMapBackendError> { |
| 122 | if (w * h) as usize * Self::PIXEL_SIZE > buf.len() { |
| 123 | return Err(BitMapBackendError::InvalidBuffer); |
| 124 | } |
| 125 | |
| 126 | Ok(Self { |
| 127 | target: Target::Buffer(PhantomData), |
| 128 | size: (w, h), |
| 129 | buffer: Buffer::Borrowed(buf), |
| 130 | saved: false, |
| 131 | _pantomdata: PhantomData, |
| 132 | }) |
| 133 | } |
| 134 | |
| 135 | #[inline (always)] |
| 136 | pub(crate) fn get_raw_pixel_buffer(&mut self) -> &mut [u8] { |
| 137 | self.buffer.borrow_buffer() |
| 138 | } |
| 139 | |
| 140 | /// Split a bitmap backend vertically into several sub drawing area which allows |
| 141 | /// multi-threading rendering. |
| 142 | /// |
| 143 | /// - `area_size`: The size of the area |
| 144 | /// - **returns**: The split backends that can be rendered in parallel |
| 145 | pub fn split(&mut self, area_size: &[u32]) -> Vec<BitMapBackend<P>> { |
| 146 | let (w, h) = self.get_size(); |
| 147 | let buf = self.get_raw_pixel_buffer(); |
| 148 | |
| 149 | let base_addr = &mut buf[0] as *mut u8; |
| 150 | let mut split_points = vec![0]; |
| 151 | for size in area_size { |
| 152 | let next = split_points.last().unwrap() + size; |
| 153 | if next >= h { |
| 154 | break; |
| 155 | } |
| 156 | split_points.push(next); |
| 157 | } |
| 158 | split_points.push(h); |
| 159 | |
| 160 | split_points |
| 161 | .iter() |
| 162 | .zip(split_points.iter().skip(1)) |
| 163 | .map(|(begin, end)| { |
| 164 | let actual_buf = unsafe { |
| 165 | std::slice::from_raw_parts_mut( |
| 166 | base_addr.offset((begin * w) as isize * Self::PIXEL_SIZE as isize), |
| 167 | ((end - begin) * w) as usize * Self::PIXEL_SIZE, |
| 168 | ) |
| 169 | }; |
| 170 | Self::with_buffer_and_format(actual_buf, (w, end - begin)).unwrap() |
| 171 | }) |
| 172 | .collect() |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | impl<'a, P: PixelFormat> DrawingBackend for BitMapBackend<'a, P> { |
| 177 | type ErrorType = BitMapBackendError; |
| 178 | |
| 179 | fn get_size(&self) -> (u32, u32) { |
| 180 | self.size |
| 181 | } |
| 182 | |
| 183 | fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<BitMapBackendError>> { |
| 184 | self.saved = false; |
| 185 | Ok(()) |
| 186 | } |
| 187 | |
| 188 | #[cfg (any(target_arch = "wasm32" , not(feature = "image" )))] |
| 189 | fn present(&mut self) -> Result<(), DrawingErrorKind<BitMapBackendError>> { |
| 190 | Ok(()) |
| 191 | } |
| 192 | |
| 193 | #[cfg (all(not(target_arch = "wasm32" ), feature = "image" ))] |
| 194 | fn present(&mut self) -> Result<(), DrawingErrorKind<BitMapBackendError>> { |
| 195 | if !P::can_be_saved() { |
| 196 | return Ok(()); |
| 197 | } |
| 198 | let (w, h) = self.get_size(); |
| 199 | match &mut self.target { |
| 200 | Target::File(path) => { |
| 201 | if let Some(img) = BorrowedImage::from_raw(w, h, self.buffer.borrow_buffer()) { |
| 202 | img.save(&path).map_err(|x| { |
| 203 | DrawingErrorKind::DrawingError(BitMapBackendError::ImageError(x)) |
| 204 | })?; |
| 205 | self.saved = true; |
| 206 | Ok(()) |
| 207 | } else { |
| 208 | Err(DrawingErrorKind::DrawingError( |
| 209 | BitMapBackendError::InvalidBuffer, |
| 210 | )) |
| 211 | } |
| 212 | } |
| 213 | Target::Buffer(_) => Ok(()), |
| 214 | |
| 215 | #[cfg (all(feature = "gif" , not(target_arch = "wasm32" ), feature = "image" ))] |
| 216 | Target::Gif(target) => { |
| 217 | target |
| 218 | .flush_frame(self.buffer.borrow_buffer()) |
| 219 | .map_err(DrawingErrorKind::DrawingError)?; |
| 220 | self.saved = true; |
| 221 | Ok(()) |
| 222 | } |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | fn draw_pixel( |
| 227 | &mut self, |
| 228 | point: BackendCoord, |
| 229 | color: BackendColor, |
| 230 | ) -> Result<(), DrawingErrorKind<BitMapBackendError>> { |
| 231 | if point.0 < 0 |
| 232 | || point.1 < 0 |
| 233 | || point.0 as u32 >= self.size.0 |
| 234 | || point.1 as u32 >= self.size.1 |
| 235 | { |
| 236 | return Ok(()); |
| 237 | } |
| 238 | |
| 239 | let alpha = color.alpha; |
| 240 | let rgb = color.rgb; |
| 241 | |
| 242 | P::draw_pixel(self, point, rgb, alpha); |
| 243 | |
| 244 | Ok(()) |
| 245 | } |
| 246 | |
| 247 | fn draw_line<S: BackendStyle>( |
| 248 | &mut self, |
| 249 | from: (i32, i32), |
| 250 | to: (i32, i32), |
| 251 | style: &S, |
| 252 | ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { |
| 253 | let alpha = style.color().alpha; |
| 254 | let (r, g, b) = style.color().rgb; |
| 255 | |
| 256 | if (from.0 == to.0 || from.1 == to.1) && style.stroke_width() == 1 { |
| 257 | if alpha >= 1.0 { |
| 258 | if from.1 == to.1 { |
| 259 | P::fill_rect_fast(self, from, (to.0 + 1, to.1 + 1), r, g, b); |
| 260 | } else { |
| 261 | P::fill_vertical_line_fast(self, from.0, (from.1, to.1), r, g, b); |
| 262 | } |
| 263 | } else { |
| 264 | P::blend_rect_fast(self, from, (to.0 + 1, to.1 + 1), r, g, b, alpha); |
| 265 | } |
| 266 | return Ok(()); |
| 267 | } |
| 268 | |
| 269 | plotters_backend::rasterizer::draw_line(self, from, to, style) |
| 270 | } |
| 271 | |
| 272 | fn draw_rect<S: BackendStyle>( |
| 273 | &mut self, |
| 274 | upper_left: (i32, i32), |
| 275 | bottom_right: (i32, i32), |
| 276 | style: &S, |
| 277 | fill: bool, |
| 278 | ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { |
| 279 | let alpha = style.color().alpha; |
| 280 | let (r, g, b) = style.color().rgb; |
| 281 | if fill { |
| 282 | if alpha >= 1.0 { |
| 283 | P::fill_rect_fast(self, upper_left, bottom_right, r, g, b); |
| 284 | } else { |
| 285 | P::blend_rect_fast(self, upper_left, bottom_right, r, g, b, alpha); |
| 286 | } |
| 287 | return Ok(()); |
| 288 | } |
| 289 | plotters_backend::rasterizer::draw_rect(self, upper_left, bottom_right, style, fill) |
| 290 | } |
| 291 | |
| 292 | fn blit_bitmap( |
| 293 | &mut self, |
| 294 | pos: BackendCoord, |
| 295 | (sw, sh): (u32, u32), |
| 296 | src: &[u8], |
| 297 | ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { |
| 298 | let (dw, dh) = self.get_size(); |
| 299 | |
| 300 | let (x0, y0) = pos; |
| 301 | let (x1, y1) = (x0 + sw as i32, y0 + sh as i32); |
| 302 | |
| 303 | let (x0, y0, x1, y1) = (x0.max(0), y0.max(0), x1.min(dw as i32), y1.min(dh as i32)); |
| 304 | |
| 305 | if x0 == x1 || y0 == y1 { |
| 306 | return Ok(()); |
| 307 | } |
| 308 | |
| 309 | let mut chunk_size = (x1 - x0) as usize; |
| 310 | let mut num_chunks = (y1 - y0) as usize; |
| 311 | let dst_gap = dw as usize - chunk_size; |
| 312 | let src_gap = sw as usize - chunk_size; |
| 313 | |
| 314 | let dst_start = Self::PIXEL_SIZE * (y0 as usize * dw as usize + x0 as usize); |
| 315 | |
| 316 | let mut dst = &mut self.get_raw_pixel_buffer()[dst_start..]; |
| 317 | |
| 318 | let src_start = |
| 319 | Self::PIXEL_SIZE * ((sh as i32 + y0 - y1) * sw as i32 + (sw as i32 + x0 - x1)) as usize; |
| 320 | let mut src = &src[src_start..]; |
| 321 | |
| 322 | if src_gap == 0 && dst_gap == 0 { |
| 323 | chunk_size *= num_chunks; |
| 324 | num_chunks = 1; |
| 325 | } |
| 326 | for i in 0..num_chunks { |
| 327 | dst[0..(chunk_size * Self::PIXEL_SIZE)] |
| 328 | .copy_from_slice(&src[0..(chunk_size * Self::PIXEL_SIZE)]); |
| 329 | if i != num_chunks - 1 { |
| 330 | dst = &mut dst[((chunk_size + dst_gap) * Self::PIXEL_SIZE)..]; |
| 331 | src = &src[((chunk_size + src_gap) * Self::PIXEL_SIZE)..]; |
| 332 | } |
| 333 | } |
| 334 | |
| 335 | Ok(()) |
| 336 | } |
| 337 | } |
| 338 | |
| 339 | impl<P: PixelFormat> Drop for BitMapBackend<'_, P> { |
| 340 | fn drop(&mut self) { |
| 341 | if !self.saved { |
| 342 | // drop should not panic, so we ignore a failed present |
| 343 | let _ = self.present(); |
| 344 | } |
| 345 | } |
| 346 | } |
| 347 | |
| 348 | #[cfg (test)] |
| 349 | mod test; |
| 350 | |