1use crate::bindgen::{
4 FPDFBitmap_BGR, FPDFBitmap_BGRA, FPDFBitmap_BGRx, FPDFBitmap_Gray, FPDFBitmap_Unknown,
5 FPDF_BITMAP,
6};
7use crate::bindings::PdfiumLibraryBindings;
8use crate::error::{PdfiumError, PdfiumInternalError};
9use crate::pdf::document::page::render_config::PdfPageRenderSettings;
10use crate::pdfium::PdfiumLibraryBindingsAccessor;
11use crate::utils::pixels::{aligned_bgr_to_rgba, aligned_rgb_to_rgba, bgra_to_rgba};
12use std::marker::PhantomData;
13use std::os::raw::c_int;
14
15#[cfg(feature = "image_025")]
16use image_025::{DynamicImage, GrayImage, RgbaImage};
17
18#[cfg(feature = "image_024")]
19use image_024::{DynamicImage, GrayImage, RgbaImage};
20
21#[cfg(feature = "image_023")]
22use image_023::{DynamicImage, GrayImage, RgbaImage};
23
24#[cfg(not(target_arch = "wasm32"))]
25use std::os::raw::c_void;
26
27#[cfg(target_arch = "wasm32")]
28use {
29 js_sys::Uint8Array,
30 wasm_bindgen::{Clamped, JsValue},
31 web_sys::ImageData,
32};
33
34#[cfg(doc)]
39struct Uint8Array;
40
41#[cfg(doc)]
42struct ImageData;
43
44#[cfg(doc)]
45struct JsValue;
46
47pub type Pixels = i32;
54
55#[derive(Copy, Clone, Debug, PartialEq)]
57pub enum PdfBitmapFormat {
58 Gray = FPDFBitmap_Gray as isize,
59 BGR = FPDFBitmap_BGR as isize,
60 BGRx = FPDFBitmap_BGRx as isize,
61 BGRA = FPDFBitmap_BGRA as isize,
62}
63
64impl PdfBitmapFormat {
65 #[inline]
66 #[allow(non_upper_case_globals)]
67 pub(crate) fn from_pdfium(format: u32) -> Result<Self, PdfiumError> {
68 match format {
69 FPDFBitmap_Unknown => Err(PdfiumError::UnknownBitmapFormat),
70 FPDFBitmap_Gray => Ok(PdfBitmapFormat::Gray),
71 FPDFBitmap_BGR => Ok(PdfBitmapFormat::BGR),
72 FPDFBitmap_BGRx => Ok(PdfBitmapFormat::BGRx),
73 FPDFBitmap_BGRA => Ok(PdfBitmapFormat::BGRA),
74 _ => Err(PdfiumError::UnknownBitmapFormat),
75 }
76 }
77
78 #[inline]
79 pub(crate) fn as_pdfium(&self) -> u32 {
80 match self {
81 PdfBitmapFormat::Gray => FPDFBitmap_Gray,
82 PdfBitmapFormat::BGR => FPDFBitmap_BGR,
83 PdfBitmapFormat::BGRx => FPDFBitmap_BGRx,
84 PdfBitmapFormat::BGRA => FPDFBitmap_BGRA,
85 }
86 }
87}
88
89#[allow(clippy::derivable_impls)]
92impl Default for PdfBitmapFormat {
93 #[inline]
94 fn default() -> Self {
95 PdfBitmapFormat::BGRA
96 }
97}
98
99pub struct PdfBitmap<'a> {
101 handle: FPDF_BITMAP,
102 was_byte_order_reversed_during_rendering: bool,
103 lifetime: PhantomData<&'a FPDF_BITMAP>,
104}
105
106impl<'a> PdfBitmap<'a> {
107 pub(crate) fn from_pdfium(handle: FPDF_BITMAP) -> Self {
109 PdfBitmap {
110 handle,
111 was_byte_order_reversed_during_rendering: false,
112 lifetime: PhantomData,
113 }
114 }
115
116 pub fn empty(
119 width: Pixels,
120 height: Pixels,
121 format: PdfBitmapFormat,
122 bindings: &'a dyn PdfiumLibraryBindings,
123 ) -> Result<PdfBitmap<'a>, PdfiumError> {
124 let handle = unsafe {
125 bindings.FPDFBitmap_CreateEx(
126 width as c_int,
127 height as c_int,
128 format.as_pdfium() as c_int,
129 std::ptr::null_mut(),
130 0, )
132 };
133
134 if handle.is_null() {
135 Err(PdfiumError::PdfiumLibraryInternalError(
136 PdfiumInternalError::Unknown,
137 ))
138 } else {
139 Ok(Self::from_pdfium(handle))
140 }
141 }
142
143 #[cfg(not(target_arch = "wasm32"))]
154 pub unsafe fn from_bytes(
155 width: Pixels,
156 height: Pixels,
157 format: PdfBitmapFormat,
158 buffer: &'a mut [u8],
159 bindings: &'a dyn PdfiumLibraryBindings,
160 ) -> Result<PdfBitmap<'a>, PdfiumError> {
161 let handle = bindings.FPDFBitmap_CreateEx(
162 width as c_int,
163 height as c_int,
164 format.as_pdfium() as c_int,
165 buffer.as_mut_ptr() as *mut c_void,
166 0, );
168
169 if handle.is_null() {
170 Err(PdfiumError::PdfiumLibraryInternalError(
171 PdfiumInternalError::Unknown,
172 ))
173 } else {
174 Ok(Self::from_pdfium(handle))
175 }
176 }
177
178 #[inline]
180 pub(crate) fn handle(&self) -> FPDF_BITMAP {
181 self.handle
182 }
183
184 #[inline]
189 pub(crate) fn set_byte_order_from_render_settings(&mut self, settings: &PdfPageRenderSettings) {
190 self.was_byte_order_reversed_during_rendering = settings.is_reversed_byte_order_flag_set
191 }
192
193 #[inline]
195 pub fn width(&self) -> Pixels {
196 (unsafe { self.bindings().FPDFBitmap_GetWidth(self.handle()) }) as Pixels
197 }
198
199 #[inline]
201 pub fn height(&self) -> Pixels {
202 (unsafe { self.bindings().FPDFBitmap_GetHeight(self.handle()) }) as Pixels
203 }
204
205 #[inline]
207 pub fn format(&self) -> Result<PdfBitmapFormat, PdfiumError> {
208 PdfBitmapFormat::from_pdfium(
209 unsafe { self.bindings().FPDFBitmap_GetFormat(self.handle()) } as u32,
210 )
211 }
212
213 pub fn as_raw_bytes(&self) -> Vec<u8> {
220 unsafe { self.bindings().FPDFBitmap_GetBuffer_as_vec(self.handle) }
221 }
222
223 pub fn as_rgba_bytes(&self) -> Vec<u8> {
226 let bytes = self.as_raw_bytes();
227
228 let format = self.format().unwrap_or_default();
229
230 let width = self.width() as usize;
231
232 let stride = bytes.len() / self.height() as usize;
233
234 if self.was_byte_order_reversed_during_rendering {
235 match format {
239 PdfBitmapFormat::BGRA | PdfBitmapFormat::BGRx => {
240 bytes
243 }
244 PdfBitmapFormat::BGR => aligned_rgb_to_rgba(bytes.as_slice(), width, stride),
245 PdfBitmapFormat::Gray => bytes,
246 }
247 } else {
248 match format {
249 PdfBitmapFormat::BGRA | PdfBitmapFormat::BGRx => bgra_to_rgba(bytes.as_slice()),
250 PdfBitmapFormat::BGR => aligned_bgr_to_rgba(bytes.as_slice(), width, stride),
251 PdfBitmapFormat::Gray => bytes,
252 }
253 }
254 }
255
256 #[cfg(feature = "image_api")]
260 pub fn as_image(&self) -> Result<DynamicImage, PdfiumError> {
261 let bytes = self.as_rgba_bytes();
262
263 let width = self.width() as u32;
264
265 let height = self.height() as u32;
266
267 let image = match self.format().unwrap_or_default() {
268 PdfBitmapFormat::BGRA | PdfBitmapFormat::BGRx | PdfBitmapFormat::BGR => {
269 RgbaImage::from_raw(width, height, bytes).map(DynamicImage::ImageRgba8)
270 }
271 PdfBitmapFormat::Gray => {
272 GrayImage::from_raw(width, height, bytes).map(DynamicImage::ImageLuma8)
273 }
274 };
275
276 match image {
277 Some(image) => Ok(image),
278 None => Err(PdfiumError::ImageError),
279 }
280 }
281
282 #[cfg(any(doc, target_arch = "wasm32"))]
296 #[inline]
297 pub fn as_array(&self) -> Uint8Array {
298 unsafe { self.bindings().FPDFBitmap_GetBuffer_as_array(self.handle()) }
299 }
300
301 #[cfg(any(doc, target_arch = "wasm32"))]
313 #[inline]
314 pub fn as_image_data(&self) -> Result<ImageData, JsValue> {
315 ImageData::new_with_u8_clamped_array_and_sh(
316 Clamped(&self.as_rgba_bytes()),
317 self.width() as u32,
318 self.height() as u32,
319 )
320 }
321
322 #[inline]
329 pub fn bytes_required_for_size(width: Pixels, height: Pixels) -> usize {
330 4 * width as usize * height as usize
331 }
332}
333
334impl<'a> Drop for PdfBitmap<'a> {
335 #[inline]
337 fn drop(&mut self) {
338 unsafe {
339 self.bindings().FPDFBitmap_Destroy(self.handle());
340 }
341 }
342}
343
344impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfBitmap<'a> {}
345
346#[cfg(feature = "thread_safe")]
347unsafe impl<'a> Send for PdfBitmap<'a> {}
348
349#[cfg(feature = "thread_safe")]
350unsafe impl<'a> Sync for PdfBitmap<'a> {}
351
352#[cfg(test)]
353mod tests {
354 use crate::prelude::*;
355 use crate::utils::mem::create_sized_buffer;
356 use crate::utils::test::test_bind_to_pdfium;
357
358 #[test]
359 fn test_from_bytes() -> Result<(), PdfiumError> {
360 let pdfium = test_bind_to_pdfium();
361
362 let test_width = 2000;
363 let test_height = 4000;
364
365 let mut buffer =
366 create_sized_buffer(PdfBitmap::bytes_required_for_size(test_width, test_height));
367
368 let buffer_ptr = buffer.as_ptr();
369
370 let bitmap = unsafe {
371 PdfBitmap::from_bytes(
372 test_width,
373 test_height,
374 PdfBitmapFormat::BGRx,
375 buffer.as_mut_slice(),
376 pdfium.bindings(),
377 )?
378 };
379
380 assert_eq!(bitmap.width(), test_width);
381 assert_eq!(bitmap.height(), test_height);
382 assert_eq!(
383 unsafe { pdfium.bindings().FPDFBitmap_GetBuffer(bitmap.handle) } as usize,
384 buffer_ptr as usize
385 );
386 assert_eq!(
387 unsafe { pdfium.bindings().FPDFBitmap_GetStride(bitmap.handle) },
388 test_width * 4
394 );
395
396 Ok(())
397 }
398}