Skip to main content

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