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