1use crate::objects::{Dictionary, Object};
7use crate::{PdfError, Result};
8use std::fs::File;
9use std::io::Read;
10use std::path::Path;
11
12#[derive(Debug, Clone)]
14pub struct Image {
15 data: Vec<u8>,
17 format: ImageFormat,
19 width: u32,
21 height: u32,
23 color_space: ColorSpace,
25 bits_per_component: u8,
27 alpha_data: Option<Vec<u8>>,
29 soft_mask: Option<Box<Image>>,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq)]
35pub enum ImageFormat {
36 Jpeg,
38 Png,
40 Tiff,
42 Raw,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq)]
48pub enum MaskType {
49 Soft,
51 Stencil,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq)]
57pub enum ColorSpace {
58 DeviceGray,
60 DeviceRGB,
62 DeviceCMYK,
64}
65
66impl Image {
67 pub fn from_jpeg_file<P: AsRef<Path>>(path: P) -> Result<Self> {
69 let mut file = File::open(path)?;
70 let mut data = Vec::new();
71 file.read_to_end(&mut data)?;
72 Self::from_jpeg_data(data)
73 }
74
75 pub fn from_jpeg_data(data: Vec<u8>) -> Result<Self> {
77 let (width, height, color_space, bits_per_component) = parse_jpeg_header(&data)?;
79
80 Ok(Image {
81 data,
82 format: ImageFormat::Jpeg,
83 width,
84 height,
85 color_space,
86 bits_per_component,
87 alpha_data: None,
88 soft_mask: None,
89 })
90 }
91
92 pub fn from_png_file<P: AsRef<Path>>(path: P) -> Result<Self> {
94 let mut file = File::open(path)?;
95 let mut data = Vec::new();
96 file.read_to_end(&mut data)?;
97 Self::from_png_data(data)
98 }
99
100 pub fn from_png_data(data: Vec<u8>) -> Result<Self> {
102 use crate::graphics::png_decoder::{decode_png, PngColorType};
103
104 let decoded = decode_png(&data)?;
106
107 let color_space = match decoded.color_type {
109 PngColorType::Grayscale | PngColorType::GrayscaleAlpha => ColorSpace::DeviceGray,
110 PngColorType::Rgb | PngColorType::RgbAlpha | PngColorType::Palette => {
111 ColorSpace::DeviceRGB
112 }
113 };
114
115 let soft_mask = if let Some(alpha) = &decoded.alpha_data {
117 Some(Box::new(Image {
118 data: alpha.clone(),
119 format: ImageFormat::Raw,
120 width: decoded.width,
121 height: decoded.height,
122 color_space: ColorSpace::DeviceGray,
123 bits_per_component: 8,
124 alpha_data: None,
125 soft_mask: None,
126 }))
127 } else {
128 None
129 };
130
131 Ok(Image {
132 data, format: ImageFormat::Png, width: decoded.width,
135 height: decoded.height,
136 color_space,
137 bits_per_component: 8, alpha_data: decoded.alpha_data,
139 soft_mask,
140 })
141 }
142
143 pub fn from_tiff_file<P: AsRef<Path>>(path: P) -> Result<Self> {
145 let mut file = File::open(path)?;
146 let mut data = Vec::new();
147 file.read_to_end(&mut data)?;
148 Self::from_tiff_data(data)
149 }
150
151 pub fn from_tiff_data(data: Vec<u8>) -> Result<Self> {
153 let (width, height, color_space, bits_per_component) = parse_tiff_header(&data)?;
155
156 Ok(Image {
157 data,
158 format: ImageFormat::Tiff,
159 width,
160 height,
161 color_space,
162 bits_per_component,
163 alpha_data: None,
164 soft_mask: None,
165 })
166 }
167
168 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
181 let path = path.as_ref();
182 let ext = path
183 .extension()
184 .and_then(|e| e.to_str())
185 .map(|e| e.to_ascii_lowercase())
186 .unwrap_or_default();
187
188 match ext.as_str() {
189 "jpg" | "jpeg" => Self::from_jpeg_file(path),
190 "png" => Self::from_png_file(path),
191 "tif" | "tiff" => Self::from_tiff_file(path),
192 _ => Err(crate::PdfError::InvalidFormat(format!(
193 "Unsupported image format: .{ext}. Supported: jpg, jpeg, png, tif, tiff"
194 ))),
195 }
196 }
197
198 pub fn width(&self) -> u32 {
200 self.width
201 }
202
203 pub fn height(&self) -> u32 {
205 self.height
206 }
207
208 pub fn data(&self) -> &[u8] {
210 &self.data
211 }
212
213 pub fn format(&self) -> ImageFormat {
215 self.format
216 }
217
218 pub fn bits_per_component(&self) -> u8 {
220 self.bits_per_component
221 }
222
223 pub fn from_raw_data(
225 data: Vec<u8>,
226 width: u32,
227 height: u32,
228 color_space: ColorSpace,
229 bits_per_component: u8,
230 ) -> Self {
231 Image {
232 data,
233 format: ImageFormat::Raw,
234 width,
235 height,
236 color_space,
237 bits_per_component,
238 alpha_data: None,
239 soft_mask: None,
240 }
241 }
242
243 pub fn from_rgba_data(rgba_data: Vec<u8>, width: u32, height: u32) -> Result<Self> {
245 if rgba_data.len() != (width * height * 4) as usize {
246 return Err(PdfError::InvalidImage(
247 "RGBA data size doesn't match dimensions".to_string(),
248 ));
249 }
250
251 let mut rgb_data = Vec::with_capacity((width * height * 3) as usize);
253 let mut alpha_data = Vec::with_capacity((width * height) as usize);
254
255 for chunk in rgba_data.chunks(4) {
256 rgb_data.push(chunk[0]); rgb_data.push(chunk[1]); rgb_data.push(chunk[2]); alpha_data.push(chunk[3]); }
261
262 let soft_mask = Some(Box::new(Image {
264 data: alpha_data.clone(),
265 format: ImageFormat::Raw,
266 width,
267 height,
268 color_space: ColorSpace::DeviceGray,
269 bits_per_component: 8,
270 alpha_data: None,
271 soft_mask: None,
272 }));
273
274 Ok(Image {
275 data: rgb_data,
276 format: ImageFormat::Raw,
277 width,
278 height,
279 color_space: ColorSpace::DeviceRGB,
280 bits_per_component: 8,
281 alpha_data: Some(alpha_data),
282 soft_mask,
283 })
284 }
285
286 pub fn from_gray_data(gray_data: Vec<u8>, width: u32, height: u32) -> Result<Self> {
288 if gray_data.len() != (width * height) as usize {
289 return Err(PdfError::InvalidImage(
290 "Gray data size doesn't match dimensions".to_string(),
291 ));
292 }
293
294 Ok(Image {
295 data: gray_data,
296 format: ImageFormat::Raw,
297 width,
298 height,
299 color_space: ColorSpace::DeviceGray,
300 bits_per_component: 8,
301 alpha_data: None,
302 soft_mask: None,
303 })
304 }
305
306 #[cfg(feature = "external-images")]
308 pub fn from_file_raw<P: AsRef<Path>>(
309 path: P,
310 width: u32,
311 height: u32,
312 format: ImageFormat,
313 ) -> Result<Self> {
314 let data = std::fs::read(path)
315 .map_err(|e| PdfError::InvalidImage(format!("Failed to read image file: {}", e)))?;
316
317 Ok(Image {
318 data,
319 format,
320 width,
321 height,
322 color_space: ColorSpace::DeviceRGB,
323 bits_per_component: 8,
324 alpha_data: None,
325 soft_mask: None,
326 })
327 }
328
329 pub fn to_pdf_object(&self) -> Object {
331 let mut dict = Dictionary::new();
332
333 dict.set("Type", Object::Name("XObject".to_string()));
335 dict.set("Subtype", Object::Name("Image".to_string()));
336 dict.set("Width", Object::Integer(self.width as i64));
337 dict.set("Height", Object::Integer(self.height as i64));
338
339 let color_space_name = match self.color_space {
341 ColorSpace::DeviceGray => "DeviceGray",
342 ColorSpace::DeviceRGB => "DeviceRGB",
343 ColorSpace::DeviceCMYK => "DeviceCMYK",
344 };
345 dict.set("ColorSpace", Object::Name(color_space_name.to_string()));
346
347 dict.set(
349 "BitsPerComponent",
350 Object::Integer(self.bits_per_component as i64),
351 );
352
353 match self.format {
355 ImageFormat::Jpeg => {
356 dict.set("Filter", Object::Name("DCTDecode".to_string()));
357 }
358 ImageFormat::Png => {
359 dict.set("Filter", Object::Name("FlateDecode".to_string()));
360 }
361 ImageFormat::Tiff => {
362 dict.set("Filter", Object::Name("FlateDecode".to_string()));
364 }
365 ImageFormat::Raw => {
366 }
368 }
369
370 Object::Stream(dict, self.data.clone())
372 }
373
374 pub fn to_pdf_object_with_transparency(&self) -> Result<(Object, Option<Object>)> {
376 use flate2::write::ZlibEncoder;
377 use flate2::Compression;
378 use std::io::Write as IoWrite;
379
380 let mut main_dict = Dictionary::new();
381
382 main_dict.set("Type", Object::Name("XObject".to_string()));
384 main_dict.set("Subtype", Object::Name("Image".to_string()));
385 main_dict.set("Width", Object::Integer(self.width as i64));
386 main_dict.set("Height", Object::Integer(self.height as i64));
387
388 let color_space_name = match self.color_space {
390 ColorSpace::DeviceGray => "DeviceGray",
391 ColorSpace::DeviceRGB => "DeviceRGB",
392 ColorSpace::DeviceCMYK => "DeviceCMYK",
393 };
394 main_dict.set("ColorSpace", Object::Name(color_space_name.to_string()));
395
396 main_dict.set(
398 "BitsPerComponent",
399 Object::Integer(self.bits_per_component as i64),
400 );
401
402 let main_data = match self.format {
404 ImageFormat::Jpeg => {
405 main_dict.set("Filter", Object::Name("DCTDecode".to_string()));
406 self.data.clone()
407 }
408 ImageFormat::Png | ImageFormat::Raw => {
409 main_dict.set("Filter", Object::Name("FlateDecode".to_string()));
411 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
412 encoder.write_all(&self.data).map_err(|e| {
413 PdfError::InvalidImage(format!("Failed to compress image data: {}", e))
414 })?;
415 encoder.finish().map_err(|e| {
416 PdfError::InvalidImage(format!("Failed to finalize image compression: {}", e))
417 })?
418 }
419 ImageFormat::Tiff => {
420 main_dict.set("Filter", Object::Name("FlateDecode".to_string()));
421 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
422 encoder.write_all(&self.data).map_err(|e| {
423 PdfError::InvalidImage(format!("Failed to compress TIFF data: {}", e))
424 })?;
425 encoder.finish().map_err(|e| {
426 PdfError::InvalidImage(format!("Failed to finalize TIFF compression: {}", e))
427 })?
428 }
429 };
430
431 main_dict.set("Length", Object::Integer(main_data.len() as i64));
433
434 let smask_obj = if let Some(mask) = &self.soft_mask {
436 let mut mask_dict = Dictionary::new();
437 mask_dict.set("Type", Object::Name("XObject".to_string()));
438 mask_dict.set("Subtype", Object::Name("Image".to_string()));
439 mask_dict.set("Width", Object::Integer(mask.width as i64));
440 mask_dict.set("Height", Object::Integer(mask.height as i64));
441 mask_dict.set("ColorSpace", Object::Name("DeviceGray".to_string()));
442 mask_dict.set("BitsPerComponent", Object::Integer(8));
443 mask_dict.set("Filter", Object::Name("FlateDecode".to_string()));
444
445 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
447 encoder.write_all(&mask.data).map_err(|e| {
448 PdfError::InvalidImage(format!("Failed to compress alpha channel: {}", e))
449 })?;
450 let compressed_mask_data = encoder.finish().map_err(|e| {
451 PdfError::InvalidImage(format!("Failed to finalize alpha compression: {}", e))
452 })?;
453
454 mask_dict.set("Length", Object::Integer(compressed_mask_data.len() as i64));
456
457 Some(Object::Stream(mask_dict, compressed_mask_data))
458 } else {
459 None
460 };
461
462 Ok((Object::Stream(main_dict, main_data), smask_obj))
466 }
467
468 pub fn has_transparency(&self) -> bool {
470 self.soft_mask.is_some() || self.alpha_data.is_some()
471 }
472
473 pub fn create_stencil_mask(&self, threshold: u8) -> Option<Image> {
476 if let Some(alpha) = &self.alpha_data {
477 let mut mask_data = Vec::new();
479 let mut current_byte = 0u8;
480 let mut bit_count = 0;
481
482 for &alpha_value in alpha.iter() {
483 if alpha_value > threshold {
485 current_byte |= 1 << (7 - bit_count);
486 }
487
488 bit_count += 1;
489 if bit_count == 8 {
490 mask_data.push(current_byte);
491 current_byte = 0;
492 bit_count = 0;
493 }
494 }
495
496 if bit_count > 0 {
498 mask_data.push(current_byte);
499 }
500
501 Some(Image {
502 data: mask_data,
503 format: ImageFormat::Raw,
504 width: self.width,
505 height: self.height,
506 color_space: ColorSpace::DeviceGray,
507 bits_per_component: 1,
508 alpha_data: None,
509 soft_mask: None,
510 })
511 } else {
512 None
513 }
514 }
515
516 pub fn create_mask(&self, mask_type: MaskType, threshold: Option<u8>) -> Option<Image> {
518 match mask_type {
519 MaskType::Soft => self.soft_mask.as_ref().map(|m| m.as_ref().clone()),
520 MaskType::Stencil => self.create_stencil_mask(threshold.unwrap_or(128)),
521 }
522 }
523
524 pub fn with_mask(mut self, mask: Image, mask_type: MaskType) -> Self {
526 match mask_type {
527 MaskType::Soft => {
528 self.soft_mask = Some(Box::new(mask));
529 }
530 MaskType::Stencil => {
531 self.soft_mask = Some(Box::new(mask));
533 }
534 }
535 self
536 }
537
538 pub fn soft_mask(&self) -> Option<&Image> {
540 self.soft_mask.as_ref().map(|m| m.as_ref())
541 }
542
543 pub fn alpha_data(&self) -> Option<&[u8]> {
545 self.alpha_data.as_deref()
546 }
547}
548
549fn parse_jpeg_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
551 if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
552 return Err(PdfError::InvalidImage("Not a valid JPEG file".to_string()));
553 }
554
555 let mut pos = 2;
556 let mut width = 0;
557 let mut height = 0;
558 let mut components = 0;
559
560 while pos < data.len() - 1 {
561 if data[pos] != 0xFF {
562 return Err(PdfError::InvalidImage("Invalid JPEG marker".to_string()));
563 }
564
565 let marker = data[pos + 1];
566 pos += 2;
567
568 if marker == 0xFF {
570 continue;
571 }
572
573 if (0xC0..=0xCF).contains(&marker) && marker != 0xC4 && marker != 0xC8 && marker != 0xCC {
575 if pos + 7 >= data.len() {
577 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
578 }
579
580 pos += 2;
582
583 pos += 1;
585
586 height = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
588 pos += 2;
589 width = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
590 pos += 2;
591
592 components = data[pos];
594 break;
595 } else if marker == 0xD9 {
596 break;
598 } else if marker == 0xD8 || (0xD0..=0xD7).contains(&marker) {
599 continue;
601 } else {
602 if pos + 1 >= data.len() {
604 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
605 }
606 let length = ((data[pos] as usize) << 8) | (data[pos + 1] as usize);
607 pos += length;
608 }
609 }
610
611 if width == 0 || height == 0 {
612 return Err(PdfError::InvalidImage(
613 "Could not find image dimensions".to_string(),
614 ));
615 }
616
617 let color_space = match components {
618 1 => ColorSpace::DeviceGray,
619 3 => ColorSpace::DeviceRGB,
620 4 => ColorSpace::DeviceCMYK,
621 _ => {
622 return Err(PdfError::InvalidImage(format!(
623 "Unsupported number of components: {components}"
624 )))
625 }
626 };
627
628 Ok((width, height, color_space, 8)) }
630
631#[allow(dead_code)]
633fn parse_png_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
634 if data.len() < 8 || &data[0..8] != b"\x89PNG\r\n\x1a\n" {
636 return Err(PdfError::InvalidImage("Not a valid PNG file".to_string()));
637 }
638
639 let mut pos = 8;
641
642 while pos + 8 < data.len() {
643 let chunk_length =
645 u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
646
647 let chunk_type = &data[pos + 4..pos + 8];
649
650 if chunk_type == b"IHDR" {
651 if pos + 8 + chunk_length > data.len() || chunk_length < 13 {
653 return Err(PdfError::InvalidImage("Invalid PNG IHDR chunk".to_string()));
654 }
655
656 let ihdr_data = &data[pos + 8..pos + 8 + chunk_length];
657
658 let width =
660 u32::from_be_bytes([ihdr_data[0], ihdr_data[1], ihdr_data[2], ihdr_data[3]]);
661
662 let height =
663 u32::from_be_bytes([ihdr_data[4], ihdr_data[5], ihdr_data[6], ihdr_data[7]]);
664
665 let bit_depth = ihdr_data[8];
666 let color_type = ihdr_data[9];
667
668 let color_space = match color_type {
670 0 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 3 => ColorSpace::DeviceRGB, 4 => ColorSpace::DeviceGray, 6 => ColorSpace::DeviceRGB, _ => {
676 return Err(PdfError::InvalidImage(format!(
677 "Unsupported PNG color type: {color_type}"
678 )))
679 }
680 };
681
682 return Ok((width, height, color_space, bit_depth));
683 }
684
685 pos += 8 + chunk_length + 4; }
688
689 Err(PdfError::InvalidImage(
690 "PNG IHDR chunk not found".to_string(),
691 ))
692}
693
694fn parse_tiff_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
696 if data.len() < 8 {
697 return Err(PdfError::InvalidImage(
698 "Invalid TIFF file: too short".to_string(),
699 ));
700 }
701
702 let (is_little_endian, offset) = if &data[0..2] == b"II" {
704 (true, 2) } else if &data[0..2] == b"MM" {
706 (false, 2) } else {
708 return Err(PdfError::InvalidImage(
709 "Invalid TIFF byte order".to_string(),
710 ));
711 };
712
713 let magic = if is_little_endian {
715 u16::from_le_bytes([data[offset], data[offset + 1]])
716 } else {
717 u16::from_be_bytes([data[offset], data[offset + 1]])
718 };
719
720 if magic != 42 {
721 return Err(PdfError::InvalidImage(
722 "Invalid TIFF magic number".to_string(),
723 ));
724 }
725
726 let ifd_offset = if is_little_endian {
728 u32::from_le_bytes([
729 data[offset + 2],
730 data[offset + 3],
731 data[offset + 4],
732 data[offset + 5],
733 ])
734 } else {
735 u32::from_be_bytes([
736 data[offset + 2],
737 data[offset + 3],
738 data[offset + 4],
739 data[offset + 5],
740 ])
741 } as usize;
742
743 if ifd_offset + 2 > data.len() {
744 return Err(PdfError::InvalidImage(
745 "Invalid TIFF IFD offset".to_string(),
746 ));
747 }
748
749 let num_entries = if is_little_endian {
751 u16::from_le_bytes([data[ifd_offset], data[ifd_offset + 1]])
752 } else {
753 u16::from_be_bytes([data[ifd_offset], data[ifd_offset + 1]])
754 };
755
756 let mut width = 0u32;
757 let mut height = 0u32;
758 let mut bits_per_sample = 8u16;
759 let mut photometric_interpretation = 0u16;
760
761 for i in 0..num_entries {
763 let entry_offset = ifd_offset + 2 + (i as usize * 12);
764
765 if entry_offset + 12 > data.len() {
766 break;
767 }
768
769 let tag = if is_little_endian {
770 u16::from_le_bytes([data[entry_offset], data[entry_offset + 1]])
771 } else {
772 u16::from_be_bytes([data[entry_offset], data[entry_offset + 1]])
773 };
774
775 let value_offset = entry_offset + 8;
776
777 match tag {
778 256 => {
779 width = if is_little_endian {
781 u32::from_le_bytes([
782 data[value_offset],
783 data[value_offset + 1],
784 data[value_offset + 2],
785 data[value_offset + 3],
786 ])
787 } else {
788 u32::from_be_bytes([
789 data[value_offset],
790 data[value_offset + 1],
791 data[value_offset + 2],
792 data[value_offset + 3],
793 ])
794 };
795 }
796 257 => {
797 height = if is_little_endian {
799 u32::from_le_bytes([
800 data[value_offset],
801 data[value_offset + 1],
802 data[value_offset + 2],
803 data[value_offset + 3],
804 ])
805 } else {
806 u32::from_be_bytes([
807 data[value_offset],
808 data[value_offset + 1],
809 data[value_offset + 2],
810 data[value_offset + 3],
811 ])
812 };
813 }
814 258 => {
815 bits_per_sample = if is_little_endian {
817 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
818 } else {
819 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
820 };
821 }
822 262 => {
823 photometric_interpretation = if is_little_endian {
825 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
826 } else {
827 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
828 };
829 }
830 _ => {} }
832 }
833
834 if width == 0 || height == 0 {
835 return Err(PdfError::InvalidImage(
836 "TIFF dimensions not found".to_string(),
837 ));
838 }
839
840 let color_space = match photometric_interpretation {
842 0 | 1 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 5 => ColorSpace::DeviceCMYK, _ => ColorSpace::DeviceRGB, };
847
848 Ok((width, height, color_space, bits_per_sample as u8))
849}
850
851#[cfg(test)]
852mod tests {
853 use super::*;
854
855 fn create_minimal_png(width: u32, height: u32, color_type: u8) -> Vec<u8> {
857 match color_type {
860 0 => {
861 vec![
863 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x7E, 0x9B, 0x55, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00,
874 0x01, 0xE2, 0xF9, 0x8C, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
880 }
881 2 => {
882 vec![
884 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xDE, 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x04, 0x00,
895 0x01, 0x27, 0x18, 0xAA, 0x61, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
901 }
902 3 => {
903 vec![
905 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x03, 0x00, 0x00, 0x00, 0xDB, 0xB4, 0x05, 0x70, 0x00, 0x00, 0x00, 0x03, 0x50, 0x4C, 0x54, 0x45, 0xFF, 0x00, 0x00, 0x19, 0xE2, 0x09, 0x37, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00,
920 0x01, 0xE5, 0x27, 0xDE, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
926 }
927 6 => {
928 vec![
930 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0B, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0x63, 0x60, 0x00, 0x02, 0x00, 0x00, 0x05, 0x00,
941 0x01, 0x75, 0xAA, 0x50, 0x19, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
947 }
948 _ => {
949 create_minimal_png(width, height, 2)
951 }
952 }
953 }
954
955 #[test]
956 fn test_parse_jpeg_header() {
957 let jpeg_data = vec![
959 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, ];
968
969 let result = parse_jpeg_header(&jpeg_data);
970 assert!(result.is_ok());
971 let (width, height, color_space, bits) = result.unwrap();
972 assert_eq!(width, 200);
973 assert_eq!(height, 100);
974 assert_eq!(color_space, ColorSpace::DeviceRGB);
975 assert_eq!(bits, 8);
976 }
977
978 #[test]
979 fn test_invalid_jpeg() {
980 let invalid_data = vec![0x00, 0x00];
981 let result = parse_jpeg_header(&invalid_data);
982 assert!(result.is_err());
983 }
984
985 #[test]
986 fn test_parse_png_header() {
987 let mut png_data = vec![
989 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x08, 0x02, 0x00, 0x00, 0x00, ];
1000
1001 png_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
1003
1004 let result = parse_png_header(&png_data);
1005 assert!(result.is_ok());
1006 let (width, height, color_space, bits) = result.unwrap();
1007 assert_eq!(width, 100);
1008 assert_eq!(height, 100);
1009 assert_eq!(color_space, ColorSpace::DeviceRGB);
1010 assert_eq!(bits, 8);
1011 }
1012
1013 #[test]
1014 fn test_invalid_png() {
1015 let invalid_data = vec![0x00, 0x00];
1016 let result = parse_png_header(&invalid_data);
1017 assert!(result.is_err());
1018 }
1019
1020 #[test]
1021 fn test_parse_tiff_header_little_endian() {
1022 let tiff_data = vec![
1024 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
1030 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
1032 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1034 0x00, 0x00, ];
1036
1037 let result = parse_tiff_header(&tiff_data);
1038 assert!(result.is_ok());
1039 let (width, height, color_space, bits) = result.unwrap();
1040 assert_eq!(width, 100);
1041 assert_eq!(height, 100);
1042 assert_eq!(color_space, ColorSpace::DeviceGray);
1043 assert_eq!(bits, 8);
1044 }
1045
1046 #[test]
1047 fn test_parse_tiff_header_big_endian() {
1048 let tiff_data = vec![
1050 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
1056 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
1058 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
1060 0x00, 0x00, ];
1062
1063 let result = parse_tiff_header(&tiff_data);
1064 assert!(result.is_ok());
1065 let (width, height, color_space, bits) = result.unwrap();
1066 assert_eq!(width, 100);
1067 assert_eq!(height, 100);
1068 assert_eq!(color_space, ColorSpace::DeviceGray);
1069 assert_eq!(bits, 8);
1070 }
1071
1072 #[test]
1073 fn test_invalid_tiff() {
1074 let invalid_data = vec![0x00, 0x00];
1075 let result = parse_tiff_header(&invalid_data);
1076 assert!(result.is_err());
1077 }
1078
1079 #[test]
1080 fn test_image_format_enum() {
1081 assert_eq!(ImageFormat::Jpeg, ImageFormat::Jpeg);
1082 assert_eq!(ImageFormat::Png, ImageFormat::Png);
1083 assert_eq!(ImageFormat::Tiff, ImageFormat::Tiff);
1084 assert_ne!(ImageFormat::Jpeg, ImageFormat::Png);
1085 }
1086
1087 mod comprehensive_tests {
1089 use super::*;
1090 use std::fs;
1091 use tempfile::TempDir;
1092
1093 #[test]
1094 fn test_image_format_variants() {
1095 let jpeg = ImageFormat::Jpeg;
1097 let png = ImageFormat::Png;
1098 let tiff = ImageFormat::Tiff;
1099
1100 assert_eq!(jpeg, ImageFormat::Jpeg);
1101 assert_eq!(png, ImageFormat::Png);
1102 assert_eq!(tiff, ImageFormat::Tiff);
1103
1104 assert_ne!(jpeg, png);
1105 assert_ne!(png, tiff);
1106 assert_ne!(tiff, jpeg);
1107 }
1108
1109 #[test]
1110 fn test_image_format_debug() {
1111 let jpeg = ImageFormat::Jpeg;
1112 let png = ImageFormat::Png;
1113 let tiff = ImageFormat::Tiff;
1114
1115 assert_eq!(format!("{jpeg:?}"), "Jpeg");
1116 assert_eq!(format!("{png:?}"), "Png");
1117 assert_eq!(format!("{tiff:?}"), "Tiff");
1118 }
1119
1120 #[test]
1121 fn test_image_format_clone_copy() {
1122 let jpeg = ImageFormat::Jpeg;
1123 let jpeg_clone = jpeg;
1124 let jpeg_copy = jpeg;
1125
1126 assert_eq!(jpeg_clone, ImageFormat::Jpeg);
1127 assert_eq!(jpeg_copy, ImageFormat::Jpeg);
1128 }
1129
1130 #[test]
1131 fn test_color_space_variants() {
1132 let gray = ColorSpace::DeviceGray;
1134 let rgb = ColorSpace::DeviceRGB;
1135 let cmyk = ColorSpace::DeviceCMYK;
1136
1137 assert_eq!(gray, ColorSpace::DeviceGray);
1138 assert_eq!(rgb, ColorSpace::DeviceRGB);
1139 assert_eq!(cmyk, ColorSpace::DeviceCMYK);
1140
1141 assert_ne!(gray, rgb);
1142 assert_ne!(rgb, cmyk);
1143 assert_ne!(cmyk, gray);
1144 }
1145
1146 #[test]
1147 fn test_color_space_debug() {
1148 let gray = ColorSpace::DeviceGray;
1149 let rgb = ColorSpace::DeviceRGB;
1150 let cmyk = ColorSpace::DeviceCMYK;
1151
1152 assert_eq!(format!("{gray:?}"), "DeviceGray");
1153 assert_eq!(format!("{rgb:?}"), "DeviceRGB");
1154 assert_eq!(format!("{cmyk:?}"), "DeviceCMYK");
1155 }
1156
1157 #[test]
1158 fn test_color_space_clone_copy() {
1159 let rgb = ColorSpace::DeviceRGB;
1160 let rgb_clone = rgb;
1161 let rgb_copy = rgb;
1162
1163 assert_eq!(rgb_clone, ColorSpace::DeviceRGB);
1164 assert_eq!(rgb_copy, ColorSpace::DeviceRGB);
1165 }
1166
1167 #[test]
1168 fn test_image_from_jpeg_data() {
1169 let jpeg_data = vec![
1171 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1183
1184 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1185
1186 assert_eq!(image.width(), 200);
1187 assert_eq!(image.height(), 100);
1188 assert_eq!(image.format(), ImageFormat::Jpeg);
1189 assert_eq!(image.data(), jpeg_data);
1190 }
1191
1192 #[test]
1193 fn test_image_from_png_data() {
1194 let mut png_data = Vec::new();
1196
1197 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1199
1200 png_data.extend_from_slice(&[
1202 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, ]);
1212 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]);
1214
1215 let raw_data = vec![0x00, 0x00, 0x00, 0x00]; use flate2::write::ZlibEncoder;
1220 use flate2::Compression;
1221 use std::io::Write;
1222
1223 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1224 encoder.write_all(&raw_data).unwrap();
1225 let compressed_data = encoder.finish().unwrap();
1226
1227 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1229 png_data.extend_from_slice(b"IDAT");
1230 png_data.extend_from_slice(&compressed_data);
1231 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); png_data.extend_from_slice(&[
1235 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]);
1239
1240 let image = Image::from_png_data(png_data.clone()).unwrap();
1241
1242 assert_eq!(image.width(), 1);
1243 assert_eq!(image.height(), 1);
1244 assert_eq!(image.format(), ImageFormat::Png);
1245 assert_eq!(image.data(), png_data);
1246 }
1247
1248 #[test]
1249 fn test_image_from_tiff_data() {
1250 let tiff_data = vec![
1252 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
1258 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
1260 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1262 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
1264 0x00, 0x00, ];
1266
1267 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
1268
1269 assert_eq!(image.width(), 128);
1270 assert_eq!(image.height(), 128);
1271 assert_eq!(image.format(), ImageFormat::Tiff);
1272 assert_eq!(image.data(), tiff_data);
1273 }
1274
1275 #[test]
1276 fn test_image_from_jpeg_file() {
1277 let temp_dir = TempDir::new().unwrap();
1278 let file_path = temp_dir.path().join("test.jpg");
1279
1280 let jpeg_data = vec![
1282 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1294
1295 fs::write(&file_path, &jpeg_data).unwrap();
1296
1297 let image = Image::from_jpeg_file(&file_path).unwrap();
1298
1299 assert_eq!(image.width(), 100);
1300 assert_eq!(image.height(), 50);
1301 assert_eq!(image.format(), ImageFormat::Jpeg);
1302 assert_eq!(image.data(), jpeg_data);
1303 }
1304
1305 #[test]
1306 fn test_image_from_png_file() {
1307 let temp_dir = TempDir::new().unwrap();
1308 let file_path = temp_dir.path().join("test.png");
1309
1310 let mut png_data = Vec::new();
1312
1313 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1315
1316 png_data.extend_from_slice(&[
1318 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, ]);
1328 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]); let raw_data = vec![0x00, 0x00, 0x00, 0x00]; use flate2::write::ZlibEncoder;
1334 use flate2::Compression;
1335 use std::io::Write;
1336
1337 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1338 encoder.write_all(&raw_data).unwrap();
1339 let compressed_data = encoder.finish().unwrap();
1340
1341 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1342 png_data.extend_from_slice(b"IDAT");
1343 png_data.extend_from_slice(&compressed_data);
1344 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); png_data.extend_from_slice(&[
1348 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]);
1352
1353 fs::write(&file_path, &png_data).unwrap();
1354
1355 let image = Image::from_png_file(&file_path).unwrap();
1356
1357 assert_eq!(image.width(), 1);
1358 assert_eq!(image.height(), 1);
1359 assert_eq!(image.format(), ImageFormat::Png);
1360 assert_eq!(image.data(), png_data);
1361 }
1362
1363 #[test]
1364 fn test_image_from_tiff_file() {
1365 let temp_dir = TempDir::new().unwrap();
1366 let file_path = temp_dir.path().join("test.tiff");
1367
1368 let tiff_data = vec![
1370 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
1376 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
1378 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1380 0x00, 0x00, ];
1382
1383 fs::write(&file_path, &tiff_data).unwrap();
1384
1385 let image = Image::from_tiff_file(&file_path).unwrap();
1386
1387 assert_eq!(image.width(), 96);
1388 assert_eq!(image.height(), 96);
1389 assert_eq!(image.format(), ImageFormat::Tiff);
1390 assert_eq!(image.data(), tiff_data);
1391 }
1392
1393 #[test]
1394 fn test_image_from_file_jpeg() {
1395 let temp_dir = TempDir::new().unwrap();
1396 let file_path = temp_dir.path().join("test.jpg");
1397
1398 let jpeg_data = vec![
1399 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11,
1400 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9,
1401 ];
1402 fs::write(&file_path, &jpeg_data).unwrap();
1403
1404 let image = Image::from_file(&file_path).unwrap();
1405 assert_eq!(image.format(), ImageFormat::Jpeg);
1406 assert_eq!(image.width(), 100);
1407 }
1408
1409 #[test]
1410 fn test_image_from_file_uppercase_extension() {
1411 let temp_dir = TempDir::new().unwrap();
1412 let file_path = temp_dir.path().join("test.JPEG");
1413
1414 let jpeg_data = vec![
1415 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11,
1416 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9,
1417 ];
1418 fs::write(&file_path, &jpeg_data).unwrap();
1419
1420 let image = Image::from_file(&file_path).unwrap();
1421 assert_eq!(image.format(), ImageFormat::Jpeg);
1422 }
1423
1424 #[test]
1425 fn test_image_from_file_unsupported_extension() {
1426 let temp_dir = TempDir::new().unwrap();
1427 let file_path = temp_dir.path().join("test.bmp");
1428 fs::write(&file_path, b"dummy").unwrap();
1429
1430 let result = Image::from_file(&file_path);
1431 assert!(result.is_err());
1432 }
1433
1434 #[test]
1435 fn test_image_from_file_no_extension() {
1436 let temp_dir = TempDir::new().unwrap();
1437 let file_path = temp_dir.path().join("noext");
1438 fs::write(&file_path, b"dummy").unwrap();
1439
1440 let result = Image::from_file(&file_path);
1441 assert!(result.is_err());
1442 }
1443
1444 #[test]
1445 fn test_image_to_pdf_object_jpeg() {
1446 let jpeg_data = vec![
1447 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1459
1460 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1461 let pdf_obj = image.to_pdf_object();
1462
1463 if let Object::Stream(dict, data) = pdf_obj {
1464 assert_eq!(
1465 dict.get("Type").unwrap(),
1466 &Object::Name("XObject".to_string())
1467 );
1468 assert_eq!(
1469 dict.get("Subtype").unwrap(),
1470 &Object::Name("Image".to_string())
1471 );
1472 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
1473 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
1474 assert_eq!(
1475 dict.get("ColorSpace").unwrap(),
1476 &Object::Name("DeviceRGB".to_string())
1477 );
1478 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1479 assert_eq!(
1480 dict.get("Filter").unwrap(),
1481 &Object::Name("DCTDecode".to_string())
1482 );
1483 assert_eq!(data, jpeg_data);
1484 } else {
1485 panic!("Expected Stream object");
1486 }
1487 }
1488
1489 #[test]
1490 fn test_image_to_pdf_object_png() {
1491 let mut png_data = Vec::new();
1493
1494 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1495 png_data.extend_from_slice(&[
1496 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
1497 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00,
1498 ]);
1499 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]);
1500
1501 let raw_data = vec![0x00, 0x00, 0x00, 0x00];
1502 use flate2::write::ZlibEncoder;
1503 use flate2::Compression;
1504 use std::io::Write;
1505
1506 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1507 encoder.write_all(&raw_data).unwrap();
1508 let compressed_data = encoder.finish().unwrap();
1509
1510 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1511 png_data.extend_from_slice(b"IDAT");
1512 png_data.extend_from_slice(&compressed_data);
1513 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
1514
1515 png_data.extend_from_slice(&[
1516 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
1517 ]);
1518
1519 let image = Image::from_png_data(png_data.clone()).unwrap();
1520 let pdf_obj = image.to_pdf_object();
1521
1522 if let Object::Stream(dict, data) = pdf_obj {
1523 assert_eq!(
1524 dict.get("Type").unwrap(),
1525 &Object::Name("XObject".to_string())
1526 );
1527 assert_eq!(
1528 dict.get("Subtype").unwrap(),
1529 &Object::Name("Image".to_string())
1530 );
1531 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(1));
1532 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(1));
1533 assert_eq!(
1534 dict.get("ColorSpace").unwrap(),
1535 &Object::Name("DeviceRGB".to_string())
1536 );
1537 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1538 assert_eq!(
1539 dict.get("Filter").unwrap(),
1540 &Object::Name("FlateDecode".to_string())
1541 );
1542 assert_eq!(data, png_data);
1543 } else {
1544 panic!("Expected Stream object");
1545 }
1546 }
1547
1548 #[test]
1549 fn test_image_to_pdf_object_tiff() {
1550 let tiff_data = vec![
1551 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
1557 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
1559 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1561 0x00, 0x00, ];
1563
1564 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
1565 let pdf_obj = image.to_pdf_object();
1566
1567 if let Object::Stream(dict, data) = pdf_obj {
1568 assert_eq!(
1569 dict.get("Type").unwrap(),
1570 &Object::Name("XObject".to_string())
1571 );
1572 assert_eq!(
1573 dict.get("Subtype").unwrap(),
1574 &Object::Name("Image".to_string())
1575 );
1576 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(64));
1577 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(64));
1578 assert_eq!(
1579 dict.get("ColorSpace").unwrap(),
1580 &Object::Name("DeviceGray".to_string())
1581 );
1582 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1583 assert_eq!(
1584 dict.get("Filter").unwrap(),
1585 &Object::Name("FlateDecode".to_string())
1586 );
1587 assert_eq!(data, tiff_data);
1588 } else {
1589 panic!("Expected Stream object");
1590 }
1591 }
1592
1593 #[test]
1594 fn test_image_clone() {
1595 let jpeg_data = vec![
1596 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1608
1609 let image1 = Image::from_jpeg_data(jpeg_data).unwrap();
1610 let image2 = image1.clone();
1611
1612 assert_eq!(image1.width(), image2.width());
1613 assert_eq!(image1.height(), image2.height());
1614 assert_eq!(image1.format(), image2.format());
1615 assert_eq!(image1.data(), image2.data());
1616 }
1617
1618 #[test]
1619 fn test_image_debug() {
1620 let jpeg_data = vec![
1621 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1633
1634 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1635 let debug_str = format!("{image:?}");
1636
1637 assert!(debug_str.contains("Image"));
1638 assert!(debug_str.contains("width"));
1639 assert!(debug_str.contains("height"));
1640 assert!(debug_str.contains("format"));
1641 }
1642
1643 #[test]
1644 fn test_jpeg_grayscale_image() {
1645 let jpeg_data = vec![
1646 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x01, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD9, ];
1657
1658 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1659 let pdf_obj = image.to_pdf_object();
1660
1661 if let Object::Stream(dict, _) = pdf_obj {
1662 assert_eq!(
1663 dict.get("ColorSpace").unwrap(),
1664 &Object::Name("DeviceGray".to_string())
1665 );
1666 } else {
1667 panic!("Expected Stream object");
1668 }
1669 }
1670
1671 #[test]
1672 fn test_jpeg_cmyk_image() {
1673 let jpeg_data = vec![
1674 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x04, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1686
1687 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1688 let pdf_obj = image.to_pdf_object();
1689
1690 if let Object::Stream(dict, _) = pdf_obj {
1691 assert_eq!(
1692 dict.get("ColorSpace").unwrap(),
1693 &Object::Name("DeviceCMYK".to_string())
1694 );
1695 } else {
1696 panic!("Expected Stream object");
1697 }
1698 }
1699
1700 #[test]
1701 fn test_png_grayscale_image() {
1702 let png_data = create_minimal_png(1, 1, 0); let image = Image::from_png_data(png_data).unwrap();
1705 let pdf_obj = image.to_pdf_object();
1706
1707 if let Object::Stream(dict, _) = pdf_obj {
1708 assert_eq!(
1709 dict.get("ColorSpace").unwrap(),
1710 &Object::Name("DeviceGray".to_string())
1711 );
1712 } else {
1713 panic!("Expected Stream object");
1714 }
1715 }
1716
1717 #[test]
1718 fn test_png_palette_image_incomplete() {
1719 let png_data = vec![
1721 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x50, 0x08, 0x03, 0x00, 0x00, 0x00, 0x5C, 0x72, 0x6E, 0x38, ];
1733
1734 let result = Image::from_png_data(png_data);
1736 assert!(result.is_err(), "Incomplete PNG should return error");
1737 }
1738
1739 #[test]
1740 fn test_tiff_big_endian() {
1741 let tiff_data = vec![
1742 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1748 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1750 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00,
1752 0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
1754 0x00, 0x00, ];
1756
1757 let image = Image::from_tiff_data(tiff_data).unwrap();
1758
1759 assert_eq!(image.width(), 128);
1760 assert_eq!(image.height(), 128);
1761 assert_eq!(image.format(), ImageFormat::Tiff);
1762 }
1763
1764 #[test]
1765 fn test_tiff_cmyk_image() {
1766 let tiff_data = vec![
1767 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1773 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1775 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1777 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
1779 0x00, 0x00, ];
1781
1782 let image = Image::from_tiff_data(tiff_data).unwrap();
1783 let pdf_obj = image.to_pdf_object();
1784
1785 if let Object::Stream(dict, _) = pdf_obj {
1786 assert_eq!(
1787 dict.get("ColorSpace").unwrap(),
1788 &Object::Name("DeviceCMYK".to_string())
1789 );
1790 } else {
1791 panic!("Expected Stream object");
1792 }
1793 }
1794
1795 #[test]
1796 fn test_error_invalid_jpeg() {
1797 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_jpeg_data(invalid_data);
1799 assert!(result.is_err());
1800 }
1801
1802 #[test]
1803 fn test_error_invalid_png() {
1804 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_png_data(invalid_data);
1806 assert!(result.is_err());
1807 }
1808
1809 #[test]
1810 fn test_error_invalid_tiff() {
1811 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_tiff_data(invalid_data);
1813 assert!(result.is_err());
1814 }
1815
1816 #[test]
1817 fn test_error_truncated_jpeg() {
1818 let truncated_data = vec![0xFF, 0xD8, 0xFF]; let result = Image::from_jpeg_data(truncated_data);
1820 assert!(result.is_err());
1821 }
1822
1823 #[test]
1824 fn test_error_truncated_png() {
1825 let truncated_data = vec![0x89, 0x50, 0x4E, 0x47]; let result = Image::from_png_data(truncated_data);
1827 assert!(result.is_err());
1828 }
1829
1830 #[test]
1831 fn test_error_truncated_tiff() {
1832 let truncated_data = vec![0x49, 0x49, 0x2A]; let result = Image::from_tiff_data(truncated_data);
1834 assert!(result.is_err());
1835 }
1836
1837 #[test]
1838 fn test_error_jpeg_unsupported_components() {
1839 let invalid_jpeg = vec![
1840 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x05, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1852
1853 let result = Image::from_jpeg_data(invalid_jpeg);
1854 assert!(result.is_err());
1855 }
1856
1857 #[test]
1858 fn test_error_png_unsupported_color_type() {
1859 let invalid_png = vec![
1860 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x50, 0x08, 0x07, 0x00, 0x00, 0x00, 0x5C, 0x72, 0x6E, 0x38, ];
1872
1873 let result = Image::from_png_data(invalid_png);
1874 assert!(result.is_err());
1875 }
1876
1877 #[test]
1878 fn test_error_nonexistent_file() {
1879 let result = Image::from_jpeg_file("/nonexistent/path/image.jpg");
1880 assert!(result.is_err());
1881
1882 let result = Image::from_png_file("/nonexistent/path/image.png");
1883 assert!(result.is_err());
1884
1885 let result = Image::from_tiff_file("/nonexistent/path/image.tiff");
1886 assert!(result.is_err());
1887 }
1888
1889 #[test]
1890 fn test_jpeg_no_dimensions() {
1891 let jpeg_no_dims = vec![
1892 0xFF, 0xD8, 0xFF, 0xD9, ];
1895
1896 let result = Image::from_jpeg_data(jpeg_no_dims);
1897 assert!(result.is_err());
1898 }
1899
1900 #[test]
1901 fn test_png_no_ihdr() {
1902 let png_no_ihdr = vec![
1903 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x45, 0x4E, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5C,
1907 0x72, 0x6E, 0x38, ];
1909
1910 let result = Image::from_png_data(png_no_ihdr);
1911 assert!(result.is_err());
1912 }
1913
1914 #[test]
1915 fn test_tiff_no_dimensions() {
1916 let tiff_no_dims = vec![
1917 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1923 0x00, 0x00, ];
1925
1926 let result = Image::from_tiff_data(tiff_no_dims);
1927 assert!(result.is_err());
1928 }
1929
1930 fn png_crc32(data: &[u8]) -> u32 {
1932 let mut crc = 0xFFFFFFFF_u32;
1934 for &byte in data {
1935 crc ^= byte as u32;
1936 for _ in 0..8 {
1937 if crc & 1 != 0 {
1938 crc = (crc >> 1) ^ 0xEDB88320;
1939 } else {
1940 crc >>= 1;
1941 }
1942 }
1943 }
1944 !crc
1945 }
1946
1947 fn create_valid_png_data(
1949 width: u32,
1950 height: u32,
1951 bit_depth: u8,
1952 color_type: u8,
1953 ) -> Vec<u8> {
1954 use flate2::write::ZlibEncoder;
1955 use flate2::Compression;
1956 use std::io::Write;
1957
1958 let mut png_data = Vec::new();
1959
1960 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1962
1963 let mut ihdr_data = Vec::new();
1965 ihdr_data.extend_from_slice(&width.to_be_bytes()); ihdr_data.extend_from_slice(&height.to_be_bytes()); ihdr_data.push(bit_depth); ihdr_data.push(color_type); ihdr_data.push(0); ihdr_data.push(0); ihdr_data.push(0); let mut ihdr_crc_data = Vec::new();
1975 ihdr_crc_data.extend_from_slice(b"IHDR");
1976 ihdr_crc_data.extend_from_slice(&ihdr_data);
1977 let ihdr_crc = png_crc32(&ihdr_crc_data);
1978
1979 png_data.extend_from_slice(&(ihdr_data.len() as u32).to_be_bytes());
1981 png_data.extend_from_slice(b"IHDR");
1982 png_data.extend_from_slice(&ihdr_data);
1983 png_data.extend_from_slice(&ihdr_crc.to_be_bytes());
1984
1985 let bytes_per_pixel = match (color_type, bit_depth) {
1987 (0, _) => (bit_depth / 8).max(1) as usize, (2, _) => (bit_depth * 3 / 8).max(3) as usize, (3, _) => 1, (4, _) => (bit_depth * 2 / 8).max(2) as usize, (6, _) => (bit_depth * 4 / 8).max(4) as usize, _ => 3,
1993 };
1994
1995 let mut raw_data = Vec::new();
1996 for _y in 0..height {
1997 raw_data.push(0); for _x in 0..width {
1999 for _c in 0..bytes_per_pixel {
2000 raw_data.push(0); }
2002 }
2003 }
2004
2005 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
2007 encoder.write_all(&raw_data).unwrap();
2008 let compressed_data = encoder.finish().unwrap();
2009
2010 let mut idat_crc_data = Vec::new();
2012 idat_crc_data.extend_from_slice(b"IDAT");
2013 idat_crc_data.extend_from_slice(&compressed_data);
2014 let idat_crc = png_crc32(&idat_crc_data);
2015
2016 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
2018 png_data.extend_from_slice(b"IDAT");
2019 png_data.extend_from_slice(&compressed_data);
2020 png_data.extend_from_slice(&idat_crc.to_be_bytes());
2021
2022 png_data.extend_from_slice(&[0, 0, 0, 0]); png_data.extend_from_slice(b"IEND");
2025 png_data.extend_from_slice(&[0xAE, 0x42, 0x60, 0x82]); png_data
2028 }
2029
2030 #[test]
2031 fn test_different_bit_depths() {
2032 let png_8bit = create_valid_png_data(2, 2, 8, 2); let image_8bit = Image::from_png_data(png_8bit).unwrap();
2037 let pdf_obj_8bit = image_8bit.to_pdf_object();
2038
2039 if let Object::Stream(dict, _) = pdf_obj_8bit {
2040 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
2041 } else {
2042 panic!("Expected Stream object");
2043 }
2044
2045 let png_16bit = create_valid_png_data(2, 2, 16, 2); let image_16bit = Image::from_png_data(png_16bit).unwrap();
2048 let pdf_obj_16bit = image_16bit.to_pdf_object();
2049
2050 if let Object::Stream(dict, _) = pdf_obj_16bit {
2051 let bits = dict.get("BitsPerComponent").unwrap();
2053 assert!(matches!(bits, &Object::Integer(8) | &Object::Integer(16)));
2054 } else {
2055 panic!("Expected Stream object");
2056 }
2057 }
2058
2059 #[test]
2060 fn test_performance_large_image_data() {
2061 let mut large_jpeg = vec![
2063 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x04, 0x00, 0x04, 0x00, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, ];
2074
2075 large_jpeg.extend(vec![0x00; 10000]);
2077 large_jpeg.extend(vec![0xFF, 0xD9]); let start = std::time::Instant::now();
2080 let image = Image::from_jpeg_data(large_jpeg.clone()).unwrap();
2081 let duration = start.elapsed();
2082
2083 assert_eq!(image.width(), 1024);
2084 assert_eq!(image.height(), 1024);
2085 assert_eq!(image.data().len(), large_jpeg.len());
2086 assert!(duration.as_millis() < 100); }
2088
2089 #[test]
2090 fn test_memory_efficiency() {
2091 let jpeg_data = vec![
2092 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
2104
2105 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
2106
2107 assert_eq!(image.data().len(), jpeg_data.len());
2109 assert_eq!(image.data(), jpeg_data);
2110
2111 let cloned = image.clone();
2113 assert_eq!(cloned.data(), image.data());
2114 }
2115
2116 #[test]
2117 fn test_complete_workflow() {
2118 let test_cases = vec![
2120 (ImageFormat::Jpeg, "DCTDecode", "DeviceRGB"),
2121 (ImageFormat::Png, "FlateDecode", "DeviceRGB"),
2122 (ImageFormat::Tiff, "FlateDecode", "DeviceGray"),
2123 ];
2124
2125 for (expected_format, expected_filter, expected_color_space) in test_cases {
2126 let data = match expected_format {
2127 ImageFormat::Jpeg => vec![
2128 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ],
2140 ImageFormat::Png => create_valid_png_data(2, 2, 8, 2), ImageFormat::Tiff => vec![
2142 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00,
2148 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
2150 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
2152 0x00, 0x00, 0x00, 0x00, ],
2154 ImageFormat::Raw => Vec::new(), };
2156
2157 let image = match expected_format {
2158 ImageFormat::Jpeg => Image::from_jpeg_data(data.clone()).unwrap(),
2159 ImageFormat::Png => Image::from_png_data(data.clone()).unwrap(),
2160 ImageFormat::Tiff => Image::from_tiff_data(data.clone()).unwrap(),
2161 ImageFormat::Raw => continue, };
2163
2164 assert_eq!(image.format(), expected_format);
2166 if expected_format == ImageFormat::Png {
2168 assert_eq!(image.width(), 2);
2169 assert_eq!(image.height(), 2);
2170 } else if expected_format == ImageFormat::Jpeg {
2171 assert_eq!(image.width(), 200);
2172 assert_eq!(image.height(), 100);
2173 } else if expected_format == ImageFormat::Tiff {
2174 assert_eq!(image.width(), 200);
2175 assert_eq!(image.height(), 100);
2176 }
2177 assert_eq!(image.data(), data);
2178
2179 let pdf_obj = image.to_pdf_object();
2181 if let Object::Stream(dict, stream_data) = pdf_obj {
2182 assert_eq!(
2183 dict.get("Type").unwrap(),
2184 &Object::Name("XObject".to_string())
2185 );
2186 assert_eq!(
2187 dict.get("Subtype").unwrap(),
2188 &Object::Name("Image".to_string())
2189 );
2190 if expected_format == ImageFormat::Png {
2192 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(2));
2193 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(2));
2194 } else {
2195 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
2196 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
2197 }
2198 assert_eq!(
2199 dict.get("ColorSpace").unwrap(),
2200 &Object::Name(expected_color_space.to_string())
2201 );
2202 assert_eq!(
2203 dict.get("Filter").unwrap(),
2204 &Object::Name(expected_filter.to_string())
2205 );
2206 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
2207 assert_eq!(stream_data, data);
2208 } else {
2209 panic!("Expected Stream object for format {expected_format:?}");
2210 }
2211 }
2212 }
2213 }
2214}