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::utils::pixels::{aligned_bgr_to_rgba, aligned_rgb_to_rgba, bgra_to_rgba};
11use std::os::raw::c_int;
12
13#[cfg(feature = "image_025")]
14use image_025::{DynamicImage, GrayImage, RgbaImage};
15
16#[cfg(feature = "image_024")]
17use image_024::{DynamicImage, GrayImage, RgbaImage};
18
19#[cfg(feature = "image_023")]
20use image_023::{DynamicImage, GrayImage, RgbaImage};
21
22#[cfg(not(target_arch = "wasm32"))]
23use std::os::raw::c_void;
24
25#[cfg(target_arch = "wasm32")]
26use {
27    js_sys::Uint8Array,
28    wasm_bindgen::{Clamped, JsValue},
29    web_sys::ImageData,
30};
31
32// The following dummy declarations are used only when running cargo doc.
33// They allow documentation of WASM-specific functionality to be included
34// in documentation generated on non-WASM targets.
35
36#[cfg(doc)]
37struct Uint8Array;
38
39#[cfg(doc)]
40struct ImageData;
41
42#[cfg(doc)]
43struct JsValue;
44
45/// The device coordinate system when rendering or displaying an image.
46///
47/// While Pdfium will accept pixel sizes in either dimension up to the limits of [i32],
48/// in practice the maximum size of a bitmap image is limited to approximately 2,320,723,080 bytes
49/// (a little over 2 Gb). You can use the [PdfBitmap::bytes_required_for_size] function
50/// to estimate the maximum size of a bitmap image for a given target pixel width and height.
51pub type Pixels = i32;
52
53/// The pixel format of the rendered image data in the backing buffer of a [PdfBitmap].
54#[derive(Copy, Clone, Debug, PartialEq)]
55#[allow(clippy::manual_non_exhaustive)] // triggered by deprecation below, can be removed in 0.9.0
56pub enum PdfBitmapFormat {
57    Gray = FPDFBitmap_Gray as isize,
58    BGR = FPDFBitmap_BGR as isize,
59    BGRx = FPDFBitmap_BGRx as isize,
60    BGRA = FPDFBitmap_BGRA as isize,
61
62    // TODO: AJRC - 22/7/23 - remove deprecated variant in 0.9.0
63    // as part of tracking issue https://github.com/ajrcarey/pdfium-render/issues/36
64    #[deprecated(
65        since = "0.8.7",
66        note = "This variant has been renamed to correct a misspelling. Use the BGRx variant instead."
67    )]
68    #[doc(hidden)]
69    BRGx = 999,
70}
71
72impl PdfBitmapFormat {
73    #[inline]
74    #[allow(non_upper_case_globals)]
75    pub(crate) fn from_pdfium(format: u32) -> Result<Self, PdfiumError> {
76        match format {
77            FPDFBitmap_Unknown => Err(PdfiumError::UnknownBitmapFormat),
78            FPDFBitmap_Gray => Ok(PdfBitmapFormat::Gray),
79            FPDFBitmap_BGR => Ok(PdfBitmapFormat::BGR),
80            FPDFBitmap_BGRx => Ok(PdfBitmapFormat::BGRx),
81            FPDFBitmap_BGRA => Ok(PdfBitmapFormat::BGRA),
82            _ => Err(PdfiumError::UnknownBitmapFormat),
83        }
84    }
85
86    #[inline]
87    pub(crate) fn as_pdfium(&self) -> u32 {
88        match self {
89            PdfBitmapFormat::Gray => FPDFBitmap_Gray,
90            PdfBitmapFormat::BGR => FPDFBitmap_BGR,
91            #[allow(deprecated)]
92            PdfBitmapFormat::BRGx | PdfBitmapFormat::BGRx => FPDFBitmap_BGRx,
93            PdfBitmapFormat::BGRA => FPDFBitmap_BGRA,
94        }
95    }
96}
97
98// Deriving Default for enums is experimental. We implement the trait ourselves
99// to provide better compatibility with older Rust versions.
100#[allow(clippy::derivable_impls)]
101impl Default for PdfBitmapFormat {
102    #[inline]
103    fn default() -> Self {
104        PdfBitmapFormat::BGRA
105    }
106}
107
108/// A bitmap image with a specific width and height.
109pub struct PdfBitmap<'a> {
110    handle: FPDF_BITMAP,
111    was_byte_order_reversed_during_rendering: bool,
112    bindings: &'a dyn PdfiumLibraryBindings,
113}
114
115impl<'a> PdfBitmap<'a> {
116    /// Wraps an existing `FPDF_BITMAP` handle inside a new [PdfBitmap].
117    pub(crate) fn from_pdfium(
118        handle: FPDF_BITMAP,
119        bindings: &'a dyn PdfiumLibraryBindings,
120    ) -> Self {
121        PdfBitmap {
122            handle,
123            was_byte_order_reversed_during_rendering: false,
124            bindings,
125        }
126    }
127
128    /// Creates an empty [PdfBitmap] with a buffer capable of storing an image of the given
129    /// pixel width and height in the given pixel format.
130    pub fn empty(
131        width: Pixels,
132        height: Pixels,
133        format: PdfBitmapFormat,
134        bindings: &'a dyn PdfiumLibraryBindings,
135    ) -> Result<PdfBitmap<'a>, PdfiumError> {
136        let handle = bindings.FPDFBitmap_CreateEx(
137            width as c_int,
138            height as c_int,
139            format.as_pdfium() as c_int,
140            std::ptr::null_mut(),
141            0, // Not relevant because Pdfium will create the buffer itself.
142        );
143
144        if handle.is_null() {
145            Err(PdfiumError::PdfiumLibraryInternalError(
146                PdfiumInternalError::Unknown,
147            ))
148        } else {
149            Ok(Self::from_pdfium(handle, bindings))
150        }
151    }
152
153    /// Creates a new [PdfBitmap] that wraps the given byte buffer. The buffer must be capable
154    /// of storing an image of the given pixel width and height in the given pixel format,
155    /// otherwise a buffer overflow may occur during rendering.
156    ///
157    /// This function is not available when compiling to WASM.
158    ///
159    /// # Safety
160    ///
161    /// This function is unsafe because a buffer overflow may occur during rendering if the buffer
162    /// is too small to store a rendered image of the given pixel dimensions.
163    #[cfg(not(target_arch = "wasm32"))]
164    pub unsafe fn from_bytes(
165        width: Pixels,
166        height: Pixels,
167        format: PdfBitmapFormat,
168        buffer: &'a mut [u8],
169        bindings: &'a dyn PdfiumLibraryBindings,
170    ) -> Result<PdfBitmap<'a>, PdfiumError> {
171        let handle = bindings.FPDFBitmap_CreateEx(
172            width as c_int,
173            height as c_int,
174            format.as_pdfium() as c_int,
175            buffer.as_mut_ptr() as *mut c_void,
176            0, // Not relevant because Pdfium will compute the stride value itself.
177        );
178
179        if handle.is_null() {
180            Err(PdfiumError::PdfiumLibraryInternalError(
181                PdfiumInternalError::Unknown,
182            ))
183        } else {
184            Ok(Self::from_pdfium(handle, bindings))
185        }
186    }
187
188    /// Returns the internal `FPDF_BITMAP` handle for this [PdfBitmap].
189    #[inline]
190    pub(crate) fn handle(&self) -> FPDF_BITMAP {
191        self.handle
192    }
193
194    /// Lets this [PdfBitmap] know whether it was created from a rendering configuration
195    /// that instructed Pdfium to reverse the byte order of generated image data from its
196    /// default of BGR8 to RGB8. The setting of this flag determines the color channel
197    /// normalization strategy used by [PdfBitmap::as_rgba_bytes].
198    #[inline]
199    pub(crate) fn set_byte_order_from_render_settings(&mut self, settings: &PdfPageRenderSettings) {
200        self.was_byte_order_reversed_during_rendering = settings.is_reversed_byte_order_flag_set
201    }
202
203    /// Returns the [PdfiumLibraryBindings] used by this [PdfBitmap].
204    #[inline]
205    pub fn bindings(&self) -> &dyn PdfiumLibraryBindings {
206        self.bindings
207    }
208
209    /// Returns the width of the image in the bitmap buffer backing this [PdfBitmap].
210    #[inline]
211    pub fn width(&self) -> Pixels {
212        self.bindings().FPDFBitmap_GetWidth(self.handle()) as Pixels
213    }
214
215    /// Returns the height of the image in the bitmap buffer backing this [PdfBitmap].
216    #[inline]
217    pub fn height(&self) -> Pixels {
218        self.bindings().FPDFBitmap_GetHeight(self.handle()) as Pixels
219    }
220
221    /// Returns the pixel format of the image in the bitmap buffer backing this [PdfBitmap].
222    #[inline]
223    pub fn format(&self) -> Result<PdfBitmapFormat, PdfiumError> {
224        PdfBitmapFormat::from_pdfium(self.bindings().FPDFBitmap_GetFormat(self.handle()) as u32)
225    }
226
227    // TODO: AJRC - 25/11/22 - remove deprecated PdfBitmap::as_bytes() function in 0.9.0
228    // as part of tracking issue https://github.com/ajrcarey/pdfium-render/issues/36
229    /// Returns an immutable reference to the bitmap buffer backing this [PdfBitmap].
230    #[deprecated(
231        since = "0.8.16",
232        note = "This function has been renamed to better reflect its purpose. Use the PdfBitmap::as_raw_bytes() function instead."
233    )]
234    #[doc(hidden)]
235    #[inline]
236    pub fn as_bytes(&self) -> Vec<u8> {
237        self.as_raw_bytes()
238    }
239
240    /// Returns an immutable reference to the bitmap buffer backing this [PdfBitmap].
241    ///
242    /// Unlike [PdfBitmap::as_rgba_bytes], this function does not attempt any color channel normalization.
243    /// To adjust color channels in your own code, use the [PdfiumLibraryBindings::bgr_to_rgba],
244    /// [PdfiumLibraryBindings::bgra_to_rgba], [PdfiumLibraryBindings::rgb_to_bgra],
245    /// and [PdfiumLibraryBindings::rgba_to_bgra] functions.
246    pub fn as_raw_bytes(&self) -> Vec<u8> {
247        self.bindings().FPDFBitmap_GetBuffer_as_vec(self.handle)
248    }
249
250    /// Returns an owned copy of the bitmap buffer backing this [PdfBitmap], normalizing all
251    /// color channels into RGBA irrespective of the original pixel format.
252    pub fn as_rgba_bytes(&self) -> Vec<u8> {
253        let bytes = self.as_raw_bytes();
254
255        let format = self.format().unwrap_or_default();
256
257        let width = self.width() as usize;
258
259        let stride = bytes.len() / self.height() as usize;
260
261        if self.was_byte_order_reversed_during_rendering {
262            // The R and B channels were swapped by Pdfium during rendering, as configured by
263            // a call to PdfRenderConfig::set_reverse_byte_order(true).
264
265            match format {
266                #[allow(deprecated)]
267                PdfBitmapFormat::BGRA | PdfBitmapFormat::BGRx | PdfBitmapFormat::BRGx => {
268                    // No color conversion necessary; data was already swapped from BGRx
269                    // to four-channel RGB during rendering.
270                    bytes
271                }
272                PdfBitmapFormat::BGR => aligned_rgb_to_rgba(bytes.as_slice(), width, stride),
273                PdfBitmapFormat::Gray => bytes,
274            }
275        } else {
276            match format {
277                #[allow(deprecated)]
278                PdfBitmapFormat::BGRA | PdfBitmapFormat::BRGx | PdfBitmapFormat::BGRx => {
279                    bgra_to_rgba(bytes.as_slice())
280                }
281                PdfBitmapFormat::BGR => aligned_bgr_to_rgba(bytes.as_slice(), width, stride),
282                PdfBitmapFormat::Gray => bytes,
283            }
284        }
285    }
286
287    /// Returns a new `Image::DynamicImage` created from the bitmap buffer backing this [PdfBitmap].
288    ///
289    /// This function is only available when this crate's `image` feature is enabled.
290    #[cfg(feature = "image_api")]
291    pub fn as_image(&self) -> DynamicImage {
292        let bytes = self.as_rgba_bytes();
293
294        let width = self.width() as u32;
295
296        let height = self.height() as u32;
297
298        match self.format().unwrap_or_default() {
299            #[allow(deprecated)]
300            PdfBitmapFormat::BGRA
301            | PdfBitmapFormat::BRGx
302            | PdfBitmapFormat::BGRx
303            | PdfBitmapFormat::BGR => {
304                RgbaImage::from_raw(width, height, bytes).map(DynamicImage::ImageRgba8)
305            }
306            PdfBitmapFormat::Gray => {
307                GrayImage::from_raw(width, height, bytes).map(DynamicImage::ImageLuma8)
308            }
309        }
310        // TODO: AJRC - 3/11/23 - change function signature to return Result<DynamicImage, PdfiumError>
311        // in 0.9.0 so we can account for any image conversion failure here. Tracked
312        // as part of https://github.com/ajrcarey/pdfium-render/issues/36
313        .unwrap()
314    }
315
316    // TODO: AJRC - 29/7/22 - remove deprecated PdfBitmap::render() function in 0.9.0
317    // as part of tracking issue https://github.com/ajrcarey/pdfium-render/issues/36
318    /// Prior to 0.7.12, this function rendered the referenced page into a bitmap buffer.
319    ///
320    /// This is no longer necessary since all page rendering operations are now processed eagerly
321    /// rather than lazily.
322    ///
323    /// This function is now deprecated and will be removed in release 0.9.0.
324    #[deprecated(
325        since = "0.7.12",
326        note = "This function is no longer necessary since all page rendering operations are now processed eagerly rather than lazily. Calls to this function can be removed."
327    )]
328    #[doc(hidden)]
329    #[inline]
330    pub fn render(&self) {}
331
332    /// Returns a Javascript `Uint8Array` object representing the bitmap buffer backing
333    /// this [PdfBitmap].
334    ///
335    /// This function avoids a memory allocation and copy required by both
336    /// [PdfBitmap::as_rgba_bytes] and [PdfBitmap::as_image_data], making it preferable for
337    /// situations where performance is paramount.
338    ///
339    /// Unlike [PdfBitmap::as_rgba_bytes], this function does not attempt any color channel normalization.
340    /// To adjust color channels in your own code, use the [PdfiumLibraryBindings::bgr_to_rgba],
341    /// [PdfiumLibraryBindings::bgra_to_rgba], [PdfiumLibraryBindings::rgb_to_bgra],
342    /// and [PdfiumLibraryBindings::rgba_to_bgra] functions.
343    ///
344    /// This function is only available when compiling to WASM.
345    #[cfg(any(doc, target_arch = "wasm32"))]
346    #[inline]
347    pub fn as_array(&self) -> Uint8Array {
348        self.bindings().FPDFBitmap_GetBuffer_as_array(self.handle())
349    }
350
351    /// Returns a new Javascript `ImageData` object created from the bitmap buffer backing
352    /// this [PdfBitmap]. The resulting `ImageData` can be easily displayed in an
353    /// HTML `<canvas>` element like so:
354    ///
355    /// `canvas.getContext('2d').putImageData(image_data);`
356    ///
357    /// This function is slower than calling [PdfBitmap::as_array] because it must perform
358    /// an additional memory allocation in order to create the `ImageData` object. Consider calling
359    /// the [PdfBitmap::as_array] function directly if performance is paramount.
360    ///
361    /// This function is only available when compiling to WASM.
362    #[cfg(any(doc, target_arch = "wasm32"))]
363    #[inline]
364    pub fn as_image_data(&self) -> Result<ImageData, JsValue> {
365        ImageData::new_with_u8_clamped_array_and_sh(
366            Clamped(&self.as_rgba_bytes()),
367            self.width() as u32,
368            self.height() as u32,
369        )
370    }
371
372    /// Estimates the maximum memory buffer size required for a [PdfBitmap] of the given dimensions.
373    ///
374    /// Certain platforms, architectures, and operating systems may limit the maximum size of a
375    /// bitmap buffer that can be created by Pdfium.
376    ///
377    /// The returned value assumes four bytes of memory will be consumed for each rendered pixel.
378    #[inline]
379    pub fn bytes_required_for_size(width: Pixels, height: Pixels) -> usize {
380        4 * width as usize * height as usize
381    }
382}
383
384impl<'a> Drop for PdfBitmap<'a> {
385    /// Closes this [PdfBitmap], releasing the memory held by the bitmap buffer.
386    #[inline]
387    fn drop(&mut self) {
388        self.bindings().FPDFBitmap_Destroy(self.handle());
389    }
390}
391
392#[cfg(test)]
393mod tests {
394    use crate::prelude::*;
395    use crate::utils::mem::create_sized_buffer;
396    use crate::utils::test::test_bind_to_pdfium;
397
398    #[test]
399    fn test_from_bytes() -> Result<(), PdfiumError> {
400        let pdfium = test_bind_to_pdfium();
401
402        let test_width = 2000;
403        let test_height = 4000;
404
405        let mut buffer =
406            create_sized_buffer(PdfBitmap::bytes_required_for_size(test_width, test_height));
407
408        let buffer_ptr = buffer.as_ptr();
409
410        let bitmap = unsafe {
411            PdfBitmap::from_bytes(
412                test_width,
413                test_height,
414                PdfBitmapFormat::BGRx,
415                buffer.as_mut_slice(),
416                pdfium.bindings(),
417            )?
418        };
419
420        assert_eq!(bitmap.width(), test_width);
421        assert_eq!(bitmap.height(), test_height);
422        assert_eq!(
423            pdfium.bindings().FPDFBitmap_GetBuffer(bitmap.handle) as usize,
424            buffer_ptr as usize
425        );
426        assert_eq!(
427            pdfium.bindings().FPDFBitmap_GetStride(bitmap.handle),
428            // The stride length is always a multiple of four bytes; for image formats
429            // that require less than four bytes per pixel, the extra bytes serve as
430            // alignment padding. For this test, we use the PdfBitmapFormat::BGRx which
431            // consumes four bytes per pixel, so test_width * 4 should indeed match
432            // the returned stride length.
433            test_width * 4
434        );
435
436        Ok(())
437    }
438}