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 occcur (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 maniuplates 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 splitted 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 | |