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 Self::from_external_jpeg_file(path)
72 }
73 #[cfg(not(feature = "external-images"))]
74 {
75 let mut file = File::open(path)?;
76 let mut data = Vec::new();
77 file.read_to_end(&mut data)?;
78 Self::from_jpeg_data(data)
79 }
80 }
81
82 pub fn from_jpeg_data(data: Vec<u8>) -> Result<Self> {
84 let (width, height, color_space, bits_per_component) = parse_jpeg_header(&data)?;
86
87 Ok(Image {
88 data,
89 format: ImageFormat::Jpeg,
90 width,
91 height,
92 color_space,
93 bits_per_component,
94 alpha_data: None,
95 soft_mask: None,
96 })
97 }
98
99 pub fn from_png_file<P: AsRef<Path>>(path: P) -> Result<Self> {
101 #[cfg(feature = "external-images")]
102 {
103 Self::from_external_png_file(path)
104 }
105 #[cfg(not(feature = "external-images"))]
106 {
107 let mut file = File::open(path)?;
108 let mut data = Vec::new();
109 file.read_to_end(&mut data)?;
110 Self::from_png_data(data)
111 }
112 }
113
114 pub fn from_png_data(data: Vec<u8>) -> Result<Self> {
116 use crate::graphics::png_decoder::{decode_png, PngColorType};
117
118 let decoded = decode_png(&data)?;
120
121 let color_space = match decoded.color_type {
123 PngColorType::Grayscale | PngColorType::GrayscaleAlpha => ColorSpace::DeviceGray,
124 PngColorType::Rgb | PngColorType::RgbAlpha | PngColorType::Palette => {
125 ColorSpace::DeviceRGB
126 }
127 };
128
129 let soft_mask = if let Some(alpha) = &decoded.alpha_data {
131 Some(Box::new(Image {
132 data: alpha.clone(),
133 format: ImageFormat::Raw,
134 width: decoded.width,
135 height: decoded.height,
136 color_space: ColorSpace::DeviceGray,
137 bits_per_component: 8,
138 alpha_data: None,
139 soft_mask: None,
140 }))
141 } else {
142 None
143 };
144
145 Ok(Image {
146 data, format: ImageFormat::Png, width: decoded.width,
149 height: decoded.height,
150 color_space,
151 bits_per_component: 8, alpha_data: decoded.alpha_data,
153 soft_mask,
154 })
155 }
156
157 pub fn from_tiff_file<P: AsRef<Path>>(path: P) -> Result<Self> {
159 let mut file = File::open(path)?;
160 let mut data = Vec::new();
161 file.read_to_end(&mut data)?;
162 Self::from_tiff_data(data)
163 }
164
165 pub fn from_tiff_data(data: Vec<u8>) -> Result<Self> {
167 let (width, height, color_space, bits_per_component) = parse_tiff_header(&data)?;
169
170 Ok(Image {
171 data,
172 format: ImageFormat::Tiff,
173 width,
174 height,
175 color_space,
176 bits_per_component,
177 alpha_data: None,
178 soft_mask: None,
179 })
180 }
181
182 pub fn width(&self) -> u32 {
184 self.width
185 }
186
187 pub fn height(&self) -> u32 {
189 self.height
190 }
191
192 pub fn data(&self) -> &[u8] {
194 &self.data
195 }
196
197 pub fn format(&self) -> ImageFormat {
199 self.format
200 }
201
202 pub fn bits_per_component(&self) -> u8 {
204 self.bits_per_component
205 }
206
207 pub fn from_raw_data(
209 data: Vec<u8>,
210 width: u32,
211 height: u32,
212 color_space: ColorSpace,
213 bits_per_component: u8,
214 ) -> Self {
215 Image {
216 data,
217 format: ImageFormat::Raw,
218 width,
219 height,
220 color_space,
221 bits_per_component,
222 alpha_data: None,
223 soft_mask: None,
224 }
225 }
226
227 pub fn from_rgba_data(rgba_data: Vec<u8>, width: u32, height: u32) -> Result<Self> {
229 if rgba_data.len() != (width * height * 4) as usize {
230 return Err(PdfError::InvalidImage(
231 "RGBA data size doesn't match dimensions".to_string(),
232 ));
233 }
234
235 let mut rgb_data = Vec::with_capacity((width * height * 3) as usize);
237 let mut alpha_data = Vec::with_capacity((width * height) as usize);
238
239 for chunk in rgba_data.chunks(4) {
240 rgb_data.push(chunk[0]); rgb_data.push(chunk[1]); rgb_data.push(chunk[2]); alpha_data.push(chunk[3]); }
245
246 let soft_mask = Some(Box::new(Image {
248 data: alpha_data.clone(),
249 format: ImageFormat::Raw,
250 width,
251 height,
252 color_space: ColorSpace::DeviceGray,
253 bits_per_component: 8,
254 alpha_data: None,
255 soft_mask: None,
256 }));
257
258 Ok(Image {
259 data: rgb_data,
260 format: ImageFormat::Raw,
261 width,
262 height,
263 color_space: ColorSpace::DeviceRGB,
264 bits_per_component: 8,
265 alpha_data: Some(alpha_data),
266 soft_mask,
267 })
268 }
269
270 pub fn from_gray_data(gray_data: Vec<u8>, width: u32, height: u32) -> Result<Self> {
272 if gray_data.len() != (width * height) as usize {
273 return Err(PdfError::InvalidImage(
274 "Gray data size doesn't match dimensions".to_string(),
275 ));
276 }
277
278 Ok(Image {
279 data: gray_data,
280 format: ImageFormat::Raw,
281 width,
282 height,
283 color_space: ColorSpace::DeviceGray,
284 bits_per_component: 8,
285 alpha_data: None,
286 soft_mask: None,
287 })
288 }
289
290 #[cfg(feature = "external-images")]
292 pub fn from_external_png_file<P: AsRef<Path>>(path: P) -> Result<Self> {
293 let img = image::ImageReader::open(path)?
294 .decode()
295 .map_err(|e| PdfError::InvalidImage(format!("Failed to decode PNG: {}", e)))?;
296
297 Self::from_dynamic_image(img)
298 }
299
300 #[cfg(feature = "external-images")]
302 pub fn from_external_jpeg_file<P: AsRef<Path>>(path: P) -> Result<Self> {
303 let img = image::ImageReader::open(path)?
304 .decode()
305 .map_err(|e| PdfError::InvalidImage(format!("Failed to decode JPEG: {}", e)))?;
306
307 Self::from_dynamic_image(img)
308 }
309
310 #[cfg(feature = "external-images")]
312 fn from_dynamic_image(img: image::DynamicImage) -> Result<Self> {
313 use image::DynamicImage;
314
315 let (width, height) = (img.width(), img.height());
316
317 let (rgb_data, color_space) = match img {
318 DynamicImage::ImageLuma8(gray_img) => (gray_img.into_raw(), ColorSpace::DeviceGray),
319 DynamicImage::ImageLumaA8(gray_alpha_img) => {
320 let rgb_data: Vec<u8> = gray_alpha_img
322 .pixels()
323 .flat_map(|p| [p[0], p[0], p[0]]) .collect();
325 (rgb_data, ColorSpace::DeviceRGB)
326 }
327 DynamicImage::ImageRgb8(rgb_img) => (rgb_img.into_raw(), ColorSpace::DeviceRGB),
328 DynamicImage::ImageRgba8(rgba_img) => {
329 let rgb_data: Vec<u8> = rgba_img
331 .pixels()
332 .flat_map(|p| [p[0], p[1], p[2]]) .collect();
334 (rgb_data, ColorSpace::DeviceRGB)
335 }
336 _ => {
337 let rgb_img = img.to_rgb8();
339 (rgb_img.into_raw(), ColorSpace::DeviceRGB)
340 }
341 };
342
343 Ok(Image {
344 data: rgb_data,
345 format: ImageFormat::Raw,
346 width,
347 height,
348 color_space,
349 bits_per_component: 8,
350 })
351 }
352
353 pub fn to_pdf_object(&self) -> Object {
355 let mut dict = Dictionary::new();
356
357 dict.set("Type", Object::Name("XObject".to_string()));
359 dict.set("Subtype", Object::Name("Image".to_string()));
360 dict.set("Width", Object::Integer(self.width as i64));
361 dict.set("Height", Object::Integer(self.height as i64));
362
363 let color_space_name = match self.color_space {
365 ColorSpace::DeviceGray => "DeviceGray",
366 ColorSpace::DeviceRGB => "DeviceRGB",
367 ColorSpace::DeviceCMYK => "DeviceCMYK",
368 };
369 dict.set("ColorSpace", Object::Name(color_space_name.to_string()));
370
371 dict.set(
373 "BitsPerComponent",
374 Object::Integer(self.bits_per_component as i64),
375 );
376
377 match self.format {
379 ImageFormat::Jpeg => {
380 dict.set("Filter", Object::Name("DCTDecode".to_string()));
381 }
382 ImageFormat::Png => {
383 dict.set("Filter", Object::Name("FlateDecode".to_string()));
384 }
385 ImageFormat::Tiff => {
386 dict.set("Filter", Object::Name("FlateDecode".to_string()));
388 }
389 ImageFormat::Raw => {
390 }
392 }
393
394 Object::Stream(dict, self.data.clone())
396 }
397
398 pub fn to_pdf_object_with_transparency(&self) -> (Object, Option<Object>) {
400 let mut main_dict = Dictionary::new();
401
402 main_dict.set("Type", Object::Name("XObject".to_string()));
404 main_dict.set("Subtype", Object::Name("Image".to_string()));
405 main_dict.set("Width", Object::Integer(self.width as i64));
406 main_dict.set("Height", Object::Integer(self.height as i64));
407
408 let color_space_name = match self.color_space {
410 ColorSpace::DeviceGray => "DeviceGray",
411 ColorSpace::DeviceRGB => "DeviceRGB",
412 ColorSpace::DeviceCMYK => "DeviceCMYK",
413 };
414 main_dict.set("ColorSpace", Object::Name(color_space_name.to_string()));
415
416 main_dict.set(
418 "BitsPerComponent",
419 Object::Integer(self.bits_per_component as i64),
420 );
421
422 match self.format {
424 ImageFormat::Jpeg => {
425 main_dict.set("Filter", Object::Name("DCTDecode".to_string()));
426 }
427 ImageFormat::Png | ImageFormat::Raw => {
428 main_dict.set("Filter", Object::Name("FlateDecode".to_string()));
430 }
431 ImageFormat::Tiff => {
432 main_dict.set("Filter", Object::Name("FlateDecode".to_string()));
433 }
434 }
435
436 let smask_obj = if let Some(mask) = &self.soft_mask {
438 let mut mask_dict = Dictionary::new();
439 mask_dict.set("Type", Object::Name("XObject".to_string()));
440 mask_dict.set("Subtype", Object::Name("Image".to_string()));
441 mask_dict.set("Width", Object::Integer(mask.width as i64));
442 mask_dict.set("Height", Object::Integer(mask.height as i64));
443 mask_dict.set("ColorSpace", Object::Name("DeviceGray".to_string()));
444 mask_dict.set("BitsPerComponent", Object::Integer(8));
445 mask_dict.set("Filter", Object::Name("FlateDecode".to_string()));
446
447 Some(Object::Stream(mask_dict, mask.data.clone()))
448 } else {
449 None
450 };
451
452 (Object::Stream(main_dict, self.data.clone()), smask_obj)
456 }
457
458 pub fn has_transparency(&self) -> bool {
460 self.soft_mask.is_some() || self.alpha_data.is_some()
461 }
462
463 pub fn create_stencil_mask(&self, threshold: u8) -> Option<Image> {
466 if let Some(alpha) = &self.alpha_data {
467 let mut mask_data = Vec::new();
469 let mut current_byte = 0u8;
470 let mut bit_count = 0;
471
472 for &alpha_value in alpha.iter() {
473 if alpha_value > threshold {
475 current_byte |= 1 << (7 - bit_count);
476 }
477
478 bit_count += 1;
479 if bit_count == 8 {
480 mask_data.push(current_byte);
481 current_byte = 0;
482 bit_count = 0;
483 }
484 }
485
486 if bit_count > 0 {
488 mask_data.push(current_byte);
489 }
490
491 Some(Image {
492 data: mask_data,
493 format: ImageFormat::Raw,
494 width: self.width,
495 height: self.height,
496 color_space: ColorSpace::DeviceGray,
497 bits_per_component: 1,
498 alpha_data: None,
499 soft_mask: None,
500 })
501 } else {
502 None
503 }
504 }
505
506 pub fn create_mask(&self, mask_type: MaskType, threshold: Option<u8>) -> Option<Image> {
508 match mask_type {
509 MaskType::Soft => self.soft_mask.as_ref().map(|m| m.as_ref().clone()),
510 MaskType::Stencil => self.create_stencil_mask(threshold.unwrap_or(128)),
511 }
512 }
513
514 pub fn with_mask(mut self, mask: Image, mask_type: MaskType) -> Self {
516 match mask_type {
517 MaskType::Soft => {
518 self.soft_mask = Some(Box::new(mask));
519 }
520 MaskType::Stencil => {
521 self.soft_mask = Some(Box::new(mask));
523 }
524 }
525 self
526 }
527
528 pub fn soft_mask(&self) -> Option<&Image> {
530 self.soft_mask.as_ref().map(|m| m.as_ref())
531 }
532
533 pub fn alpha_data(&self) -> Option<&[u8]> {
535 self.alpha_data.as_deref()
536 }
537}
538
539fn parse_jpeg_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
541 if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
542 return Err(PdfError::InvalidImage("Not a valid JPEG file".to_string()));
543 }
544
545 let mut pos = 2;
546 let mut width = 0;
547 let mut height = 0;
548 let mut components = 0;
549
550 while pos < data.len() - 1 {
551 if data[pos] != 0xFF {
552 return Err(PdfError::InvalidImage("Invalid JPEG marker".to_string()));
553 }
554
555 let marker = data[pos + 1];
556 pos += 2;
557
558 if marker == 0xFF {
560 continue;
561 }
562
563 if (0xC0..=0xCF).contains(&marker) && marker != 0xC4 && marker != 0xC8 && marker != 0xCC {
565 if pos + 7 >= data.len() {
567 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
568 }
569
570 pos += 2;
572
573 pos += 1;
575
576 height = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
578 pos += 2;
579 width = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
580 pos += 2;
581
582 components = data[pos];
584 break;
585 } else if marker == 0xD9 {
586 break;
588 } else if marker == 0xD8 || (0xD0..=0xD7).contains(&marker) {
589 continue;
591 } else {
592 if pos + 1 >= data.len() {
594 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
595 }
596 let length = ((data[pos] as usize) << 8) | (data[pos + 1] as usize);
597 pos += length;
598 }
599 }
600
601 if width == 0 || height == 0 {
602 return Err(PdfError::InvalidImage(
603 "Could not find image dimensions".to_string(),
604 ));
605 }
606
607 let color_space = match components {
608 1 => ColorSpace::DeviceGray,
609 3 => ColorSpace::DeviceRGB,
610 4 => ColorSpace::DeviceCMYK,
611 _ => {
612 return Err(PdfError::InvalidImage(format!(
613 "Unsupported number of components: {components}"
614 )))
615 }
616 };
617
618 Ok((width, height, color_space, 8)) }
620
621#[allow(dead_code)]
623fn parse_png_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
624 if data.len() < 8 || &data[0..8] != b"\x89PNG\r\n\x1a\n" {
626 return Err(PdfError::InvalidImage("Not a valid PNG file".to_string()));
627 }
628
629 let mut pos = 8;
631
632 while pos + 8 < data.len() {
633 let chunk_length =
635 u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
636
637 let chunk_type = &data[pos + 4..pos + 8];
639
640 if chunk_type == b"IHDR" {
641 if pos + 8 + chunk_length > data.len() || chunk_length < 13 {
643 return Err(PdfError::InvalidImage("Invalid PNG IHDR chunk".to_string()));
644 }
645
646 let ihdr_data = &data[pos + 8..pos + 8 + chunk_length];
647
648 let width =
650 u32::from_be_bytes([ihdr_data[0], ihdr_data[1], ihdr_data[2], ihdr_data[3]]);
651
652 let height =
653 u32::from_be_bytes([ihdr_data[4], ihdr_data[5], ihdr_data[6], ihdr_data[7]]);
654
655 let bit_depth = ihdr_data[8];
656 let color_type = ihdr_data[9];
657
658 let color_space = match color_type {
660 0 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 3 => ColorSpace::DeviceRGB, 4 => ColorSpace::DeviceGray, 6 => ColorSpace::DeviceRGB, _ => {
666 return Err(PdfError::InvalidImage(format!(
667 "Unsupported PNG color type: {color_type}"
668 )))
669 }
670 };
671
672 return Ok((width, height, color_space, bit_depth));
673 }
674
675 pos += 8 + chunk_length + 4; }
678
679 Err(PdfError::InvalidImage(
680 "PNG IHDR chunk not found".to_string(),
681 ))
682}
683
684fn parse_tiff_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
686 if data.len() < 8 {
687 return Err(PdfError::InvalidImage(
688 "Invalid TIFF file: too short".to_string(),
689 ));
690 }
691
692 let (is_little_endian, offset) = if &data[0..2] == b"II" {
694 (true, 2) } else if &data[0..2] == b"MM" {
696 (false, 2) } else {
698 return Err(PdfError::InvalidImage(
699 "Invalid TIFF byte order".to_string(),
700 ));
701 };
702
703 let magic = if is_little_endian {
705 u16::from_le_bytes([data[offset], data[offset + 1]])
706 } else {
707 u16::from_be_bytes([data[offset], data[offset + 1]])
708 };
709
710 if magic != 42 {
711 return Err(PdfError::InvalidImage(
712 "Invalid TIFF magic number".to_string(),
713 ));
714 }
715
716 let ifd_offset = if is_little_endian {
718 u32::from_le_bytes([
719 data[offset + 2],
720 data[offset + 3],
721 data[offset + 4],
722 data[offset + 5],
723 ])
724 } else {
725 u32::from_be_bytes([
726 data[offset + 2],
727 data[offset + 3],
728 data[offset + 4],
729 data[offset + 5],
730 ])
731 } as usize;
732
733 if ifd_offset + 2 > data.len() {
734 return Err(PdfError::InvalidImage(
735 "Invalid TIFF IFD offset".to_string(),
736 ));
737 }
738
739 let num_entries = if is_little_endian {
741 u16::from_le_bytes([data[ifd_offset], data[ifd_offset + 1]])
742 } else {
743 u16::from_be_bytes([data[ifd_offset], data[ifd_offset + 1]])
744 };
745
746 let mut width = 0u32;
747 let mut height = 0u32;
748 let mut bits_per_sample = 8u16;
749 let mut photometric_interpretation = 0u16;
750
751 for i in 0..num_entries {
753 let entry_offset = ifd_offset + 2 + (i as usize * 12);
754
755 if entry_offset + 12 > data.len() {
756 break;
757 }
758
759 let tag = if is_little_endian {
760 u16::from_le_bytes([data[entry_offset], data[entry_offset + 1]])
761 } else {
762 u16::from_be_bytes([data[entry_offset], data[entry_offset + 1]])
763 };
764
765 let value_offset = entry_offset + 8;
766
767 match tag {
768 256 => {
769 width = if is_little_endian {
771 u32::from_le_bytes([
772 data[value_offset],
773 data[value_offset + 1],
774 data[value_offset + 2],
775 data[value_offset + 3],
776 ])
777 } else {
778 u32::from_be_bytes([
779 data[value_offset],
780 data[value_offset + 1],
781 data[value_offset + 2],
782 data[value_offset + 3],
783 ])
784 };
785 }
786 257 => {
787 height = if is_little_endian {
789 u32::from_le_bytes([
790 data[value_offset],
791 data[value_offset + 1],
792 data[value_offset + 2],
793 data[value_offset + 3],
794 ])
795 } else {
796 u32::from_be_bytes([
797 data[value_offset],
798 data[value_offset + 1],
799 data[value_offset + 2],
800 data[value_offset + 3],
801 ])
802 };
803 }
804 258 => {
805 bits_per_sample = if is_little_endian {
807 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
808 } else {
809 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
810 };
811 }
812 262 => {
813 photometric_interpretation = if is_little_endian {
815 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
816 } else {
817 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
818 };
819 }
820 _ => {} }
822 }
823
824 if width == 0 || height == 0 {
825 return Err(PdfError::InvalidImage(
826 "TIFF dimensions not found".to_string(),
827 ));
828 }
829
830 let color_space = match photometric_interpretation {
832 0 | 1 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 5 => ColorSpace::DeviceCMYK, _ => ColorSpace::DeviceRGB, };
837
838 Ok((width, height, color_space, bits_per_sample as u8))
839}
840
841#[cfg(test)]
842mod tests {
843 use super::*;
844
845 fn create_minimal_png(width: u32, height: u32, color_type: u8) -> Vec<u8> {
847 match color_type {
850 0 => {
851 vec![
853 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,
864 0x01, 0xE2, 0xF9, 0x8C, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
870 }
871 2 => {
872 vec![
874 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,
885 0x01, 0x27, 0x18, 0xAA, 0x61, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
891 }
892 3 => {
893 vec![
895 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,
910 0x01, 0xE5, 0x27, 0xDE, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
916 }
917 6 => {
918 vec![
920 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,
931 0x01, 0x75, 0xAA, 0x50, 0x19, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
937 }
938 _ => {
939 create_minimal_png(width, height, 2)
941 }
942 }
943 }
944
945 #[test]
946 fn test_parse_jpeg_header() {
947 let jpeg_data = vec![
949 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, ];
958
959 let result = parse_jpeg_header(&jpeg_data);
960 assert!(result.is_ok());
961 let (width, height, color_space, bits) = result.unwrap();
962 assert_eq!(width, 200);
963 assert_eq!(height, 100);
964 assert_eq!(color_space, ColorSpace::DeviceRGB);
965 assert_eq!(bits, 8);
966 }
967
968 #[test]
969 fn test_invalid_jpeg() {
970 let invalid_data = vec![0x00, 0x00];
971 let result = parse_jpeg_header(&invalid_data);
972 assert!(result.is_err());
973 }
974
975 #[test]
976 fn test_parse_png_header() {
977 let mut png_data = vec![
979 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, ];
990
991 png_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
993
994 let result = parse_png_header(&png_data);
995 assert!(result.is_ok());
996 let (width, height, color_space, bits) = result.unwrap();
997 assert_eq!(width, 100);
998 assert_eq!(height, 100);
999 assert_eq!(color_space, ColorSpace::DeviceRGB);
1000 assert_eq!(bits, 8);
1001 }
1002
1003 #[test]
1004 fn test_invalid_png() {
1005 let invalid_data = vec![0x00, 0x00];
1006 let result = parse_png_header(&invalid_data);
1007 assert!(result.is_err());
1008 }
1009
1010 #[test]
1011 fn test_parse_tiff_header_little_endian() {
1012 let tiff_data = vec![
1014 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
1020 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
1022 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1024 0x00, 0x00, ];
1026
1027 let result = parse_tiff_header(&tiff_data);
1028 assert!(result.is_ok());
1029 let (width, height, color_space, bits) = result.unwrap();
1030 assert_eq!(width, 100);
1031 assert_eq!(height, 100);
1032 assert_eq!(color_space, ColorSpace::DeviceGray);
1033 assert_eq!(bits, 8);
1034 }
1035
1036 #[test]
1037 fn test_parse_tiff_header_big_endian() {
1038 let tiff_data = vec![
1040 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
1046 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
1048 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
1050 0x00, 0x00, ];
1052
1053 let result = parse_tiff_header(&tiff_data);
1054 assert!(result.is_ok());
1055 let (width, height, color_space, bits) = result.unwrap();
1056 assert_eq!(width, 100);
1057 assert_eq!(height, 100);
1058 assert_eq!(color_space, ColorSpace::DeviceGray);
1059 assert_eq!(bits, 8);
1060 }
1061
1062 #[test]
1063 fn test_invalid_tiff() {
1064 let invalid_data = vec![0x00, 0x00];
1065 let result = parse_tiff_header(&invalid_data);
1066 assert!(result.is_err());
1067 }
1068
1069 #[test]
1070 fn test_image_format_enum() {
1071 assert_eq!(ImageFormat::Jpeg, ImageFormat::Jpeg);
1072 assert_eq!(ImageFormat::Png, ImageFormat::Png);
1073 assert_eq!(ImageFormat::Tiff, ImageFormat::Tiff);
1074 assert_ne!(ImageFormat::Jpeg, ImageFormat::Png);
1075 }
1076
1077 mod comprehensive_tests {
1079 use super::*;
1080 use std::fs;
1081 use tempfile::TempDir;
1082
1083 #[test]
1084 fn test_image_format_variants() {
1085 let jpeg = ImageFormat::Jpeg;
1087 let png = ImageFormat::Png;
1088 let tiff = ImageFormat::Tiff;
1089
1090 assert_eq!(jpeg, ImageFormat::Jpeg);
1091 assert_eq!(png, ImageFormat::Png);
1092 assert_eq!(tiff, ImageFormat::Tiff);
1093
1094 assert_ne!(jpeg, png);
1095 assert_ne!(png, tiff);
1096 assert_ne!(tiff, jpeg);
1097 }
1098
1099 #[test]
1100 fn test_image_format_debug() {
1101 let jpeg = ImageFormat::Jpeg;
1102 let png = ImageFormat::Png;
1103 let tiff = ImageFormat::Tiff;
1104
1105 assert_eq!(format!("{jpeg:?}"), "Jpeg");
1106 assert_eq!(format!("{png:?}"), "Png");
1107 assert_eq!(format!("{tiff:?}"), "Tiff");
1108 }
1109
1110 #[test]
1111 fn test_image_format_clone_copy() {
1112 let jpeg = ImageFormat::Jpeg;
1113 let jpeg_clone = jpeg;
1114 let jpeg_copy = jpeg;
1115
1116 assert_eq!(jpeg_clone, ImageFormat::Jpeg);
1117 assert_eq!(jpeg_copy, ImageFormat::Jpeg);
1118 }
1119
1120 #[test]
1121 fn test_color_space_variants() {
1122 let gray = ColorSpace::DeviceGray;
1124 let rgb = ColorSpace::DeviceRGB;
1125 let cmyk = ColorSpace::DeviceCMYK;
1126
1127 assert_eq!(gray, ColorSpace::DeviceGray);
1128 assert_eq!(rgb, ColorSpace::DeviceRGB);
1129 assert_eq!(cmyk, ColorSpace::DeviceCMYK);
1130
1131 assert_ne!(gray, rgb);
1132 assert_ne!(rgb, cmyk);
1133 assert_ne!(cmyk, gray);
1134 }
1135
1136 #[test]
1137 fn test_color_space_debug() {
1138 let gray = ColorSpace::DeviceGray;
1139 let rgb = ColorSpace::DeviceRGB;
1140 let cmyk = ColorSpace::DeviceCMYK;
1141
1142 assert_eq!(format!("{gray:?}"), "DeviceGray");
1143 assert_eq!(format!("{rgb:?}"), "DeviceRGB");
1144 assert_eq!(format!("{cmyk:?}"), "DeviceCMYK");
1145 }
1146
1147 #[test]
1148 fn test_color_space_clone_copy() {
1149 let rgb = ColorSpace::DeviceRGB;
1150 let rgb_clone = rgb;
1151 let rgb_copy = rgb;
1152
1153 assert_eq!(rgb_clone, ColorSpace::DeviceRGB);
1154 assert_eq!(rgb_copy, ColorSpace::DeviceRGB);
1155 }
1156
1157 #[test]
1158 fn test_image_from_jpeg_data() {
1159 let jpeg_data = vec![
1161 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1173
1174 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1175
1176 assert_eq!(image.width(), 200);
1177 assert_eq!(image.height(), 100);
1178 assert_eq!(image.format(), ImageFormat::Jpeg);
1179 assert_eq!(image.data(), jpeg_data);
1180 }
1181
1182 #[test]
1183 fn test_image_from_png_data() {
1184 let mut png_data = Vec::new();
1186
1187 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1189
1190 png_data.extend_from_slice(&[
1192 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, ]);
1202 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]);
1204
1205 let raw_data = vec![0x00, 0x00, 0x00, 0x00]; use flate2::write::ZlibEncoder;
1210 use flate2::Compression;
1211 use std::io::Write;
1212
1213 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1214 encoder.write_all(&raw_data).unwrap();
1215 let compressed_data = encoder.finish().unwrap();
1216
1217 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1219 png_data.extend_from_slice(b"IDAT");
1220 png_data.extend_from_slice(&compressed_data);
1221 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); png_data.extend_from_slice(&[
1225 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]);
1229
1230 let image = Image::from_png_data(png_data.clone()).unwrap();
1231
1232 assert_eq!(image.width(), 1);
1233 assert_eq!(image.height(), 1);
1234 assert_eq!(image.format(), ImageFormat::Png);
1235 assert_eq!(image.data(), png_data);
1236 }
1237
1238 #[test]
1239 fn test_image_from_tiff_data() {
1240 let tiff_data = vec![
1242 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
1248 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
1250 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1252 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
1254 0x00, 0x00, ];
1256
1257 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
1258
1259 assert_eq!(image.width(), 128);
1260 assert_eq!(image.height(), 128);
1261 assert_eq!(image.format(), ImageFormat::Tiff);
1262 assert_eq!(image.data(), tiff_data);
1263 }
1264
1265 #[test]
1266 fn test_image_from_jpeg_file() {
1267 let temp_dir = TempDir::new().unwrap();
1268 let file_path = temp_dir.path().join("test.jpg");
1269
1270 let jpeg_data = vec![
1272 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1284
1285 fs::write(&file_path, &jpeg_data).unwrap();
1286
1287 let image = Image::from_jpeg_file(&file_path).unwrap();
1288
1289 assert_eq!(image.width(), 100);
1290 assert_eq!(image.height(), 50);
1291 assert_eq!(image.format(), ImageFormat::Jpeg);
1292 assert_eq!(image.data(), jpeg_data);
1293 }
1294
1295 #[test]
1296 fn test_image_from_png_file() {
1297 let temp_dir = TempDir::new().unwrap();
1298 let file_path = temp_dir.path().join("test.png");
1299
1300 let mut png_data = Vec::new();
1302
1303 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1305
1306 png_data.extend_from_slice(&[
1308 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, ]);
1318 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]); let raw_data = vec![0x00, 0x00, 0x00, 0x00]; use flate2::write::ZlibEncoder;
1324 use flate2::Compression;
1325 use std::io::Write;
1326
1327 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1328 encoder.write_all(&raw_data).unwrap();
1329 let compressed_data = encoder.finish().unwrap();
1330
1331 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1332 png_data.extend_from_slice(b"IDAT");
1333 png_data.extend_from_slice(&compressed_data);
1334 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); png_data.extend_from_slice(&[
1338 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]);
1342
1343 fs::write(&file_path, &png_data).unwrap();
1344
1345 let image = Image::from_png_file(&file_path).unwrap();
1346
1347 assert_eq!(image.width(), 1);
1348 assert_eq!(image.height(), 1);
1349 assert_eq!(image.format(), ImageFormat::Png);
1350 assert_eq!(image.data(), png_data);
1351 }
1352
1353 #[test]
1354 fn test_image_from_tiff_file() {
1355 let temp_dir = TempDir::new().unwrap();
1356 let file_path = temp_dir.path().join("test.tiff");
1357
1358 let tiff_data = vec![
1360 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
1366 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
1368 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1370 0x00, 0x00, ];
1372
1373 fs::write(&file_path, &tiff_data).unwrap();
1374
1375 let image = Image::from_tiff_file(&file_path).unwrap();
1376
1377 assert_eq!(image.width(), 96);
1378 assert_eq!(image.height(), 96);
1379 assert_eq!(image.format(), ImageFormat::Tiff);
1380 assert_eq!(image.data(), tiff_data);
1381 }
1382
1383 #[test]
1384 fn test_image_to_pdf_object_jpeg() {
1385 let jpeg_data = vec![
1386 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1398
1399 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1400 let pdf_obj = image.to_pdf_object();
1401
1402 if let Object::Stream(dict, data) = pdf_obj {
1403 assert_eq!(
1404 dict.get("Type").unwrap(),
1405 &Object::Name("XObject".to_string())
1406 );
1407 assert_eq!(
1408 dict.get("Subtype").unwrap(),
1409 &Object::Name("Image".to_string())
1410 );
1411 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
1412 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
1413 assert_eq!(
1414 dict.get("ColorSpace").unwrap(),
1415 &Object::Name("DeviceRGB".to_string())
1416 );
1417 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1418 assert_eq!(
1419 dict.get("Filter").unwrap(),
1420 &Object::Name("DCTDecode".to_string())
1421 );
1422 assert_eq!(data, jpeg_data);
1423 } else {
1424 panic!("Expected Stream object");
1425 }
1426 }
1427
1428 #[test]
1429 fn test_image_to_pdf_object_png() {
1430 let mut png_data = Vec::new();
1432
1433 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1434 png_data.extend_from_slice(&[
1435 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
1436 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00,
1437 ]);
1438 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]);
1439
1440 let raw_data = vec![0x00, 0x00, 0x00, 0x00];
1441 use flate2::write::ZlibEncoder;
1442 use flate2::Compression;
1443 use std::io::Write;
1444
1445 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1446 encoder.write_all(&raw_data).unwrap();
1447 let compressed_data = encoder.finish().unwrap();
1448
1449 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1450 png_data.extend_from_slice(b"IDAT");
1451 png_data.extend_from_slice(&compressed_data);
1452 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
1453
1454 png_data.extend_from_slice(&[
1455 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
1456 ]);
1457
1458 let image = Image::from_png_data(png_data.clone()).unwrap();
1459 let pdf_obj = image.to_pdf_object();
1460
1461 if let Object::Stream(dict, data) = pdf_obj {
1462 assert_eq!(
1463 dict.get("Type").unwrap(),
1464 &Object::Name("XObject".to_string())
1465 );
1466 assert_eq!(
1467 dict.get("Subtype").unwrap(),
1468 &Object::Name("Image".to_string())
1469 );
1470 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(1));
1471 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(1));
1472 assert_eq!(
1473 dict.get("ColorSpace").unwrap(),
1474 &Object::Name("DeviceRGB".to_string())
1475 );
1476 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1477 assert_eq!(
1478 dict.get("Filter").unwrap(),
1479 &Object::Name("FlateDecode".to_string())
1480 );
1481 assert_eq!(data, png_data);
1482 } else {
1483 panic!("Expected Stream object");
1484 }
1485 }
1486
1487 #[test]
1488 fn test_image_to_pdf_object_tiff() {
1489 let tiff_data = vec![
1490 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
1496 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
1498 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1500 0x00, 0x00, ];
1502
1503 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
1504 let pdf_obj = image.to_pdf_object();
1505
1506 if let Object::Stream(dict, data) = pdf_obj {
1507 assert_eq!(
1508 dict.get("Type").unwrap(),
1509 &Object::Name("XObject".to_string())
1510 );
1511 assert_eq!(
1512 dict.get("Subtype").unwrap(),
1513 &Object::Name("Image".to_string())
1514 );
1515 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(64));
1516 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(64));
1517 assert_eq!(
1518 dict.get("ColorSpace").unwrap(),
1519 &Object::Name("DeviceGray".to_string())
1520 );
1521 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1522 assert_eq!(
1523 dict.get("Filter").unwrap(),
1524 &Object::Name("FlateDecode".to_string())
1525 );
1526 assert_eq!(data, tiff_data);
1527 } else {
1528 panic!("Expected Stream object");
1529 }
1530 }
1531
1532 #[test]
1533 fn test_image_clone() {
1534 let jpeg_data = vec![
1535 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1547
1548 let image1 = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1549 let image2 = image1.clone();
1550
1551 assert_eq!(image1.width(), image2.width());
1552 assert_eq!(image1.height(), image2.height());
1553 assert_eq!(image1.format(), image2.format());
1554 assert_eq!(image1.data(), image2.data());
1555 }
1556
1557 #[test]
1558 fn test_image_debug() {
1559 let jpeg_data = vec![
1560 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1572
1573 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1574 let debug_str = format!("{image:?}");
1575
1576 assert!(debug_str.contains("Image"));
1577 assert!(debug_str.contains("width"));
1578 assert!(debug_str.contains("height"));
1579 assert!(debug_str.contains("format"));
1580 }
1581
1582 #[test]
1583 fn test_jpeg_grayscale_image() {
1584 let jpeg_data = vec![
1585 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x01, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD9, ];
1596
1597 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1598 let pdf_obj = image.to_pdf_object();
1599
1600 if let Object::Stream(dict, _) = pdf_obj {
1601 assert_eq!(
1602 dict.get("ColorSpace").unwrap(),
1603 &Object::Name("DeviceGray".to_string())
1604 );
1605 } else {
1606 panic!("Expected Stream object");
1607 }
1608 }
1609
1610 #[test]
1611 fn test_jpeg_cmyk_image() {
1612 let jpeg_data = vec![
1613 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x04, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1625
1626 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1627 let pdf_obj = image.to_pdf_object();
1628
1629 if let Object::Stream(dict, _) = pdf_obj {
1630 assert_eq!(
1631 dict.get("ColorSpace").unwrap(),
1632 &Object::Name("DeviceCMYK".to_string())
1633 );
1634 } else {
1635 panic!("Expected Stream object");
1636 }
1637 }
1638
1639 #[test]
1640 fn test_png_grayscale_image() {
1641 let png_data = create_minimal_png(1, 1, 0); let image = Image::from_png_data(png_data).unwrap();
1644 let pdf_obj = image.to_pdf_object();
1645
1646 if let Object::Stream(dict, _) = pdf_obj {
1647 assert_eq!(
1648 dict.get("ColorSpace").unwrap(),
1649 &Object::Name("DeviceGray".to_string())
1650 );
1651 } else {
1652 panic!("Expected Stream object");
1653 }
1654 }
1655
1656 #[test]
1657 #[ignore = "Palette PNG not yet fully supported - see PNG_DECODER_ISSUES.md"]
1658 fn test_png_palette_image() {
1659 let png_data = vec![
1660 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, ];
1672
1673 let image = Image::from_png_data(png_data).unwrap();
1674 let pdf_obj = image.to_pdf_object();
1675
1676 if let Object::Stream(dict, _) = pdf_obj {
1677 assert_eq!(
1679 dict.get("ColorSpace").unwrap(),
1680 &Object::Name("DeviceRGB".to_string())
1681 );
1682 } else {
1683 panic!("Expected Stream object");
1684 }
1685 }
1686
1687 #[test]
1688 fn test_tiff_big_endian() {
1689 let tiff_data = vec![
1690 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1696 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1698 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00,
1700 0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
1702 0x00, 0x00, ];
1704
1705 let image = Image::from_tiff_data(tiff_data).unwrap();
1706
1707 assert_eq!(image.width(), 128);
1708 assert_eq!(image.height(), 128);
1709 assert_eq!(image.format(), ImageFormat::Tiff);
1710 }
1711
1712 #[test]
1713 fn test_tiff_cmyk_image() {
1714 let tiff_data = vec![
1715 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1721 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1723 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1725 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
1727 0x00, 0x00, ];
1729
1730 let image = Image::from_tiff_data(tiff_data).unwrap();
1731 let pdf_obj = image.to_pdf_object();
1732
1733 if let Object::Stream(dict, _) = pdf_obj {
1734 assert_eq!(
1735 dict.get("ColorSpace").unwrap(),
1736 &Object::Name("DeviceCMYK".to_string())
1737 );
1738 } else {
1739 panic!("Expected Stream object");
1740 }
1741 }
1742
1743 #[test]
1744 fn test_error_invalid_jpeg() {
1745 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_jpeg_data(invalid_data);
1747 assert!(result.is_err());
1748 }
1749
1750 #[test]
1751 fn test_error_invalid_png() {
1752 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_png_data(invalid_data);
1754 assert!(result.is_err());
1755 }
1756
1757 #[test]
1758 fn test_error_invalid_tiff() {
1759 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_tiff_data(invalid_data);
1761 assert!(result.is_err());
1762 }
1763
1764 #[test]
1765 fn test_error_truncated_jpeg() {
1766 let truncated_data = vec![0xFF, 0xD8, 0xFF]; let result = Image::from_jpeg_data(truncated_data);
1768 assert!(result.is_err());
1769 }
1770
1771 #[test]
1772 fn test_error_truncated_png() {
1773 let truncated_data = vec![0x89, 0x50, 0x4E, 0x47]; let result = Image::from_png_data(truncated_data);
1775 assert!(result.is_err());
1776 }
1777
1778 #[test]
1779 fn test_error_truncated_tiff() {
1780 let truncated_data = vec![0x49, 0x49, 0x2A]; let result = Image::from_tiff_data(truncated_data);
1782 assert!(result.is_err());
1783 }
1784
1785 #[test]
1786 fn test_error_jpeg_unsupported_components() {
1787 let invalid_jpeg = vec![
1788 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x05, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1800
1801 let result = Image::from_jpeg_data(invalid_jpeg);
1802 assert!(result.is_err());
1803 }
1804
1805 #[test]
1806 fn test_error_png_unsupported_color_type() {
1807 let invalid_png = vec![
1808 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, ];
1820
1821 let result = Image::from_png_data(invalid_png);
1822 assert!(result.is_err());
1823 }
1824
1825 #[test]
1826 fn test_error_nonexistent_file() {
1827 let result = Image::from_jpeg_file("/nonexistent/path/image.jpg");
1828 assert!(result.is_err());
1829
1830 let result = Image::from_png_file("/nonexistent/path/image.png");
1831 assert!(result.is_err());
1832
1833 let result = Image::from_tiff_file("/nonexistent/path/image.tiff");
1834 assert!(result.is_err());
1835 }
1836
1837 #[test]
1838 fn test_jpeg_no_dimensions() {
1839 let jpeg_no_dims = vec![
1840 0xFF, 0xD8, 0xFF, 0xD9, ];
1843
1844 let result = Image::from_jpeg_data(jpeg_no_dims);
1845 assert!(result.is_err());
1846 }
1847
1848 #[test]
1849 fn test_png_no_ihdr() {
1850 let png_no_ihdr = vec![
1851 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,
1855 0x72, 0x6E, 0x38, ];
1857
1858 let result = Image::from_png_data(png_no_ihdr);
1859 assert!(result.is_err());
1860 }
1861
1862 #[test]
1863 fn test_tiff_no_dimensions() {
1864 let tiff_no_dims = vec![
1865 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1871 0x00, 0x00, ];
1873
1874 let result = Image::from_tiff_data(tiff_no_dims);
1875 assert!(result.is_err());
1876 }
1877
1878 fn png_crc32(data: &[u8]) -> u32 {
1880 let mut crc = 0xFFFFFFFF_u32;
1882 for &byte in data {
1883 crc ^= byte as u32;
1884 for _ in 0..8 {
1885 if crc & 1 != 0 {
1886 crc = (crc >> 1) ^ 0xEDB88320;
1887 } else {
1888 crc >>= 1;
1889 }
1890 }
1891 }
1892 !crc
1893 }
1894
1895 fn create_valid_png_data(
1897 width: u32,
1898 height: u32,
1899 bit_depth: u8,
1900 color_type: u8,
1901 ) -> Vec<u8> {
1902 use flate2::write::ZlibEncoder;
1903 use flate2::Compression;
1904 use std::io::Write;
1905
1906 let mut png_data = Vec::new();
1907
1908 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1910
1911 let mut ihdr_data = Vec::new();
1913 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();
1923 ihdr_crc_data.extend_from_slice(b"IHDR");
1924 ihdr_crc_data.extend_from_slice(&ihdr_data);
1925 let ihdr_crc = png_crc32(&ihdr_crc_data);
1926
1927 png_data.extend_from_slice(&(ihdr_data.len() as u32).to_be_bytes());
1929 png_data.extend_from_slice(b"IHDR");
1930 png_data.extend_from_slice(&ihdr_data);
1931 png_data.extend_from_slice(&ihdr_crc.to_be_bytes());
1932
1933 let bytes_per_pixel = match (color_type, bit_depth) {
1935 (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,
1941 };
1942
1943 let mut raw_data = Vec::new();
1944 for _y in 0..height {
1945 raw_data.push(0); for _x in 0..width {
1947 for _c in 0..bytes_per_pixel {
1948 raw_data.push(0); }
1950 }
1951 }
1952
1953 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1955 encoder.write_all(&raw_data).unwrap();
1956 let compressed_data = encoder.finish().unwrap();
1957
1958 let mut idat_crc_data = Vec::new();
1960 idat_crc_data.extend_from_slice(b"IDAT");
1961 idat_crc_data.extend_from_slice(&compressed_data);
1962 let idat_crc = png_crc32(&idat_crc_data);
1963
1964 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1966 png_data.extend_from_slice(b"IDAT");
1967 png_data.extend_from_slice(&compressed_data);
1968 png_data.extend_from_slice(&idat_crc.to_be_bytes());
1969
1970 png_data.extend_from_slice(&[0, 0, 0, 0]); png_data.extend_from_slice(b"IEND");
1973 png_data.extend_from_slice(&[0xAE, 0x42, 0x60, 0x82]); png_data
1976 }
1977
1978 #[test]
1979 fn test_different_bit_depths() {
1980 let png_8bit = create_valid_png_data(2, 2, 8, 2); let image_8bit = Image::from_png_data(png_8bit).unwrap();
1985 let pdf_obj_8bit = image_8bit.to_pdf_object();
1986
1987 if let Object::Stream(dict, _) = pdf_obj_8bit {
1988 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1989 } else {
1990 panic!("Expected Stream object");
1991 }
1992
1993 let png_16bit = create_valid_png_data(2, 2, 16, 2); let image_16bit = Image::from_png_data(png_16bit).unwrap();
1996 let pdf_obj_16bit = image_16bit.to_pdf_object();
1997
1998 if let Object::Stream(dict, _) = pdf_obj_16bit {
1999 let bits = dict.get("BitsPerComponent").unwrap();
2001 assert!(matches!(bits, &Object::Integer(8) | &Object::Integer(16)));
2002 } else {
2003 panic!("Expected Stream object");
2004 }
2005 }
2006
2007 #[test]
2008 fn test_performance_large_image_data() {
2009 let mut large_jpeg = vec![
2011 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x04, 0x00, 0x04, 0x00, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, ];
2022
2023 large_jpeg.extend(vec![0x00; 10000]);
2025 large_jpeg.extend(vec![0xFF, 0xD9]); let start = std::time::Instant::now();
2028 let image = Image::from_jpeg_data(large_jpeg.clone()).unwrap();
2029 let duration = start.elapsed();
2030
2031 assert_eq!(image.width(), 1024);
2032 assert_eq!(image.height(), 1024);
2033 assert_eq!(image.data().len(), large_jpeg.len());
2034 assert!(duration.as_millis() < 100); }
2036
2037 #[test]
2038 fn test_memory_efficiency() {
2039 let jpeg_data = vec![
2040 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
2052
2053 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
2054
2055 assert_eq!(image.data().len(), jpeg_data.len());
2057 assert_eq!(image.data(), jpeg_data);
2058
2059 let cloned = image.clone();
2061 assert_eq!(cloned.data(), image.data());
2062 }
2063
2064 #[test]
2065 fn test_complete_workflow() {
2066 let test_cases = vec![
2068 (ImageFormat::Jpeg, "DCTDecode", "DeviceRGB"),
2069 (ImageFormat::Png, "FlateDecode", "DeviceRGB"),
2070 (ImageFormat::Tiff, "FlateDecode", "DeviceGray"),
2071 ];
2072
2073 for (expected_format, expected_filter, expected_color_space) in test_cases {
2074 let data = match expected_format {
2075 ImageFormat::Jpeg => vec![
2076 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ],
2088 ImageFormat::Png => create_valid_png_data(2, 2, 8, 2), ImageFormat::Tiff => vec![
2090 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00,
2096 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
2098 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
2100 0x00, 0x00, 0x00, 0x00, ],
2102 ImageFormat::Raw => Vec::new(), };
2104
2105 let image = match expected_format {
2106 ImageFormat::Jpeg => Image::from_jpeg_data(data.clone()).unwrap(),
2107 ImageFormat::Png => Image::from_png_data(data.clone()).unwrap(),
2108 ImageFormat::Tiff => Image::from_tiff_data(data.clone()).unwrap(),
2109 ImageFormat::Raw => continue, };
2111
2112 assert_eq!(image.format(), expected_format);
2114 if expected_format == ImageFormat::Png {
2116 assert_eq!(image.width(), 2);
2117 assert_eq!(image.height(), 2);
2118 } else if expected_format == ImageFormat::Jpeg {
2119 assert_eq!(image.width(), 200);
2120 assert_eq!(image.height(), 100);
2121 } else if expected_format == ImageFormat::Tiff {
2122 assert_eq!(image.width(), 200);
2123 assert_eq!(image.height(), 100);
2124 }
2125 assert_eq!(image.data(), data);
2126
2127 let pdf_obj = image.to_pdf_object();
2129 if let Object::Stream(dict, stream_data) = pdf_obj {
2130 assert_eq!(
2131 dict.get("Type").unwrap(),
2132 &Object::Name("XObject".to_string())
2133 );
2134 assert_eq!(
2135 dict.get("Subtype").unwrap(),
2136 &Object::Name("Image".to_string())
2137 );
2138 if expected_format == ImageFormat::Png {
2140 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(2));
2141 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(2));
2142 } else {
2143 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
2144 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
2145 }
2146 assert_eq!(
2147 dict.get("ColorSpace").unwrap(),
2148 &Object::Name(expected_color_space.to_string())
2149 );
2150 assert_eq!(
2151 dict.get("Filter").unwrap(),
2152 &Object::Name(expected_filter.to_string())
2153 );
2154 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
2155 assert_eq!(stream_data, data);
2156 } else {
2157 panic!("Expected Stream object for format {expected_format:?}");
2158 }
2159 }
2160 }
2161 }
2162}