Skip to main content

pdfium_render/pdf/
bitmap.rs

1//! Defines the [PdfBitmap] struct, a bitmap image with a specific width and height.
2
3use 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// The following dummy declarations are used only when running cargo doc.
35// They allow documentation of WASM-specific functionality to be included
36// in documentation generated on non-WASM targets.
37
38#[cfg(doc)]
39struct Uint8Array;
40
41#[cfg(doc)]
42struct ImageData;
43
44#[cfg(doc)]
45struct JsValue;
46
47/// The device coordinate system when rendering or displaying an image.
48///
49/// While Pdfium will accept pixel sizes in either dimension up to the limits of [i32],
50/// in practice the maximum size of a bitmap image is limited to approximately 2,320,723,080 bytes
51/// (a little over 2 Gb). You can use the [PdfBitmap::bytes_required_for_size] function
52/// to estimate the maximum size of a bitmap image for a given target pixel width and height.
53pub type Pixels = i32;
54
55/// The pixel format of the rendered image data in the backing buffer of a [PdfBitmap].
56#[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// Deriving Default for enums is experimental. We implement the trait ourselves
90// to provide better compatibility with older Rust versions.
91#[allow(clippy::derivable_impls)]
92impl Default for PdfBitmapFormat {
93    #[inline]
94    fn default() -> Self {
95        PdfBitmapFormat::BGRA
96    }
97}
98
99/// A bitmap image with a specific width and height.
100pub 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    /// Wraps an existing `FPDF_BITMAP` handle inside a new [PdfBitmap].
108    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    /// Creates an empty [PdfBitmap] with a buffer capable of storing an image of the given
117    /// pixel width and height in the given pixel format.
118    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, // Not relevant because Pdfium will create the buffer itself.
131            )
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    /// Creates a new [PdfBitmap] that wraps the given byte buffer. The buffer must be capable
144    /// of storing an image of the given pixel width and height in the given pixel format,
145    /// otherwise a buffer overflow may occur during rendering.
146    ///
147    /// This function is not available when compiling to WASM.
148    ///
149    /// # Safety
150    ///
151    /// This function is unsafe because a buffer overflow may occur during rendering if the buffer
152    /// is too small to store a rendered image of the given pixel dimensions.
153    #[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, // Not relevant because Pdfium will compute the stride value itself.
167        );
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    /// Returns the internal `FPDF_BITMAP` handle for this [PdfBitmap].
179    #[inline]
180    pub(crate) fn handle(&self) -> FPDF_BITMAP {
181        self.handle
182    }
183
184    /// Lets this [PdfBitmap] know whether it was created from a rendering configuration
185    /// that instructed Pdfium to reverse the byte order of generated image data from its
186    /// default of BGR8 to RGB8. The setting of this flag determines the color channel
187    /// normalization strategy used by [PdfBitmap::as_rgba_bytes].
188    #[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    /// Returns the width of the image in the bitmap buffer backing this [PdfBitmap].
194    #[inline]
195    pub fn width(&self) -> Pixels {
196        (unsafe { self.bindings().FPDFBitmap_GetWidth(self.handle()) }) as Pixels
197    }
198
199    /// Returns the height of the image in the bitmap buffer backing this [PdfBitmap].
200    #[inline]
201    pub fn height(&self) -> Pixels {
202        (unsafe { self.bindings().FPDFBitmap_GetHeight(self.handle()) }) as Pixels
203    }
204
205    /// Returns the pixel format of the image in the bitmap buffer backing this [PdfBitmap].
206    #[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    /// Returns an immutable reference to the bitmap buffer backing this [PdfBitmap].
214    ///
215    /// Unlike [PdfBitmap::as_rgba_bytes], this function does not attempt any color channel normalization.
216    /// To adjust color channels in your own code, use the [PdfiumLibraryBindings::bgr_to_rgba],
217    /// [PdfiumLibraryBindings::bgra_to_rgba], [PdfiumLibraryBindings::rgb_to_bgra],
218    /// and [PdfiumLibraryBindings::rgba_to_bgra] functions.
219    pub fn as_raw_bytes(&self) -> Vec<u8> {
220        unsafe { self.bindings().FPDFBitmap_GetBuffer_as_vec(self.handle) }
221    }
222
223    /// Returns an owned copy of the bitmap buffer backing this [PdfBitmap], normalizing all
224    /// color channels into RGBA irrespective of the original pixel format.
225    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            // The R and B channels were swapped by Pdfium during rendering, as configured by
236            // a call to PdfRenderConfig::set_reverse_byte_order(true).
237
238            match format {
239                PdfBitmapFormat::BGRA | PdfBitmapFormat::BGRx => {
240                    // No color conversion necessary; data was already swapped from BGRx
241                    // to four-channel RGB during rendering.
242                    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    /// Returns a new `Image::DynamicImage` created from the bitmap buffer backing this [PdfBitmap].
257    ///
258    /// This function is only available when this crate's `image` feature is enabled.
259    #[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    /// Returns a Javascript `Uint8Array` object representing the bitmap buffer backing
283    /// this [PdfBitmap].
284    ///
285    /// This function avoids a memory allocation and copy required by both
286    /// [PdfBitmap::as_rgba_bytes] and [PdfBitmap::as_image_data], making it preferable for
287    /// situations where performance is paramount.
288    ///
289    /// Unlike [PdfBitmap::as_rgba_bytes], this function does not attempt any color channel normalization.
290    /// To adjust color channels in your own code, use the [PdfiumLibraryBindings::bgr_to_rgba],
291    /// [PdfiumLibraryBindings::bgra_to_rgba], [PdfiumLibraryBindings::rgb_to_bgra],
292    /// and [PdfiumLibraryBindings::rgba_to_bgra] functions.
293    ///
294    /// This function is only available when compiling to WASM.
295    #[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    /// Returns a new Javascript `ImageData` object created from the bitmap buffer backing
302    /// this [PdfBitmap]. The resulting `ImageData` can be easily displayed in an
303    /// HTML `<canvas>` element like so:
304    ///
305    /// `canvas.getContext('2d').putImageData(image_data);`
306    ///
307    /// This function is slower than calling [PdfBitmap::as_array] because it must perform
308    /// an additional memory allocation in order to create the `ImageData` object. Consider calling
309    /// the [PdfBitmap::as_array] function directly if performance is paramount.
310    ///
311    /// This function is only available when compiling to WASM.
312    #[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    /// Estimates the maximum memory buffer size required for a [PdfBitmap] of the given dimensions.
323    ///
324    /// Certain platforms, architectures, and operating systems may limit the maximum size of a
325    /// bitmap buffer that can be created by Pdfium.
326    ///
327    /// The returned value assumes four bytes of memory will be consumed for each rendered pixel.
328    #[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    /// Closes this [PdfBitmap], releasing the memory held by the bitmap buffer.
336    #[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            // The stride length is always a multiple of four bytes; for image formats
389            // that require less than four bytes per pixel, the extra bytes serve as
390            // alignment padding. For this test, we use the PdfBitmapFormat::BGRx which
391            // consumes four bytes per pixel, so test_width * 4 should indeed match
392            // the returned stride length.
393            test_width * 4
394        );
395
396        Ok(())
397    }
398}