Skip to main content

rust_pdf_box/image/
mod.rs

1use crate::cos::document::COSDocument;
2use crate::cos::object::{COSDictionary, COSName, COSStream, ObjectId};
3use crate::error::{PdfError, PdfResult};
4use crate::pd::page::PDPage;
5use image::{GenericImageView, ImageFormat};
6
7/// High-level Image XObject (/Subtype /Image).
8#[derive(Debug, Clone)]
9pub struct PDImageXObject {
10    pub width: u32,
11    pub height: u32,
12    pub image_id: ObjectId,
13}
14
15impl PDImageXObject {
16    /// Creates an Image XObject from JPEG bytes using native DCTDecode passthrough.
17    pub fn create_from_jpeg(cos_doc: &mut COSDocument, jpeg_bytes: &[u8]) -> PdfResult<Self> {
18        let dyn_img = image::load_from_memory_with_format(jpeg_bytes, ImageFormat::Jpeg)
19            .map_err(|e| PdfError::Image(format!("Failed parsing JPEG: {e}")))?;
20        let (width, height) = dyn_img.dimensions();
21
22        let mut dict = COSDictionary::new();
23        dict.set("Type", COSName::new("XObject"));
24        dict.set("Subtype", COSName::new("Image"));
25        dict.set("Width", width as i64);
26        dict.set("Height", height as i64);
27        dict.set("ColorSpace", COSName::new("DeviceRGB"));
28        dict.set("BitsPerComponent", 8);
29        dict.set("Filter", COSName::new("DCTDecode"));
30
31        let stream = COSStream::new(dict, jpeg_bytes.to_vec());
32        let image_id = cos_doc.add_object(stream);
33
34        Ok(Self {
35            width,
36            height,
37            image_id,
38        })
39    }
40
41    /// Creates an Image XObject from PNG or raw image bytes with transparency / alpha mask support.
42    pub fn create_from_image_bytes(cos_doc: &mut COSDocument, img_bytes: &[u8]) -> PdfResult<Self> {
43        let dyn_img = image::load_from_memory(img_bytes)
44            .map_err(|e| PdfError::Image(format!("Failed parsing image bytes: {e}")))?;
45
46        let (width, height) = dyn_img.dimensions();
47        let rgba = dyn_img.to_rgba8();
48
49        let mut rgb_data = Vec::with_capacity((width * height * 3) as usize);
50        let mut alpha_data = Vec::with_capacity((width * height) as usize);
51        let mut has_transparency = false;
52
53        for pixel in rgba.pixels() {
54            rgb_data.push(pixel[0]);
55            rgb_data.push(pixel[1]);
56            rgb_data.push(pixel[2]);
57            let a = pixel[3];
58            alpha_data.push(a);
59            if a < 255 {
60                has_transparency = true;
61            }
62        }
63
64        let mut dict = COSDictionary::new();
65        dict.set("Type", COSName::new("XObject"));
66        dict.set("Subtype", COSName::new("Image"));
67        dict.set("Width", width as i64);
68        dict.set("Height", height as i64);
69        dict.set("ColorSpace", COSName::new("DeviceRGB"));
70        dict.set("BitsPerComponent", 8);
71
72        // If image has alpha channel, create Soft Mask (/SMask)
73        if has_transparency {
74            let mut smask_dict = COSDictionary::new();
75            smask_dict.set("Type", COSName::new("XObject"));
76            smask_dict.set("Subtype", COSName::new("Image"));
77            smask_dict.set("Width", width as i64);
78            smask_dict.set("Height", height as i64);
79            smask_dict.set("ColorSpace", COSName::new("DeviceGray"));
80            smask_dict.set("BitsPerComponent", 8);
81
82            let mut smask_stream = COSStream::new(smask_dict, alpha_data);
83            let _ = smask_stream.encode_flate();
84            let smask_id = cos_doc.add_object(smask_stream);
85            dict.set("SMask", smask_id);
86        }
87
88        let mut stream = COSStream::new(dict, rgb_data);
89        let _ = stream.encode_flate();
90        let image_id = cos_doc.add_object(stream);
91
92        Ok(Self {
93            width,
94            height,
95            image_id,
96        })
97    }
98
99    /// Registers this image with a page and returns its resource name (e.g. "Im1").
100    pub fn register_with_page(&self, page: &mut PDPage) -> String {
101        page.resources.add_xobject("Im", self.image_id)
102    }
103}