1use crate::bindgen::{
4 FPDFBitmap_BGR, FPDFBitmap_BGRA, FPDFBitmap_BGRx, FPDFBitmap_Gray, FPDFBitmap_Unknown,
5 FPDF_BITMAP,
6};
7use crate::error::{PdfiumError, PdfiumInternalError};
8use crate::pdf::document::page::render_config::PdfPageRenderSettings;
9use crate::pdfium::PdfiumLibraryBindingsAccessor;
10use crate::utils::pixels::{aligned_bgr_to_rgba, aligned_rgb_to_rgba, bgra_to_rgba};
11use std::marker::PhantomData;
12use std::os::raw::c_int;
13
14#[cfg(feature = "image_025")]
15use image_025::{DynamicImage, GrayImage, RgbaImage};
16
17#[cfg(feature = "image_024")]
18use image_024::{DynamicImage, GrayImage, RgbaImage};
19
20#[cfg(feature = "image_023")]
21use image_023::{DynamicImage, GrayImage, RgbaImage};
22
23#[cfg(not(target_arch = "wasm32"))]
24use std::os::raw::c_void;
25
26#[cfg(target_arch = "wasm32")]
27use {
28 js_sys::Uint8Array,
29 wasm_bindgen::{Clamped, JsValue},
30 web_sys::ImageData,
31};
32
33#[cfg(doc)]
34use crate::bindings::PdfiumLibraryBindings;
35
36#[cfg(doc)]
41struct Uint8Array;
42
43#[cfg(doc)]
44struct ImageData;
45
46#[cfg(doc)]
47struct JsValue;
48
49pub type Pixels = i32;
56
57#[derive(Copy, Clone, Debug, PartialEq)]
59pub enum PdfBitmapFormat {
60 Gray = FPDFBitmap_Gray as isize,
61 BGR = FPDFBitmap_BGR as isize,
62 BGRx = FPDFBitmap_BGRx as isize,
63 BGRA = FPDFBitmap_BGRA as isize,
64}
65
66impl PdfBitmapFormat {
67 #[inline]
68 #[allow(non_upper_case_globals)]
69 pub(crate) fn from_pdfium(format: u32) -> Result<Self, PdfiumError> {
70 match format {
71 FPDFBitmap_Unknown => Err(PdfiumError::UnknownBitmapFormat),
72 FPDFBitmap_Gray => Ok(PdfBitmapFormat::Gray),
73 FPDFBitmap_BGR => Ok(PdfBitmapFormat::BGR),
74 FPDFBitmap_BGRx => Ok(PdfBitmapFormat::BGRx),
75 FPDFBitmap_BGRA => Ok(PdfBitmapFormat::BGRA),
76 _ => Err(PdfiumError::UnknownBitmapFormat),
77 }
78 }
79
80 #[inline]
81 pub(crate) fn as_pdfium(&self) -> u32 {
82 match self {
83 PdfBitmapFormat::Gray => FPDFBitmap_Gray,
84 PdfBitmapFormat::BGR => FPDFBitmap_BGR,
85 PdfBitmapFormat::BGRx => FPDFBitmap_BGRx,
86 PdfBitmapFormat::BGRA => FPDFBitmap_BGRA,
87 }
88 }
89
90 #[inline]
92 pub(crate) fn bytes_per_pixel(&self) -> i32 {
93 match self {
94 PdfBitmapFormat::Gray => 1,
96 PdfBitmapFormat::BGR => 3,
97 PdfBitmapFormat::BGRx => 4,
98 PdfBitmapFormat::BGRA => 4,
99 }
100 }
101}
102
103#[allow(clippy::derivable_impls)]
106impl Default for PdfBitmapFormat {
107 #[inline]
108 fn default() -> Self {
109 PdfBitmapFormat::BGRA
110 }
111}
112
113pub struct PdfBitmap<'a> {
115 handle: FPDF_BITMAP,
116 was_byte_order_reversed_during_rendering: bool,
117 lifetime: PhantomData<&'a FPDF_BITMAP>,
118}
119
120impl<'a> PdfBitmap<'a> {
121 pub(crate) fn from_pdfium(handle: FPDF_BITMAP) -> Self {
123 PdfBitmap {
124 handle,
125 was_byte_order_reversed_during_rendering: false,
126 lifetime: PhantomData,
127 }
128 }
129
130 pub fn empty(
133 width: Pixels,
134 height: Pixels,
135 format: PdfBitmapFormat,
136 ) -> Result<PdfBitmap<'a>, PdfiumError> {
137 let handle = unsafe {
138 Self::from_pdfium(0 as FPDF_BITMAP)
139 .bindings()
140 .FPDFBitmap_CreateEx(
141 width as c_int,
142 height as c_int,
143 format.as_pdfium() as c_int,
144 std::ptr::null_mut(),
145 0, )
147 };
148
149 if handle.is_null() {
150 Err(PdfiumError::PdfiumLibraryInternalError(
151 PdfiumInternalError::Unknown,
152 ))
153 } else {
154 Ok(Self::from_pdfium(handle))
155 }
156 }
157
158 #[cfg(not(target_arch = "wasm32"))]
164 pub fn from_bytes(
165 width: Pixels,
166 height: Pixels,
167 format: PdfBitmapFormat,
168 buffer: &'a mut [u8],
169 ) -> Result<PdfBitmap<'a>, PdfiumError> {
170 let stride =
173 Self::preferred_stride_bytes(width, format).ok_or(PdfiumError::ImageSizeOutOfBounds)?;
174
175 let minimum_buffer_size = stride
179 .checked_mul(height)
180 .ok_or(PdfiumError::ImageSizeOutOfBounds)?;
181
182 if minimum_buffer_size < 0 {
183 return Err(PdfiumError::ImageSizeOutOfBounds);
184 }
185
186 if minimum_buffer_size as usize > buffer.len() {
187 return Err(PdfiumError::ImageBufferTooSmall);
188 }
189
190 let handle = unsafe {
191 Self::from_pdfium(0 as FPDF_BITMAP)
192 .bindings()
193 .FPDFBitmap_CreateEx(
194 width as c_int,
195 height as c_int,
196 format.as_pdfium() as c_int,
197 buffer.as_mut_ptr() as *mut c_void,
198 stride,
199 )
200 };
201
202 if handle.is_null() {
203 Err(PdfiumError::PdfiumLibraryInternalError(
204 PdfiumInternalError::Unknown,
205 ))
206 } else {
207 Ok(Self::from_pdfium(handle))
208 }
209 }
210
211 #[cfg(not(target_arch = "wasm32"))]
222 pub unsafe fn from_bytes_unchecked(
223 width: Pixels,
224 height: Pixels,
225 format: PdfBitmapFormat,
226 buffer: &'a mut [u8],
227 ) -> Result<PdfBitmap<'a>, PdfiumError> {
228 let handle = Self::from_pdfium(0 as FPDF_BITMAP)
229 .bindings()
230 .FPDFBitmap_CreateEx(
231 width as c_int,
232 height as c_int,
233 format.as_pdfium() as c_int,
234 buffer.as_mut_ptr() as *mut c_void,
235 0, );
237
238 if handle.is_null() {
239 Err(PdfiumError::PdfiumLibraryInternalError(
240 PdfiumInternalError::Unknown,
241 ))
242 } else {
243 Ok(Self::from_pdfium(handle))
244 }
245 }
246
247 #[inline]
249 pub(crate) fn handle(&self) -> FPDF_BITMAP {
250 self.handle
251 }
252
253 #[inline]
258 pub(crate) fn set_byte_order_from_render_settings(&mut self, settings: &PdfPageRenderSettings) {
259 self.was_byte_order_reversed_during_rendering = settings.is_reversed_byte_order_flag_set
260 }
261
262 #[inline]
264 pub fn width(&self) -> Pixels {
265 (unsafe { self.bindings().FPDFBitmap_GetWidth(self.handle()) }) as Pixels
266 }
267
268 #[inline]
270 pub fn height(&self) -> Pixels {
271 (unsafe { self.bindings().FPDFBitmap_GetHeight(self.handle()) }) as Pixels
272 }
273
274 #[inline]
276 pub fn format(&self) -> Result<PdfBitmapFormat, PdfiumError> {
277 PdfBitmapFormat::from_pdfium(
278 unsafe { self.bindings().FPDFBitmap_GetFormat(self.handle()) } as u32,
279 )
280 }
281
282 pub fn as_raw_bytes(&self) -> Vec<u8> {
289 unsafe { self.bindings().FPDFBitmap_GetBuffer_as_vec(self.handle) }
290 }
291
292 pub fn as_rgba_bytes(&self) -> Vec<u8> {
295 let bytes = self.as_raw_bytes();
296
297 let format = self.format().unwrap_or_default();
298
299 let width = self.width() as usize;
300
301 let stride = bytes.len() / self.height() as usize;
302
303 if self.was_byte_order_reversed_during_rendering {
304 match format {
308 PdfBitmapFormat::BGRA | PdfBitmapFormat::BGRx => {
309 bytes
312 }
313 PdfBitmapFormat::BGR => aligned_rgb_to_rgba(bytes.as_slice(), width, stride),
314 PdfBitmapFormat::Gray => bytes,
315 }
316 } else {
317 match format {
318 PdfBitmapFormat::BGRA | PdfBitmapFormat::BGRx => bgra_to_rgba(bytes.as_slice()),
319 PdfBitmapFormat::BGR => aligned_bgr_to_rgba(bytes.as_slice(), width, stride),
320 PdfBitmapFormat::Gray => bytes,
321 }
322 }
323 }
324
325 #[cfg(feature = "image_api")]
329 pub fn as_image(&self) -> Result<DynamicImage, PdfiumError> {
330 let bytes = self.as_rgba_bytes();
331
332 let width = self.width() as u32;
333
334 let height = self.height() as u32;
335
336 let image = match self.format().unwrap_or_default() {
337 PdfBitmapFormat::BGRA | PdfBitmapFormat::BGRx | PdfBitmapFormat::BGR => {
338 RgbaImage::from_raw(width, height, bytes).map(DynamicImage::ImageRgba8)
339 }
340 PdfBitmapFormat::Gray => {
341 GrayImage::from_raw(width, height, bytes).map(DynamicImage::ImageLuma8)
342 }
343 };
344
345 match image {
346 Some(image) => Ok(image),
347 None => Err(PdfiumError::ImageError),
348 }
349 }
350
351 #[cfg(any(doc, target_arch = "wasm32"))]
365 #[inline]
366 pub fn as_array(&self) -> Uint8Array {
367 unsafe { self.bindings().FPDFBitmap_GetBuffer_as_array(self.handle()) }
368 }
369
370 #[cfg(any(doc, target_arch = "wasm32"))]
382 #[inline]
383 pub fn as_image_data(&self) -> Result<ImageData, JsValue> {
384 ImageData::new_with_u8_clamped_array_and_sh(
385 Clamped(&self.as_rgba_bytes()),
386 self.width() as u32,
387 self.height() as u32,
388 )
389 }
390
391 #[inline]
398 pub fn bytes_required_for_size(width: Pixels, height: Pixels) -> usize {
399 4 * width as usize * height as usize
400 }
401
402 #[inline]
409 pub fn bytes_required_for_size_and_format(
410 width: Pixels,
411 height: Pixels,
412 format: PdfBitmapFormat,
413 ) -> usize {
414 Self::preferred_stride_bytes(width, format)
415 .map(|s| s as usize * height as usize)
416 .unwrap_or_else(|| Self::bytes_required_for_size(width, height))
417 }
418
419 #[inline]
421 pub(crate) fn preferred_stride_bytes(width: Pixels, format: PdfBitmapFormat) -> Option<i32> {
422 let min_stride_bytes = width.checked_mul(format.bytes_per_pixel())?;
425 let rounded = (min_stride_bytes.checked_add(3)? / 4) * 4; Some(rounded)
428 }
429}
430
431impl<'a> Drop for PdfBitmap<'a> {
432 #[inline]
434 fn drop(&mut self) {
435 unsafe {
436 self.bindings().FPDFBitmap_Destroy(self.handle());
437 }
438 }
439}
440
441impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfBitmap<'a> {}
442
443#[cfg(feature = "thread_safe")]
444unsafe impl<'a> Send for PdfBitmap<'a> {}
445
446#[cfg(feature = "thread_safe")]
447unsafe impl<'a> Sync for PdfBitmap<'a> {}
448
449#[cfg(test)]
450mod tests {
451 use crate::prelude::*;
452 use crate::utils::mem::create_sized_buffer;
453 use crate::utils::test::test_bind_to_pdfium;
454
455 #[test]
456 fn test_from_bytes() -> Result<(), PdfiumError> {
457 let pdfium = test_bind_to_pdfium();
458
459 let test_width = 157;
460 let test_height = 300;
461
462 let mut buffer = create_sized_buffer(PdfBitmap::bytes_required_for_size_and_format(
463 test_width,
464 test_height,
465 PdfBitmapFormat::BGR,
466 ) as usize);
467
468 let buffer_ptr = buffer.as_ptr();
469 let buffer_len = buffer.len();
470
471 let bitmap = PdfBitmap::from_bytes(
472 test_width,
473 test_height,
474 PdfBitmapFormat::BGR,
475 buffer.as_mut_slice(),
476 )?;
477
478 assert_eq!(bitmap.width(), test_width);
479 assert_eq!(bitmap.height(), test_height);
480 assert_eq!(
481 unsafe { pdfium.bindings().FPDFBitmap_GetBuffer(bitmap.handle) } as usize,
482 buffer_ptr as usize
483 );
484
485 let raw_bytes = bitmap.as_raw_bytes();
487 assert_eq!(raw_bytes.len(), buffer_len);
488
489 Ok(())
490 }
491
492 #[test]
493 fn test_from_bytes_errors() {
494 test_bind_to_pdfium();
495
496 let mut buffer = create_sized_buffer(10);
497
498 let result =
500 PdfBitmap::from_bytes(1 << 30, 10, PdfBitmapFormat::BGR, buffer.as_mut_slice());
501 assert!(result.is_err());
502 drop(result);
503
504 let result =
506 PdfBitmap::from_bytes(10, 1 << 30, PdfBitmapFormat::BGR, buffer.as_mut_slice());
507 assert!(result.is_err());
508 drop(result);
509
510 let result = PdfBitmap::from_bytes(1000, 2000, PdfBitmapFormat::BGR, buffer.as_mut_slice());
512 assert!(result.is_err());
513 drop(result);
514 }
515
516 #[test]
517 fn test_from_bytes_unchecked() -> Result<(), PdfiumError> {
518 let pdfium = test_bind_to_pdfium();
519
520 let test_width = 2000;
521 let test_height = 4000;
522
523 let mut buffer =
524 create_sized_buffer(PdfBitmap::bytes_required_for_size(test_width, test_height));
525
526 let buffer_ptr = buffer.as_ptr();
527
528 let bitmap = unsafe {
529 PdfBitmap::from_bytes_unchecked(
530 test_width,
531 test_height,
532 PdfBitmapFormat::BGRx,
533 buffer.as_mut_slice(),
534 )?
535 };
536
537 assert_eq!(bitmap.width(), test_width);
538 assert_eq!(bitmap.height(), test_height);
539 assert_eq!(
540 unsafe { pdfium.bindings().FPDFBitmap_GetBuffer(bitmap.handle) } as usize,
541 buffer_ptr as usize
542 );
543 assert_eq!(
544 unsafe { pdfium.bindings().FPDFBitmap_GetStride(bitmap.handle) },
545 test_width * 4
551 );
552
553 Ok(())
554 }
555
556 #[test]
557 fn test_preferred_stride_bytes() -> () {
558 assert_eq!(
559 PdfBitmap::preferred_stride_bytes(0, PdfBitmapFormat::Gray),
560 Some(0)
561 );
562 assert_eq!(
563 PdfBitmap::preferred_stride_bytes(0, PdfBitmapFormat::BGR),
564 Some(0)
565 );
566 assert_eq!(
567 PdfBitmap::preferred_stride_bytes(0, PdfBitmapFormat::BGRx),
568 Some(0)
569 );
570 assert_eq!(
571 PdfBitmap::preferred_stride_bytes(0, PdfBitmapFormat::BGRA),
572 Some(0)
573 );
574
575 assert_eq!(
576 PdfBitmap::preferred_stride_bytes(1, PdfBitmapFormat::Gray),
577 Some(4)
578 );
579 assert_eq!(
580 PdfBitmap::preferred_stride_bytes(1, PdfBitmapFormat::BGR),
581 Some(4)
582 );
583 assert_eq!(
584 PdfBitmap::preferred_stride_bytes(1, PdfBitmapFormat::BGRx),
585 Some(4)
586 );
587 assert_eq!(
588 PdfBitmap::preferred_stride_bytes(1, PdfBitmapFormat::BGRA),
589 Some(4)
590 );
591
592 assert_eq!(
593 PdfBitmap::preferred_stride_bytes(2, PdfBitmapFormat::Gray),
594 Some(4)
595 );
596 assert_eq!(
597 PdfBitmap::preferred_stride_bytes(2, PdfBitmapFormat::BGR),
598 Some(8)
599 );
600 assert_eq!(
601 PdfBitmap::preferred_stride_bytes(2, PdfBitmapFormat::BGRx),
602 Some(8)
603 );
604 assert_eq!(
605 PdfBitmap::preferred_stride_bytes(2, PdfBitmapFormat::BGRA),
606 Some(8)
607 );
608
609 assert_eq!(
610 PdfBitmap::preferred_stride_bytes(3, PdfBitmapFormat::Gray),
611 Some(4)
612 );
613 assert_eq!(
614 PdfBitmap::preferred_stride_bytes(3, PdfBitmapFormat::BGR),
615 Some(12)
616 );
617 assert_eq!(
618 PdfBitmap::preferred_stride_bytes(3, PdfBitmapFormat::BGRx),
619 Some(12)
620 );
621 assert_eq!(
622 PdfBitmap::preferred_stride_bytes(3, PdfBitmapFormat::BGRA),
623 Some(12)
624 );
625
626 assert_eq!(
627 PdfBitmap::preferred_stride_bytes(4, PdfBitmapFormat::Gray),
628 Some(4)
629 );
630 assert_eq!(
631 PdfBitmap::preferred_stride_bytes(4, PdfBitmapFormat::BGR),
632 Some(12)
633 );
634 assert_eq!(
635 PdfBitmap::preferred_stride_bytes(4, PdfBitmapFormat::BGRx),
636 Some(16)
637 );
638 assert_eq!(
639 PdfBitmap::preferred_stride_bytes(4, PdfBitmapFormat::BGRA),
640 Some(16)
641 );
642
643 assert_eq!(
644 PdfBitmap::preferred_stride_bytes(5, PdfBitmapFormat::Gray),
645 Some(8)
646 );
647 assert_eq!(
648 PdfBitmap::preferred_stride_bytes(5, PdfBitmapFormat::BGR),
649 Some(16)
650 );
651 assert_eq!(
652 PdfBitmap::preferred_stride_bytes(5, PdfBitmapFormat::BGRx),
653 Some(20)
654 );
655 assert_eq!(
656 PdfBitmap::preferred_stride_bytes(5, PdfBitmapFormat::BGRA),
657 Some(20)
658 );
659
660 assert_eq!(
661 PdfBitmap::preferred_stride_bytes(1 << 30, PdfBitmapFormat::BGRA),
662 None
663 );
664 }
665
666 #[test]
667 fn test_bytes_required_for_size_and_format() {
668 assert_eq!(
670 PdfBitmap::bytes_required_for_size_and_format(1, 100, PdfBitmapFormat::Gray),
671 400
672 );
673 assert_eq!(
674 PdfBitmap::bytes_required_for_size_and_format(4, 100, PdfBitmapFormat::Gray),
675 400
676 );
677 assert_eq!(
678 PdfBitmap::bytes_required_for_size_and_format(256, 256, PdfBitmapFormat::BGR),
679 256 * 256 * 3
680 );
681
682 assert_eq!(
684 PdfBitmap::bytes_required_for_size_and_format(1 << 30, 50, PdfBitmapFormat::BGRA),
685 (1 << 30 as usize) * 50 * 4,
686 );
687 }
688}