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::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// The following dummy declarations are used only when running cargo doc.
37// They allow documentation of WASM-specific functionality to be included
38// in documentation generated on non-WASM targets.
39
40#[cfg(doc)]
41struct Uint8Array;
42
43#[cfg(doc)]
44struct ImageData;
45
46#[cfg(doc)]
47struct JsValue;
48
49/// The device coordinate system when rendering or displaying an image.
50///
51/// While Pdfium will accept pixel sizes in either dimension up to the limits of [i32],
52/// in practice the maximum size of a bitmap image is limited to approximately 2,320,723,080 bytes
53/// (a little over 2 Gb). You can use the [PdfBitmap::bytes_required_for_size] function
54/// to estimate the maximum size of a bitmap image for a given target pixel width and height.
55pub type Pixels = i32;
56
57/// The pixel format of the rendered image data in the backing buffer of a [PdfBitmap].
58#[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    /// Returns the number of bytes required to store a single pixel in this pixel format.
91    #[inline]
92    pub(crate) fn bytes_per_pixel(&self) -> i32 {
93        match self {
94            // These values are taken from fpdfview.h.
95            PdfBitmapFormat::Gray => 1,
96            PdfBitmapFormat::BGR => 3,
97            PdfBitmapFormat::BGRx => 4,
98            PdfBitmapFormat::BGRA => 4,
99        }
100    }
101}
102
103// Deriving Default for enums is experimental. We implement the trait ourselves
104// to provide better compatibility with older Rust versions.
105#[allow(clippy::derivable_impls)]
106impl Default for PdfBitmapFormat {
107    #[inline]
108    fn default() -> Self {
109        PdfBitmapFormat::BGRA
110    }
111}
112
113/// A bitmap image with a specific width and height.
114pub 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    /// Wraps an existing `FPDF_BITMAP` handle inside a new [PdfBitmap].
122    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    /// Creates an empty [PdfBitmap] with a buffer capable of storing an image of the given
131    /// pixel width and height in the given pixel format.
132    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, // Not relevant because Pdfium will create the buffer itself.
146                )
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    /// Creates a new [PdfBitmap] that wraps the given byte buffer. The buffer must be capable
159    /// of storing an image of the given pixel width and height in the given pixel format,
160    /// otherwise a buffer overflow may occur during rendering.
161    ///
162    /// This function is not available when compiling to WASM.
163    #[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        // We compute the stride explicitly, because it may be greater than width * bytes_per_pixel.
171
172        let stride =
173            Self::preferred_stride_bytes(width, format).ok_or(PdfiumError::ImageSizeOutOfBounds)?;
174
175        // Avoid a buffer overflowing during rendering by confirming that the given buffer
176        // is large enough to contain the rendered image.
177
178        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    /// Creates a new [PdfBitmap] that wraps the given byte buffer. The buffer must be capable
212    /// of storing an image of the given pixel width and height in the given pixel format,
213    /// otherwise a buffer overflow may occur during rendering.
214    ///
215    /// This function is not available when compiling to WASM.
216    ///
217    /// # Safety
218    ///
219    /// This function is unsafe because a buffer overflow may occur during rendering if the buffer
220    /// is too small to store a rendered image of the given pixel dimensions.
221    #[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, // Not relevant because Pdfium will compute the stride value itself.
236            );
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    /// Returns the internal `FPDF_BITMAP` handle for this [PdfBitmap].
248    #[inline]
249    pub(crate) fn handle(&self) -> FPDF_BITMAP {
250        self.handle
251    }
252
253    /// Lets this [PdfBitmap] know whether it was created from a rendering configuration
254    /// that instructed Pdfium to reverse the byte order of generated image data from its
255    /// default of BGR8 to RGB8. The setting of this flag determines the color channel
256    /// normalization strategy used by [PdfBitmap::as_rgba_bytes].
257    #[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    /// Returns the width of the image in the bitmap buffer backing this [PdfBitmap].
263    #[inline]
264    pub fn width(&self) -> Pixels {
265        (unsafe { self.bindings().FPDFBitmap_GetWidth(self.handle()) }) as Pixels
266    }
267
268    /// Returns the height of the image in the bitmap buffer backing this [PdfBitmap].
269    #[inline]
270    pub fn height(&self) -> Pixels {
271        (unsafe { self.bindings().FPDFBitmap_GetHeight(self.handle()) }) as Pixels
272    }
273
274    /// Returns the pixel format of the image in the bitmap buffer backing this [PdfBitmap].
275    #[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    /// Returns an immutable reference to the bitmap buffer backing this [PdfBitmap].
283    ///
284    /// Unlike [PdfBitmap::as_rgba_bytes], this function does not attempt any color channel normalization.
285    /// To adjust color channels in your own code, use the [PdfiumLibraryBindings::bgr_to_rgba],
286    /// [PdfiumLibraryBindings::bgra_to_rgba], [PdfiumLibraryBindings::rgb_to_bgra],
287    /// and [PdfiumLibraryBindings::rgba_to_bgra] functions.
288    pub fn as_raw_bytes(&self) -> Vec<u8> {
289        unsafe { self.bindings().FPDFBitmap_GetBuffer_as_vec(self.handle) }
290    }
291
292    /// Returns an owned copy of the bitmap buffer backing this [PdfBitmap], normalizing all
293    /// color channels into RGBA irrespective of the original pixel format.
294    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            // The R and B channels were swapped by Pdfium during rendering, as configured by
305            // a call to PdfRenderConfig::set_reverse_byte_order(true).
306
307            match format {
308                PdfBitmapFormat::BGRA | PdfBitmapFormat::BGRx => {
309                    // No color conversion necessary; data was already swapped from BGRx
310                    // to four-channel RGB during rendering.
311                    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    /// Returns a new `Image::DynamicImage` created from the bitmap buffer backing this [PdfBitmap].
326    ///
327    /// This function is only available when this crate's `image` feature is enabled.
328    #[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    /// Returns a Javascript `Uint8Array` object representing the bitmap buffer backing
352    /// this [PdfBitmap].
353    ///
354    /// This function avoids a memory allocation and copy required by both
355    /// [PdfBitmap::as_rgba_bytes] and [PdfBitmap::as_image_data], making it preferable for
356    /// situations where performance is paramount.
357    ///
358    /// Unlike [PdfBitmap::as_rgba_bytes], this function does not attempt any color channel normalization.
359    /// To adjust color channels in your own code, use the [PdfiumLibraryBindings::bgr_to_rgba],
360    /// [PdfiumLibraryBindings::bgra_to_rgba], [PdfiumLibraryBindings::rgb_to_bgra],
361    /// and [PdfiumLibraryBindings::rgba_to_bgra] functions.
362    ///
363    /// This function is only available when compiling to WASM.
364    #[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    /// Returns a new Javascript `ImageData` object created from the bitmap buffer backing
371    /// this [PdfBitmap]. The resulting `ImageData` can be easily displayed in an
372    /// HTML `<canvas>` element like so:
373    ///
374    /// `canvas.getContext('2d').putImageData(image_data);`
375    ///
376    /// This function is slower than calling [PdfBitmap::as_array] because it must perform
377    /// an additional memory allocation in order to create the `ImageData` object. Consider calling
378    /// the [PdfBitmap::as_array] function directly if performance is paramount.
379    ///
380    /// This function is only available when compiling to WASM.
381    #[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    /// Estimates the maximum memory buffer size required for a [PdfBitmap] of the given dimensions.
392    ///
393    /// Certain platforms, architectures, and operating systems may limit the maximum size of a
394    /// bitmap buffer that can be created by Pdfium.
395    ///
396    /// The returned value assumes four bytes of memory will be consumed for each rendered pixel.
397    #[inline]
398    pub fn bytes_required_for_size(width: Pixels, height: Pixels) -> usize {
399        4 * width as usize * height as usize
400    }
401
402    /// Estimates the maximum memory buffer size required for a [PdfBitmap] of the given dimensions
403    /// and format. Providing the format allows for a more precise estimate than
404    /// [PdfBitmap::bytes_required_for_size].
405    ///
406    /// Certain platforms, architectures, and operating systems may limit the maximum size of a
407    /// bitmap buffer that can be created by Pdfium.
408    #[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    /// Returns the preferred stride for a [PdfBitmap] of the given width and format.
420    #[inline]
421    pub(crate) fn preferred_stride_bytes(width: Pixels, format: PdfBitmapFormat) -> Option<i32> {
422        // This mirrors the logic in core/fxge/calculate_pitch.cpp:CalculatePitch32Safely.
423
424        let min_stride_bytes = width.checked_mul(format.bytes_per_pixel())?;
425        let rounded = (min_stride_bytes.checked_add(3)? / 4) * 4; // round up to nearest multiple of 4 bytes
426
427        Some(rounded)
428    }
429}
430
431impl<'a> Drop for PdfBitmap<'a> {
432    /// Closes this [PdfBitmap], releasing the memory held by the bitmap buffer.
433    #[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        // Check that we can copy out of the buffer without a segfault
486        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        // error: stride is too large
499        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        // error: required buffer is too large
505        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        // error: provided buffer is too small
511        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            // The stride length is always a multiple of four bytes; for image formats
546            // that require less than four bytes per pixel, the extra bytes serve as
547            // alignment padding. For this test, we use the PdfBitmapFormat::BGRx which
548            // consumes four bytes per pixel, so test_width * 4 should indeed match
549            // the returned stride length.
550            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        // simple case: stride * height
669        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        // if stride estimation fails, fall back to 4 bytes per pixel
683        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}