1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum BitmapFormat {
11 Gray8,
13 Rgb24,
15 Rgba32,
17 Bgra32,
19}
20
21impl BitmapFormat {
22 pub fn bytes_per_pixel(self) -> u32 {
24 match self {
25 Self::Gray8 => 1,
26 Self::Rgb24 => 3,
27 Self::Rgba32 | Self::Bgra32 => 4,
28 }
29 }
30
31 pub fn components(self) -> u32 {
33 match self {
34 Self::Gray8 => 1,
35 Self::Rgb24 => 3,
36 Self::Rgba32 | Self::Bgra32 => 4,
37 }
38 }
39
40 pub fn has_alpha(self) -> bool {
42 matches!(self, Self::Rgba32 | Self::Bgra32)
43 }
44}
45
46#[derive(Debug, Clone)]
50pub struct Bitmap {
51 pub width: u32,
53 pub height: u32,
55 pub format: BitmapFormat,
57 pub stride: u32,
59 pub data: Vec<u8>,
61}
62
63impl Bitmap {
64 pub fn new(width: u32, height: u32, format: BitmapFormat) -> Self {
66 let stride = width * format.bytes_per_pixel();
67 let data = vec![0u8; (stride * height) as usize];
68 Self {
69 width,
70 height,
71 format,
72 stride,
73 data,
74 }
75 }
76
77 #[inline]
85 pub fn bitmap_create(width: u32, height: u32, alpha: bool) -> Self {
86 Self::new(
87 width,
88 height,
89 if alpha {
90 BitmapFormat::Bgra32
91 } else {
92 BitmapFormat::Rgb24
93 },
94 )
95 }
96
97 pub fn new_white(width: u32, height: u32, format: BitmapFormat) -> Self {
99 let stride = width * format.bytes_per_pixel();
100 let data = vec![0xFF; (stride * height) as usize];
101 Self {
102 width,
103 height,
104 format,
105 stride,
106 data,
107 }
108 }
109
110 pub fn width(&self) -> u32 {
114 self.width
115 }
116
117 #[inline]
121 pub fn get_width(&self) -> u32 {
122 self.width()
123 }
124
125 pub fn height(&self) -> u32 {
129 self.height
130 }
131
132 #[inline]
136 pub fn get_height(&self) -> u32 {
137 self.height()
138 }
139
140 pub fn format(&self) -> BitmapFormat {
144 self.format
145 }
146
147 #[inline]
151 pub fn get_format(&self) -> BitmapFormat {
152 self.format()
153 }
154
155 pub fn bpp(&self) -> u32 {
159 self.format.bytes_per_pixel() * 8
160 }
161
162 #[inline]
166 pub fn get_bpp(&self) -> u32 {
167 self.bpp()
168 }
169
170 pub fn pitch(&self) -> u32 {
174 self.stride
175 }
176
177 #[inline]
181 pub fn get_pitch(&self) -> u32 {
182 self.pitch()
183 }
184
185 #[inline]
189 pub fn bitmap_get_stride(&self) -> u32 {
190 self.pitch()
191 }
192
193 #[deprecated(note = "use `bitmap_get_stride()` — matches upstream `FPDFBitmap_GetStride`")]
195 #[inline]
196 pub fn get_stride(&self) -> u32 {
197 self.pitch()
198 }
199
200 #[inline]
206 #[deprecated(since = "0.0.0", note = "use pitch() or get_pitch() instead")]
207 pub fn stride(&self) -> u32 {
208 self.pitch()
209 }
210
211 pub fn is_alpha_format(&self) -> bool {
215 self.format.has_alpha()
216 }
217
218 pub fn is_mask_format(&self) -> bool {
223 self.format == BitmapFormat::Gray8
224 }
225
226 pub fn is_opaque_image(&self) -> bool {
231 !self.is_mask_format() && !self.is_alpha_format()
232 }
233
234 pub fn is_premultiplied(&self) -> bool {
240 self.format == BitmapFormat::Bgra32
241 }
242
243 pub fn bytes_per_pixel(&self) -> u32 {
245 self.format.bytes_per_pixel()
246 }
247
248 pub fn data_size(&self) -> usize {
250 (self.stride * self.height) as usize
251 }
252
253 pub fn scanline(&self, y: u32) -> &[u8] {
261 assert!(
262 y < self.height,
263 "row index {y} out of bounds (height {})",
264 self.height
265 );
266 let start = (y * self.stride) as usize;
267 let end = start + (self.width * self.bytes_per_pixel()) as usize;
268 &self.data[start..end]
269 }
270
271 #[inline]
279 pub fn get_scanline(&self, y: u32) -> &[u8] {
280 self.scanline(y)
281 }
282
283 #[inline]
289 #[deprecated(since = "0.0.0", note = "use scanline() or get_scanline() instead")]
290 pub fn row(&self, y: u32) -> &[u8] {
291 self.scanline(y)
292 }
293
294 pub fn convert_format(&self, target: BitmapFormat) -> Option<Self> {
299 match (self.format, target) {
300 (BitmapFormat::Rgba32, BitmapFormat::Gray8) => {
301 let gray_data: Vec<u8> = self
302 .data
303 .chunks_exact(4)
304 .map(|p| {
305 let r = p[0] as f32;
306 let g = p[1] as f32;
307 let b = p[2] as f32;
308 (0.299 * r + 0.587 * g + 0.114 * b).round() as u8
309 })
310 .collect();
311 let stride = self.width;
312 Some(Bitmap {
313 width: self.width,
314 height: self.height,
315 format: BitmapFormat::Gray8,
316 data: gray_data,
317 stride,
318 })
319 }
320 (a, b) if a == b => Some(self.clone()),
321 _ => None,
322 }
323 }
324
325 pub fn scanline_mut(&mut self, y: u32) -> &mut [u8] {
333 assert!(
334 y < self.height,
335 "row index {y} out of bounds (height {})",
336 self.height
337 );
338 let start = (y * self.stride) as usize;
339 let end = start + (self.width * self.bytes_per_pixel()) as usize;
340 &mut self.data[start..end]
341 }
342
343 #[inline]
351 pub fn get_writable_scanline(&mut self, y: u32) -> &mut [u8] {
352 self.scanline_mut(y)
353 }
354
355 #[inline]
361 #[deprecated(note = "use scanline_mut() or get_writable_scanline() instead")]
362 pub fn row_mut(&mut self, y: u32) -> &mut [u8] {
363 self.scanline_mut(y)
364 }
365
366 pub fn fill_rect(&mut self, x: i32, y: i32, width: i32, height: i32, color: &[u8]) -> bool {
373 let bpp = self.bytes_per_pixel() as usize;
374 if color.len() != bpp {
375 return false;
376 }
377
378 let bw = self.width as i32;
379 let bh = self.height as i32;
380
381 let x0 = x.max(0) as u32;
383 let y0 = y.max(0) as u32;
384 let x1 = (x + width).min(bw).max(0) as u32;
385 let y1 = (y + height).min(bh).max(0) as u32;
386
387 for row in y0..y1 {
388 for col in x0..x1 {
389 let offset = (row * self.stride + col * bpp as u32) as usize;
390 self.data[offset..offset + bpp].copy_from_slice(color);
391 }
392 }
393 true
394 }
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[test]
402 fn test_bitmap_format_bytes_per_pixel() {
403 assert_eq!(BitmapFormat::Gray8.bytes_per_pixel(), 1);
404 assert_eq!(BitmapFormat::Rgb24.bytes_per_pixel(), 3);
405 assert_eq!(BitmapFormat::Rgba32.bytes_per_pixel(), 4);
406 assert_eq!(BitmapFormat::Bgra32.bytes_per_pixel(), 4);
407 }
408
409 #[test]
410 fn test_bitmap_format_has_alpha() {
411 assert!(!BitmapFormat::Gray8.has_alpha());
412 assert!(!BitmapFormat::Rgb24.has_alpha());
413 assert!(BitmapFormat::Rgba32.has_alpha());
414 assert!(BitmapFormat::Bgra32.has_alpha());
415 }
416
417 #[test]
418 fn test_bitmap_new_zeroed() {
419 let bmp = Bitmap::new(100, 50, BitmapFormat::Rgba32);
420 assert_eq!(bmp.width, 100);
421 assert_eq!(bmp.height, 50);
422 assert_eq!(bmp.stride, 400);
423 assert_eq!(bmp.data.len(), 20000);
424 assert!(bmp.data.iter().all(|&b| b == 0));
425 }
426
427 #[test]
428 fn test_bitmap_new_white() {
429 let bmp = Bitmap::new_white(10, 10, BitmapFormat::Rgb24);
430 assert_eq!(bmp.data.len(), 300);
431 assert!(bmp.data.iter().all(|&b| b == 0xFF));
432 }
433
434 #[test]
435 fn test_bitmap_row_access() {
436 let mut bmp = Bitmap::new(4, 4, BitmapFormat::Gray8);
437 bmp.scanline_mut(0)[0] = 42;
438 assert_eq!(bmp.scanline(0)[0], 42);
439 assert_eq!(bmp.scanline(1)[0], 0); }
441
442 #[test]
443 fn test_bitmap_data_size() {
444 let bmp = Bitmap::new(100, 200, BitmapFormat::Rgba32);
445 assert_eq!(bmp.data_size(), 100 * 200 * 4);
446 }
447
448 #[test]
449 fn test_bitmap_zero_width() {
450 let bmp = Bitmap::new(0, 50, BitmapFormat::Rgba32);
451 assert_eq!(bmp.width, 0);
452 assert_eq!(bmp.height, 50);
453 assert!(bmp.data.is_empty());
454 }
455
456 #[test]
457 fn test_bitmap_zero_height() {
458 let bmp = Bitmap::new(100, 0, BitmapFormat::Rgb24);
459 assert_eq!(bmp.width, 100);
460 assert_eq!(bmp.height, 0);
461 assert!(bmp.data.is_empty());
462 }
463
464 #[test]
465 fn test_bitmap_zero_both() {
466 let bmp = Bitmap::new(0, 0, BitmapFormat::Gray8);
467 assert_eq!(bmp.width, 0);
468 assert_eq!(bmp.height, 0);
469 assert!(bmp.data.is_empty());
470 }
471
472 #[test]
473 fn test_bitmap_stride_method() {
474 let bmp = Bitmap::new(100, 50, BitmapFormat::Rgba32);
475 assert_eq!(bmp.pitch(), 400);
476 assert_eq!(bmp.pitch(), bmp.width * bmp.format.bytes_per_pixel());
477 }
478
479 #[test]
480 fn test_bitmap_stride_method_rgb24() {
481 let bmp = Bitmap::new(37, 10, BitmapFormat::Rgb24);
482 assert_eq!(bmp.pitch(), 37 * 3);
483 }
484
485 #[test]
486 fn test_bitmap_gray8_stride() {
487 let bmp = Bitmap::new(37, 10, BitmapFormat::Gray8);
488 assert_eq!(bmp.stride, 37);
490 }
491
492 #[test]
493 fn test_bitmap_convert_rgba32_to_gray8() {
494 let mut bmp = Bitmap::new(2, 1, BitmapFormat::Rgba32);
496 bmp.data[0] = 255;
498 bmp.data[1] = 0;
499 bmp.data[2] = 0;
500 bmp.data[3] = 255;
501 bmp.data[4] = 255;
503 bmp.data[5] = 255;
504 bmp.data[6] = 255;
505 bmp.data[7] = 255;
506
507 let gray = bmp.convert_format(BitmapFormat::Gray8).unwrap();
508 assert_eq!(gray.format, BitmapFormat::Gray8);
509 assert_eq!(gray.width, 2);
510 assert_eq!(gray.height, 1);
511 assert_eq!(gray.data.len(), 2);
512 assert_eq!(gray.data[0], 76);
514 assert_eq!(gray.data[1], 255);
516 }
517
518 #[test]
519 fn test_bitmap_convert_same_format_clones() {
520 let bmp = Bitmap::new(10, 10, BitmapFormat::Rgba32);
521 let copy = bmp.convert_format(BitmapFormat::Rgba32).unwrap();
522 assert_eq!(copy.width, bmp.width);
523 assert_eq!(copy.height, bmp.height);
524 assert_eq!(copy.format, bmp.format);
525 assert_eq!(copy.data, bmp.data);
526 }
527
528 #[test]
529 fn test_bitmap_convert_unsupported_returns_none() {
530 let bmp = Bitmap::new(10, 10, BitmapFormat::Gray8);
531 assert!(bmp.convert_format(BitmapFormat::Rgba32).is_none());
532 let bmp2 = Bitmap::new(10, 10, BitmapFormat::Rgb24);
533 assert!(bmp2.convert_format(BitmapFormat::Gray8).is_none());
534 }
535
536 #[test]
537 fn test_bitmap_data_size_matches_allocation() {
538 let formats = [
539 (BitmapFormat::Gray8, 64, 32),
540 (BitmapFormat::Rgb24, 50, 25),
541 (BitmapFormat::Rgba32, 100, 100),
542 (BitmapFormat::Bgra32, 80, 60),
543 ];
544 for (fmt, w, h) in formats {
545 let bmp = Bitmap::new(w, h, fmt);
546 assert_eq!(
547 bmp.data_size(),
548 bmp.data.len(),
549 "data_size() mismatch for {fmt:?} {w}x{h}"
550 );
551 }
552 }
553
554 #[test]
559 fn test_fill_rect_basic() {
560 let mut bmp = Bitmap::new(2, 2, BitmapFormat::Gray8);
562 let ok = bmp.fill_rect(0, 0, 2, 2, &[0xAB]);
563 assert!(ok);
564 assert!(bmp.data.iter().all(|&b| b == 0xAB));
565 }
566
567 #[test]
568 fn test_fill_rect_partial() {
569 let mut bmp = Bitmap::new(4, 4, BitmapFormat::Gray8);
571 let ok = bmp.fill_rect(0, 0, 2, 2, &[0xFF]);
572 assert!(ok);
573 for row in 0u32..2 {
575 for col in 0u32..2 {
576 let offset = (row * bmp.stride + col) as usize;
577 assert_eq!(bmp.data[offset], 0xFF, "pixel ({col},{row}) should be 0xFF");
578 }
579 }
580 for row in 0u32..4 {
582 for col in 0u32..4 {
583 if row < 2 && col < 2 {
584 continue;
585 }
586 let offset = (row * bmp.stride + col) as usize;
587 assert_eq!(bmp.data[offset], 0, "pixel ({col},{row}) should be 0");
588 }
589 }
590 }
591
592 #[test]
593 fn test_fill_rect_clamped() {
594 let mut bmp = Bitmap::new(4, 4, BitmapFormat::Gray8);
596 let ok = bmp.fill_rect(-10, -10, 100, 100, &[0x55]);
597 assert!(ok);
598 assert!(bmp.data.iter().all(|&b| b == 0x55));
600 }
601
602 #[test]
603 fn test_fill_rect_wrong_color_len() {
604 let mut bmp = Bitmap::new(4, 4, BitmapFormat::Gray8);
606 let ok = bmp.fill_rect(0, 0, 4, 4, &[0xFF, 0x00, 0x00, 0xFF]);
607 assert!(!ok);
608 assert!(bmp.data.iter().all(|&b| b == 0));
610 }
611
612 #[test]
621 fn test_bitmap_pitch_bad_dimensions() {
622 let bad_cases: &[(u32, u32)] = &[(0, 0), (0, 200), (100, 0)];
624 for &(w, h) in bad_cases {
625 let bmp = Bitmap::new(w, h, BitmapFormat::Bgra32);
626 assert!(bmp.data.is_empty(), "expected empty data for {w}x{h}");
627 }
628 }
629
630 #[test]
635 fn test_bitmap_pitch_boundary() {
636 let w: u32 = 536_870_908;
639 let h: u32 = 4;
640 let stride = w.checked_mul(BitmapFormat::Gray8.bytes_per_pixel());
641 assert!(stride.is_some());
642 let size = stride.unwrap().checked_mul(h);
643 assert!(size.is_some());
644 assert_eq!(size.unwrap(), 2_147_483_632);
645
646 let w_over: u32 = 536_870_909;
648 let stride2 = w_over.checked_mul(BitmapFormat::Gray8.bytes_per_pixel());
649 assert!(stride2.is_some()); let size2 = stride2.unwrap().checked_mul(h);
651 assert!(size2.is_some()); let w3: u32 = 68_174_085;
654 let h3: u32 = 63;
655 let stride3 = w3
656 .checked_mul(BitmapFormat::Gray8.bytes_per_pixel())
657 .unwrap();
658 let size3 = stride3.checked_mul(h3);
659 assert!(size3.is_none(), "expected overflow for 68174085 * 63");
661
662 let w4: u32 = 68_174_084;
664 let stride4 = w4
665 .checked_mul(BitmapFormat::Gray8.bytes_per_pixel())
666 .unwrap();
667 let size4 = stride4.checked_mul(h3);
668 assert!(size4.is_some());
669 assert_eq!(size4.unwrap(), 4_294_967_292);
670 }
671
672 #[test]
676 fn test_bitmap_scanline_24bpp() {
677 let bmp = Bitmap::new(3, 3, BitmapFormat::Rgb24);
678 assert_eq!(bmp.width(), 3);
679 assert_eq!(bmp.pitch(), 9);
681
682 assert_eq!(bmp.data.len(), 9 * 3);
684
685 assert_eq!(bmp.scanline(0).len(), 9);
687 assert_eq!(bmp.scanline(1).len(), 9);
688 assert_eq!(bmp.scanline(2).len(), 9);
689
690 let scanline = bmp.scanline(0);
693 let pixel_chunks: Vec<&[u8]> = scanline.chunks_exact(3).collect();
694 assert_eq!(pixel_chunks.len(), 3);
695
696 let mut bmp_mut = bmp.clone();
698 let sl = bmp_mut.scanline_mut(0);
699 assert_eq!(sl.len(), 9);
700 let pixel_chunks_mut: Vec<&mut [u8]> = sl.chunks_exact_mut(3).collect();
701 assert_eq!(pixel_chunks_mut.len(), 3);
702 }
703}