pdfium_render/pdf/document/page/object/
image.rs

1//! Defines the [PdfPageImageObject] struct, exposing functionality related to a single page
2//! object defining an image, where the image data is sourced from a [PdfBitmap] buffer.
3
4use crate::bindgen::{
5    fpdf_page_t__, FPDF_DOCUMENT, FPDF_IMAGEOBJ_METADATA, FPDF_PAGE, FPDF_PAGEOBJECT,
6};
7use crate::bindings::PdfiumLibraryBindings;
8use crate::error::{PdfiumError, PdfiumInternalError};
9use crate::pdf::bitmap::PdfBitmap;
10use crate::pdf::bitmap::Pixels;
11use crate::pdf::color_space::PdfColorSpace;
12use crate::pdf::document::page::object::private::internal::PdfPageObjectPrivate;
13use crate::pdf::document::page::object::{PdfPageObject, PdfPageObjectOwnership};
14use crate::pdf::document::PdfDocument;
15use crate::pdf::matrix::{PdfMatrix, PdfMatrixValue};
16use crate::pdf::points::PdfPoints;
17use crate::utils::mem::create_byte_buffer;
18use crate::{create_transform_getters, create_transform_setters};
19use std::convert::TryInto;
20use std::io::{Read, Seek};
21use std::ops::{Range, RangeInclusive};
22use std::os::raw::{c_int, c_void};
23
24#[cfg(not(target_arch = "wasm32"))]
25use {crate::utils::files::get_pdfium_file_accessor_from_reader, std::fs::File, std::path::Path};
26
27#[cfg(any(feature = "image_latest", feature = "image_025"))]
28use {
29    crate::pdf::bitmap::PdfBitmapFormat,
30    crate::utils::pixels::{aligned_bgr_to_rgba, bgra_to_rgba, rgba_to_bgra},
31    image_025::{DynamicImage, EncodableLayout, GrayImage, RgbaImage},
32};
33
34#[cfg(feature = "image_024")]
35use {
36    crate::pdf::bitmap::PdfBitmapFormat,
37    crate::utils::pixels::{aligned_bgr_to_rgba, bgra_to_rgba, rgba_to_bgra},
38    image_024::{DynamicImage, EncodableLayout, GrayImage, RgbaImage},
39};
40
41#[cfg(feature = "image_023")]
42use {
43    crate::pdf::bitmap::PdfBitmapFormat,
44    crate::utils::pixels::{aligned_bgr_to_rgba, bgra_to_rgba, rgba_to_bgra},
45    image_023::{DynamicImage, EncodableLayout, GenericImageView, GrayImage, RgbaImage},
46};
47
48#[cfg(doc)]
49use {crate::pdf::document::page::object::PdfPageObjectType, crate::pdf::document::page::PdfPage};
50
51/// A single [PdfPageObject] of type [PdfPageObjectType::Image]. The page object defines a
52/// single image, where the image data is sourced from a [PdfBitmap] buffer.
53///
54/// Page objects can be created either attached to a [PdfPage] (in which case the page object's
55/// memory is owned by the containing page) or detached from any page (in which case the page
56/// object's memory is owned by the object). Page objects are not rendered until they are
57/// attached to a page; page objects that are never attached to a page will be lost when they
58/// fall out of scope.
59///
60/// The simplest way to create a page image object that is immediately attached to a page
61/// is to call the `PdfPageObjects::create_image_object()` function.
62///
63/// Creating a detached page image object offers more scope for customization, but you must
64/// add the object to a containing [PdfPage] manually. To create a detached page image object,
65/// use the [PdfPageImageObject::new()] or [PdfPageImageObject::new_from_jpeg()] functions.
66/// The detached page image object can later be attached to a page by using the
67/// `PdfPageObjects::add_image_object()` function.
68pub struct PdfPageImageObject<'a> {
69    object_handle: FPDF_PAGEOBJECT,
70    ownership: PdfPageObjectOwnership,
71    bindings: &'a dyn PdfiumLibraryBindings,
72}
73
74impl<'a> PdfPageImageObject<'a> {
75    #[inline]
76    pub(crate) fn from_pdfium(
77        object_handle: FPDF_PAGEOBJECT,
78        ownership: PdfPageObjectOwnership,
79        bindings: &'a dyn PdfiumLibraryBindings,
80    ) -> Self {
81        PdfPageImageObject {
82            object_handle,
83            ownership,
84            bindings,
85        }
86    }
87
88    /// Creates a new [PdfPageImageObject] from the given [DynamicImage]. The returned
89    /// page object will not be rendered until it is added to a [PdfPage] using the
90    /// `PdfPageObjects::add_image_object()` function.
91    ///
92    /// The returned page object will have its width and height both set to 1.0 points.
93    /// Use the [PdfPageImageObject::scale()] function to apply a horizontal and vertical scale
94    /// to the object after it is created, or use one of the [PdfPageImageObject::new_with_width()],
95    /// [PdfPageImageObject::new_with_height()], or [PdfPageImageObject::new_with_size()] functions
96    /// to scale the page object to a specific width and/or height at the time the object is created.
97    ///
98    /// This function is only available when this crate's `image` feature is enabled.
99    #[cfg(feature = "image_api")]
100    #[inline]
101    pub fn new(document: &PdfDocument<'a>, image: &DynamicImage) -> Result<Self, PdfiumError> {
102        let mut result = Self::new_from_handle(document.handle(), document.bindings());
103
104        if let Ok(result) = result.as_mut() {
105            result.set_image(image)?;
106        }
107
108        result
109    }
110
111    /// Creates a new [PdfPageImageObject]. The returned page object will not be
112    /// rendered until it is added to a [PdfPage] using the
113    /// [PdfPageObjects::add_image_object()] function.
114    ///
115    /// Use the [PdfPageImageObject::set_bitmap()] function to apply image data to
116    /// the empty object.
117    ///
118    /// The returned page object will have its width and height both set to 1.0 points.
119    /// Use the [WriteTransforms::scale()] function to apply a horizontal and vertical scale
120    /// to the object after it is created.
121    #[cfg(not(feature = "image_api"))]
122    pub fn new(document: &PdfDocument<'a>) -> Result<Self, PdfiumError> {
123        Self::new_from_handle(document.handle(), document.bindings())
124    }
125
126    /// Creates a new [PdfPageImageObject] containing JPEG image data loaded from the
127    /// given file path. The returned page object will not be rendered until it is added to
128    /// a [PdfPage] using the `PdfPageObjects::add_image_object()` function.
129    ///
130    /// The returned page object will have its width and height both set to 1.0 points.
131    /// Use the [PdfPageImageObject::scale] function to apply a horizontal and vertical scale
132    /// to the object after it is created, or use one of the [PdfPageImageObject::new_with_width()],
133    /// [PdfPageImageObject::new_with_height()], or [PdfPageImageObject::new_with_size()] functions
134    /// to scale the page object to a specific width and/or height at the time the object is created.
135    ///
136    /// This function is not available when compiling to WASM.
137    #[cfg(not(target_arch = "wasm32"))]
138    pub fn new_from_jpeg_file(
139        document: &PdfDocument<'a>,
140        path: &(impl AsRef<Path> + ?Sized),
141    ) -> Result<Self, PdfiumError> {
142        Self::new_from_jpeg_reader(document, File::open(path).map_err(PdfiumError::IoError)?)
143    }
144
145    /// Creates a new [PdfPageImageObject] containing JPEG image data loaded from the
146    /// given reader. Because Pdfium must know the total content length in advance prior to
147    /// loading any portion of it, the given reader must implement the [Seek] trait
148    /// as well as the [Read] trait.
149    ///
150    /// The returned page object will not be rendered until it is added to
151    /// a [PdfPage] using the `PdfPageObjects::add_image_object()` function.
152    ///
153    /// The returned page object will have its width and height both set to 1.0 points.
154    /// Use the [PdfPageImageObject::scale] function to apply a horizontal and vertical scale
155    /// to the object after it is created, or use one of the [PdfPageImageObject::new_with_width()],
156    /// [PdfPageImageObject::new_with_height()], or [PdfPageImageObject::new_with_size()] functions
157    /// to scale the page object to a specific width and/or height at the time the object is created.
158    ///
159    /// This function is not available when compiling to WASM.
160    #[cfg(not(target_arch = "wasm32"))]
161    pub fn new_from_jpeg_reader<R: Read + Seek>(
162        document: &PdfDocument<'a>,
163        reader: R,
164    ) -> Result<Self, PdfiumError> {
165        let object = Self::new_from_handle(document.handle(), document.bindings())?;
166
167        let mut reader = get_pdfium_file_accessor_from_reader(reader);
168
169        let result = document.bindings().FPDFImageObj_LoadJpegFileInline(
170            std::ptr::null_mut(),
171            0,
172            object.object_handle(),
173            reader.as_fpdf_file_access_mut_ptr(),
174        );
175
176        if object.bindings.is_true(result) {
177            Ok(object)
178        } else {
179            Err(PdfiumError::PdfiumLibraryInternalError(
180                PdfiumInternalError::Unknown,
181            ))
182        }
183    }
184
185    // Takes a raw FPDF_DOCUMENT handle to avoid cascading lifetime problems
186    // associated with borrowing PdfDocument<'a>.
187    pub(crate) fn new_from_handle(
188        document: FPDF_DOCUMENT,
189        bindings: &'a dyn PdfiumLibraryBindings,
190    ) -> Result<Self, PdfiumError> {
191        let handle = bindings.FPDFPageObj_NewImageObj(document);
192
193        if handle.is_null() {
194            Err(PdfiumError::PdfiumLibraryInternalError(
195                PdfiumInternalError::Unknown,
196            ))
197        } else {
198            Ok(PdfPageImageObject {
199                object_handle: handle,
200                ownership: PdfPageObjectOwnership::unowned(),
201                bindings,
202            })
203        }
204    }
205
206    /// Creates a new [PdfPageImageObject] from the given arguments. The page object will be scaled
207    /// horizontally to match the given width; its height will be adjusted to maintain the aspect
208    /// ratio of the given image. The returned page object will not be rendered until it is
209    /// added to a [PdfPage] using the `PdfPageObjects::add_image_object()` function.
210    ///
211    /// This function is only available when this crate's `image` feature is enabled.
212    #[cfg(feature = "image_api")]
213    pub fn new_with_width(
214        document: &PdfDocument<'a>,
215        image: &DynamicImage,
216        width: PdfPoints,
217    ) -> Result<Self, PdfiumError> {
218        let aspect_ratio = image.height() as f32 / image.width() as f32;
219
220        let height = width * aspect_ratio;
221
222        Self::new_with_size(document, image, width, height)
223    }
224
225    /// Creates a new [PdfPageImageObject] from the given arguments. The page object will be scaled
226    /// vertically to match the given height; its width will be adjusted to maintain the aspect
227    /// ratio of the given image. The returned page object will not be rendered until it is
228    /// added to a [PdfPage] using the `PdfPageObjects::add_image_object()` function.
229    ///
230    /// This function is only available when this crate's `image` feature is enabled.
231    #[cfg(feature = "image_api")]
232    pub fn new_with_height(
233        document: &PdfDocument<'a>,
234        image: &DynamicImage,
235        height: PdfPoints,
236    ) -> Result<Self, PdfiumError> {
237        let aspect_ratio = image.height() as f32 / image.width() as f32;
238
239        let width = height / aspect_ratio;
240
241        Self::new_with_size(document, image, width, height)
242    }
243
244    /// Creates a new [PdfPageImageObject] from the given arguments. The page object will be scaled to
245    /// match the given width and height. The returned page object will not be rendered until it is
246    /// added to a [PdfPage] using the `PdfPageObjects::add_image_object()` function.
247    ///
248    /// This function is only available when this crate's `image` feature is enabled.
249    #[cfg(feature = "image_api")]
250    #[inline]
251    pub fn new_with_size(
252        document: &PdfDocument<'a>,
253        image: &DynamicImage,
254        width: PdfPoints,
255        height: PdfPoints,
256    ) -> Result<Self, PdfiumError> {
257        let mut result = Self::new(document, image)?;
258
259        result.scale(width.value, height.value)?;
260
261        Ok(result)
262    }
263
264    /// Returns a new [PdfBitmap] created from the bitmap buffer backing
265    /// this [PdfPageImageObject], ignoring any image filters, image mask, or object
266    /// transforms applied to this page object.
267    pub fn get_raw_bitmap(&self) -> Result<PdfBitmap, PdfiumError> {
268        Ok(PdfBitmap::from_pdfium(
269            self.bindings().FPDFImageObj_GetBitmap(self.object_handle()),
270            self.bindings(),
271        ))
272    }
273
274    /// Returns a new [DynamicImage] created from the bitmap buffer backing
275    /// this [PdfPageImageObject], ignoring any image filters, image mask, or object
276    /// transforms applied to this page object.
277    ///
278    /// This function is only available when this crate's `image` feature is enabled.
279    #[cfg(feature = "image_api")]
280    #[inline]
281    pub fn get_raw_image(&self) -> Result<DynamicImage, PdfiumError> {
282        self.get_image_from_bitmap(&self.get_raw_bitmap()?)
283    }
284
285    /// Returns a new [PdfBitmap] created from the bitmap buffer backing
286    /// this [PdfPageImageObject], taking into account any image filters, image mask, and
287    /// object transforms applied to this page object.
288    #[inline]
289    pub fn get_processed_bitmap(&self, document: &PdfDocument) -> Result<PdfBitmap, PdfiumError> {
290        let (width, height) = self.get_current_width_and_height_from_metadata()?;
291
292        self.get_processed_bitmap_with_size(document, width, height)
293    }
294
295    /// Returns a new [DynamicImage] created from the bitmap buffer backing
296    /// this [PdfPageImageObject], taking into account any image filters, image mask, and
297    /// object transforms applied to this page object.
298    ///
299    /// This function is only available when this crate's `image` feature is enabled.
300    #[cfg(feature = "image_api")]
301    #[inline]
302    pub fn get_processed_image(&self, document: &PdfDocument) -> Result<DynamicImage, PdfiumError> {
303        let (width, height) = self.get_current_width_and_height_from_metadata()?;
304
305        self.get_processed_image_with_size(document, width, height)
306    }
307
308    /// Returns a new [PdfBitmap] created from the bitmap buffer backing
309    /// this [PdfPageImageObject], taking into account any image filters, image mask, and
310    /// object transforms applied to this page object.
311    ///
312    /// The returned bitmap will be scaled during rendering so its width matches the given target width.
313    #[inline]
314    pub fn get_processed_bitmap_with_width(
315        &self,
316        document: &PdfDocument,
317        width: Pixels,
318    ) -> Result<PdfBitmap, PdfiumError> {
319        let (current_width, current_height) = self.get_current_width_and_height_from_metadata()?;
320
321        let aspect_ratio = current_width as f32 / current_height as f32;
322
323        self.get_processed_bitmap_with_size(
324            document,
325            width,
326            ((width as f32 * aspect_ratio) as u32)
327                .try_into()
328                .map_err(|_| PdfiumError::ImageSizeOutOfBounds)?,
329        )
330    }
331
332    /// Returns a new [DynamicImage] created from the bitmap buffer backing
333    /// this [PdfPageImageObject], taking into account any image filters, image mask, and
334    /// object transforms applied to this page object.
335    ///
336    /// The returned image will be scaled during rendering so its width matches the given target width.
337    ///
338    /// This function is only available when this crate's `image` feature is enabled.
339    #[cfg(feature = "image_api")]
340    #[inline]
341    pub fn get_processed_image_with_width(
342        &self,
343        document: &PdfDocument,
344        width: Pixels,
345    ) -> Result<DynamicImage, PdfiumError> {
346        let (current_width, current_height) = self.get_current_width_and_height_from_metadata()?;
347
348        let aspect_ratio = current_width as f32 / current_height as f32;
349
350        self.get_processed_image_with_size(
351            document,
352            width,
353            ((width as f32 * aspect_ratio) as u32)
354                .try_into()
355                .map_err(|_| PdfiumError::ImageSizeOutOfBounds)?,
356        )
357    }
358
359    /// Returns a new [PdfBitmap] created from the bitmap buffer backing
360    /// this [PdfPageImageObject], taking into account any image filters, image mask, and
361    /// object transforms applied to this page object.
362    ///
363    /// The returned bitmap will be scaled during rendering so its height matches the given target height.
364    #[inline]
365    pub fn get_processed_bitmap_with_height(
366        &self,
367        document: &PdfDocument,
368        height: Pixels,
369    ) -> Result<PdfBitmap, PdfiumError> {
370        let (current_width, current_height) = self.get_current_width_and_height_from_metadata()?;
371
372        let aspect_ratio = current_width as f32 / current_height as f32;
373
374        self.get_processed_bitmap_with_size(
375            document,
376            ((height as f32 / aspect_ratio) as u32)
377                .try_into()
378                .map_err(|_| PdfiumError::ImageSizeOutOfBounds)?,
379            height,
380        )
381    }
382
383    /// Returns a new [DynamicImage] created from the bitmap buffer backing
384    /// this [PdfPageImageObject], taking into account any image filters, image mask, and
385    /// object transforms applied to this page object.
386    ///
387    /// The returned image will be scaled during rendering so its height matches the given target height.
388    ///
389    /// This function is only available when this crate's `image` feature is enabled.
390    #[cfg(feature = "image_api")]
391    #[inline]
392    pub fn get_processed_image_with_height(
393        &self,
394        document: &PdfDocument,
395        height: Pixels,
396    ) -> Result<DynamicImage, PdfiumError> {
397        let (current_width, current_height) = self.get_current_width_and_height_from_metadata()?;
398
399        let aspect_ratio = current_width as f32 / current_height as f32;
400
401        self.get_processed_image_with_size(
402            document,
403            ((height as f32 / aspect_ratio) as u32)
404                .try_into()
405                .map_err(|_| PdfiumError::ImageSizeOutOfBounds)?,
406            height,
407        )
408    }
409
410    /// Returns a new [PdfBitmap] created from the bitmap buffer backing
411    /// this [PdfPageImageObject], taking into account any image filters, image mask, and
412    /// object transforms applied to this page object.
413    ///
414    /// The returned bitmap will be scaled during rendering so its width and height match
415    /// the given target dimensions.
416    pub fn get_processed_bitmap_with_size(
417        &self,
418        document: &PdfDocument,
419        width: Pixels,
420        height: Pixels,
421    ) -> Result<PdfBitmap, PdfiumError> {
422        // We attempt to work around two separate problems in Pdfium's
423        // FPDFImageObj_GetRenderedBitmap() function.
424
425        // First, the call to FPDFImageObj_GetRenderedBitmap() can fail, returning
426        // a null FPDF_BITMAP handle, if the image object's transformation matrix includes
427        // negative values for either the matrix.a or matrix.d values. We flip those values
428        // in the transformation matrix if they are negative, and we make sure we return them
429        // to their original values before we return to the caller.
430
431        // Second, Pdfium seems to often return a rendered bitmap that is much smaller
432        // than the image object's metadata suggests. We look at the dimensions of the bitmap
433        // returned from FPDFImageObj_GetRenderedBitmap(), and we apply a scale factor to the
434        // image object's transformation matrix if the bitmap is not the expected size.
435
436        // For more details, see: https://github.com/ajrcarey/pdfium-render/issues/52
437
438        let mut matrix = self.matrix()?;
439
440        let original_matrix = matrix; // We'll reset the matrix to this before we return.
441
442        // Ensure the matrix.a and matrix.d values are not negative.
443
444        if matrix.a() < 0f32 {
445            matrix.set_a(-matrix.a());
446            self.reset_matrix_impl(matrix)?;
447        }
448
449        if matrix.d() < 0f32 {
450            matrix.set_d(-matrix.d());
451            self.reset_matrix_impl(matrix)?;
452        }
453
454        let page_handle = match self.ownership() {
455            PdfPageObjectOwnership::Page(ownership) => Some(ownership.page_handle()),
456            PdfPageObjectOwnership::AttachedAnnotation(ownership) => Some(ownership.page_handle()),
457            _ => None,
458        };
459
460        let bitmap_handle = match page_handle {
461            Some(page_handle) => self.bindings().FPDFImageObj_GetRenderedBitmap(
462                document.handle(),
463                page_handle,
464                self.object_handle(),
465            ),
466            None => self.bindings.FPDFImageObj_GetRenderedBitmap(
467                document.handle(),
468                std::ptr::null_mut::<fpdf_page_t__>(),
469                self.object_handle(),
470            ),
471        };
472
473        if bitmap_handle.is_null() {
474            // Restore the original transformation matrix values before we return the error
475            // to the caller.
476
477            self.reset_matrix_impl(original_matrix)?;
478            return Err(PdfiumError::PdfiumLibraryInternalError(
479                PdfiumInternalError::Unknown,
480            ));
481        }
482
483        let result = PdfBitmap::from_pdfium(bitmap_handle, self.bindings());
484
485        if width == result.width() && height == result.height() {
486            // The bitmap generated by Pdfium is already at the caller's requested dimensions.
487            // Restore the original transformation matrix values before we return to the caller.
488
489            self.reset_matrix_impl(original_matrix)?;
490
491            Ok(result)
492        } else {
493            // The bitmap generated by Pdfium is not at the caller's requested dimensions.
494            // We apply a scale transform to the page object to encourage Pdfium to generate
495            // a bitmap matching the caller's requested dimensions.
496
497            self.transform_impl(
498                width as PdfMatrixValue / result.width() as PdfMatrixValue,
499                0.0,
500                0.0,
501                height as PdfMatrixValue / result.height() as PdfMatrixValue,
502                0.0,
503                0.0,
504            )?;
505
506            // Generate the bitmap again at the new scale.
507
508            let result = PdfBitmap::from_pdfium(
509                match page_handle {
510                    Some(page_handle) => self.bindings().FPDFImageObj_GetRenderedBitmap(
511                        document.handle(),
512                        page_handle,
513                        self.object_handle(),
514                    ),
515                    None => self.bindings.FPDFImageObj_GetRenderedBitmap(
516                        document.handle(),
517                        std::ptr::null_mut::<fpdf_page_t__>(),
518                        self.object_handle(),
519                    ),
520                },
521                self.bindings,
522            );
523
524            // Restore the original transformation matrix values before we return to the caller.
525
526            self.reset_matrix_impl(original_matrix)?;
527
528            Ok(result)
529        }
530    }
531
532    /// Returns a new [DynamicImage] created from the bitmap buffer backing
533    /// this [PdfPageImageObject], taking into account any image filters, image mask, and
534    /// object transforms applied to this page object.
535    ///
536    /// The returned image will be scaled during rendering so its width and height match
537    /// the given target dimensions.
538    ///
539    /// This function is only available when this crate's `image` feature is enabled.
540    #[cfg(feature = "image_api")]
541    #[inline]
542    pub fn get_processed_image_with_size(
543        &self,
544        document: &PdfDocument,
545        width: Pixels,
546        height: Pixels,
547    ) -> Result<DynamicImage, PdfiumError> {
548        self.get_processed_bitmap_with_size(document, width, height)
549            .and_then(|bitmap| self.get_image_from_bitmap(&bitmap))
550    }
551
552    #[cfg(feature = "image_api")]
553    pub(crate) fn get_image_from_bitmap(
554        &self,
555        bitmap: &PdfBitmap,
556    ) -> Result<DynamicImage, PdfiumError> {
557        let handle = *bitmap.handle();
558
559        let width = self.bindings.FPDFBitmap_GetWidth(handle);
560
561        let height = self.bindings.FPDFBitmap_GetHeight(handle);
562
563        let stride = self.bindings.FPDFBitmap_GetStride(handle);
564
565        let format =
566            PdfBitmapFormat::from_pdfium(self.bindings.FPDFBitmap_GetFormat(handle) as u32)?;
567
568        #[cfg(not(target_arch = "wasm32"))]
569        let buffer = self.bindings.FPDFBitmap_GetBuffer_as_slice(handle);
570
571        #[cfg(target_arch = "wasm32")]
572        let buffer_vec = self.bindings.FPDFBitmap_GetBuffer_as_vec(handle);
573        #[cfg(target_arch = "wasm32")]
574        let buffer = buffer_vec.as_slice();
575
576        match format {
577            #[allow(deprecated)]
578            PdfBitmapFormat::BGRA | PdfBitmapFormat::BRGx | PdfBitmapFormat::BGRx => {
579                RgbaImage::from_raw(width as u32, height as u32, bgra_to_rgba(buffer))
580                    .map(DynamicImage::ImageRgba8)
581            }
582            PdfBitmapFormat::BGR => RgbaImage::from_raw(
583                width as u32,
584                height as u32,
585                aligned_bgr_to_rgba(buffer, width as usize, stride as usize),
586            )
587            .map(DynamicImage::ImageRgba8),
588            PdfBitmapFormat::Gray => {
589                GrayImage::from_raw(width as u32, height as u32, buffer.to_vec())
590                    .map(DynamicImage::ImageLuma8)
591            }
592        }
593        .ok_or(PdfiumError::ImageError)
594    }
595
596    /// Return the expected pixel width and height of the processed image from Pdfium's metadata.
597    pub(crate) fn get_current_width_and_height_from_metadata(
598        &self,
599    ) -> Result<(Pixels, Pixels), PdfiumError> {
600        let width = self.get_raw_metadata().and_then(|metadata| {
601            metadata
602                .width
603                .try_into()
604                .map_err(|_| PdfiumError::ImageSizeOutOfBounds)
605        })?;
606
607        let height = self.get_raw_metadata().and_then(|metadata| {
608            metadata
609                .height
610                .try_into()
611                .map_err(|_| PdfiumError::ImageSizeOutOfBounds)
612        })?;
613
614        Ok((width, height))
615    }
616
617    /// Applies the byte data in the given [DynamicImage] to this [PdfPageImageObject].
618    ///
619    /// This function is only available when this crate's `image` feature is enabled.
620    #[cfg(feature = "image_api")]
621    pub fn set_image(&mut self, image: &DynamicImage) -> Result<(), PdfiumError> {
622        let width: Pixels = image
623            .width()
624            .try_into()
625            .map_err(|_| PdfiumError::ImageSizeOutOfBounds)?;
626
627        let height: Pixels = image
628            .height()
629            .try_into()
630            .map_err(|_| PdfiumError::ImageSizeOutOfBounds)?;
631
632        let bitmap = PdfBitmap::empty(width, height, PdfBitmapFormat::BGRA, self.bindings)?;
633
634        let buffer = if let Some(image) = image.as_rgba8() {
635            // The given image is already in RGBA format.
636
637            rgba_to_bgra(image.as_bytes())
638        } else {
639            // The image must be converted to RGBA first.
640
641            let image = image.to_rgba8();
642
643            rgba_to_bgra(image.as_bytes())
644        };
645
646        if !self
647            .bindings
648            .FPDFBitmap_SetBuffer(*bitmap.handle(), buffer.as_slice())
649        {
650            return Err(PdfiumError::PdfiumLibraryInternalError(
651                PdfiumInternalError::Unknown,
652            ));
653        }
654
655        self.set_bitmap(&bitmap)
656    }
657
658    /// Applies the byte data in the given [PdfBitmap] to this [PdfPageImageObject].
659    pub fn set_bitmap(&mut self, bitmap: &PdfBitmap) -> Result<(), PdfiumError> {
660        if self
661            .bindings
662            .is_true(self.bindings().FPDFImageObj_SetBitmap(
663                std::ptr::null_mut::<FPDF_PAGE>(),
664                0,
665                self.object_handle(),
666                *bitmap.handle(),
667            ))
668        {
669            Ok(())
670        } else {
671            Err(PdfiumError::PdfiumLibraryInternalError(
672                PdfiumInternalError::Unknown,
673            ))
674        }
675    }
676
677    /// Returns all internal metadata for this [PdfPageImageObject].
678    pub(crate) fn get_raw_metadata(&self) -> Result<FPDF_IMAGEOBJ_METADATA, PdfiumError> {
679        let mut metadata = FPDF_IMAGEOBJ_METADATA {
680            width: 0,
681            height: 0,
682            horizontal_dpi: 0.0,
683            vertical_dpi: 0.0,
684            bits_per_pixel: 0,
685            colorspace: 0,
686            marked_content_id: 0,
687        };
688
689        let page_handle = match self.ownership() {
690            PdfPageObjectOwnership::Page(ownership) => Some(ownership.page_handle()),
691            PdfPageObjectOwnership::AttachedAnnotation(ownership) => Some(ownership.page_handle()),
692            _ => None,
693        };
694
695        let result = self.bindings().FPDFImageObj_GetImageMetadata(
696            self.object_handle(),
697            match page_handle {
698                Some(page_handle) => page_handle,
699                None => std::ptr::null_mut::<fpdf_page_t__>(),
700            },
701            &mut metadata,
702        );
703
704        if self.bindings().is_true(result) {
705            Ok(metadata)
706        } else {
707            Err(PdfiumError::PdfiumLibraryInternalError(
708                PdfiumInternalError::Unknown,
709            ))
710        }
711    }
712
713    /// Returns the horizontal dots per inch resolution of the image assigned to this
714    /// [PdfPageImageObject], based on the intrinsic resolution of the assigned image
715    /// and the dimensions of this object.
716    #[inline]
717    pub fn horizontal_dpi(&self) -> Result<f32, PdfiumError> {
718        self.get_raw_metadata()
719            .map(|metadata| metadata.horizontal_dpi)
720    }
721
722    /// Returns the vertical dots per inch resolution of the image assigned to this
723    /// [PdfPageImageObject], based on the intrinsic resolution of the assigned image
724    /// and the dimensions of this object.
725    #[inline]
726    pub fn vertical_dpi(&self) -> Result<f32, PdfiumError> {
727        self.get_raw_metadata()
728            .map(|metadata| metadata.vertical_dpi)
729    }
730
731    /// Returns the bits per pixel for the image assigned to this [PdfPageImageObject].
732    ///
733    /// This value is not available if this object has not been attached to a `PdfPage`.
734    #[inline]
735    pub fn bits_per_pixel(&self) -> Result<u8, PdfiumError> {
736        self.get_raw_metadata()
737            .map(|metadata| metadata.bits_per_pixel as u8)
738    }
739
740    /// Returns the color space for the image assigned to this [PdfPageImageObject].
741    ///
742    /// This value is not available if this object has not been attached to a `PdfPage`.
743    #[inline]
744    pub fn color_space(&self) -> Result<PdfColorSpace, PdfiumError> {
745        self.get_raw_metadata()
746            .and_then(|metadata| PdfColorSpace::from_pdfium(metadata.colorspace as u32))
747    }
748
749    /// Returns the collection of image filters currently applied to this [PdfPageImageObject].
750    #[inline]
751    pub fn filters(&self) -> PdfPageImageObjectFilters {
752        PdfPageImageObjectFilters::new(self)
753    }
754
755    create_transform_setters!(
756        &mut Self,
757        Result<(), PdfiumError>,
758        "this [PdfPageImageObject]",
759        "this [PdfPageImageObject].",
760        "this [PdfPageImageObject],"
761    );
762
763    // The transform_impl() function required by the create_transform_setters!() macro
764    // is provided by the PdfPageObjectPrivate trait.
765
766    create_transform_getters!(
767        "this [PdfPageImageObject]",
768        "this [PdfPageImageObject].",
769        "this [PdfPageImageObject],"
770    );
771
772    // The get_matrix_impl() function required by the create_transform_getters!() macro
773    // is provided by the PdfPageObjectPrivate trait.
774}
775
776impl<'a> PdfPageObjectPrivate<'a> for PdfPageImageObject<'a> {
777    #[inline]
778    fn object_handle(&self) -> FPDF_PAGEOBJECT {
779        self.object_handle
780    }
781
782    #[inline]
783    fn ownership(&self) -> &PdfPageObjectOwnership {
784        &self.ownership
785    }
786
787    #[inline]
788    fn set_ownership(&mut self, ownership: PdfPageObjectOwnership) {
789        self.ownership = ownership;
790    }
791
792    #[inline]
793    fn bindings(&self) -> &dyn PdfiumLibraryBindings {
794        self.bindings
795    }
796
797    #[inline]
798    fn is_copyable_impl(&self) -> bool {
799        // Image filters cannot be copied.
800
801        self.filters().is_empty()
802    }
803
804    #[inline]
805    fn try_copy_impl<'b>(
806        &self,
807        document: FPDF_DOCUMENT,
808        bindings: &'b dyn PdfiumLibraryBindings,
809    ) -> Result<PdfPageObject<'b>, PdfiumError> {
810        if !self.filters().is_empty() {
811            // Image filters cannot be copied.
812
813            return Err(PdfiumError::ImageObjectFiltersNotCopyable);
814        }
815
816        let mut copy = PdfPageImageObject::new_from_handle(document, bindings)?;
817
818        copy.set_bitmap(&self.get_raw_bitmap()?)?;
819        copy.reset_matrix(self.matrix()?)?;
820
821        Ok(PdfPageObject::Image(copy))
822    }
823}
824
825/// The zero-based index of a single [PdfPageImageObjectFilter] inside its containing
826/// [PdfPageImageObjectFilters] collection.
827pub type PdfPageImageObjectFilterIndex = usize;
828
829/// A collection of all the image filters applied to a [PdfPageImageObject].
830pub struct PdfPageImageObjectFilters<'a> {
831    object: &'a PdfPageImageObject<'a>,
832}
833
834impl<'a> PdfPageImageObjectFilters<'a> {
835    #[inline]
836    pub(crate) fn new(object: &'a PdfPageImageObject<'a>) -> Self {
837        PdfPageImageObjectFilters { object }
838    }
839
840    /// Returns the number of image filters applied to the parent [PdfPageImageObject].
841    pub fn len(&self) -> usize {
842        self.object
843            .bindings()
844            .FPDFImageObj_GetImageFilterCount(self.object.object_handle()) as usize
845    }
846
847    /// Returns true if this [PdfPageImageObjectFilters] collection is empty.
848    #[inline]
849    pub fn is_empty(&self) -> bool {
850        self.len() == 0
851    }
852
853    /// Returns a Range from `0..(number of filters)` for this [PdfPageImageObjectFilters] collection.
854    #[inline]
855    pub fn as_range(&self) -> Range<PdfPageImageObjectFilterIndex> {
856        0..self.len()
857    }
858
859    /// Returns an inclusive Range from `0..=(number of filters - 1)` for this [PdfPageImageObjectFilters] collection.
860    #[inline]
861    pub fn as_range_inclusive(&self) -> RangeInclusive<PdfPageImageObjectFilterIndex> {
862        if self.is_empty() {
863            0..=0
864        } else {
865            0..=(self.len() - 1)
866        }
867    }
868
869    /// Returns a single [PdfPageImageObjectFilter] from this [PdfPageImageObjectFilters] collection.
870    pub fn get(
871        &self,
872        index: PdfPageImageObjectFilterIndex,
873    ) -> Result<PdfPageImageObjectFilter, PdfiumError> {
874        if index >= self.len() {
875            return Err(PdfiumError::ImageObjectFilterIndexOutOfBounds);
876        }
877
878        // Retrieving the image filter name from Pdfium is a two-step operation. First, we call
879        // FPDFImageObj_GetImageFilter() with a null buffer; this will retrieve the length of
880        // the image filter name in bytes. If the length is zero, then there is no image filter name.
881
882        // If the length is non-zero, then we reserve a byte buffer of the given
883        // length and call FPDFImageObj_GetImageFilter() again with a pointer to the buffer;
884        // this will write the font name into the buffer. Unlike most text handling in
885        // Pdfium, image filter names are returned in UTF-8 format.
886
887        let buffer_length = self.object.bindings().FPDFImageObj_GetImageFilter(
888            self.object.object_handle(),
889            index as c_int,
890            std::ptr::null_mut(),
891            0,
892        );
893
894        if buffer_length == 0 {
895            // The image filter name is not present.
896
897            return Err(PdfiumError::ImageObjectFilterIndexInBoundsButFilterUndefined);
898        }
899
900        let mut buffer = create_byte_buffer(buffer_length as usize);
901
902        let result = self.object.bindings().FPDFImageObj_GetImageFilter(
903            self.object.object_handle(),
904            index as c_int,
905            buffer.as_mut_ptr() as *mut c_void,
906            buffer_length,
907        );
908
909        assert_eq!(result, buffer_length);
910
911        Ok(PdfPageImageObjectFilter::new(
912            String::from_utf8(buffer)
913                // Trim any trailing nulls. All strings returned from Pdfium are generally terminated
914                // by one null byte.
915                .map(|str| str.trim_end_matches(char::from(0)).to_owned())
916                .unwrap_or_default(),
917        ))
918    }
919
920    /// Returns an iterator over all the [PdfPageImageObjectFilter] objects in this
921    /// [PdfPageImageObjectFilters] collection.
922    #[inline]
923    pub fn iter(&self) -> PdfPageImageObjectFiltersIterator {
924        PdfPageImageObjectFiltersIterator::new(self)
925    }
926}
927
928/// A single image filter applied to a [PdfPageImageObject].
929pub struct PdfPageImageObjectFilter {
930    name: String,
931}
932
933impl PdfPageImageObjectFilter {
934    #[inline]
935    pub(crate) fn new(name: String) -> Self {
936        PdfPageImageObjectFilter { name }
937    }
938
939    /// Returns the name of this [PdfPageImageObjectFilter].
940    pub fn name(&self) -> &str {
941        self.name.as_str()
942    }
943}
944
945/// An iterator over all the [PdfPageImageObjectFilter] objects in a
946/// [PdfPageImageObjectFilters] collection.
947pub struct PdfPageImageObjectFiltersIterator<'a> {
948    filters: &'a PdfPageImageObjectFilters<'a>,
949    next_index: PdfPageImageObjectFilterIndex,
950}
951
952impl<'a> PdfPageImageObjectFiltersIterator<'a> {
953    #[inline]
954    pub(crate) fn new(filters: &'a PdfPageImageObjectFilters<'a>) -> Self {
955        PdfPageImageObjectFiltersIterator {
956            filters,
957            next_index: 0,
958        }
959    }
960}
961
962impl<'a> Iterator for PdfPageImageObjectFiltersIterator<'a> {
963    type Item = PdfPageImageObjectFilter;
964
965    fn next(&mut self) -> Option<Self::Item> {
966        let next = self.filters.get(self.next_index);
967
968        self.next_index += 1;
969
970        next.ok()
971    }
972}
973
974#[cfg(test)]
975mod tests {
976    use crate::prelude::*;
977    use crate::utils::test::test_bind_to_pdfium;
978
979    #[test]
980    fn test_page_image_object_retains_format() -> Result<(), PdfiumError> {
981        // Make sure the format of the image we pass into a new PdfPageImageObject is the
982        // same when we later retrieve it.
983
984        let pdfium = test_bind_to_pdfium();
985
986        let image = pdfium
987            .load_pdf_from_file("./test/path-test.pdf", None)?
988            .pages()
989            .get(0)?
990            .render_with_config(&PdfRenderConfig::new().set_target_width(1000))?
991            .as_image();
992
993        let mut document = pdfium.create_new_pdf()?;
994
995        let mut page = document
996            .pages_mut()
997            .create_page_at_end(PdfPagePaperSize::a4())?;
998
999        let object = page.objects_mut().create_image_object(
1000            PdfPoints::new(100.0),
1001            PdfPoints::new(100.0),
1002            &image,
1003            Some(PdfPoints::new(image.width() as f32)),
1004            Some(PdfPoints::new(image.height() as f32)),
1005        )?;
1006
1007        // Since the object has no image filters applied, both the raw and processed images should
1008        // be identical to the source image we assigned to the object. The processed image will
1009        // take the object's scale factors into account, but we made sure to set those to the actual
1010        // pixel dimensions of the source image.
1011
1012        // A visual inspection can be carried out by uncommenting the PNG save commands below.
1013
1014        let raw_image = object.as_image_object().unwrap().get_raw_image()?;
1015
1016        // raw_image
1017        //     .save_with_format("./test/1.png", ImageFormat::Png)
1018        //     .unwrap();
1019
1020        let processed_image = object
1021            .as_image_object()
1022            .unwrap()
1023            .get_processed_image(&document)?;
1024
1025        // processed_image
1026        //     .save_with_format("./test/2.png", ImageFormat::Png)
1027        //     .unwrap();
1028
1029        assert!(compare_equality_of_byte_arrays(
1030            image.as_bytes(),
1031            raw_image.into_rgba8().as_raw().as_slice()
1032        ));
1033
1034        assert!(compare_equality_of_byte_arrays(
1035            image.as_bytes(),
1036            processed_image.into_rgba8().as_raw().as_slice()
1037        ));
1038
1039        Ok(())
1040    }
1041
1042    fn compare_equality_of_byte_arrays(a: &[u8], b: &[u8]) -> bool {
1043        if a.len() != b.len() {
1044            return false;
1045        }
1046
1047        for index in 0..a.len() {
1048            if a[index] != b[index] {
1049                return false;
1050            }
1051        }
1052
1053        true
1054    }
1055}