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.clone();
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.clone();
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,
767 0x00, 0x00, 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,
855 0x00, 0x00, 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!(dict.get("Type").unwrap(), &Object::Name("XObject".to_string()));
889 assert_eq!(dict.get("Subtype").unwrap(), &Object::Name("Image".to_string()));
890 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
891 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
892 assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceRGB".to_string()));
893 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
894 assert_eq!(dict.get("Filter").unwrap(), &Object::Name("DCTDecode".to_string()));
895 assert_eq!(data, jpeg_data);
896 } else {
897 panic!("Expected Stream object");
898 }
899 }
900
901 #[test]
902 fn test_image_to_pdf_object_png() {
903 let png_data = vec![
904 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, ];
916
917 let image = Image::from_png_data(png_data.clone()).unwrap();
918 let pdf_obj = image.to_pdf_object();
919
920 if let Object::Stream(dict, data) = pdf_obj {
921 assert_eq!(dict.get("Type").unwrap(), &Object::Name("XObject".to_string()));
922 assert_eq!(dict.get("Subtype").unwrap(), &Object::Name("Image".to_string()));
923 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(80));
924 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(80));
925 assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceRGB".to_string()));
926 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
927 assert_eq!(dict.get("Filter").unwrap(), &Object::Name("FlateDecode".to_string()));
928 assert_eq!(data, png_data);
929 } else {
930 panic!("Expected Stream object");
931 }
932 }
933
934 #[test]
935 fn test_image_to_pdf_object_tiff() {
936 let tiff_data = vec![
937 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
943 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
945 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
947 0x00, 0x00, 0x00, 0x00, ];
949
950 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
951 let pdf_obj = image.to_pdf_object();
952
953 if let Object::Stream(dict, data) = pdf_obj {
954 assert_eq!(dict.get("Type").unwrap(), &Object::Name("XObject".to_string()));
955 assert_eq!(dict.get("Subtype").unwrap(), &Object::Name("Image".to_string()));
956 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(64));
957 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(64));
958 assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceGray".to_string()));
959 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
960 assert_eq!(dict.get("Filter").unwrap(), &Object::Name("FlateDecode".to_string()));
961 assert_eq!(data, tiff_data);
962 } else {
963 panic!("Expected Stream object");
964 }
965 }
966
967 #[test]
968 fn test_image_clone() {
969 let jpeg_data = vec![
970 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
982
983 let image1 = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
984 let image2 = image1.clone();
985
986 assert_eq!(image1.width(), image2.width());
987 assert_eq!(image1.height(), image2.height());
988 assert_eq!(image1.format(), image2.format());
989 assert_eq!(image1.data(), image2.data());
990 }
991
992 #[test]
993 fn test_image_debug() {
994 let jpeg_data = vec![
995 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1007
1008 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1009 let debug_str = format!("{:?}", image);
1010
1011 assert!(debug_str.contains("Image"));
1012 assert!(debug_str.contains("width"));
1013 assert!(debug_str.contains("height"));
1014 assert!(debug_str.contains("format"));
1015 }
1016
1017 #[test]
1018 fn test_jpeg_grayscale_image() {
1019 let jpeg_data = vec![
1020 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x01, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD9, ];
1031
1032 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1033 let pdf_obj = image.to_pdf_object();
1034
1035 if let Object::Stream(dict, _) = pdf_obj {
1036 assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceGray".to_string()));
1037 } else {
1038 panic!("Expected Stream object");
1039 }
1040 }
1041
1042 #[test]
1043 fn test_jpeg_cmyk_image() {
1044 let jpeg_data = vec![
1045 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x04, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1057
1058 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1059 let pdf_obj = image.to_pdf_object();
1060
1061 if let Object::Stream(dict, _) = pdf_obj {
1062 assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceCMYK".to_string()));
1063 } else {
1064 panic!("Expected Stream object");
1065 }
1066 }
1067
1068 #[test]
1069 fn test_png_grayscale_image() {
1070 let png_data = vec![
1071 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, ];
1083
1084 let image = Image::from_png_data(png_data).unwrap();
1085 let pdf_obj = image.to_pdf_object();
1086
1087 if let Object::Stream(dict, _) = pdf_obj {
1088 assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceGray".to_string()));
1089 } else {
1090 panic!("Expected Stream object");
1091 }
1092 }
1093
1094 #[test]
1095 fn test_png_palette_image() {
1096 let png_data = vec![
1097 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, ];
1109
1110 let image = Image::from_png_data(png_data).unwrap();
1111 let pdf_obj = image.to_pdf_object();
1112
1113 if let Object::Stream(dict, _) = pdf_obj {
1114 assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceRGB".to_string()));
1116 } else {
1117 panic!("Expected Stream object");
1118 }
1119 }
1120
1121 #[test]
1122 fn test_tiff_big_endian() {
1123 let tiff_data = vec![
1124 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1130 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1132 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00,
1134 0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00,
1136 0x00, 0x00, 0x00, 0x00, ];
1138
1139 let image = Image::from_tiff_data(tiff_data).unwrap();
1140
1141 assert_eq!(image.width(), 128);
1142 assert_eq!(image.height(), 128);
1143 assert_eq!(image.format(), ImageFormat::Tiff);
1144 }
1145
1146 #[test]
1147 fn test_tiff_cmyk_image() {
1148 let tiff_data = vec![
1149 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1155 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1157 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1159 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
1161 0x00, 0x00, 0x00, 0x00, ];
1163
1164 let image = Image::from_tiff_data(tiff_data).unwrap();
1165 let pdf_obj = image.to_pdf_object();
1166
1167 if let Object::Stream(dict, _) = pdf_obj {
1168 assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceCMYK".to_string()));
1169 } else {
1170 panic!("Expected Stream object");
1171 }
1172 }
1173
1174 #[test]
1175 fn test_error_invalid_jpeg() {
1176 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_jpeg_data(invalid_data);
1178 assert!(result.is_err());
1179 }
1180
1181 #[test]
1182 fn test_error_invalid_png() {
1183 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_png_data(invalid_data);
1185 assert!(result.is_err());
1186 }
1187
1188 #[test]
1189 fn test_error_invalid_tiff() {
1190 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_tiff_data(invalid_data);
1192 assert!(result.is_err());
1193 }
1194
1195 #[test]
1196 fn test_error_truncated_jpeg() {
1197 let truncated_data = vec![0xFF, 0xD8, 0xFF]; let result = Image::from_jpeg_data(truncated_data);
1199 assert!(result.is_err());
1200 }
1201
1202 #[test]
1203 fn test_error_truncated_png() {
1204 let truncated_data = vec![0x89, 0x50, 0x4E, 0x47]; let result = Image::from_png_data(truncated_data);
1206 assert!(result.is_err());
1207 }
1208
1209 #[test]
1210 fn test_error_truncated_tiff() {
1211 let truncated_data = vec![0x49, 0x49, 0x2A]; let result = Image::from_tiff_data(truncated_data);
1213 assert!(result.is_err());
1214 }
1215
1216 #[test]
1217 fn test_error_jpeg_unsupported_components() {
1218 let invalid_jpeg = vec![
1219 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x05, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1231
1232 let result = Image::from_jpeg_data(invalid_jpeg);
1233 assert!(result.is_err());
1234 }
1235
1236 #[test]
1237 fn test_error_png_unsupported_color_type() {
1238 let invalid_png = vec![
1239 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, ];
1251
1252 let result = Image::from_png_data(invalid_png);
1253 assert!(result.is_err());
1254 }
1255
1256 #[test]
1257 fn test_error_nonexistent_file() {
1258 let result = Image::from_jpeg_file("/nonexistent/path/image.jpg");
1259 assert!(result.is_err());
1260
1261 let result = Image::from_png_file("/nonexistent/path/image.png");
1262 assert!(result.is_err());
1263
1264 let result = Image::from_tiff_file("/nonexistent/path/image.tiff");
1265 assert!(result.is_err());
1266 }
1267
1268 #[test]
1269 fn test_jpeg_no_dimensions() {
1270 let jpeg_no_dims = vec![
1271 0xFF, 0xD8, 0xFF, 0xD9, ];
1274
1275 let result = Image::from_jpeg_data(jpeg_no_dims);
1276 assert!(result.is_err());
1277 }
1278
1279 #[test]
1280 fn test_png_no_ihdr() {
1281 let png_no_ihdr = vec![
1282 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,
1286 0x5C, 0x72, 0x6E, 0x38, ];
1288
1289 let result = Image::from_png_data(png_no_ihdr);
1290 assert!(result.is_err());
1291 }
1292
1293 #[test]
1294 fn test_tiff_no_dimensions() {
1295 let tiff_no_dims = vec![
1296 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1302 0x00, 0x00, 0x00, 0x00, ];
1304
1305 let result = Image::from_tiff_data(tiff_no_dims);
1306 assert!(result.is_err());
1307 }
1308
1309 #[test]
1310 fn test_different_bit_depths() {
1311 let png_16bit = vec![
1313 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, ];
1325
1326 let image = Image::from_png_data(png_16bit).unwrap();
1327 let pdf_obj = image.to_pdf_object();
1328
1329 if let Object::Stream(dict, _) = pdf_obj {
1330 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(16));
1331 } else {
1332 panic!("Expected Stream object");
1333 }
1334 }
1335
1336 #[test]
1337 fn test_performance_large_image_data() {
1338 let mut large_jpeg = vec![
1340 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x04, 0x00, 0x04, 0x00, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, ];
1351
1352 large_jpeg.extend(vec![0x00; 10000]);
1354 large_jpeg.extend(vec![0xFF, 0xD9]); let start = std::time::Instant::now();
1357 let image = Image::from_jpeg_data(large_jpeg.clone()).unwrap();
1358 let duration = start.elapsed();
1359
1360 assert_eq!(image.width(), 1024);
1361 assert_eq!(image.height(), 1024);
1362 assert_eq!(image.data().len(), large_jpeg.len());
1363 assert!(duration.as_millis() < 100); }
1365
1366 #[test]
1367 fn test_memory_efficiency() {
1368 let jpeg_data = vec![
1369 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1381
1382 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1383
1384 assert_eq!(image.data().len(), jpeg_data.len());
1386 assert_eq!(image.data(), jpeg_data);
1387
1388 let cloned = image.clone();
1390 assert_eq!(cloned.data(), image.data());
1391 }
1392
1393 #[test]
1394 fn test_complete_workflow() {
1395 let test_cases = vec![
1397 (ImageFormat::Jpeg, "DCTDecode", "DeviceRGB"),
1398 (ImageFormat::Png, "FlateDecode", "DeviceRGB"),
1399 (ImageFormat::Tiff, "FlateDecode", "DeviceGray"),
1400 ];
1401
1402 for (expected_format, expected_filter, expected_color_space) in test_cases {
1403 let data = match expected_format {
1404 ImageFormat::Jpeg => vec![
1405 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ],
1417 ImageFormat::Png => vec![
1418 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, ],
1430 ImageFormat::Tiff => vec![
1431 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00,
1437 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
1439 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1441 0x00, 0x00, 0x00, 0x00, ],
1443 };
1444
1445 let image = match expected_format {
1446 ImageFormat::Jpeg => Image::from_jpeg_data(data.clone()).unwrap(),
1447 ImageFormat::Png => Image::from_png_data(data.clone()).unwrap(),
1448 ImageFormat::Tiff => Image::from_tiff_data(data.clone()).unwrap(),
1449 };
1450
1451 assert_eq!(image.format(), expected_format);
1453 assert_eq!(image.width(), 200);
1454 assert_eq!(image.height(), 100);
1455 assert_eq!(image.data(), data);
1456
1457 let pdf_obj = image.to_pdf_object();
1459 if let Object::Stream(dict, stream_data) = pdf_obj {
1460 assert_eq!(dict.get("Type").unwrap(), &Object::Name("XObject".to_string()));
1461 assert_eq!(dict.get("Subtype").unwrap(), &Object::Name("Image".to_string()));
1462 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
1463 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
1464 assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name(expected_color_space.to_string()));
1465 assert_eq!(dict.get("Filter").unwrap(), &Object::Name(expected_filter.to_string()));
1466 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1467 assert_eq!(stream_data, data);
1468 } else {
1469 panic!("Expected Stream object for format {:?}", expected_format);
1470 }
1471 }
1472 }
1473 }
1474}