rust_pdf_box/image/
mod.rs1use 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#[derive(Debug, Clone)]
9pub struct PDImageXObject {
10 pub width: u32,
11 pub height: u32,
12 pub image_id: ObjectId,
13}
14
15impl PDImageXObject {
16 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 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 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 pub fn register_with_page(&self, page: &mut PDPage) -> String {
101 page.resources.add_xobject("Im", self.image_id)
102 }
103}