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}
28
29#[derive(Debug, Clone, Copy, PartialEq)]
31pub enum ImageFormat {
32 Jpeg,
34 Png,
36 Tiff,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq)]
42pub enum ColorSpace {
43 DeviceGray,
45 DeviceRGB,
47 DeviceCMYK,
49}
50
51impl Image {
52 pub fn from_jpeg_file<P: AsRef<Path>>(path: P) -> Result<Self> {
54 let mut file = File::open(path)?;
55 let mut data = Vec::new();
56 file.read_to_end(&mut data)?;
57 Self::from_jpeg_data(data)
58 }
59
60 pub fn from_jpeg_data(data: Vec<u8>) -> Result<Self> {
62 let (width, height, color_space, bits_per_component) = parse_jpeg_header(&data)?;
64
65 Ok(Image {
66 data,
67 format: ImageFormat::Jpeg,
68 width,
69 height,
70 color_space,
71 bits_per_component,
72 })
73 }
74
75 pub fn from_png_file<P: AsRef<Path>>(path: P) -> Result<Self> {
77 let mut file = File::open(path)?;
78 let mut data = Vec::new();
79 file.read_to_end(&mut data)?;
80 Self::from_png_data(data)
81 }
82
83 pub fn from_png_data(data: Vec<u8>) -> Result<Self> {
85 let (width, height, color_space, bits_per_component) = parse_png_header(&data)?;
87
88 Ok(Image {
89 data,
90 format: ImageFormat::Png,
91 width,
92 height,
93 color_space,
94 bits_per_component,
95 })
96 }
97
98 pub fn from_tiff_file<P: AsRef<Path>>(path: P) -> Result<Self> {
100 let mut file = File::open(path)?;
101 let mut data = Vec::new();
102 file.read_to_end(&mut data)?;
103 Self::from_tiff_data(data)
104 }
105
106 pub fn from_tiff_data(data: Vec<u8>) -> Result<Self> {
108 let (width, height, color_space, bits_per_component) = parse_tiff_header(&data)?;
110
111 Ok(Image {
112 data,
113 format: ImageFormat::Tiff,
114 width,
115 height,
116 color_space,
117 bits_per_component,
118 })
119 }
120
121 pub fn width(&self) -> u32 {
123 self.width
124 }
125
126 pub fn height(&self) -> u32 {
128 self.height
129 }
130
131 pub fn data(&self) -> &[u8] {
133 &self.data
134 }
135
136 pub fn format(&self) -> ImageFormat {
138 self.format
139 }
140
141 pub fn to_pdf_object(&self) -> Object {
143 let mut dict = Dictionary::new();
144
145 dict.set("Type", Object::Name("XObject".to_string()));
147 dict.set("Subtype", Object::Name("Image".to_string()));
148 dict.set("Width", Object::Integer(self.width as i64));
149 dict.set("Height", Object::Integer(self.height as i64));
150
151 let color_space_name = match self.color_space {
153 ColorSpace::DeviceGray => "DeviceGray",
154 ColorSpace::DeviceRGB => "DeviceRGB",
155 ColorSpace::DeviceCMYK => "DeviceCMYK",
156 };
157 dict.set("ColorSpace", Object::Name(color_space_name.to_string()));
158
159 dict.set(
161 "BitsPerComponent",
162 Object::Integer(self.bits_per_component as i64),
163 );
164
165 match self.format {
167 ImageFormat::Jpeg => {
168 dict.set("Filter", Object::Name("DCTDecode".to_string()));
169 }
170 ImageFormat::Png => {
171 dict.set("Filter", Object::Name("FlateDecode".to_string()));
172 }
173 ImageFormat::Tiff => {
174 dict.set("Filter", Object::Name("FlateDecode".to_string()));
176 }
177 }
178
179 Object::Stream(dict, self.data.clone())
181 }
182}
183
184fn parse_jpeg_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
186 if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
187 return Err(PdfError::InvalidImage("Not a valid JPEG file".to_string()));
188 }
189
190 let mut pos = 2;
191 let mut width = 0;
192 let mut height = 0;
193 let mut components = 0;
194
195 while pos < data.len() - 1 {
196 if data[pos] != 0xFF {
197 return Err(PdfError::InvalidImage("Invalid JPEG marker".to_string()));
198 }
199
200 let marker = data[pos + 1];
201 pos += 2;
202
203 if marker == 0xFF {
205 continue;
206 }
207
208 if (0xC0..=0xCF).contains(&marker) && marker != 0xC4 && marker != 0xC8 && marker != 0xCC {
210 if pos + 7 >= data.len() {
212 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
213 }
214
215 pos += 2;
217
218 pos += 1;
220
221 height = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
223 pos += 2;
224 width = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
225 pos += 2;
226
227 components = data[pos];
229 break;
230 } else if marker == 0xD9 {
231 break;
233 } else if marker == 0xD8 || (0xD0..=0xD7).contains(&marker) {
234 continue;
236 } else {
237 if pos + 1 >= data.len() {
239 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
240 }
241 let length = ((data[pos] as usize) << 8) | (data[pos + 1] as usize);
242 pos += length;
243 }
244 }
245
246 if width == 0 || height == 0 {
247 return Err(PdfError::InvalidImage(
248 "Could not find image dimensions".to_string(),
249 ));
250 }
251
252 let color_space = match components {
253 1 => ColorSpace::DeviceGray,
254 3 => ColorSpace::DeviceRGB,
255 4 => ColorSpace::DeviceCMYK,
256 _ => {
257 return Err(PdfError::InvalidImage(format!(
258 "Unsupported number of components: {components}"
259 )))
260 }
261 };
262
263 Ok((width, height, color_space, 8)) }
265
266fn parse_png_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
268 if data.len() < 8 || &data[0..8] != b"\x89PNG\r\n\x1a\n" {
270 return Err(PdfError::InvalidImage("Not a valid PNG file".to_string()));
271 }
272
273 let mut pos = 8;
275
276 while pos + 8 < data.len() {
277 let chunk_length =
279 u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
280
281 let chunk_type = &data[pos + 4..pos + 8];
283
284 if chunk_type == b"IHDR" {
285 if pos + 8 + chunk_length > data.len() || chunk_length < 13 {
287 return Err(PdfError::InvalidImage("Invalid PNG IHDR chunk".to_string()));
288 }
289
290 let ihdr_data = &data[pos + 8..pos + 8 + chunk_length];
291
292 let width =
294 u32::from_be_bytes([ihdr_data[0], ihdr_data[1], ihdr_data[2], ihdr_data[3]]);
295
296 let height =
297 u32::from_be_bytes([ihdr_data[4], ihdr_data[5], ihdr_data[6], ihdr_data[7]]);
298
299 let bit_depth = ihdr_data[8];
300 let color_type = ihdr_data[9];
301
302 let color_space = match color_type {
304 0 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 3 => ColorSpace::DeviceRGB, 4 => ColorSpace::DeviceGray, 6 => ColorSpace::DeviceRGB, _ => {
310 return Err(PdfError::InvalidImage(format!(
311 "Unsupported PNG color type: {color_type}"
312 )))
313 }
314 };
315
316 return Ok((width, height, color_space, bit_depth));
317 }
318
319 pos += 8 + chunk_length + 4; }
322
323 Err(PdfError::InvalidImage(
324 "PNG IHDR chunk not found".to_string(),
325 ))
326}
327
328fn parse_tiff_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
330 if data.len() < 8 {
331 return Err(PdfError::InvalidImage(
332 "Invalid TIFF file: too short".to_string(),
333 ));
334 }
335
336 let (is_little_endian, offset) = if &data[0..2] == b"II" {
338 (true, 2) } else if &data[0..2] == b"MM" {
340 (false, 2) } else {
342 return Err(PdfError::InvalidImage(
343 "Invalid TIFF byte order".to_string(),
344 ));
345 };
346
347 let magic = if is_little_endian {
349 u16::from_le_bytes([data[offset], data[offset + 1]])
350 } else {
351 u16::from_be_bytes([data[offset], data[offset + 1]])
352 };
353
354 if magic != 42 {
355 return Err(PdfError::InvalidImage(
356 "Invalid TIFF magic number".to_string(),
357 ));
358 }
359
360 let ifd_offset = if is_little_endian {
362 u32::from_le_bytes([
363 data[offset + 2],
364 data[offset + 3],
365 data[offset + 4],
366 data[offset + 5],
367 ])
368 } else {
369 u32::from_be_bytes([
370 data[offset + 2],
371 data[offset + 3],
372 data[offset + 4],
373 data[offset + 5],
374 ])
375 } as usize;
376
377 if ifd_offset + 2 > data.len() {
378 return Err(PdfError::InvalidImage(
379 "Invalid TIFF IFD offset".to_string(),
380 ));
381 }
382
383 let num_entries = if is_little_endian {
385 u16::from_le_bytes([data[ifd_offset], data[ifd_offset + 1]])
386 } else {
387 u16::from_be_bytes([data[ifd_offset], data[ifd_offset + 1]])
388 };
389
390 let mut width = 0u32;
391 let mut height = 0u32;
392 let mut bits_per_sample = 8u16;
393 let mut photometric_interpretation = 0u16;
394
395 for i in 0..num_entries {
397 let entry_offset = ifd_offset + 2 + (i as usize * 12);
398
399 if entry_offset + 12 > data.len() {
400 break;
401 }
402
403 let tag = if is_little_endian {
404 u16::from_le_bytes([data[entry_offset], data[entry_offset + 1]])
405 } else {
406 u16::from_be_bytes([data[entry_offset], data[entry_offset + 1]])
407 };
408
409 let value_offset = entry_offset + 8;
410
411 match tag {
412 256 => {
413 width = if is_little_endian {
415 u32::from_le_bytes([
416 data[value_offset],
417 data[value_offset + 1],
418 data[value_offset + 2],
419 data[value_offset + 3],
420 ])
421 } else {
422 u32::from_be_bytes([
423 data[value_offset],
424 data[value_offset + 1],
425 data[value_offset + 2],
426 data[value_offset + 3],
427 ])
428 };
429 }
430 257 => {
431 height = if is_little_endian {
433 u32::from_le_bytes([
434 data[value_offset],
435 data[value_offset + 1],
436 data[value_offset + 2],
437 data[value_offset + 3],
438 ])
439 } else {
440 u32::from_be_bytes([
441 data[value_offset],
442 data[value_offset + 1],
443 data[value_offset + 2],
444 data[value_offset + 3],
445 ])
446 };
447 }
448 258 => {
449 bits_per_sample = if is_little_endian {
451 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
452 } else {
453 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
454 };
455 }
456 262 => {
457 photometric_interpretation = if is_little_endian {
459 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
460 } else {
461 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
462 };
463 }
464 _ => {} }
466 }
467
468 if width == 0 || height == 0 {
469 return Err(PdfError::InvalidImage(
470 "TIFF dimensions not found".to_string(),
471 ));
472 }
473
474 let color_space = match photometric_interpretation {
476 0 | 1 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 5 => ColorSpace::DeviceCMYK, _ => ColorSpace::DeviceRGB, };
481
482 Ok((width, height, color_space, bits_per_sample as u8))
483}
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488
489 #[test]
490 fn test_parse_jpeg_header() {
491 let jpeg_data = vec![
493 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, ];
502
503 let result = parse_jpeg_header(&jpeg_data);
504 assert!(result.is_ok());
505 let (width, height, color_space, bits) = result.unwrap();
506 assert_eq!(width, 200);
507 assert_eq!(height, 100);
508 assert_eq!(color_space, ColorSpace::DeviceRGB);
509 assert_eq!(bits, 8);
510 }
511
512 #[test]
513 fn test_invalid_jpeg() {
514 let invalid_data = vec![0x00, 0x00];
515 let result = parse_jpeg_header(&invalid_data);
516 assert!(result.is_err());
517 }
518
519 #[test]
520 fn test_parse_png_header() {
521 let mut png_data = vec![
523 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, ];
534
535 png_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
537
538 let result = parse_png_header(&png_data);
539 assert!(result.is_ok());
540 let (width, height, color_space, bits) = result.unwrap();
541 assert_eq!(width, 100);
542 assert_eq!(height, 100);
543 assert_eq!(color_space, ColorSpace::DeviceRGB);
544 assert_eq!(bits, 8);
545 }
546
547 #[test]
548 fn test_invalid_png() {
549 let invalid_data = vec![0x00, 0x00];
550 let result = parse_png_header(&invalid_data);
551 assert!(result.is_err());
552 }
553
554 #[test]
555 fn test_parse_tiff_header_little_endian() {
556 let tiff_data = vec![
558 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
564 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
566 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
568 0x00, 0x00, ];
570
571 let result = parse_tiff_header(&tiff_data);
572 assert!(result.is_ok());
573 let (width, height, color_space, bits) = result.unwrap();
574 assert_eq!(width, 100);
575 assert_eq!(height, 100);
576 assert_eq!(color_space, ColorSpace::DeviceGray);
577 assert_eq!(bits, 8);
578 }
579
580 #[test]
581 fn test_parse_tiff_header_big_endian() {
582 let tiff_data = vec![
584 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
590 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
592 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
594 0x00, 0x00, ];
596
597 let result = parse_tiff_header(&tiff_data);
598 assert!(result.is_ok());
599 let (width, height, color_space, bits) = result.unwrap();
600 assert_eq!(width, 100);
601 assert_eq!(height, 100);
602 assert_eq!(color_space, ColorSpace::DeviceGray);
603 assert_eq!(bits, 8);
604 }
605
606 #[test]
607 fn test_invalid_tiff() {
608 let invalid_data = vec![0x00, 0x00];
609 let result = parse_tiff_header(&invalid_data);
610 assert!(result.is_err());
611 }
612
613 #[test]
614 fn test_image_format_enum() {
615 assert_eq!(ImageFormat::Jpeg, ImageFormat::Jpeg);
616 assert_eq!(ImageFormat::Png, ImageFormat::Png);
617 assert_eq!(ImageFormat::Tiff, ImageFormat::Tiff);
618 assert_ne!(ImageFormat::Jpeg, ImageFormat::Png);
619 }
620
621 mod comprehensive_tests {
623 use super::*;
624 use std::fs;
625 use tempfile::TempDir;
626
627 #[test]
628 fn test_image_format_variants() {
629 let jpeg = ImageFormat::Jpeg;
631 let png = ImageFormat::Png;
632 let tiff = ImageFormat::Tiff;
633
634 assert_eq!(jpeg, ImageFormat::Jpeg);
635 assert_eq!(png, ImageFormat::Png);
636 assert_eq!(tiff, ImageFormat::Tiff);
637
638 assert_ne!(jpeg, png);
639 assert_ne!(png, tiff);
640 assert_ne!(tiff, jpeg);
641 }
642
643 #[test]
644 fn test_image_format_debug() {
645 let jpeg = ImageFormat::Jpeg;
646 let png = ImageFormat::Png;
647 let tiff = ImageFormat::Tiff;
648
649 assert_eq!(format!("{:?}", jpeg), "Jpeg");
650 assert_eq!(format!("{:?}", png), "Png");
651 assert_eq!(format!("{:?}", tiff), "Tiff");
652 }
653
654 #[test]
655 fn test_image_format_clone_copy() {
656 let jpeg = ImageFormat::Jpeg;
657 let jpeg_clone = jpeg;
658 let jpeg_copy = jpeg;
659
660 assert_eq!(jpeg_clone, ImageFormat::Jpeg);
661 assert_eq!(jpeg_copy, ImageFormat::Jpeg);
662 }
663
664 #[test]
665 fn test_color_space_variants() {
666 let gray = ColorSpace::DeviceGray;
668 let rgb = ColorSpace::DeviceRGB;
669 let cmyk = ColorSpace::DeviceCMYK;
670
671 assert_eq!(gray, ColorSpace::DeviceGray);
672 assert_eq!(rgb, ColorSpace::DeviceRGB);
673 assert_eq!(cmyk, ColorSpace::DeviceCMYK);
674
675 assert_ne!(gray, rgb);
676 assert_ne!(rgb, cmyk);
677 assert_ne!(cmyk, gray);
678 }
679
680 #[test]
681 fn test_color_space_debug() {
682 let gray = ColorSpace::DeviceGray;
683 let rgb = ColorSpace::DeviceRGB;
684 let cmyk = ColorSpace::DeviceCMYK;
685
686 assert_eq!(format!("{:?}", gray), "DeviceGray");
687 assert_eq!(format!("{:?}", rgb), "DeviceRGB");
688 assert_eq!(format!("{:?}", cmyk), "DeviceCMYK");
689 }
690
691 #[test]
692 fn test_color_space_clone_copy() {
693 let rgb = ColorSpace::DeviceRGB;
694 let rgb_clone = rgb;
695 let rgb_copy = rgb;
696
697 assert_eq!(rgb_clone, ColorSpace::DeviceRGB);
698 assert_eq!(rgb_copy, ColorSpace::DeviceRGB);
699 }
700
701 #[test]
702 fn test_image_from_jpeg_data() {
703 let jpeg_data = vec![
705 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
717
718 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
719
720 assert_eq!(image.width(), 200);
721 assert_eq!(image.height(), 100);
722 assert_eq!(image.format(), ImageFormat::Jpeg);
723 assert_eq!(image.data(), jpeg_data);
724 }
725
726 #[test]
727 fn test_image_from_png_data() {
728 let png_data = vec![
730 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x02, 0x00, 0x00, 0x00, 0x5C, 0x72, 0x6E, 0x38, ];
742
743 let image = Image::from_png_data(png_data.clone()).unwrap();
744
745 assert_eq!(image.width(), 256);
746 assert_eq!(image.height(), 256);
747 assert_eq!(image.format(), ImageFormat::Png);
748 assert_eq!(image.data(), png_data);
749 }
750
751 #[test]
752 fn test_image_from_tiff_data() {
753 let tiff_data = vec![
755 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
761 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
763 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
765 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
767 0x00, 0x00, ];
769
770 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
771
772 assert_eq!(image.width(), 128);
773 assert_eq!(image.height(), 128);
774 assert_eq!(image.format(), ImageFormat::Tiff);
775 assert_eq!(image.data(), tiff_data);
776 }
777
778 #[test]
779 fn test_image_from_jpeg_file() {
780 let temp_dir = TempDir::new().unwrap();
781 let file_path = temp_dir.path().join("test.jpg");
782
783 let jpeg_data = vec![
785 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
797
798 fs::write(&file_path, &jpeg_data).unwrap();
799
800 let image = Image::from_jpeg_file(&file_path).unwrap();
801
802 assert_eq!(image.width(), 100);
803 assert_eq!(image.height(), 50);
804 assert_eq!(image.format(), ImageFormat::Jpeg);
805 assert_eq!(image.data(), jpeg_data);
806 }
807
808 #[test]
809 fn test_image_from_png_file() {
810 let temp_dir = TempDir::new().unwrap();
811 let file_path = temp_dir.path().join("test.png");
812
813 let png_data = vec![
815 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, 0x02, 0x00, 0x00, 0x00, 0x5C, 0x72, 0x6E, 0x38, ];
827
828 fs::write(&file_path, &png_data).unwrap();
829
830 let image = Image::from_png_file(&file_path).unwrap();
831
832 assert_eq!(image.width(), 80);
833 assert_eq!(image.height(), 80);
834 assert_eq!(image.format(), ImageFormat::Png);
835 assert_eq!(image.data(), png_data);
836 }
837
838 #[test]
839 fn test_image_from_tiff_file() {
840 let temp_dir = TempDir::new().unwrap();
841 let file_path = temp_dir.path().join("test.tiff");
842
843 let tiff_data = vec![
845 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
851 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
853 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
855 0x00, 0x00, ];
857
858 fs::write(&file_path, &tiff_data).unwrap();
859
860 let image = Image::from_tiff_file(&file_path).unwrap();
861
862 assert_eq!(image.width(), 96);
863 assert_eq!(image.height(), 96);
864 assert_eq!(image.format(), ImageFormat::Tiff);
865 assert_eq!(image.data(), tiff_data);
866 }
867
868 #[test]
869 fn test_image_to_pdf_object_jpeg() {
870 let jpeg_data = vec![
871 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
883
884 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
885 let pdf_obj = image.to_pdf_object();
886
887 if let Object::Stream(dict, data) = pdf_obj {
888 assert_eq!(
889 dict.get("Type").unwrap(),
890 &Object::Name("XObject".to_string())
891 );
892 assert_eq!(
893 dict.get("Subtype").unwrap(),
894 &Object::Name("Image".to_string())
895 );
896 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
897 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
898 assert_eq!(
899 dict.get("ColorSpace").unwrap(),
900 &Object::Name("DeviceRGB".to_string())
901 );
902 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
903 assert_eq!(
904 dict.get("Filter").unwrap(),
905 &Object::Name("DCTDecode".to_string())
906 );
907 assert_eq!(data, jpeg_data);
908 } else {
909 panic!("Expected Stream object");
910 }
911 }
912
913 #[test]
914 fn test_image_to_pdf_object_png() {
915 let png_data = vec![
916 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, 0x06, 0x00, 0x00, 0x00, 0x5C, 0x72, 0x6E, 0x38, ];
928
929 let image = Image::from_png_data(png_data.clone()).unwrap();
930 let pdf_obj = image.to_pdf_object();
931
932 if let Object::Stream(dict, data) = pdf_obj {
933 assert_eq!(
934 dict.get("Type").unwrap(),
935 &Object::Name("XObject".to_string())
936 );
937 assert_eq!(
938 dict.get("Subtype").unwrap(),
939 &Object::Name("Image".to_string())
940 );
941 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(80));
942 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(80));
943 assert_eq!(
944 dict.get("ColorSpace").unwrap(),
945 &Object::Name("DeviceRGB".to_string())
946 );
947 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
948 assert_eq!(
949 dict.get("Filter").unwrap(),
950 &Object::Name("FlateDecode".to_string())
951 );
952 assert_eq!(data, png_data);
953 } else {
954 panic!("Expected Stream object");
955 }
956 }
957
958 #[test]
959 fn test_image_to_pdf_object_tiff() {
960 let tiff_data = vec![
961 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
967 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
969 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
971 0x00, 0x00, ];
973
974 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
975 let pdf_obj = image.to_pdf_object();
976
977 if let Object::Stream(dict, data) = pdf_obj {
978 assert_eq!(
979 dict.get("Type").unwrap(),
980 &Object::Name("XObject".to_string())
981 );
982 assert_eq!(
983 dict.get("Subtype").unwrap(),
984 &Object::Name("Image".to_string())
985 );
986 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(64));
987 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(64));
988 assert_eq!(
989 dict.get("ColorSpace").unwrap(),
990 &Object::Name("DeviceGray".to_string())
991 );
992 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
993 assert_eq!(
994 dict.get("Filter").unwrap(),
995 &Object::Name("FlateDecode".to_string())
996 );
997 assert_eq!(data, tiff_data);
998 } else {
999 panic!("Expected Stream object");
1000 }
1001 }
1002
1003 #[test]
1004 fn test_image_clone() {
1005 let jpeg_data = vec![
1006 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1018
1019 let image1 = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1020 let image2 = image1.clone();
1021
1022 assert_eq!(image1.width(), image2.width());
1023 assert_eq!(image1.height(), image2.height());
1024 assert_eq!(image1.format(), image2.format());
1025 assert_eq!(image1.data(), image2.data());
1026 }
1027
1028 #[test]
1029 fn test_image_debug() {
1030 let jpeg_data = vec![
1031 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1043
1044 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1045 let debug_str = format!("{:?}", image);
1046
1047 assert!(debug_str.contains("Image"));
1048 assert!(debug_str.contains("width"));
1049 assert!(debug_str.contains("height"));
1050 assert!(debug_str.contains("format"));
1051 }
1052
1053 #[test]
1054 fn test_jpeg_grayscale_image() {
1055 let jpeg_data = vec![
1056 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x01, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD9, ];
1067
1068 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1069 let pdf_obj = image.to_pdf_object();
1070
1071 if let Object::Stream(dict, _) = pdf_obj {
1072 assert_eq!(
1073 dict.get("ColorSpace").unwrap(),
1074 &Object::Name("DeviceGray".to_string())
1075 );
1076 } else {
1077 panic!("Expected Stream object");
1078 }
1079 }
1080
1081 #[test]
1082 fn test_jpeg_cmyk_image() {
1083 let jpeg_data = vec![
1084 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x04, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1096
1097 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1098 let pdf_obj = image.to_pdf_object();
1099
1100 if let Object::Stream(dict, _) = pdf_obj {
1101 assert_eq!(
1102 dict.get("ColorSpace").unwrap(),
1103 &Object::Name("DeviceCMYK".to_string())
1104 );
1105 } else {
1106 panic!("Expected Stream object");
1107 }
1108 }
1109
1110 #[test]
1111 fn test_png_grayscale_image() {
1112 let png_data = vec![
1113 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, 0x00, 0x00, 0x00, 0x00, 0x5C, 0x72, 0x6E, 0x38, ];
1125
1126 let image = Image::from_png_data(png_data).unwrap();
1127 let pdf_obj = image.to_pdf_object();
1128
1129 if let Object::Stream(dict, _) = pdf_obj {
1130 assert_eq!(
1131 dict.get("ColorSpace").unwrap(),
1132 &Object::Name("DeviceGray".to_string())
1133 );
1134 } else {
1135 panic!("Expected Stream object");
1136 }
1137 }
1138
1139 #[test]
1140 fn test_png_palette_image() {
1141 let png_data = vec![
1142 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, ];
1154
1155 let image = Image::from_png_data(png_data).unwrap();
1156 let pdf_obj = image.to_pdf_object();
1157
1158 if let Object::Stream(dict, _) = pdf_obj {
1159 assert_eq!(
1161 dict.get("ColorSpace").unwrap(),
1162 &Object::Name("DeviceRGB".to_string())
1163 );
1164 } else {
1165 panic!("Expected Stream object");
1166 }
1167 }
1168
1169 #[test]
1170 fn test_tiff_big_endian() {
1171 let tiff_data = vec![
1172 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1178 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1180 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00,
1182 0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
1184 0x00, 0x00, ];
1186
1187 let image = Image::from_tiff_data(tiff_data).unwrap();
1188
1189 assert_eq!(image.width(), 128);
1190 assert_eq!(image.height(), 128);
1191 assert_eq!(image.format(), ImageFormat::Tiff);
1192 }
1193
1194 #[test]
1195 fn test_tiff_cmyk_image() {
1196 let tiff_data = vec![
1197 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1203 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1205 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1207 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
1209 0x00, 0x00, ];
1211
1212 let image = Image::from_tiff_data(tiff_data).unwrap();
1213 let pdf_obj = image.to_pdf_object();
1214
1215 if let Object::Stream(dict, _) = pdf_obj {
1216 assert_eq!(
1217 dict.get("ColorSpace").unwrap(),
1218 &Object::Name("DeviceCMYK".to_string())
1219 );
1220 } else {
1221 panic!("Expected Stream object");
1222 }
1223 }
1224
1225 #[test]
1226 fn test_error_invalid_jpeg() {
1227 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_jpeg_data(invalid_data);
1229 assert!(result.is_err());
1230 }
1231
1232 #[test]
1233 fn test_error_invalid_png() {
1234 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_png_data(invalid_data);
1236 assert!(result.is_err());
1237 }
1238
1239 #[test]
1240 fn test_error_invalid_tiff() {
1241 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_tiff_data(invalid_data);
1243 assert!(result.is_err());
1244 }
1245
1246 #[test]
1247 fn test_error_truncated_jpeg() {
1248 let truncated_data = vec![0xFF, 0xD8, 0xFF]; let result = Image::from_jpeg_data(truncated_data);
1250 assert!(result.is_err());
1251 }
1252
1253 #[test]
1254 fn test_error_truncated_png() {
1255 let truncated_data = vec![0x89, 0x50, 0x4E, 0x47]; let result = Image::from_png_data(truncated_data);
1257 assert!(result.is_err());
1258 }
1259
1260 #[test]
1261 fn test_error_truncated_tiff() {
1262 let truncated_data = vec![0x49, 0x49, 0x2A]; let result = Image::from_tiff_data(truncated_data);
1264 assert!(result.is_err());
1265 }
1266
1267 #[test]
1268 fn test_error_jpeg_unsupported_components() {
1269 let invalid_jpeg = vec![
1270 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x05, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1282
1283 let result = Image::from_jpeg_data(invalid_jpeg);
1284 assert!(result.is_err());
1285 }
1286
1287 #[test]
1288 fn test_error_png_unsupported_color_type() {
1289 let invalid_png = vec![
1290 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, ];
1302
1303 let result = Image::from_png_data(invalid_png);
1304 assert!(result.is_err());
1305 }
1306
1307 #[test]
1308 fn test_error_nonexistent_file() {
1309 let result = Image::from_jpeg_file("/nonexistent/path/image.jpg");
1310 assert!(result.is_err());
1311
1312 let result = Image::from_png_file("/nonexistent/path/image.png");
1313 assert!(result.is_err());
1314
1315 let result = Image::from_tiff_file("/nonexistent/path/image.tiff");
1316 assert!(result.is_err());
1317 }
1318
1319 #[test]
1320 fn test_jpeg_no_dimensions() {
1321 let jpeg_no_dims = vec![
1322 0xFF, 0xD8, 0xFF, 0xD9, ];
1325
1326 let result = Image::from_jpeg_data(jpeg_no_dims);
1327 assert!(result.is_err());
1328 }
1329
1330 #[test]
1331 fn test_png_no_ihdr() {
1332 let png_no_ihdr = vec![
1333 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,
1337 0x72, 0x6E, 0x38, ];
1339
1340 let result = Image::from_png_data(png_no_ihdr);
1341 assert!(result.is_err());
1342 }
1343
1344 #[test]
1345 fn test_tiff_no_dimensions() {
1346 let tiff_no_dims = vec![
1347 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1353 0x00, 0x00, ];
1355
1356 let result = Image::from_tiff_data(tiff_no_dims);
1357 assert!(result.is_err());
1358 }
1359
1360 #[test]
1361 fn test_different_bit_depths() {
1362 let png_16bit = vec![
1364 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x50, 0x10, 0x02, 0x00, 0x00, 0x00, 0x5C, 0x72, 0x6E, 0x38, ];
1376
1377 let image = Image::from_png_data(png_16bit).unwrap();
1378 let pdf_obj = image.to_pdf_object();
1379
1380 if let Object::Stream(dict, _) = pdf_obj {
1381 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(16));
1382 } else {
1383 panic!("Expected Stream object");
1384 }
1385 }
1386
1387 #[test]
1388 fn test_performance_large_image_data() {
1389 let mut large_jpeg = vec![
1391 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x04, 0x00, 0x04, 0x00, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, ];
1402
1403 large_jpeg.extend(vec![0x00; 10000]);
1405 large_jpeg.extend(vec![0xFF, 0xD9]); let start = std::time::Instant::now();
1408 let image = Image::from_jpeg_data(large_jpeg.clone()).unwrap();
1409 let duration = start.elapsed();
1410
1411 assert_eq!(image.width(), 1024);
1412 assert_eq!(image.height(), 1024);
1413 assert_eq!(image.data().len(), large_jpeg.len());
1414 assert!(duration.as_millis() < 100); }
1416
1417 #[test]
1418 fn test_memory_efficiency() {
1419 let jpeg_data = vec![
1420 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1432
1433 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1434
1435 assert_eq!(image.data().len(), jpeg_data.len());
1437 assert_eq!(image.data(), jpeg_data);
1438
1439 let cloned = image.clone();
1441 assert_eq!(cloned.data(), image.data());
1442 }
1443
1444 #[test]
1445 fn test_complete_workflow() {
1446 let test_cases = vec![
1448 (ImageFormat::Jpeg, "DCTDecode", "DeviceRGB"),
1449 (ImageFormat::Png, "FlateDecode", "DeviceRGB"),
1450 (ImageFormat::Tiff, "FlateDecode", "DeviceGray"),
1451 ];
1452
1453 for (expected_format, expected_filter, expected_color_space) in test_cases {
1454 let data = match expected_format {
1455 ImageFormat::Jpeg => vec![
1456 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ],
1468 ImageFormat::Png => vec![
1469 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00, 0x64, 0x08, 0x02, 0x00, 0x00, 0x00, 0x5C, 0x72, 0x6E, 0x38, ],
1481 ImageFormat::Tiff => vec![
1482 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00,
1488 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
1490 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1492 0x00, 0x00, 0x00, 0x00, ],
1494 };
1495
1496 let image = match expected_format {
1497 ImageFormat::Jpeg => Image::from_jpeg_data(data.clone()).unwrap(),
1498 ImageFormat::Png => Image::from_png_data(data.clone()).unwrap(),
1499 ImageFormat::Tiff => Image::from_tiff_data(data.clone()).unwrap(),
1500 };
1501
1502 assert_eq!(image.format(), expected_format);
1504 assert_eq!(image.width(), 200);
1505 assert_eq!(image.height(), 100);
1506 assert_eq!(image.data(), data);
1507
1508 let pdf_obj = image.to_pdf_object();
1510 if let Object::Stream(dict, stream_data) = pdf_obj {
1511 assert_eq!(
1512 dict.get("Type").unwrap(),
1513 &Object::Name("XObject".to_string())
1514 );
1515 assert_eq!(
1516 dict.get("Subtype").unwrap(),
1517 &Object::Name("Image".to_string())
1518 );
1519 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
1520 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
1521 assert_eq!(
1522 dict.get("ColorSpace").unwrap(),
1523 &Object::Name(expected_color_space.to_string())
1524 );
1525 assert_eq!(
1526 dict.get("Filter").unwrap(),
1527 &Object::Name(expected_filter.to_string())
1528 );
1529 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1530 assert_eq!(stream_data, data);
1531 } else {
1532 panic!("Expected Stream object for format {:?}", expected_format);
1533 }
1534 }
1535 }
1536 }
1537}