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 fn test_png_palette_image_incomplete() {
1648 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 result = Image::from_png_data(png_data);
1665 assert!(result.is_err(), "Incomplete PNG should return error");
1666 }
1667
1668 #[test]
1669 fn test_tiff_big_endian() {
1670 let tiff_data = vec![
1671 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1677 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1679 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00,
1681 0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
1683 0x00, 0x00, ];
1685
1686 let image = Image::from_tiff_data(tiff_data).unwrap();
1687
1688 assert_eq!(image.width(), 128);
1689 assert_eq!(image.height(), 128);
1690 assert_eq!(image.format(), ImageFormat::Tiff);
1691 }
1692
1693 #[test]
1694 fn test_tiff_cmyk_image() {
1695 let tiff_data = vec![
1696 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1702 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1704 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1706 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
1708 0x00, 0x00, ];
1710
1711 let image = Image::from_tiff_data(tiff_data).unwrap();
1712 let pdf_obj = image.to_pdf_object();
1713
1714 if let Object::Stream(dict, _) = pdf_obj {
1715 assert_eq!(
1716 dict.get("ColorSpace").unwrap(),
1717 &Object::Name("DeviceCMYK".to_string())
1718 );
1719 } else {
1720 panic!("Expected Stream object");
1721 }
1722 }
1723
1724 #[test]
1725 fn test_error_invalid_jpeg() {
1726 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_jpeg_data(invalid_data);
1728 assert!(result.is_err());
1729 }
1730
1731 #[test]
1732 fn test_error_invalid_png() {
1733 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_png_data(invalid_data);
1735 assert!(result.is_err());
1736 }
1737
1738 #[test]
1739 fn test_error_invalid_tiff() {
1740 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_tiff_data(invalid_data);
1742 assert!(result.is_err());
1743 }
1744
1745 #[test]
1746 fn test_error_truncated_jpeg() {
1747 let truncated_data = vec![0xFF, 0xD8, 0xFF]; let result = Image::from_jpeg_data(truncated_data);
1749 assert!(result.is_err());
1750 }
1751
1752 #[test]
1753 fn test_error_truncated_png() {
1754 let truncated_data = vec![0x89, 0x50, 0x4E, 0x47]; let result = Image::from_png_data(truncated_data);
1756 assert!(result.is_err());
1757 }
1758
1759 #[test]
1760 fn test_error_truncated_tiff() {
1761 let truncated_data = vec![0x49, 0x49, 0x2A]; let result = Image::from_tiff_data(truncated_data);
1763 assert!(result.is_err());
1764 }
1765
1766 #[test]
1767 fn test_error_jpeg_unsupported_components() {
1768 let invalid_jpeg = vec![
1769 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x05, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1781
1782 let result = Image::from_jpeg_data(invalid_jpeg);
1783 assert!(result.is_err());
1784 }
1785
1786 #[test]
1787 fn test_error_png_unsupported_color_type() {
1788 let invalid_png = vec![
1789 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, ];
1801
1802 let result = Image::from_png_data(invalid_png);
1803 assert!(result.is_err());
1804 }
1805
1806 #[test]
1807 fn test_error_nonexistent_file() {
1808 let result = Image::from_jpeg_file("/nonexistent/path/image.jpg");
1809 assert!(result.is_err());
1810
1811 let result = Image::from_png_file("/nonexistent/path/image.png");
1812 assert!(result.is_err());
1813
1814 let result = Image::from_tiff_file("/nonexistent/path/image.tiff");
1815 assert!(result.is_err());
1816 }
1817
1818 #[test]
1819 fn test_jpeg_no_dimensions() {
1820 let jpeg_no_dims = vec![
1821 0xFF, 0xD8, 0xFF, 0xD9, ];
1824
1825 let result = Image::from_jpeg_data(jpeg_no_dims);
1826 assert!(result.is_err());
1827 }
1828
1829 #[test]
1830 fn test_png_no_ihdr() {
1831 let png_no_ihdr = vec![
1832 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,
1836 0x72, 0x6E, 0x38, ];
1838
1839 let result = Image::from_png_data(png_no_ihdr);
1840 assert!(result.is_err());
1841 }
1842
1843 #[test]
1844 fn test_tiff_no_dimensions() {
1845 let tiff_no_dims = vec![
1846 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1852 0x00, 0x00, ];
1854
1855 let result = Image::from_tiff_data(tiff_no_dims);
1856 assert!(result.is_err());
1857 }
1858
1859 fn png_crc32(data: &[u8]) -> u32 {
1861 let mut crc = 0xFFFFFFFF_u32;
1863 for &byte in data {
1864 crc ^= byte as u32;
1865 for _ in 0..8 {
1866 if crc & 1 != 0 {
1867 crc = (crc >> 1) ^ 0xEDB88320;
1868 } else {
1869 crc >>= 1;
1870 }
1871 }
1872 }
1873 !crc
1874 }
1875
1876 fn create_valid_png_data(
1878 width: u32,
1879 height: u32,
1880 bit_depth: u8,
1881 color_type: u8,
1882 ) -> Vec<u8> {
1883 use flate2::write::ZlibEncoder;
1884 use flate2::Compression;
1885 use std::io::Write;
1886
1887 let mut png_data = Vec::new();
1888
1889 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1891
1892 let mut ihdr_data = Vec::new();
1894 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();
1904 ihdr_crc_data.extend_from_slice(b"IHDR");
1905 ihdr_crc_data.extend_from_slice(&ihdr_data);
1906 let ihdr_crc = png_crc32(&ihdr_crc_data);
1907
1908 png_data.extend_from_slice(&(ihdr_data.len() as u32).to_be_bytes());
1910 png_data.extend_from_slice(b"IHDR");
1911 png_data.extend_from_slice(&ihdr_data);
1912 png_data.extend_from_slice(&ihdr_crc.to_be_bytes());
1913
1914 let bytes_per_pixel = match (color_type, bit_depth) {
1916 (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,
1922 };
1923
1924 let mut raw_data = Vec::new();
1925 for _y in 0..height {
1926 raw_data.push(0); for _x in 0..width {
1928 for _c in 0..bytes_per_pixel {
1929 raw_data.push(0); }
1931 }
1932 }
1933
1934 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1936 encoder.write_all(&raw_data).unwrap();
1937 let compressed_data = encoder.finish().unwrap();
1938
1939 let mut idat_crc_data = Vec::new();
1941 idat_crc_data.extend_from_slice(b"IDAT");
1942 idat_crc_data.extend_from_slice(&compressed_data);
1943 let idat_crc = png_crc32(&idat_crc_data);
1944
1945 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1947 png_data.extend_from_slice(b"IDAT");
1948 png_data.extend_from_slice(&compressed_data);
1949 png_data.extend_from_slice(&idat_crc.to_be_bytes());
1950
1951 png_data.extend_from_slice(&[0, 0, 0, 0]); png_data.extend_from_slice(b"IEND");
1954 png_data.extend_from_slice(&[0xAE, 0x42, 0x60, 0x82]); png_data
1957 }
1958
1959 #[test]
1960 fn test_different_bit_depths() {
1961 let png_8bit = create_valid_png_data(2, 2, 8, 2); let image_8bit = Image::from_png_data(png_8bit).unwrap();
1966 let pdf_obj_8bit = image_8bit.to_pdf_object();
1967
1968 if let Object::Stream(dict, _) = pdf_obj_8bit {
1969 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1970 } else {
1971 panic!("Expected Stream object");
1972 }
1973
1974 let png_16bit = create_valid_png_data(2, 2, 16, 2); let image_16bit = Image::from_png_data(png_16bit).unwrap();
1977 let pdf_obj_16bit = image_16bit.to_pdf_object();
1978
1979 if let Object::Stream(dict, _) = pdf_obj_16bit {
1980 let bits = dict.get("BitsPerComponent").unwrap();
1982 assert!(matches!(bits, &Object::Integer(8) | &Object::Integer(16)));
1983 } else {
1984 panic!("Expected Stream object");
1985 }
1986 }
1987
1988 #[test]
1989 fn test_performance_large_image_data() {
1990 let mut large_jpeg = vec![
1992 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x04, 0x00, 0x04, 0x00, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, ];
2003
2004 large_jpeg.extend(vec![0x00; 10000]);
2006 large_jpeg.extend(vec![0xFF, 0xD9]); let start = std::time::Instant::now();
2009 let image = Image::from_jpeg_data(large_jpeg.clone()).unwrap();
2010 let duration = start.elapsed();
2011
2012 assert_eq!(image.width(), 1024);
2013 assert_eq!(image.height(), 1024);
2014 assert_eq!(image.data().len(), large_jpeg.len());
2015 assert!(duration.as_millis() < 100); }
2017
2018 #[test]
2019 fn test_memory_efficiency() {
2020 let jpeg_data = vec![
2021 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
2033
2034 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
2035
2036 assert_eq!(image.data().len(), jpeg_data.len());
2038 assert_eq!(image.data(), jpeg_data);
2039
2040 let cloned = image.clone();
2042 assert_eq!(cloned.data(), image.data());
2043 }
2044
2045 #[test]
2046 fn test_complete_workflow() {
2047 let test_cases = vec![
2049 (ImageFormat::Jpeg, "DCTDecode", "DeviceRGB"),
2050 (ImageFormat::Png, "FlateDecode", "DeviceRGB"),
2051 (ImageFormat::Tiff, "FlateDecode", "DeviceGray"),
2052 ];
2053
2054 for (expected_format, expected_filter, expected_color_space) in test_cases {
2055 let data = match expected_format {
2056 ImageFormat::Jpeg => vec![
2057 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ],
2069 ImageFormat::Png => create_valid_png_data(2, 2, 8, 2), ImageFormat::Tiff => vec![
2071 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00,
2077 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
2079 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
2081 0x00, 0x00, 0x00, 0x00, ],
2083 ImageFormat::Raw => Vec::new(), };
2085
2086 let image = match expected_format {
2087 ImageFormat::Jpeg => Image::from_jpeg_data(data.clone()).unwrap(),
2088 ImageFormat::Png => Image::from_png_data(data.clone()).unwrap(),
2089 ImageFormat::Tiff => Image::from_tiff_data(data.clone()).unwrap(),
2090 ImageFormat::Raw => continue, };
2092
2093 assert_eq!(image.format(), expected_format);
2095 if expected_format == ImageFormat::Png {
2097 assert_eq!(image.width(), 2);
2098 assert_eq!(image.height(), 2);
2099 } else if expected_format == ImageFormat::Jpeg {
2100 assert_eq!(image.width(), 200);
2101 assert_eq!(image.height(), 100);
2102 } else if expected_format == ImageFormat::Tiff {
2103 assert_eq!(image.width(), 200);
2104 assert_eq!(image.height(), 100);
2105 }
2106 assert_eq!(image.data(), data);
2107
2108 let pdf_obj = image.to_pdf_object();
2110 if let Object::Stream(dict, stream_data) = pdf_obj {
2111 assert_eq!(
2112 dict.get("Type").unwrap(),
2113 &Object::Name("XObject".to_string())
2114 );
2115 assert_eq!(
2116 dict.get("Subtype").unwrap(),
2117 &Object::Name("Image".to_string())
2118 );
2119 if expected_format == ImageFormat::Png {
2121 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(2));
2122 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(2));
2123 } else {
2124 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
2125 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
2126 }
2127 assert_eq!(
2128 dict.get("ColorSpace").unwrap(),
2129 &Object::Name(expected_color_space.to_string())
2130 );
2131 assert_eq!(
2132 dict.get("Filter").unwrap(),
2133 &Object::Name(expected_filter.to_string())
2134 );
2135 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
2136 assert_eq!(stream_data, data);
2137 } else {
2138 panic!("Expected Stream object for format {expected_format:?}");
2139 }
2140 }
2141 }
2142 }
2143}