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