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 #[cfg(feature = "external-images")]
70 {
71 let mut file = File::open(path)?;
73 let mut data = Vec::new();
74 file.read_to_end(&mut data)?;
75 Self::from_jpeg_data(data)
76 }
77 #[cfg(not(feature = "external-images"))]
78 {
79 let mut file = File::open(path)?;
80 let mut data = Vec::new();
81 file.read_to_end(&mut data)?;
82 Self::from_jpeg_data(data)
83 }
84 }
85
86 pub fn from_jpeg_data(data: Vec<u8>) -> Result<Self> {
88 let (width, height, color_space, bits_per_component) = parse_jpeg_header(&data)?;
90
91 Ok(Image {
92 data,
93 format: ImageFormat::Jpeg,
94 width,
95 height,
96 color_space,
97 bits_per_component,
98 alpha_data: None,
99 soft_mask: None,
100 })
101 }
102
103 pub fn from_png_file<P: AsRef<Path>>(path: P) -> Result<Self> {
105 #[cfg(feature = "external-images")]
106 {
107 let mut file = File::open(path)?;
109 let mut data = Vec::new();
110 file.read_to_end(&mut data)?;
111 Self::from_png_data(data)
112 }
113 #[cfg(not(feature = "external-images"))]
114 {
115 let mut file = File::open(path)?;
116 let mut data = Vec::new();
117 file.read_to_end(&mut data)?;
118 Self::from_png_data(data)
119 }
120 }
121
122 pub fn from_png_data(data: Vec<u8>) -> Result<Self> {
124 use crate::graphics::png_decoder::{decode_png, PngColorType};
125
126 let decoded = decode_png(&data)?;
128
129 let color_space = match decoded.color_type {
131 PngColorType::Grayscale | PngColorType::GrayscaleAlpha => ColorSpace::DeviceGray,
132 PngColorType::Rgb | PngColorType::RgbAlpha | PngColorType::Palette => {
133 ColorSpace::DeviceRGB
134 }
135 };
136
137 let soft_mask = if let Some(alpha) = &decoded.alpha_data {
139 Some(Box::new(Image {
140 data: alpha.clone(),
141 format: ImageFormat::Raw,
142 width: decoded.width,
143 height: decoded.height,
144 color_space: ColorSpace::DeviceGray,
145 bits_per_component: 8,
146 alpha_data: None,
147 soft_mask: None,
148 }))
149 } else {
150 None
151 };
152
153 Ok(Image {
154 data, format: ImageFormat::Png, width: decoded.width,
157 height: decoded.height,
158 color_space,
159 bits_per_component: 8, alpha_data: decoded.alpha_data,
161 soft_mask,
162 })
163 }
164
165 pub fn from_tiff_file<P: AsRef<Path>>(path: P) -> Result<Self> {
167 let mut file = File::open(path)?;
168 let mut data = Vec::new();
169 file.read_to_end(&mut data)?;
170 Self::from_tiff_data(data)
171 }
172
173 pub fn from_tiff_data(data: Vec<u8>) -> Result<Self> {
175 let (width, height, color_space, bits_per_component) = parse_tiff_header(&data)?;
177
178 Ok(Image {
179 data,
180 format: ImageFormat::Tiff,
181 width,
182 height,
183 color_space,
184 bits_per_component,
185 alpha_data: None,
186 soft_mask: None,
187 })
188 }
189
190 pub fn width(&self) -> u32 {
192 self.width
193 }
194
195 pub fn height(&self) -> u32 {
197 self.height
198 }
199
200 pub fn data(&self) -> &[u8] {
202 &self.data
203 }
204
205 pub fn format(&self) -> ImageFormat {
207 self.format
208 }
209
210 pub fn bits_per_component(&self) -> u8 {
212 self.bits_per_component
213 }
214
215 pub fn from_raw_data(
217 data: Vec<u8>,
218 width: u32,
219 height: u32,
220 color_space: ColorSpace,
221 bits_per_component: u8,
222 ) -> Self {
223 Image {
224 data,
225 format: ImageFormat::Raw,
226 width,
227 height,
228 color_space,
229 bits_per_component,
230 alpha_data: None,
231 soft_mask: None,
232 }
233 }
234
235 pub fn from_rgba_data(rgba_data: Vec<u8>, width: u32, height: u32) -> Result<Self> {
237 if rgba_data.len() != (width * height * 4) as usize {
238 return Err(PdfError::InvalidImage(
239 "RGBA data size doesn't match dimensions".to_string(),
240 ));
241 }
242
243 let mut rgb_data = Vec::with_capacity((width * height * 3) as usize);
245 let mut alpha_data = Vec::with_capacity((width * height) as usize);
246
247 for chunk in rgba_data.chunks(4) {
248 rgb_data.push(chunk[0]); rgb_data.push(chunk[1]); rgb_data.push(chunk[2]); alpha_data.push(chunk[3]); }
253
254 let soft_mask = Some(Box::new(Image {
256 data: alpha_data.clone(),
257 format: ImageFormat::Raw,
258 width,
259 height,
260 color_space: ColorSpace::DeviceGray,
261 bits_per_component: 8,
262 alpha_data: None,
263 soft_mask: None,
264 }));
265
266 Ok(Image {
267 data: rgb_data,
268 format: ImageFormat::Raw,
269 width,
270 height,
271 color_space: ColorSpace::DeviceRGB,
272 bits_per_component: 8,
273 alpha_data: Some(alpha_data),
274 soft_mask,
275 })
276 }
277
278 pub fn from_gray_data(gray_data: Vec<u8>, width: u32, height: u32) -> Result<Self> {
280 if gray_data.len() != (width * height) as usize {
281 return Err(PdfError::InvalidImage(
282 "Gray data size doesn't match dimensions".to_string(),
283 ));
284 }
285
286 Ok(Image {
287 data: gray_data,
288 format: ImageFormat::Raw,
289 width,
290 height,
291 color_space: ColorSpace::DeviceGray,
292 bits_per_component: 8,
293 alpha_data: None,
294 soft_mask: None,
295 })
296 }
297
298 #[cfg(feature = "external-images")]
300 pub fn from_file_raw<P: AsRef<Path>>(
301 path: P,
302 width: u32,
303 height: u32,
304 format: ImageFormat,
305 ) -> Result<Self> {
306 let data = std::fs::read(path)
307 .map_err(|e| PdfError::InvalidImage(format!("Failed to read image file: {}", e)))?;
308
309 Ok(Image {
310 data,
311 format,
312 width,
313 height,
314 color_space: ColorSpace::DeviceRGB,
315 bits_per_component: 8,
316 alpha_data: None,
317 soft_mask: None,
318 })
319 }
320
321 pub fn to_pdf_object(&self) -> Object {
323 let mut dict = Dictionary::new();
324
325 dict.set("Type", Object::Name("XObject".to_string()));
327 dict.set("Subtype", Object::Name("Image".to_string()));
328 dict.set("Width", Object::Integer(self.width as i64));
329 dict.set("Height", Object::Integer(self.height as i64));
330
331 let color_space_name = match self.color_space {
333 ColorSpace::DeviceGray => "DeviceGray",
334 ColorSpace::DeviceRGB => "DeviceRGB",
335 ColorSpace::DeviceCMYK => "DeviceCMYK",
336 };
337 dict.set("ColorSpace", Object::Name(color_space_name.to_string()));
338
339 dict.set(
341 "BitsPerComponent",
342 Object::Integer(self.bits_per_component as i64),
343 );
344
345 match self.format {
347 ImageFormat::Jpeg => {
348 dict.set("Filter", Object::Name("DCTDecode".to_string()));
349 }
350 ImageFormat::Png => {
351 dict.set("Filter", Object::Name("FlateDecode".to_string()));
352 }
353 ImageFormat::Tiff => {
354 dict.set("Filter", Object::Name("FlateDecode".to_string()));
356 }
357 ImageFormat::Raw => {
358 }
360 }
361
362 Object::Stream(dict, self.data.clone())
364 }
365
366 pub fn to_pdf_object_with_transparency(&self) -> (Object, Option<Object>) {
368 use flate2::write::ZlibEncoder;
369 use flate2::Compression;
370 use std::io::Write as IoWrite;
371
372 let mut main_dict = Dictionary::new();
373
374 main_dict.set("Type", Object::Name("XObject".to_string()));
376 main_dict.set("Subtype", Object::Name("Image".to_string()));
377 main_dict.set("Width", Object::Integer(self.width as i64));
378 main_dict.set("Height", Object::Integer(self.height as i64));
379
380 let color_space_name = match self.color_space {
382 ColorSpace::DeviceGray => "DeviceGray",
383 ColorSpace::DeviceRGB => "DeviceRGB",
384 ColorSpace::DeviceCMYK => "DeviceCMYK",
385 };
386 main_dict.set("ColorSpace", Object::Name(color_space_name.to_string()));
387
388 main_dict.set(
390 "BitsPerComponent",
391 Object::Integer(self.bits_per_component as i64),
392 );
393
394 let main_data = match self.format {
396 ImageFormat::Jpeg => {
397 main_dict.set("Filter", Object::Name("DCTDecode".to_string()));
398 self.data.clone()
399 }
400 ImageFormat::Png | ImageFormat::Raw => {
401 main_dict.set("Filter", Object::Name("FlateDecode".to_string()));
403 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
404 encoder.write_all(&self.data).unwrap();
405 encoder.finish().unwrap()
406 }
407 ImageFormat::Tiff => {
408 main_dict.set("Filter", Object::Name("FlateDecode".to_string()));
409 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
410 encoder.write_all(&self.data).unwrap();
411 encoder.finish().unwrap()
412 }
413 };
414
415 main_dict.set("Length", Object::Integer(main_data.len() as i64));
417
418 let smask_obj = if let Some(mask) = &self.soft_mask {
420 let mut mask_dict = Dictionary::new();
421 mask_dict.set("Type", Object::Name("XObject".to_string()));
422 mask_dict.set("Subtype", Object::Name("Image".to_string()));
423 mask_dict.set("Width", Object::Integer(mask.width as i64));
424 mask_dict.set("Height", Object::Integer(mask.height as i64));
425 mask_dict.set("ColorSpace", Object::Name("DeviceGray".to_string()));
426 mask_dict.set("BitsPerComponent", Object::Integer(8));
427 mask_dict.set("Filter", Object::Name("FlateDecode".to_string()));
428
429 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
431 encoder.write_all(&mask.data).unwrap();
432 let compressed_mask_data = encoder.finish().unwrap();
433
434 mask_dict.set("Length", Object::Integer(compressed_mask_data.len() as i64));
436
437 Some(Object::Stream(mask_dict, compressed_mask_data))
438 } else {
439 None
440 };
441
442 (Object::Stream(main_dict, main_data), smask_obj)
446 }
447
448 pub fn has_transparency(&self) -> bool {
450 self.soft_mask.is_some() || self.alpha_data.is_some()
451 }
452
453 pub fn create_stencil_mask(&self, threshold: u8) -> Option<Image> {
456 if let Some(alpha) = &self.alpha_data {
457 let mut mask_data = Vec::new();
459 let mut current_byte = 0u8;
460 let mut bit_count = 0;
461
462 for &alpha_value in alpha.iter() {
463 if alpha_value > threshold {
465 current_byte |= 1 << (7 - bit_count);
466 }
467
468 bit_count += 1;
469 if bit_count == 8 {
470 mask_data.push(current_byte);
471 current_byte = 0;
472 bit_count = 0;
473 }
474 }
475
476 if bit_count > 0 {
478 mask_data.push(current_byte);
479 }
480
481 Some(Image {
482 data: mask_data,
483 format: ImageFormat::Raw,
484 width: self.width,
485 height: self.height,
486 color_space: ColorSpace::DeviceGray,
487 bits_per_component: 1,
488 alpha_data: None,
489 soft_mask: None,
490 })
491 } else {
492 None
493 }
494 }
495
496 pub fn create_mask(&self, mask_type: MaskType, threshold: Option<u8>) -> Option<Image> {
498 match mask_type {
499 MaskType::Soft => self.soft_mask.as_ref().map(|m| m.as_ref().clone()),
500 MaskType::Stencil => self.create_stencil_mask(threshold.unwrap_or(128)),
501 }
502 }
503
504 pub fn with_mask(mut self, mask: Image, mask_type: MaskType) -> Self {
506 match mask_type {
507 MaskType::Soft => {
508 self.soft_mask = Some(Box::new(mask));
509 }
510 MaskType::Stencil => {
511 self.soft_mask = Some(Box::new(mask));
513 }
514 }
515 self
516 }
517
518 pub fn soft_mask(&self) -> Option<&Image> {
520 self.soft_mask.as_ref().map(|m| m.as_ref())
521 }
522
523 pub fn alpha_data(&self) -> Option<&[u8]> {
525 self.alpha_data.as_deref()
526 }
527}
528
529fn parse_jpeg_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
531 if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
532 return Err(PdfError::InvalidImage("Not a valid JPEG file".to_string()));
533 }
534
535 let mut pos = 2;
536 let mut width = 0;
537 let mut height = 0;
538 let mut components = 0;
539
540 while pos < data.len() - 1 {
541 if data[pos] != 0xFF {
542 return Err(PdfError::InvalidImage("Invalid JPEG marker".to_string()));
543 }
544
545 let marker = data[pos + 1];
546 pos += 2;
547
548 if marker == 0xFF {
550 continue;
551 }
552
553 if (0xC0..=0xCF).contains(&marker) && marker != 0xC4 && marker != 0xC8 && marker != 0xCC {
555 if pos + 7 >= data.len() {
557 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
558 }
559
560 pos += 2;
562
563 pos += 1;
565
566 height = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
568 pos += 2;
569 width = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
570 pos += 2;
571
572 components = data[pos];
574 break;
575 } else if marker == 0xD9 {
576 break;
578 } else if marker == 0xD8 || (0xD0..=0xD7).contains(&marker) {
579 continue;
581 } else {
582 if pos + 1 >= data.len() {
584 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
585 }
586 let length = ((data[pos] as usize) << 8) | (data[pos + 1] as usize);
587 pos += length;
588 }
589 }
590
591 if width == 0 || height == 0 {
592 return Err(PdfError::InvalidImage(
593 "Could not find image dimensions".to_string(),
594 ));
595 }
596
597 let color_space = match components {
598 1 => ColorSpace::DeviceGray,
599 3 => ColorSpace::DeviceRGB,
600 4 => ColorSpace::DeviceCMYK,
601 _ => {
602 return Err(PdfError::InvalidImage(format!(
603 "Unsupported number of components: {components}"
604 )))
605 }
606 };
607
608 Ok((width, height, color_space, 8)) }
610
611#[allow(dead_code)]
613fn parse_png_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
614 if data.len() < 8 || &data[0..8] != b"\x89PNG\r\n\x1a\n" {
616 return Err(PdfError::InvalidImage("Not a valid PNG file".to_string()));
617 }
618
619 let mut pos = 8;
621
622 while pos + 8 < data.len() {
623 let chunk_length =
625 u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
626
627 let chunk_type = &data[pos + 4..pos + 8];
629
630 if chunk_type == b"IHDR" {
631 if pos + 8 + chunk_length > data.len() || chunk_length < 13 {
633 return Err(PdfError::InvalidImage("Invalid PNG IHDR chunk".to_string()));
634 }
635
636 let ihdr_data = &data[pos + 8..pos + 8 + chunk_length];
637
638 let width =
640 u32::from_be_bytes([ihdr_data[0], ihdr_data[1], ihdr_data[2], ihdr_data[3]]);
641
642 let height =
643 u32::from_be_bytes([ihdr_data[4], ihdr_data[5], ihdr_data[6], ihdr_data[7]]);
644
645 let bit_depth = ihdr_data[8];
646 let color_type = ihdr_data[9];
647
648 let color_space = match color_type {
650 0 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 3 => ColorSpace::DeviceRGB, 4 => ColorSpace::DeviceGray, 6 => ColorSpace::DeviceRGB, _ => {
656 return Err(PdfError::InvalidImage(format!(
657 "Unsupported PNG color type: {color_type}"
658 )))
659 }
660 };
661
662 return Ok((width, height, color_space, bit_depth));
663 }
664
665 pos += 8 + chunk_length + 4; }
668
669 Err(PdfError::InvalidImage(
670 "PNG IHDR chunk not found".to_string(),
671 ))
672}
673
674fn parse_tiff_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
676 if data.len() < 8 {
677 return Err(PdfError::InvalidImage(
678 "Invalid TIFF file: too short".to_string(),
679 ));
680 }
681
682 let (is_little_endian, offset) = if &data[0..2] == b"II" {
684 (true, 2) } else if &data[0..2] == b"MM" {
686 (false, 2) } else {
688 return Err(PdfError::InvalidImage(
689 "Invalid TIFF byte order".to_string(),
690 ));
691 };
692
693 let magic = if is_little_endian {
695 u16::from_le_bytes([data[offset], data[offset + 1]])
696 } else {
697 u16::from_be_bytes([data[offset], data[offset + 1]])
698 };
699
700 if magic != 42 {
701 return Err(PdfError::InvalidImage(
702 "Invalid TIFF magic number".to_string(),
703 ));
704 }
705
706 let ifd_offset = if is_little_endian {
708 u32::from_le_bytes([
709 data[offset + 2],
710 data[offset + 3],
711 data[offset + 4],
712 data[offset + 5],
713 ])
714 } else {
715 u32::from_be_bytes([
716 data[offset + 2],
717 data[offset + 3],
718 data[offset + 4],
719 data[offset + 5],
720 ])
721 } as usize;
722
723 if ifd_offset + 2 > data.len() {
724 return Err(PdfError::InvalidImage(
725 "Invalid TIFF IFD offset".to_string(),
726 ));
727 }
728
729 let num_entries = if is_little_endian {
731 u16::from_le_bytes([data[ifd_offset], data[ifd_offset + 1]])
732 } else {
733 u16::from_be_bytes([data[ifd_offset], data[ifd_offset + 1]])
734 };
735
736 let mut width = 0u32;
737 let mut height = 0u32;
738 let mut bits_per_sample = 8u16;
739 let mut photometric_interpretation = 0u16;
740
741 for i in 0..num_entries {
743 let entry_offset = ifd_offset + 2 + (i as usize * 12);
744
745 if entry_offset + 12 > data.len() {
746 break;
747 }
748
749 let tag = if is_little_endian {
750 u16::from_le_bytes([data[entry_offset], data[entry_offset + 1]])
751 } else {
752 u16::from_be_bytes([data[entry_offset], data[entry_offset + 1]])
753 };
754
755 let value_offset = entry_offset + 8;
756
757 match tag {
758 256 => {
759 width = if is_little_endian {
761 u32::from_le_bytes([
762 data[value_offset],
763 data[value_offset + 1],
764 data[value_offset + 2],
765 data[value_offset + 3],
766 ])
767 } else {
768 u32::from_be_bytes([
769 data[value_offset],
770 data[value_offset + 1],
771 data[value_offset + 2],
772 data[value_offset + 3],
773 ])
774 };
775 }
776 257 => {
777 height = if is_little_endian {
779 u32::from_le_bytes([
780 data[value_offset],
781 data[value_offset + 1],
782 data[value_offset + 2],
783 data[value_offset + 3],
784 ])
785 } else {
786 u32::from_be_bytes([
787 data[value_offset],
788 data[value_offset + 1],
789 data[value_offset + 2],
790 data[value_offset + 3],
791 ])
792 };
793 }
794 258 => {
795 bits_per_sample = if is_little_endian {
797 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
798 } else {
799 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
800 };
801 }
802 262 => {
803 photometric_interpretation = if is_little_endian {
805 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
806 } else {
807 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
808 };
809 }
810 _ => {} }
812 }
813
814 if width == 0 || height == 0 {
815 return Err(PdfError::InvalidImage(
816 "TIFF dimensions not found".to_string(),
817 ));
818 }
819
820 let color_space = match photometric_interpretation {
822 0 | 1 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 5 => ColorSpace::DeviceCMYK, _ => ColorSpace::DeviceRGB, };
827
828 Ok((width, height, color_space, bits_per_sample as u8))
829}
830
831#[cfg(test)]
832mod tests {
833 use super::*;
834
835 fn create_minimal_png(width: u32, height: u32, color_type: u8) -> Vec<u8> {
837 match color_type {
840 0 => {
841 vec![
843 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,
854 0x01, 0xE2, 0xF9, 0x8C, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
860 }
861 2 => {
862 vec![
864 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,
875 0x01, 0x27, 0x18, 0xAA, 0x61, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
881 }
882 3 => {
883 vec![
885 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,
900 0x01, 0xE5, 0x27, 0xDE, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
906 }
907 6 => {
908 vec![
910 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,
921 0x01, 0x75, 0xAA, 0x50, 0x19, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
927 }
928 _ => {
929 create_minimal_png(width, height, 2)
931 }
932 }
933 }
934
935 #[test]
936 fn test_parse_jpeg_header() {
937 let jpeg_data = vec![
939 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, ];
948
949 let result = parse_jpeg_header(&jpeg_data);
950 assert!(result.is_ok());
951 let (width, height, color_space, bits) = result.unwrap();
952 assert_eq!(width, 200);
953 assert_eq!(height, 100);
954 assert_eq!(color_space, ColorSpace::DeviceRGB);
955 assert_eq!(bits, 8);
956 }
957
958 #[test]
959 fn test_invalid_jpeg() {
960 let invalid_data = vec![0x00, 0x00];
961 let result = parse_jpeg_header(&invalid_data);
962 assert!(result.is_err());
963 }
964
965 #[test]
966 fn test_parse_png_header() {
967 let mut png_data = vec![
969 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, ];
980
981 png_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
983
984 let result = parse_png_header(&png_data);
985 assert!(result.is_ok());
986 let (width, height, color_space, bits) = result.unwrap();
987 assert_eq!(width, 100);
988 assert_eq!(height, 100);
989 assert_eq!(color_space, ColorSpace::DeviceRGB);
990 assert_eq!(bits, 8);
991 }
992
993 #[test]
994 fn test_invalid_png() {
995 let invalid_data = vec![0x00, 0x00];
996 let result = parse_png_header(&invalid_data);
997 assert!(result.is_err());
998 }
999
1000 #[test]
1001 fn test_parse_tiff_header_little_endian() {
1002 let tiff_data = vec![
1004 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
1010 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
1012 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1014 0x00, 0x00, ];
1016
1017 let result = parse_tiff_header(&tiff_data);
1018 assert!(result.is_ok());
1019 let (width, height, color_space, bits) = result.unwrap();
1020 assert_eq!(width, 100);
1021 assert_eq!(height, 100);
1022 assert_eq!(color_space, ColorSpace::DeviceGray);
1023 assert_eq!(bits, 8);
1024 }
1025
1026 #[test]
1027 fn test_parse_tiff_header_big_endian() {
1028 let tiff_data = vec![
1030 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
1036 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
1038 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
1040 0x00, 0x00, ];
1042
1043 let result = parse_tiff_header(&tiff_data);
1044 assert!(result.is_ok());
1045 let (width, height, color_space, bits) = result.unwrap();
1046 assert_eq!(width, 100);
1047 assert_eq!(height, 100);
1048 assert_eq!(color_space, ColorSpace::DeviceGray);
1049 assert_eq!(bits, 8);
1050 }
1051
1052 #[test]
1053 fn test_invalid_tiff() {
1054 let invalid_data = vec![0x00, 0x00];
1055 let result = parse_tiff_header(&invalid_data);
1056 assert!(result.is_err());
1057 }
1058
1059 #[test]
1060 fn test_image_format_enum() {
1061 assert_eq!(ImageFormat::Jpeg, ImageFormat::Jpeg);
1062 assert_eq!(ImageFormat::Png, ImageFormat::Png);
1063 assert_eq!(ImageFormat::Tiff, ImageFormat::Tiff);
1064 assert_ne!(ImageFormat::Jpeg, ImageFormat::Png);
1065 }
1066
1067 mod comprehensive_tests {
1069 use super::*;
1070 use std::fs;
1071 use tempfile::TempDir;
1072
1073 #[test]
1074 fn test_image_format_variants() {
1075 let jpeg = ImageFormat::Jpeg;
1077 let png = ImageFormat::Png;
1078 let tiff = ImageFormat::Tiff;
1079
1080 assert_eq!(jpeg, ImageFormat::Jpeg);
1081 assert_eq!(png, ImageFormat::Png);
1082 assert_eq!(tiff, ImageFormat::Tiff);
1083
1084 assert_ne!(jpeg, png);
1085 assert_ne!(png, tiff);
1086 assert_ne!(tiff, jpeg);
1087 }
1088
1089 #[test]
1090 fn test_image_format_debug() {
1091 let jpeg = ImageFormat::Jpeg;
1092 let png = ImageFormat::Png;
1093 let tiff = ImageFormat::Tiff;
1094
1095 assert_eq!(format!("{jpeg:?}"), "Jpeg");
1096 assert_eq!(format!("{png:?}"), "Png");
1097 assert_eq!(format!("{tiff:?}"), "Tiff");
1098 }
1099
1100 #[test]
1101 fn test_image_format_clone_copy() {
1102 let jpeg = ImageFormat::Jpeg;
1103 let jpeg_clone = jpeg;
1104 let jpeg_copy = jpeg;
1105
1106 assert_eq!(jpeg_clone, ImageFormat::Jpeg);
1107 assert_eq!(jpeg_copy, ImageFormat::Jpeg);
1108 }
1109
1110 #[test]
1111 fn test_color_space_variants() {
1112 let gray = ColorSpace::DeviceGray;
1114 let rgb = ColorSpace::DeviceRGB;
1115 let cmyk = ColorSpace::DeviceCMYK;
1116
1117 assert_eq!(gray, ColorSpace::DeviceGray);
1118 assert_eq!(rgb, ColorSpace::DeviceRGB);
1119 assert_eq!(cmyk, ColorSpace::DeviceCMYK);
1120
1121 assert_ne!(gray, rgb);
1122 assert_ne!(rgb, cmyk);
1123 assert_ne!(cmyk, gray);
1124 }
1125
1126 #[test]
1127 fn test_color_space_debug() {
1128 let gray = ColorSpace::DeviceGray;
1129 let rgb = ColorSpace::DeviceRGB;
1130 let cmyk = ColorSpace::DeviceCMYK;
1131
1132 assert_eq!(format!("{gray:?}"), "DeviceGray");
1133 assert_eq!(format!("{rgb:?}"), "DeviceRGB");
1134 assert_eq!(format!("{cmyk:?}"), "DeviceCMYK");
1135 }
1136
1137 #[test]
1138 fn test_color_space_clone_copy() {
1139 let rgb = ColorSpace::DeviceRGB;
1140 let rgb_clone = rgb;
1141 let rgb_copy = rgb;
1142
1143 assert_eq!(rgb_clone, ColorSpace::DeviceRGB);
1144 assert_eq!(rgb_copy, ColorSpace::DeviceRGB);
1145 }
1146
1147 #[test]
1148 fn test_image_from_jpeg_data() {
1149 let jpeg_data = vec![
1151 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1163
1164 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1165
1166 assert_eq!(image.width(), 200);
1167 assert_eq!(image.height(), 100);
1168 assert_eq!(image.format(), ImageFormat::Jpeg);
1169 assert_eq!(image.data(), jpeg_data);
1170 }
1171
1172 #[test]
1173 fn test_image_from_png_data() {
1174 let mut png_data = Vec::new();
1176
1177 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1179
1180 png_data.extend_from_slice(&[
1182 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, ]);
1192 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]);
1194
1195 let raw_data = vec![0x00, 0x00, 0x00, 0x00]; use flate2::write::ZlibEncoder;
1200 use flate2::Compression;
1201 use std::io::Write;
1202
1203 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1204 encoder.write_all(&raw_data).unwrap();
1205 let compressed_data = encoder.finish().unwrap();
1206
1207 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1209 png_data.extend_from_slice(b"IDAT");
1210 png_data.extend_from_slice(&compressed_data);
1211 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); png_data.extend_from_slice(&[
1215 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]);
1219
1220 let image = Image::from_png_data(png_data.clone()).unwrap();
1221
1222 assert_eq!(image.width(), 1);
1223 assert_eq!(image.height(), 1);
1224 assert_eq!(image.format(), ImageFormat::Png);
1225 assert_eq!(image.data(), png_data);
1226 }
1227
1228 #[test]
1229 fn test_image_from_tiff_data() {
1230 let tiff_data = vec![
1232 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
1238 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
1240 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1242 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
1244 0x00, 0x00, ];
1246
1247 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
1248
1249 assert_eq!(image.width(), 128);
1250 assert_eq!(image.height(), 128);
1251 assert_eq!(image.format(), ImageFormat::Tiff);
1252 assert_eq!(image.data(), tiff_data);
1253 }
1254
1255 #[test]
1256 fn test_image_from_jpeg_file() {
1257 let temp_dir = TempDir::new().unwrap();
1258 let file_path = temp_dir.path().join("test.jpg");
1259
1260 let jpeg_data = vec![
1262 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1274
1275 fs::write(&file_path, &jpeg_data).unwrap();
1276
1277 let image = Image::from_jpeg_file(&file_path).unwrap();
1278
1279 assert_eq!(image.width(), 100);
1280 assert_eq!(image.height(), 50);
1281 assert_eq!(image.format(), ImageFormat::Jpeg);
1282 assert_eq!(image.data(), jpeg_data);
1283 }
1284
1285 #[test]
1286 fn test_image_from_png_file() {
1287 let temp_dir = TempDir::new().unwrap();
1288 let file_path = temp_dir.path().join("test.png");
1289
1290 let mut png_data = Vec::new();
1292
1293 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1295
1296 png_data.extend_from_slice(&[
1298 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, ]);
1308 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]); let raw_data = vec![0x00, 0x00, 0x00, 0x00]; use flate2::write::ZlibEncoder;
1314 use flate2::Compression;
1315 use std::io::Write;
1316
1317 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1318 encoder.write_all(&raw_data).unwrap();
1319 let compressed_data = encoder.finish().unwrap();
1320
1321 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1322 png_data.extend_from_slice(b"IDAT");
1323 png_data.extend_from_slice(&compressed_data);
1324 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); png_data.extend_from_slice(&[
1328 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]);
1332
1333 fs::write(&file_path, &png_data).unwrap();
1334
1335 let image = Image::from_png_file(&file_path).unwrap();
1336
1337 assert_eq!(image.width(), 1);
1338 assert_eq!(image.height(), 1);
1339 assert_eq!(image.format(), ImageFormat::Png);
1340 assert_eq!(image.data(), png_data);
1341 }
1342
1343 #[test]
1344 fn test_image_from_tiff_file() {
1345 let temp_dir = TempDir::new().unwrap();
1346 let file_path = temp_dir.path().join("test.tiff");
1347
1348 let tiff_data = vec![
1350 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
1356 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
1358 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1360 0x00, 0x00, ];
1362
1363 fs::write(&file_path, &tiff_data).unwrap();
1364
1365 let image = Image::from_tiff_file(&file_path).unwrap();
1366
1367 assert_eq!(image.width(), 96);
1368 assert_eq!(image.height(), 96);
1369 assert_eq!(image.format(), ImageFormat::Tiff);
1370 assert_eq!(image.data(), tiff_data);
1371 }
1372
1373 #[test]
1374 fn test_image_to_pdf_object_jpeg() {
1375 let jpeg_data = vec![
1376 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1388
1389 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1390 let pdf_obj = image.to_pdf_object();
1391
1392 if let Object::Stream(dict, data) = pdf_obj {
1393 assert_eq!(
1394 dict.get("Type").unwrap(),
1395 &Object::Name("XObject".to_string())
1396 );
1397 assert_eq!(
1398 dict.get("Subtype").unwrap(),
1399 &Object::Name("Image".to_string())
1400 );
1401 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
1402 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
1403 assert_eq!(
1404 dict.get("ColorSpace").unwrap(),
1405 &Object::Name("DeviceRGB".to_string())
1406 );
1407 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1408 assert_eq!(
1409 dict.get("Filter").unwrap(),
1410 &Object::Name("DCTDecode".to_string())
1411 );
1412 assert_eq!(data, jpeg_data);
1413 } else {
1414 panic!("Expected Stream object");
1415 }
1416 }
1417
1418 #[test]
1419 fn test_image_to_pdf_object_png() {
1420 let mut png_data = Vec::new();
1422
1423 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1424 png_data.extend_from_slice(&[
1425 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
1426 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00,
1427 ]);
1428 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]);
1429
1430 let raw_data = vec![0x00, 0x00, 0x00, 0x00];
1431 use flate2::write::ZlibEncoder;
1432 use flate2::Compression;
1433 use std::io::Write;
1434
1435 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1436 encoder.write_all(&raw_data).unwrap();
1437 let compressed_data = encoder.finish().unwrap();
1438
1439 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1440 png_data.extend_from_slice(b"IDAT");
1441 png_data.extend_from_slice(&compressed_data);
1442 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
1443
1444 png_data.extend_from_slice(&[
1445 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
1446 ]);
1447
1448 let image = Image::from_png_data(png_data.clone()).unwrap();
1449 let pdf_obj = image.to_pdf_object();
1450
1451 if let Object::Stream(dict, data) = pdf_obj {
1452 assert_eq!(
1453 dict.get("Type").unwrap(),
1454 &Object::Name("XObject".to_string())
1455 );
1456 assert_eq!(
1457 dict.get("Subtype").unwrap(),
1458 &Object::Name("Image".to_string())
1459 );
1460 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(1));
1461 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(1));
1462 assert_eq!(
1463 dict.get("ColorSpace").unwrap(),
1464 &Object::Name("DeviceRGB".to_string())
1465 );
1466 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1467 assert_eq!(
1468 dict.get("Filter").unwrap(),
1469 &Object::Name("FlateDecode".to_string())
1470 );
1471 assert_eq!(data, png_data);
1472 } else {
1473 panic!("Expected Stream object");
1474 }
1475 }
1476
1477 #[test]
1478 fn test_image_to_pdf_object_tiff() {
1479 let tiff_data = vec![
1480 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
1486 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
1488 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1490 0x00, 0x00, ];
1492
1493 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
1494 let pdf_obj = image.to_pdf_object();
1495
1496 if let Object::Stream(dict, data) = pdf_obj {
1497 assert_eq!(
1498 dict.get("Type").unwrap(),
1499 &Object::Name("XObject".to_string())
1500 );
1501 assert_eq!(
1502 dict.get("Subtype").unwrap(),
1503 &Object::Name("Image".to_string())
1504 );
1505 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(64));
1506 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(64));
1507 assert_eq!(
1508 dict.get("ColorSpace").unwrap(),
1509 &Object::Name("DeviceGray".to_string())
1510 );
1511 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1512 assert_eq!(
1513 dict.get("Filter").unwrap(),
1514 &Object::Name("FlateDecode".to_string())
1515 );
1516 assert_eq!(data, tiff_data);
1517 } else {
1518 panic!("Expected Stream object");
1519 }
1520 }
1521
1522 #[test]
1523 fn test_image_clone() {
1524 let jpeg_data = vec![
1525 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1537
1538 let image1 = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1539 let image2 = image1.clone();
1540
1541 assert_eq!(image1.width(), image2.width());
1542 assert_eq!(image1.height(), image2.height());
1543 assert_eq!(image1.format(), image2.format());
1544 assert_eq!(image1.data(), image2.data());
1545 }
1546
1547 #[test]
1548 fn test_image_debug() {
1549 let jpeg_data = vec![
1550 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1562
1563 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1564 let debug_str = format!("{image:?}");
1565
1566 assert!(debug_str.contains("Image"));
1567 assert!(debug_str.contains("width"));
1568 assert!(debug_str.contains("height"));
1569 assert!(debug_str.contains("format"));
1570 }
1571
1572 #[test]
1573 fn test_jpeg_grayscale_image() {
1574 let jpeg_data = vec![
1575 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x01, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD9, ];
1586
1587 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1588 let pdf_obj = image.to_pdf_object();
1589
1590 if let Object::Stream(dict, _) = pdf_obj {
1591 assert_eq!(
1592 dict.get("ColorSpace").unwrap(),
1593 &Object::Name("DeviceGray".to_string())
1594 );
1595 } else {
1596 panic!("Expected Stream object");
1597 }
1598 }
1599
1600 #[test]
1601 fn test_jpeg_cmyk_image() {
1602 let jpeg_data = vec![
1603 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x04, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1615
1616 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1617 let pdf_obj = image.to_pdf_object();
1618
1619 if let Object::Stream(dict, _) = pdf_obj {
1620 assert_eq!(
1621 dict.get("ColorSpace").unwrap(),
1622 &Object::Name("DeviceCMYK".to_string())
1623 );
1624 } else {
1625 panic!("Expected Stream object");
1626 }
1627 }
1628
1629 #[test]
1630 fn test_png_grayscale_image() {
1631 let png_data = create_minimal_png(1, 1, 0); let image = Image::from_png_data(png_data).unwrap();
1634 let pdf_obj = image.to_pdf_object();
1635
1636 if let Object::Stream(dict, _) = pdf_obj {
1637 assert_eq!(
1638 dict.get("ColorSpace").unwrap(),
1639 &Object::Name("DeviceGray".to_string())
1640 );
1641 } else {
1642 panic!("Expected Stream object");
1643 }
1644 }
1645
1646 #[test]
1647 #[ignore = "Palette PNG not yet fully supported - see PNG_DECODER_ISSUES.md"]
1648 fn test_png_palette_image() {
1649 let png_data = vec![
1650 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, ];
1662
1663 let image = Image::from_png_data(png_data).unwrap();
1664 let pdf_obj = image.to_pdf_object();
1665
1666 if let Object::Stream(dict, _) = pdf_obj {
1667 assert_eq!(
1669 dict.get("ColorSpace").unwrap(),
1670 &Object::Name("DeviceRGB".to_string())
1671 );
1672 } else {
1673 panic!("Expected Stream object");
1674 }
1675 }
1676
1677 #[test]
1678 fn test_tiff_big_endian() {
1679 let tiff_data = vec![
1680 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1686 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1688 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00,
1690 0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
1692 0x00, 0x00, ];
1694
1695 let image = Image::from_tiff_data(tiff_data).unwrap();
1696
1697 assert_eq!(image.width(), 128);
1698 assert_eq!(image.height(), 128);
1699 assert_eq!(image.format(), ImageFormat::Tiff);
1700 }
1701
1702 #[test]
1703 fn test_tiff_cmyk_image() {
1704 let tiff_data = vec![
1705 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1711 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1713 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1715 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
1717 0x00, 0x00, ];
1719
1720 let image = Image::from_tiff_data(tiff_data).unwrap();
1721 let pdf_obj = image.to_pdf_object();
1722
1723 if let Object::Stream(dict, _) = pdf_obj {
1724 assert_eq!(
1725 dict.get("ColorSpace").unwrap(),
1726 &Object::Name("DeviceCMYK".to_string())
1727 );
1728 } else {
1729 panic!("Expected Stream object");
1730 }
1731 }
1732
1733 #[test]
1734 fn test_error_invalid_jpeg() {
1735 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_jpeg_data(invalid_data);
1737 assert!(result.is_err());
1738 }
1739
1740 #[test]
1741 fn test_error_invalid_png() {
1742 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_png_data(invalid_data);
1744 assert!(result.is_err());
1745 }
1746
1747 #[test]
1748 fn test_error_invalid_tiff() {
1749 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_tiff_data(invalid_data);
1751 assert!(result.is_err());
1752 }
1753
1754 #[test]
1755 fn test_error_truncated_jpeg() {
1756 let truncated_data = vec![0xFF, 0xD8, 0xFF]; let result = Image::from_jpeg_data(truncated_data);
1758 assert!(result.is_err());
1759 }
1760
1761 #[test]
1762 fn test_error_truncated_png() {
1763 let truncated_data = vec![0x89, 0x50, 0x4E, 0x47]; let result = Image::from_png_data(truncated_data);
1765 assert!(result.is_err());
1766 }
1767
1768 #[test]
1769 fn test_error_truncated_tiff() {
1770 let truncated_data = vec![0x49, 0x49, 0x2A]; let result = Image::from_tiff_data(truncated_data);
1772 assert!(result.is_err());
1773 }
1774
1775 #[test]
1776 fn test_error_jpeg_unsupported_components() {
1777 let invalid_jpeg = vec![
1778 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x05, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1790
1791 let result = Image::from_jpeg_data(invalid_jpeg);
1792 assert!(result.is_err());
1793 }
1794
1795 #[test]
1796 fn test_error_png_unsupported_color_type() {
1797 let invalid_png = vec![
1798 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, ];
1810
1811 let result = Image::from_png_data(invalid_png);
1812 assert!(result.is_err());
1813 }
1814
1815 #[test]
1816 fn test_error_nonexistent_file() {
1817 let result = Image::from_jpeg_file("/nonexistent/path/image.jpg");
1818 assert!(result.is_err());
1819
1820 let result = Image::from_png_file("/nonexistent/path/image.png");
1821 assert!(result.is_err());
1822
1823 let result = Image::from_tiff_file("/nonexistent/path/image.tiff");
1824 assert!(result.is_err());
1825 }
1826
1827 #[test]
1828 fn test_jpeg_no_dimensions() {
1829 let jpeg_no_dims = vec![
1830 0xFF, 0xD8, 0xFF, 0xD9, ];
1833
1834 let result = Image::from_jpeg_data(jpeg_no_dims);
1835 assert!(result.is_err());
1836 }
1837
1838 #[test]
1839 fn test_png_no_ihdr() {
1840 let png_no_ihdr = vec![
1841 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,
1845 0x72, 0x6E, 0x38, ];
1847
1848 let result = Image::from_png_data(png_no_ihdr);
1849 assert!(result.is_err());
1850 }
1851
1852 #[test]
1853 fn test_tiff_no_dimensions() {
1854 let tiff_no_dims = vec![
1855 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1861 0x00, 0x00, ];
1863
1864 let result = Image::from_tiff_data(tiff_no_dims);
1865 assert!(result.is_err());
1866 }
1867
1868 fn png_crc32(data: &[u8]) -> u32 {
1870 let mut crc = 0xFFFFFFFF_u32;
1872 for &byte in data {
1873 crc ^= byte as u32;
1874 for _ in 0..8 {
1875 if crc & 1 != 0 {
1876 crc = (crc >> 1) ^ 0xEDB88320;
1877 } else {
1878 crc >>= 1;
1879 }
1880 }
1881 }
1882 !crc
1883 }
1884
1885 fn create_valid_png_data(
1887 width: u32,
1888 height: u32,
1889 bit_depth: u8,
1890 color_type: u8,
1891 ) -> Vec<u8> {
1892 use flate2::write::ZlibEncoder;
1893 use flate2::Compression;
1894 use std::io::Write;
1895
1896 let mut png_data = Vec::new();
1897
1898 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1900
1901 let mut ihdr_data = Vec::new();
1903 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();
1913 ihdr_crc_data.extend_from_slice(b"IHDR");
1914 ihdr_crc_data.extend_from_slice(&ihdr_data);
1915 let ihdr_crc = png_crc32(&ihdr_crc_data);
1916
1917 png_data.extend_from_slice(&(ihdr_data.len() as u32).to_be_bytes());
1919 png_data.extend_from_slice(b"IHDR");
1920 png_data.extend_from_slice(&ihdr_data);
1921 png_data.extend_from_slice(&ihdr_crc.to_be_bytes());
1922
1923 let bytes_per_pixel = match (color_type, bit_depth) {
1925 (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,
1931 };
1932
1933 let mut raw_data = Vec::new();
1934 for _y in 0..height {
1935 raw_data.push(0); for _x in 0..width {
1937 for _c in 0..bytes_per_pixel {
1938 raw_data.push(0); }
1940 }
1941 }
1942
1943 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1945 encoder.write_all(&raw_data).unwrap();
1946 let compressed_data = encoder.finish().unwrap();
1947
1948 let mut idat_crc_data = Vec::new();
1950 idat_crc_data.extend_from_slice(b"IDAT");
1951 idat_crc_data.extend_from_slice(&compressed_data);
1952 let idat_crc = png_crc32(&idat_crc_data);
1953
1954 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1956 png_data.extend_from_slice(b"IDAT");
1957 png_data.extend_from_slice(&compressed_data);
1958 png_data.extend_from_slice(&idat_crc.to_be_bytes());
1959
1960 png_data.extend_from_slice(&[0, 0, 0, 0]); png_data.extend_from_slice(b"IEND");
1963 png_data.extend_from_slice(&[0xAE, 0x42, 0x60, 0x82]); png_data
1966 }
1967
1968 #[test]
1969 fn test_different_bit_depths() {
1970 let png_8bit = create_valid_png_data(2, 2, 8, 2); let image_8bit = Image::from_png_data(png_8bit).unwrap();
1975 let pdf_obj_8bit = image_8bit.to_pdf_object();
1976
1977 if let Object::Stream(dict, _) = pdf_obj_8bit {
1978 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1979 } else {
1980 panic!("Expected Stream object");
1981 }
1982
1983 let png_16bit = create_valid_png_data(2, 2, 16, 2); let image_16bit = Image::from_png_data(png_16bit).unwrap();
1986 let pdf_obj_16bit = image_16bit.to_pdf_object();
1987
1988 if let Object::Stream(dict, _) = pdf_obj_16bit {
1989 let bits = dict.get("BitsPerComponent").unwrap();
1991 assert!(matches!(bits, &Object::Integer(8) | &Object::Integer(16)));
1992 } else {
1993 panic!("Expected Stream object");
1994 }
1995 }
1996
1997 #[test]
1998 fn test_performance_large_image_data() {
1999 let mut large_jpeg = vec![
2001 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x04, 0x00, 0x04, 0x00, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, ];
2012
2013 large_jpeg.extend(vec![0x00; 10000]);
2015 large_jpeg.extend(vec![0xFF, 0xD9]); let start = std::time::Instant::now();
2018 let image = Image::from_jpeg_data(large_jpeg.clone()).unwrap();
2019 let duration = start.elapsed();
2020
2021 assert_eq!(image.width(), 1024);
2022 assert_eq!(image.height(), 1024);
2023 assert_eq!(image.data().len(), large_jpeg.len());
2024 assert!(duration.as_millis() < 100); }
2026
2027 #[test]
2028 fn test_memory_efficiency() {
2029 let jpeg_data = vec![
2030 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
2042
2043 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
2044
2045 assert_eq!(image.data().len(), jpeg_data.len());
2047 assert_eq!(image.data(), jpeg_data);
2048
2049 let cloned = image.clone();
2051 assert_eq!(cloned.data(), image.data());
2052 }
2053
2054 #[test]
2055 fn test_complete_workflow() {
2056 let test_cases = vec![
2058 (ImageFormat::Jpeg, "DCTDecode", "DeviceRGB"),
2059 (ImageFormat::Png, "FlateDecode", "DeviceRGB"),
2060 (ImageFormat::Tiff, "FlateDecode", "DeviceGray"),
2061 ];
2062
2063 for (expected_format, expected_filter, expected_color_space) in test_cases {
2064 let data = match expected_format {
2065 ImageFormat::Jpeg => vec![
2066 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ],
2078 ImageFormat::Png => create_valid_png_data(2, 2, 8, 2), ImageFormat::Tiff => vec![
2080 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00,
2086 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
2088 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
2090 0x00, 0x00, 0x00, 0x00, ],
2092 ImageFormat::Raw => Vec::new(), };
2094
2095 let image = match expected_format {
2096 ImageFormat::Jpeg => Image::from_jpeg_data(data.clone()).unwrap(),
2097 ImageFormat::Png => Image::from_png_data(data.clone()).unwrap(),
2098 ImageFormat::Tiff => Image::from_tiff_data(data.clone()).unwrap(),
2099 ImageFormat::Raw => continue, };
2101
2102 assert_eq!(image.format(), expected_format);
2104 if expected_format == ImageFormat::Png {
2106 assert_eq!(image.width(), 2);
2107 assert_eq!(image.height(), 2);
2108 } else if expected_format == ImageFormat::Jpeg {
2109 assert_eq!(image.width(), 200);
2110 assert_eq!(image.height(), 100);
2111 } else if expected_format == ImageFormat::Tiff {
2112 assert_eq!(image.width(), 200);
2113 assert_eq!(image.height(), 100);
2114 }
2115 assert_eq!(image.data(), data);
2116
2117 let pdf_obj = image.to_pdf_object();
2119 if let Object::Stream(dict, stream_data) = pdf_obj {
2120 assert_eq!(
2121 dict.get("Type").unwrap(),
2122 &Object::Name("XObject".to_string())
2123 );
2124 assert_eq!(
2125 dict.get("Subtype").unwrap(),
2126 &Object::Name("Image".to_string())
2127 );
2128 if expected_format == ImageFormat::Png {
2130 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(2));
2131 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(2));
2132 } else {
2133 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
2134 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
2135 }
2136 assert_eq!(
2137 dict.get("ColorSpace").unwrap(),
2138 &Object::Name(expected_color_space.to_string())
2139 );
2140 assert_eq!(
2141 dict.get("Filter").unwrap(),
2142 &Object::Name(expected_filter.to_string())
2143 );
2144 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
2145 assert_eq!(stream_data, data);
2146 } else {
2147 panic!("Expected Stream object for format {expected_format:?}");
2148 }
2149 }
2150 }
2151 }
2152}