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) -> Result<(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).map_err(|e| {
405 PdfError::InvalidImage(format!("Failed to compress image data: {}", e))
406 })?;
407 encoder.finish().map_err(|e| {
408 PdfError::InvalidImage(format!("Failed to finalize image compression: {}", e))
409 })?
410 }
411 ImageFormat::Tiff => {
412 main_dict.set("Filter", Object::Name("FlateDecode".to_string()));
413 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
414 encoder.write_all(&self.data).map_err(|e| {
415 PdfError::InvalidImage(format!("Failed to compress TIFF data: {}", e))
416 })?;
417 encoder.finish().map_err(|e| {
418 PdfError::InvalidImage(format!("Failed to finalize TIFF compression: {}", e))
419 })?
420 }
421 };
422
423 main_dict.set("Length", Object::Integer(main_data.len() as i64));
425
426 let smask_obj = if let Some(mask) = &self.soft_mask {
428 let mut mask_dict = Dictionary::new();
429 mask_dict.set("Type", Object::Name("XObject".to_string()));
430 mask_dict.set("Subtype", Object::Name("Image".to_string()));
431 mask_dict.set("Width", Object::Integer(mask.width as i64));
432 mask_dict.set("Height", Object::Integer(mask.height as i64));
433 mask_dict.set("ColorSpace", Object::Name("DeviceGray".to_string()));
434 mask_dict.set("BitsPerComponent", Object::Integer(8));
435 mask_dict.set("Filter", Object::Name("FlateDecode".to_string()));
436
437 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
439 encoder.write_all(&mask.data).map_err(|e| {
440 PdfError::InvalidImage(format!("Failed to compress alpha channel: {}", e))
441 })?;
442 let compressed_mask_data = encoder.finish().map_err(|e| {
443 PdfError::InvalidImage(format!("Failed to finalize alpha compression: {}", e))
444 })?;
445
446 mask_dict.set("Length", Object::Integer(compressed_mask_data.len() as i64));
448
449 Some(Object::Stream(mask_dict, compressed_mask_data))
450 } else {
451 None
452 };
453
454 Ok((Object::Stream(main_dict, main_data), smask_obj))
458 }
459
460 pub fn has_transparency(&self) -> bool {
462 self.soft_mask.is_some() || self.alpha_data.is_some()
463 }
464
465 pub fn create_stencil_mask(&self, threshold: u8) -> Option<Image> {
468 if let Some(alpha) = &self.alpha_data {
469 let mut mask_data = Vec::new();
471 let mut current_byte = 0u8;
472 let mut bit_count = 0;
473
474 for &alpha_value in alpha.iter() {
475 if alpha_value > threshold {
477 current_byte |= 1 << (7 - bit_count);
478 }
479
480 bit_count += 1;
481 if bit_count == 8 {
482 mask_data.push(current_byte);
483 current_byte = 0;
484 bit_count = 0;
485 }
486 }
487
488 if bit_count > 0 {
490 mask_data.push(current_byte);
491 }
492
493 Some(Image {
494 data: mask_data,
495 format: ImageFormat::Raw,
496 width: self.width,
497 height: self.height,
498 color_space: ColorSpace::DeviceGray,
499 bits_per_component: 1,
500 alpha_data: None,
501 soft_mask: None,
502 })
503 } else {
504 None
505 }
506 }
507
508 pub fn create_mask(&self, mask_type: MaskType, threshold: Option<u8>) -> Option<Image> {
510 match mask_type {
511 MaskType::Soft => self.soft_mask.as_ref().map(|m| m.as_ref().clone()),
512 MaskType::Stencil => self.create_stencil_mask(threshold.unwrap_or(128)),
513 }
514 }
515
516 pub fn with_mask(mut self, mask: Image, mask_type: MaskType) -> Self {
518 match mask_type {
519 MaskType::Soft => {
520 self.soft_mask = Some(Box::new(mask));
521 }
522 MaskType::Stencil => {
523 self.soft_mask = Some(Box::new(mask));
525 }
526 }
527 self
528 }
529
530 pub fn soft_mask(&self) -> Option<&Image> {
532 self.soft_mask.as_ref().map(|m| m.as_ref())
533 }
534
535 pub fn alpha_data(&self) -> Option<&[u8]> {
537 self.alpha_data.as_deref()
538 }
539}
540
541fn parse_jpeg_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
543 if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
544 return Err(PdfError::InvalidImage("Not a valid JPEG file".to_string()));
545 }
546
547 let mut pos = 2;
548 let mut width = 0;
549 let mut height = 0;
550 let mut components = 0;
551
552 while pos < data.len() - 1 {
553 if data[pos] != 0xFF {
554 return Err(PdfError::InvalidImage("Invalid JPEG marker".to_string()));
555 }
556
557 let marker = data[pos + 1];
558 pos += 2;
559
560 if marker == 0xFF {
562 continue;
563 }
564
565 if (0xC0..=0xCF).contains(&marker) && marker != 0xC4 && marker != 0xC8 && marker != 0xCC {
567 if pos + 7 >= data.len() {
569 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
570 }
571
572 pos += 2;
574
575 pos += 1;
577
578 height = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
580 pos += 2;
581 width = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
582 pos += 2;
583
584 components = data[pos];
586 break;
587 } else if marker == 0xD9 {
588 break;
590 } else if marker == 0xD8 || (0xD0..=0xD7).contains(&marker) {
591 continue;
593 } else {
594 if pos + 1 >= data.len() {
596 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
597 }
598 let length = ((data[pos] as usize) << 8) | (data[pos + 1] as usize);
599 pos += length;
600 }
601 }
602
603 if width == 0 || height == 0 {
604 return Err(PdfError::InvalidImage(
605 "Could not find image dimensions".to_string(),
606 ));
607 }
608
609 let color_space = match components {
610 1 => ColorSpace::DeviceGray,
611 3 => ColorSpace::DeviceRGB,
612 4 => ColorSpace::DeviceCMYK,
613 _ => {
614 return Err(PdfError::InvalidImage(format!(
615 "Unsupported number of components: {components}"
616 )))
617 }
618 };
619
620 Ok((width, height, color_space, 8)) }
622
623#[allow(dead_code)]
625fn parse_png_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
626 if data.len() < 8 || &data[0..8] != b"\x89PNG\r\n\x1a\n" {
628 return Err(PdfError::InvalidImage("Not a valid PNG file".to_string()));
629 }
630
631 let mut pos = 8;
633
634 while pos + 8 < data.len() {
635 let chunk_length =
637 u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
638
639 let chunk_type = &data[pos + 4..pos + 8];
641
642 if chunk_type == b"IHDR" {
643 if pos + 8 + chunk_length > data.len() || chunk_length < 13 {
645 return Err(PdfError::InvalidImage("Invalid PNG IHDR chunk".to_string()));
646 }
647
648 let ihdr_data = &data[pos + 8..pos + 8 + chunk_length];
649
650 let width =
652 u32::from_be_bytes([ihdr_data[0], ihdr_data[1], ihdr_data[2], ihdr_data[3]]);
653
654 let height =
655 u32::from_be_bytes([ihdr_data[4], ihdr_data[5], ihdr_data[6], ihdr_data[7]]);
656
657 let bit_depth = ihdr_data[8];
658 let color_type = ihdr_data[9];
659
660 let color_space = match color_type {
662 0 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 3 => ColorSpace::DeviceRGB, 4 => ColorSpace::DeviceGray, 6 => ColorSpace::DeviceRGB, _ => {
668 return Err(PdfError::InvalidImage(format!(
669 "Unsupported PNG color type: {color_type}"
670 )))
671 }
672 };
673
674 return Ok((width, height, color_space, bit_depth));
675 }
676
677 pos += 8 + chunk_length + 4; }
680
681 Err(PdfError::InvalidImage(
682 "PNG IHDR chunk not found".to_string(),
683 ))
684}
685
686fn parse_tiff_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
688 if data.len() < 8 {
689 return Err(PdfError::InvalidImage(
690 "Invalid TIFF file: too short".to_string(),
691 ));
692 }
693
694 let (is_little_endian, offset) = if &data[0..2] == b"II" {
696 (true, 2) } else if &data[0..2] == b"MM" {
698 (false, 2) } else {
700 return Err(PdfError::InvalidImage(
701 "Invalid TIFF byte order".to_string(),
702 ));
703 };
704
705 let magic = if is_little_endian {
707 u16::from_le_bytes([data[offset], data[offset + 1]])
708 } else {
709 u16::from_be_bytes([data[offset], data[offset + 1]])
710 };
711
712 if magic != 42 {
713 return Err(PdfError::InvalidImage(
714 "Invalid TIFF magic number".to_string(),
715 ));
716 }
717
718 let ifd_offset = if is_little_endian {
720 u32::from_le_bytes([
721 data[offset + 2],
722 data[offset + 3],
723 data[offset + 4],
724 data[offset + 5],
725 ])
726 } else {
727 u32::from_be_bytes([
728 data[offset + 2],
729 data[offset + 3],
730 data[offset + 4],
731 data[offset + 5],
732 ])
733 } as usize;
734
735 if ifd_offset + 2 > data.len() {
736 return Err(PdfError::InvalidImage(
737 "Invalid TIFF IFD offset".to_string(),
738 ));
739 }
740
741 let num_entries = if is_little_endian {
743 u16::from_le_bytes([data[ifd_offset], data[ifd_offset + 1]])
744 } else {
745 u16::from_be_bytes([data[ifd_offset], data[ifd_offset + 1]])
746 };
747
748 let mut width = 0u32;
749 let mut height = 0u32;
750 let mut bits_per_sample = 8u16;
751 let mut photometric_interpretation = 0u16;
752
753 for i in 0..num_entries {
755 let entry_offset = ifd_offset + 2 + (i as usize * 12);
756
757 if entry_offset + 12 > data.len() {
758 break;
759 }
760
761 let tag = if is_little_endian {
762 u16::from_le_bytes([data[entry_offset], data[entry_offset + 1]])
763 } else {
764 u16::from_be_bytes([data[entry_offset], data[entry_offset + 1]])
765 };
766
767 let value_offset = entry_offset + 8;
768
769 match tag {
770 256 => {
771 width = if is_little_endian {
773 u32::from_le_bytes([
774 data[value_offset],
775 data[value_offset + 1],
776 data[value_offset + 2],
777 data[value_offset + 3],
778 ])
779 } else {
780 u32::from_be_bytes([
781 data[value_offset],
782 data[value_offset + 1],
783 data[value_offset + 2],
784 data[value_offset + 3],
785 ])
786 };
787 }
788 257 => {
789 height = if is_little_endian {
791 u32::from_le_bytes([
792 data[value_offset],
793 data[value_offset + 1],
794 data[value_offset + 2],
795 data[value_offset + 3],
796 ])
797 } else {
798 u32::from_be_bytes([
799 data[value_offset],
800 data[value_offset + 1],
801 data[value_offset + 2],
802 data[value_offset + 3],
803 ])
804 };
805 }
806 258 => {
807 bits_per_sample = if is_little_endian {
809 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
810 } else {
811 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
812 };
813 }
814 262 => {
815 photometric_interpretation = if is_little_endian {
817 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
818 } else {
819 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
820 };
821 }
822 _ => {} }
824 }
825
826 if width == 0 || height == 0 {
827 return Err(PdfError::InvalidImage(
828 "TIFF dimensions not found".to_string(),
829 ));
830 }
831
832 let color_space = match photometric_interpretation {
834 0 | 1 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 5 => ColorSpace::DeviceCMYK, _ => ColorSpace::DeviceRGB, };
839
840 Ok((width, height, color_space, bits_per_sample as u8))
841}
842
843#[cfg(test)]
844mod tests {
845 use super::*;
846
847 fn create_minimal_png(width: u32, height: u32, color_type: u8) -> Vec<u8> {
849 match color_type {
852 0 => {
853 vec![
855 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,
866 0x01, 0xE2, 0xF9, 0x8C, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
872 }
873 2 => {
874 vec![
876 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,
887 0x01, 0x27, 0x18, 0xAA, 0x61, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
893 }
894 3 => {
895 vec![
897 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,
912 0x01, 0xE5, 0x27, 0xDE, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
918 }
919 6 => {
920 vec![
922 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,
933 0x01, 0x75, 0xAA, 0x50, 0x19, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
939 }
940 _ => {
941 create_minimal_png(width, height, 2)
943 }
944 }
945 }
946
947 #[test]
948 fn test_parse_jpeg_header() {
949 let jpeg_data = vec![
951 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, ];
960
961 let result = parse_jpeg_header(&jpeg_data);
962 assert!(result.is_ok());
963 let (width, height, color_space, bits) = result.unwrap();
964 assert_eq!(width, 200);
965 assert_eq!(height, 100);
966 assert_eq!(color_space, ColorSpace::DeviceRGB);
967 assert_eq!(bits, 8);
968 }
969
970 #[test]
971 fn test_invalid_jpeg() {
972 let invalid_data = vec![0x00, 0x00];
973 let result = parse_jpeg_header(&invalid_data);
974 assert!(result.is_err());
975 }
976
977 #[test]
978 fn test_parse_png_header() {
979 let mut png_data = vec![
981 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, ];
992
993 png_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
995
996 let result = parse_png_header(&png_data);
997 assert!(result.is_ok());
998 let (width, height, color_space, bits) = result.unwrap();
999 assert_eq!(width, 100);
1000 assert_eq!(height, 100);
1001 assert_eq!(color_space, ColorSpace::DeviceRGB);
1002 assert_eq!(bits, 8);
1003 }
1004
1005 #[test]
1006 fn test_invalid_png() {
1007 let invalid_data = vec![0x00, 0x00];
1008 let result = parse_png_header(&invalid_data);
1009 assert!(result.is_err());
1010 }
1011
1012 #[test]
1013 fn test_parse_tiff_header_little_endian() {
1014 let tiff_data = vec![
1016 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
1022 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
1024 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1026 0x00, 0x00, ];
1028
1029 let result = parse_tiff_header(&tiff_data);
1030 assert!(result.is_ok());
1031 let (width, height, color_space, bits) = result.unwrap();
1032 assert_eq!(width, 100);
1033 assert_eq!(height, 100);
1034 assert_eq!(color_space, ColorSpace::DeviceGray);
1035 assert_eq!(bits, 8);
1036 }
1037
1038 #[test]
1039 fn test_parse_tiff_header_big_endian() {
1040 let tiff_data = vec![
1042 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
1048 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
1050 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
1052 0x00, 0x00, ];
1054
1055 let result = parse_tiff_header(&tiff_data);
1056 assert!(result.is_ok());
1057 let (width, height, color_space, bits) = result.unwrap();
1058 assert_eq!(width, 100);
1059 assert_eq!(height, 100);
1060 assert_eq!(color_space, ColorSpace::DeviceGray);
1061 assert_eq!(bits, 8);
1062 }
1063
1064 #[test]
1065 fn test_invalid_tiff() {
1066 let invalid_data = vec![0x00, 0x00];
1067 let result = parse_tiff_header(&invalid_data);
1068 assert!(result.is_err());
1069 }
1070
1071 #[test]
1072 fn test_image_format_enum() {
1073 assert_eq!(ImageFormat::Jpeg, ImageFormat::Jpeg);
1074 assert_eq!(ImageFormat::Png, ImageFormat::Png);
1075 assert_eq!(ImageFormat::Tiff, ImageFormat::Tiff);
1076 assert_ne!(ImageFormat::Jpeg, ImageFormat::Png);
1077 }
1078
1079 mod comprehensive_tests {
1081 use super::*;
1082 use std::fs;
1083 use tempfile::TempDir;
1084
1085 #[test]
1086 fn test_image_format_variants() {
1087 let jpeg = ImageFormat::Jpeg;
1089 let png = ImageFormat::Png;
1090 let tiff = ImageFormat::Tiff;
1091
1092 assert_eq!(jpeg, ImageFormat::Jpeg);
1093 assert_eq!(png, ImageFormat::Png);
1094 assert_eq!(tiff, ImageFormat::Tiff);
1095
1096 assert_ne!(jpeg, png);
1097 assert_ne!(png, tiff);
1098 assert_ne!(tiff, jpeg);
1099 }
1100
1101 #[test]
1102 fn test_image_format_debug() {
1103 let jpeg = ImageFormat::Jpeg;
1104 let png = ImageFormat::Png;
1105 let tiff = ImageFormat::Tiff;
1106
1107 assert_eq!(format!("{jpeg:?}"), "Jpeg");
1108 assert_eq!(format!("{png:?}"), "Png");
1109 assert_eq!(format!("{tiff:?}"), "Tiff");
1110 }
1111
1112 #[test]
1113 fn test_image_format_clone_copy() {
1114 let jpeg = ImageFormat::Jpeg;
1115 let jpeg_clone = jpeg;
1116 let jpeg_copy = jpeg;
1117
1118 assert_eq!(jpeg_clone, ImageFormat::Jpeg);
1119 assert_eq!(jpeg_copy, ImageFormat::Jpeg);
1120 }
1121
1122 #[test]
1123 fn test_color_space_variants() {
1124 let gray = ColorSpace::DeviceGray;
1126 let rgb = ColorSpace::DeviceRGB;
1127 let cmyk = ColorSpace::DeviceCMYK;
1128
1129 assert_eq!(gray, ColorSpace::DeviceGray);
1130 assert_eq!(rgb, ColorSpace::DeviceRGB);
1131 assert_eq!(cmyk, ColorSpace::DeviceCMYK);
1132
1133 assert_ne!(gray, rgb);
1134 assert_ne!(rgb, cmyk);
1135 assert_ne!(cmyk, gray);
1136 }
1137
1138 #[test]
1139 fn test_color_space_debug() {
1140 let gray = ColorSpace::DeviceGray;
1141 let rgb = ColorSpace::DeviceRGB;
1142 let cmyk = ColorSpace::DeviceCMYK;
1143
1144 assert_eq!(format!("{gray:?}"), "DeviceGray");
1145 assert_eq!(format!("{rgb:?}"), "DeviceRGB");
1146 assert_eq!(format!("{cmyk:?}"), "DeviceCMYK");
1147 }
1148
1149 #[test]
1150 fn test_color_space_clone_copy() {
1151 let rgb = ColorSpace::DeviceRGB;
1152 let rgb_clone = rgb;
1153 let rgb_copy = rgb;
1154
1155 assert_eq!(rgb_clone, ColorSpace::DeviceRGB);
1156 assert_eq!(rgb_copy, ColorSpace::DeviceRGB);
1157 }
1158
1159 #[test]
1160 fn test_image_from_jpeg_data() {
1161 let jpeg_data = vec![
1163 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1175
1176 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1177
1178 assert_eq!(image.width(), 200);
1179 assert_eq!(image.height(), 100);
1180 assert_eq!(image.format(), ImageFormat::Jpeg);
1181 assert_eq!(image.data(), jpeg_data);
1182 }
1183
1184 #[test]
1185 fn test_image_from_png_data() {
1186 let mut png_data = Vec::new();
1188
1189 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1191
1192 png_data.extend_from_slice(&[
1194 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, ]);
1204 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]);
1206
1207 let raw_data = vec![0x00, 0x00, 0x00, 0x00]; use flate2::write::ZlibEncoder;
1212 use flate2::Compression;
1213 use std::io::Write;
1214
1215 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1216 encoder.write_all(&raw_data).unwrap();
1217 let compressed_data = encoder.finish().unwrap();
1218
1219 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1221 png_data.extend_from_slice(b"IDAT");
1222 png_data.extend_from_slice(&compressed_data);
1223 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); png_data.extend_from_slice(&[
1227 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]);
1231
1232 let image = Image::from_png_data(png_data.clone()).unwrap();
1233
1234 assert_eq!(image.width(), 1);
1235 assert_eq!(image.height(), 1);
1236 assert_eq!(image.format(), ImageFormat::Png);
1237 assert_eq!(image.data(), png_data);
1238 }
1239
1240 #[test]
1241 fn test_image_from_tiff_data() {
1242 let tiff_data = vec![
1244 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
1250 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
1252 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1254 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
1256 0x00, 0x00, ];
1258
1259 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
1260
1261 assert_eq!(image.width(), 128);
1262 assert_eq!(image.height(), 128);
1263 assert_eq!(image.format(), ImageFormat::Tiff);
1264 assert_eq!(image.data(), tiff_data);
1265 }
1266
1267 #[test]
1268 fn test_image_from_jpeg_file() {
1269 let temp_dir = TempDir::new().unwrap();
1270 let file_path = temp_dir.path().join("test.jpg");
1271
1272 let jpeg_data = vec![
1274 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1286
1287 fs::write(&file_path, &jpeg_data).unwrap();
1288
1289 let image = Image::from_jpeg_file(&file_path).unwrap();
1290
1291 assert_eq!(image.width(), 100);
1292 assert_eq!(image.height(), 50);
1293 assert_eq!(image.format(), ImageFormat::Jpeg);
1294 assert_eq!(image.data(), jpeg_data);
1295 }
1296
1297 #[test]
1298 fn test_image_from_png_file() {
1299 let temp_dir = TempDir::new().unwrap();
1300 let file_path = temp_dir.path().join("test.png");
1301
1302 let mut png_data = Vec::new();
1304
1305 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1307
1308 png_data.extend_from_slice(&[
1310 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, ]);
1320 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]); let raw_data = vec![0x00, 0x00, 0x00, 0x00]; use flate2::write::ZlibEncoder;
1326 use flate2::Compression;
1327 use std::io::Write;
1328
1329 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1330 encoder.write_all(&raw_data).unwrap();
1331 let compressed_data = encoder.finish().unwrap();
1332
1333 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1334 png_data.extend_from_slice(b"IDAT");
1335 png_data.extend_from_slice(&compressed_data);
1336 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); png_data.extend_from_slice(&[
1340 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]);
1344
1345 fs::write(&file_path, &png_data).unwrap();
1346
1347 let image = Image::from_png_file(&file_path).unwrap();
1348
1349 assert_eq!(image.width(), 1);
1350 assert_eq!(image.height(), 1);
1351 assert_eq!(image.format(), ImageFormat::Png);
1352 assert_eq!(image.data(), png_data);
1353 }
1354
1355 #[test]
1356 fn test_image_from_tiff_file() {
1357 let temp_dir = TempDir::new().unwrap();
1358 let file_path = temp_dir.path().join("test.tiff");
1359
1360 let tiff_data = vec![
1362 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
1368 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
1370 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1372 0x00, 0x00, ];
1374
1375 fs::write(&file_path, &tiff_data).unwrap();
1376
1377 let image = Image::from_tiff_file(&file_path).unwrap();
1378
1379 assert_eq!(image.width(), 96);
1380 assert_eq!(image.height(), 96);
1381 assert_eq!(image.format(), ImageFormat::Tiff);
1382 assert_eq!(image.data(), tiff_data);
1383 }
1384
1385 #[test]
1386 fn test_image_to_pdf_object_jpeg() {
1387 let jpeg_data = vec![
1388 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1400
1401 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1402 let pdf_obj = image.to_pdf_object();
1403
1404 if let Object::Stream(dict, data) = pdf_obj {
1405 assert_eq!(
1406 dict.get("Type").unwrap(),
1407 &Object::Name("XObject".to_string())
1408 );
1409 assert_eq!(
1410 dict.get("Subtype").unwrap(),
1411 &Object::Name("Image".to_string())
1412 );
1413 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
1414 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
1415 assert_eq!(
1416 dict.get("ColorSpace").unwrap(),
1417 &Object::Name("DeviceRGB".to_string())
1418 );
1419 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1420 assert_eq!(
1421 dict.get("Filter").unwrap(),
1422 &Object::Name("DCTDecode".to_string())
1423 );
1424 assert_eq!(data, jpeg_data);
1425 } else {
1426 panic!("Expected Stream object");
1427 }
1428 }
1429
1430 #[test]
1431 fn test_image_to_pdf_object_png() {
1432 let mut png_data = Vec::new();
1434
1435 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1436 png_data.extend_from_slice(&[
1437 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
1438 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00,
1439 ]);
1440 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]);
1441
1442 let raw_data = vec![0x00, 0x00, 0x00, 0x00];
1443 use flate2::write::ZlibEncoder;
1444 use flate2::Compression;
1445 use std::io::Write;
1446
1447 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1448 encoder.write_all(&raw_data).unwrap();
1449 let compressed_data = encoder.finish().unwrap();
1450
1451 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1452 png_data.extend_from_slice(b"IDAT");
1453 png_data.extend_from_slice(&compressed_data);
1454 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
1455
1456 png_data.extend_from_slice(&[
1457 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
1458 ]);
1459
1460 let image = Image::from_png_data(png_data.clone()).unwrap();
1461 let pdf_obj = image.to_pdf_object();
1462
1463 if let Object::Stream(dict, data) = pdf_obj {
1464 assert_eq!(
1465 dict.get("Type").unwrap(),
1466 &Object::Name("XObject".to_string())
1467 );
1468 assert_eq!(
1469 dict.get("Subtype").unwrap(),
1470 &Object::Name("Image".to_string())
1471 );
1472 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(1));
1473 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(1));
1474 assert_eq!(
1475 dict.get("ColorSpace").unwrap(),
1476 &Object::Name("DeviceRGB".to_string())
1477 );
1478 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1479 assert_eq!(
1480 dict.get("Filter").unwrap(),
1481 &Object::Name("FlateDecode".to_string())
1482 );
1483 assert_eq!(data, png_data);
1484 } else {
1485 panic!("Expected Stream object");
1486 }
1487 }
1488
1489 #[test]
1490 fn test_image_to_pdf_object_tiff() {
1491 let tiff_data = vec![
1492 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
1498 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
1500 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1502 0x00, 0x00, ];
1504
1505 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
1506 let pdf_obj = image.to_pdf_object();
1507
1508 if let Object::Stream(dict, data) = pdf_obj {
1509 assert_eq!(
1510 dict.get("Type").unwrap(),
1511 &Object::Name("XObject".to_string())
1512 );
1513 assert_eq!(
1514 dict.get("Subtype").unwrap(),
1515 &Object::Name("Image".to_string())
1516 );
1517 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(64));
1518 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(64));
1519 assert_eq!(
1520 dict.get("ColorSpace").unwrap(),
1521 &Object::Name("DeviceGray".to_string())
1522 );
1523 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1524 assert_eq!(
1525 dict.get("Filter").unwrap(),
1526 &Object::Name("FlateDecode".to_string())
1527 );
1528 assert_eq!(data, tiff_data);
1529 } else {
1530 panic!("Expected Stream object");
1531 }
1532 }
1533
1534 #[test]
1535 fn test_image_clone() {
1536 let jpeg_data = vec![
1537 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1549
1550 let image1 = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1551 let image2 = image1.clone();
1552
1553 assert_eq!(image1.width(), image2.width());
1554 assert_eq!(image1.height(), image2.height());
1555 assert_eq!(image1.format(), image2.format());
1556 assert_eq!(image1.data(), image2.data());
1557 }
1558
1559 #[test]
1560 fn test_image_debug() {
1561 let jpeg_data = vec![
1562 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1574
1575 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1576 let debug_str = format!("{image:?}");
1577
1578 assert!(debug_str.contains("Image"));
1579 assert!(debug_str.contains("width"));
1580 assert!(debug_str.contains("height"));
1581 assert!(debug_str.contains("format"));
1582 }
1583
1584 #[test]
1585 fn test_jpeg_grayscale_image() {
1586 let jpeg_data = vec![
1587 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x01, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD9, ];
1598
1599 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1600 let pdf_obj = image.to_pdf_object();
1601
1602 if let Object::Stream(dict, _) = pdf_obj {
1603 assert_eq!(
1604 dict.get("ColorSpace").unwrap(),
1605 &Object::Name("DeviceGray".to_string())
1606 );
1607 } else {
1608 panic!("Expected Stream object");
1609 }
1610 }
1611
1612 #[test]
1613 fn test_jpeg_cmyk_image() {
1614 let jpeg_data = vec![
1615 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x04, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1627
1628 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1629 let pdf_obj = image.to_pdf_object();
1630
1631 if let Object::Stream(dict, _) = pdf_obj {
1632 assert_eq!(
1633 dict.get("ColorSpace").unwrap(),
1634 &Object::Name("DeviceCMYK".to_string())
1635 );
1636 } else {
1637 panic!("Expected Stream object");
1638 }
1639 }
1640
1641 #[test]
1642 fn test_png_grayscale_image() {
1643 let png_data = create_minimal_png(1, 1, 0); let image = Image::from_png_data(png_data).unwrap();
1646 let pdf_obj = image.to_pdf_object();
1647
1648 if let Object::Stream(dict, _) = pdf_obj {
1649 assert_eq!(
1650 dict.get("ColorSpace").unwrap(),
1651 &Object::Name("DeviceGray".to_string())
1652 );
1653 } else {
1654 panic!("Expected Stream object");
1655 }
1656 }
1657
1658 #[test]
1659 fn test_png_palette_image_incomplete() {
1660 let png_data = vec![
1662 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, ];
1674
1675 let result = Image::from_png_data(png_data);
1677 assert!(result.is_err(), "Incomplete PNG should return error");
1678 }
1679
1680 #[test]
1681 fn test_tiff_big_endian() {
1682 let tiff_data = vec![
1683 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1689 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1691 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00,
1693 0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
1695 0x00, 0x00, ];
1697
1698 let image = Image::from_tiff_data(tiff_data).unwrap();
1699
1700 assert_eq!(image.width(), 128);
1701 assert_eq!(image.height(), 128);
1702 assert_eq!(image.format(), ImageFormat::Tiff);
1703 }
1704
1705 #[test]
1706 fn test_tiff_cmyk_image() {
1707 let tiff_data = vec![
1708 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1714 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1716 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1718 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
1720 0x00, 0x00, ];
1722
1723 let image = Image::from_tiff_data(tiff_data).unwrap();
1724 let pdf_obj = image.to_pdf_object();
1725
1726 if let Object::Stream(dict, _) = pdf_obj {
1727 assert_eq!(
1728 dict.get("ColorSpace").unwrap(),
1729 &Object::Name("DeviceCMYK".to_string())
1730 );
1731 } else {
1732 panic!("Expected Stream object");
1733 }
1734 }
1735
1736 #[test]
1737 fn test_error_invalid_jpeg() {
1738 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_jpeg_data(invalid_data);
1740 assert!(result.is_err());
1741 }
1742
1743 #[test]
1744 fn test_error_invalid_png() {
1745 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_png_data(invalid_data);
1747 assert!(result.is_err());
1748 }
1749
1750 #[test]
1751 fn test_error_invalid_tiff() {
1752 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_tiff_data(invalid_data);
1754 assert!(result.is_err());
1755 }
1756
1757 #[test]
1758 fn test_error_truncated_jpeg() {
1759 let truncated_data = vec![0xFF, 0xD8, 0xFF]; let result = Image::from_jpeg_data(truncated_data);
1761 assert!(result.is_err());
1762 }
1763
1764 #[test]
1765 fn test_error_truncated_png() {
1766 let truncated_data = vec![0x89, 0x50, 0x4E, 0x47]; let result = Image::from_png_data(truncated_data);
1768 assert!(result.is_err());
1769 }
1770
1771 #[test]
1772 fn test_error_truncated_tiff() {
1773 let truncated_data = vec![0x49, 0x49, 0x2A]; let result = Image::from_tiff_data(truncated_data);
1775 assert!(result.is_err());
1776 }
1777
1778 #[test]
1779 fn test_error_jpeg_unsupported_components() {
1780 let invalid_jpeg = vec![
1781 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x05, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1793
1794 let result = Image::from_jpeg_data(invalid_jpeg);
1795 assert!(result.is_err());
1796 }
1797
1798 #[test]
1799 fn test_error_png_unsupported_color_type() {
1800 let invalid_png = vec![
1801 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, ];
1813
1814 let result = Image::from_png_data(invalid_png);
1815 assert!(result.is_err());
1816 }
1817
1818 #[test]
1819 fn test_error_nonexistent_file() {
1820 let result = Image::from_jpeg_file("/nonexistent/path/image.jpg");
1821 assert!(result.is_err());
1822
1823 let result = Image::from_png_file("/nonexistent/path/image.png");
1824 assert!(result.is_err());
1825
1826 let result = Image::from_tiff_file("/nonexistent/path/image.tiff");
1827 assert!(result.is_err());
1828 }
1829
1830 #[test]
1831 fn test_jpeg_no_dimensions() {
1832 let jpeg_no_dims = vec![
1833 0xFF, 0xD8, 0xFF, 0xD9, ];
1836
1837 let result = Image::from_jpeg_data(jpeg_no_dims);
1838 assert!(result.is_err());
1839 }
1840
1841 #[test]
1842 fn test_png_no_ihdr() {
1843 let png_no_ihdr = vec![
1844 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,
1848 0x72, 0x6E, 0x38, ];
1850
1851 let result = Image::from_png_data(png_no_ihdr);
1852 assert!(result.is_err());
1853 }
1854
1855 #[test]
1856 fn test_tiff_no_dimensions() {
1857 let tiff_no_dims = vec![
1858 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1864 0x00, 0x00, ];
1866
1867 let result = Image::from_tiff_data(tiff_no_dims);
1868 assert!(result.is_err());
1869 }
1870
1871 fn png_crc32(data: &[u8]) -> u32 {
1873 let mut crc = 0xFFFFFFFF_u32;
1875 for &byte in data {
1876 crc ^= byte as u32;
1877 for _ in 0..8 {
1878 if crc & 1 != 0 {
1879 crc = (crc >> 1) ^ 0xEDB88320;
1880 } else {
1881 crc >>= 1;
1882 }
1883 }
1884 }
1885 !crc
1886 }
1887
1888 fn create_valid_png_data(
1890 width: u32,
1891 height: u32,
1892 bit_depth: u8,
1893 color_type: u8,
1894 ) -> Vec<u8> {
1895 use flate2::write::ZlibEncoder;
1896 use flate2::Compression;
1897 use std::io::Write;
1898
1899 let mut png_data = Vec::new();
1900
1901 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1903
1904 let mut ihdr_data = Vec::new();
1906 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();
1916 ihdr_crc_data.extend_from_slice(b"IHDR");
1917 ihdr_crc_data.extend_from_slice(&ihdr_data);
1918 let ihdr_crc = png_crc32(&ihdr_crc_data);
1919
1920 png_data.extend_from_slice(&(ihdr_data.len() as u32).to_be_bytes());
1922 png_data.extend_from_slice(b"IHDR");
1923 png_data.extend_from_slice(&ihdr_data);
1924 png_data.extend_from_slice(&ihdr_crc.to_be_bytes());
1925
1926 let bytes_per_pixel = match (color_type, bit_depth) {
1928 (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,
1934 };
1935
1936 let mut raw_data = Vec::new();
1937 for _y in 0..height {
1938 raw_data.push(0); for _x in 0..width {
1940 for _c in 0..bytes_per_pixel {
1941 raw_data.push(0); }
1943 }
1944 }
1945
1946 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1948 encoder.write_all(&raw_data).unwrap();
1949 let compressed_data = encoder.finish().unwrap();
1950
1951 let mut idat_crc_data = Vec::new();
1953 idat_crc_data.extend_from_slice(b"IDAT");
1954 idat_crc_data.extend_from_slice(&compressed_data);
1955 let idat_crc = png_crc32(&idat_crc_data);
1956
1957 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1959 png_data.extend_from_slice(b"IDAT");
1960 png_data.extend_from_slice(&compressed_data);
1961 png_data.extend_from_slice(&idat_crc.to_be_bytes());
1962
1963 png_data.extend_from_slice(&[0, 0, 0, 0]); png_data.extend_from_slice(b"IEND");
1966 png_data.extend_from_slice(&[0xAE, 0x42, 0x60, 0x82]); png_data
1969 }
1970
1971 #[test]
1972 fn test_different_bit_depths() {
1973 let png_8bit = create_valid_png_data(2, 2, 8, 2); let image_8bit = Image::from_png_data(png_8bit).unwrap();
1978 let pdf_obj_8bit = image_8bit.to_pdf_object();
1979
1980 if let Object::Stream(dict, _) = pdf_obj_8bit {
1981 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1982 } else {
1983 panic!("Expected Stream object");
1984 }
1985
1986 let png_16bit = create_valid_png_data(2, 2, 16, 2); let image_16bit = Image::from_png_data(png_16bit).unwrap();
1989 let pdf_obj_16bit = image_16bit.to_pdf_object();
1990
1991 if let Object::Stream(dict, _) = pdf_obj_16bit {
1992 let bits = dict.get("BitsPerComponent").unwrap();
1994 assert!(matches!(bits, &Object::Integer(8) | &Object::Integer(16)));
1995 } else {
1996 panic!("Expected Stream object");
1997 }
1998 }
1999
2000 #[test]
2001 fn test_performance_large_image_data() {
2002 let mut large_jpeg = vec![
2004 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x04, 0x00, 0x04, 0x00, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, ];
2015
2016 large_jpeg.extend(vec![0x00; 10000]);
2018 large_jpeg.extend(vec![0xFF, 0xD9]); let start = std::time::Instant::now();
2021 let image = Image::from_jpeg_data(large_jpeg.clone()).unwrap();
2022 let duration = start.elapsed();
2023
2024 assert_eq!(image.width(), 1024);
2025 assert_eq!(image.height(), 1024);
2026 assert_eq!(image.data().len(), large_jpeg.len());
2027 assert!(duration.as_millis() < 100); }
2029
2030 #[test]
2031 fn test_memory_efficiency() {
2032 let jpeg_data = vec![
2033 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
2045
2046 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
2047
2048 assert_eq!(image.data().len(), jpeg_data.len());
2050 assert_eq!(image.data(), jpeg_data);
2051
2052 let cloned = image.clone();
2054 assert_eq!(cloned.data(), image.data());
2055 }
2056
2057 #[test]
2058 fn test_complete_workflow() {
2059 let test_cases = vec![
2061 (ImageFormat::Jpeg, "DCTDecode", "DeviceRGB"),
2062 (ImageFormat::Png, "FlateDecode", "DeviceRGB"),
2063 (ImageFormat::Tiff, "FlateDecode", "DeviceGray"),
2064 ];
2065
2066 for (expected_format, expected_filter, expected_color_space) in test_cases {
2067 let data = match expected_format {
2068 ImageFormat::Jpeg => vec![
2069 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ],
2081 ImageFormat::Png => create_valid_png_data(2, 2, 8, 2), ImageFormat::Tiff => vec![
2083 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00,
2089 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
2091 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
2093 0x00, 0x00, 0x00, 0x00, ],
2095 ImageFormat::Raw => Vec::new(), };
2097
2098 let image = match expected_format {
2099 ImageFormat::Jpeg => Image::from_jpeg_data(data.clone()).unwrap(),
2100 ImageFormat::Png => Image::from_png_data(data.clone()).unwrap(),
2101 ImageFormat::Tiff => Image::from_tiff_data(data.clone()).unwrap(),
2102 ImageFormat::Raw => continue, };
2104
2105 assert_eq!(image.format(), expected_format);
2107 if expected_format == ImageFormat::Png {
2109 assert_eq!(image.width(), 2);
2110 assert_eq!(image.height(), 2);
2111 } else if expected_format == ImageFormat::Jpeg {
2112 assert_eq!(image.width(), 200);
2113 assert_eq!(image.height(), 100);
2114 } else if expected_format == ImageFormat::Tiff {
2115 assert_eq!(image.width(), 200);
2116 assert_eq!(image.height(), 100);
2117 }
2118 assert_eq!(image.data(), data);
2119
2120 let pdf_obj = image.to_pdf_object();
2122 if let Object::Stream(dict, stream_data) = pdf_obj {
2123 assert_eq!(
2124 dict.get("Type").unwrap(),
2125 &Object::Name("XObject".to_string())
2126 );
2127 assert_eq!(
2128 dict.get("Subtype").unwrap(),
2129 &Object::Name("Image".to_string())
2130 );
2131 if expected_format == ImageFormat::Png {
2133 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(2));
2134 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(2));
2135 } else {
2136 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
2137 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
2138 }
2139 assert_eq!(
2140 dict.get("ColorSpace").unwrap(),
2141 &Object::Name(expected_color_space.to_string())
2142 );
2143 assert_eq!(
2144 dict.get("Filter").unwrap(),
2145 &Object::Name(expected_filter.to_string())
2146 );
2147 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
2148 assert_eq!(stream_data, data);
2149 } else {
2150 panic!("Expected Stream object for format {expected_format:?}");
2151 }
2152 }
2153 }
2154 }
2155}