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 let mut file = File::open(path)?;
70 let mut data = Vec::new();
71 file.read_to_end(&mut data)?;
72 Self::from_jpeg_data(data)
73 }
74
75 pub fn from_jpeg_data(data: Vec<u8>) -> Result<Self> {
77 let (width, height, color_space, bits_per_component) = parse_jpeg_header(&data)?;
79
80 Ok(Image {
81 data,
82 format: ImageFormat::Jpeg,
83 width,
84 height,
85 color_space,
86 bits_per_component,
87 alpha_data: None,
88 soft_mask: None,
89 })
90 }
91
92 pub fn from_png_file<P: AsRef<Path>>(path: P) -> Result<Self> {
94 let mut file = File::open(path)?;
95 let mut data = Vec::new();
96 file.read_to_end(&mut data)?;
97 Self::from_png_data(data)
98 }
99
100 pub fn from_png_data(data: Vec<u8>) -> Result<Self> {
102 use crate::graphics::png_decoder::{decode_png, PngColorType};
103
104 let decoded = decode_png(&data)?;
106
107 let color_space = match decoded.color_type {
109 PngColorType::Grayscale | PngColorType::GrayscaleAlpha => ColorSpace::DeviceGray,
110 PngColorType::Rgb | PngColorType::RgbAlpha | PngColorType::Palette => {
111 ColorSpace::DeviceRGB
112 }
113 };
114
115 let soft_mask = if let Some(alpha) = &decoded.alpha_data {
117 Some(Box::new(Image {
118 data: alpha.clone(),
119 format: ImageFormat::Raw,
120 width: decoded.width,
121 height: decoded.height,
122 color_space: ColorSpace::DeviceGray,
123 bits_per_component: 8,
124 alpha_data: None,
125 soft_mask: None,
126 }))
127 } else {
128 None
129 };
130
131 Ok(Image {
132 data, format: ImageFormat::Png, width: decoded.width,
135 height: decoded.height,
136 color_space,
137 bits_per_component: 8, alpha_data: decoded.alpha_data,
139 soft_mask,
140 })
141 }
142
143 pub fn from_tiff_file<P: AsRef<Path>>(path: P) -> Result<Self> {
145 let mut file = File::open(path)?;
146 let mut data = Vec::new();
147 file.read_to_end(&mut data)?;
148 Self::from_tiff_data(data)
149 }
150
151 pub fn from_tiff_data(data: Vec<u8>) -> Result<Self> {
153 let (width, height, color_space, bits_per_component) = parse_tiff_header(&data)?;
155
156 Ok(Image {
157 data,
158 format: ImageFormat::Tiff,
159 width,
160 height,
161 color_space,
162 bits_per_component,
163 alpha_data: None,
164 soft_mask: None,
165 })
166 }
167
168 pub fn width(&self) -> u32 {
170 self.width
171 }
172
173 pub fn height(&self) -> u32 {
175 self.height
176 }
177
178 pub fn data(&self) -> &[u8] {
180 &self.data
181 }
182
183 pub fn format(&self) -> ImageFormat {
185 self.format
186 }
187
188 pub fn bits_per_component(&self) -> u8 {
190 self.bits_per_component
191 }
192
193 pub fn from_raw_data(
195 data: Vec<u8>,
196 width: u32,
197 height: u32,
198 color_space: ColorSpace,
199 bits_per_component: u8,
200 ) -> Self {
201 Image {
202 data,
203 format: ImageFormat::Raw,
204 width,
205 height,
206 color_space,
207 bits_per_component,
208 alpha_data: None,
209 soft_mask: None,
210 }
211 }
212
213 pub fn from_rgba_data(rgba_data: Vec<u8>, width: u32, height: u32) -> Result<Self> {
215 if rgba_data.len() != (width * height * 4) as usize {
216 return Err(PdfError::InvalidImage(
217 "RGBA data size doesn't match dimensions".to_string(),
218 ));
219 }
220
221 let mut rgb_data = Vec::with_capacity((width * height * 3) as usize);
223 let mut alpha_data = Vec::with_capacity((width * height) as usize);
224
225 for chunk in rgba_data.chunks(4) {
226 rgb_data.push(chunk[0]); rgb_data.push(chunk[1]); rgb_data.push(chunk[2]); alpha_data.push(chunk[3]); }
231
232 let soft_mask = Some(Box::new(Image {
234 data: alpha_data.clone(),
235 format: ImageFormat::Raw,
236 width,
237 height,
238 color_space: ColorSpace::DeviceGray,
239 bits_per_component: 8,
240 alpha_data: None,
241 soft_mask: None,
242 }));
243
244 Ok(Image {
245 data: rgb_data,
246 format: ImageFormat::Raw,
247 width,
248 height,
249 color_space: ColorSpace::DeviceRGB,
250 bits_per_component: 8,
251 alpha_data: Some(alpha_data),
252 soft_mask,
253 })
254 }
255
256 pub fn from_gray_data(gray_data: Vec<u8>, width: u32, height: u32) -> Result<Self> {
258 if gray_data.len() != (width * height) as usize {
259 return Err(PdfError::InvalidImage(
260 "Gray data size doesn't match dimensions".to_string(),
261 ));
262 }
263
264 Ok(Image {
265 data: gray_data,
266 format: ImageFormat::Raw,
267 width,
268 height,
269 color_space: ColorSpace::DeviceGray,
270 bits_per_component: 8,
271 alpha_data: None,
272 soft_mask: None,
273 })
274 }
275
276 #[cfg(feature = "external-images")]
278 pub fn from_file_raw<P: AsRef<Path>>(
279 path: P,
280 width: u32,
281 height: u32,
282 format: ImageFormat,
283 ) -> Result<Self> {
284 let data = std::fs::read(path)
285 .map_err(|e| PdfError::InvalidImage(format!("Failed to read image file: {}", e)))?;
286
287 Ok(Image {
288 data,
289 format,
290 width,
291 height,
292 color_space: ColorSpace::DeviceRGB,
293 bits_per_component: 8,
294 alpha_data: None,
295 soft_mask: None,
296 })
297 }
298
299 pub fn to_pdf_object(&self) -> Object {
301 let mut dict = Dictionary::new();
302
303 dict.set("Type", Object::Name("XObject".to_string()));
305 dict.set("Subtype", Object::Name("Image".to_string()));
306 dict.set("Width", Object::Integer(self.width as i64));
307 dict.set("Height", Object::Integer(self.height as i64));
308
309 let color_space_name = match self.color_space {
311 ColorSpace::DeviceGray => "DeviceGray",
312 ColorSpace::DeviceRGB => "DeviceRGB",
313 ColorSpace::DeviceCMYK => "DeviceCMYK",
314 };
315 dict.set("ColorSpace", Object::Name(color_space_name.to_string()));
316
317 dict.set(
319 "BitsPerComponent",
320 Object::Integer(self.bits_per_component as i64),
321 );
322
323 match self.format {
325 ImageFormat::Jpeg => {
326 dict.set("Filter", Object::Name("DCTDecode".to_string()));
327 }
328 ImageFormat::Png => {
329 dict.set("Filter", Object::Name("FlateDecode".to_string()));
330 }
331 ImageFormat::Tiff => {
332 dict.set("Filter", Object::Name("FlateDecode".to_string()));
334 }
335 ImageFormat::Raw => {
336 }
338 }
339
340 Object::Stream(dict, self.data.clone())
342 }
343
344 pub fn to_pdf_object_with_transparency(&self) -> Result<(Object, Option<Object>)> {
346 use flate2::write::ZlibEncoder;
347 use flate2::Compression;
348 use std::io::Write as IoWrite;
349
350 let mut main_dict = Dictionary::new();
351
352 main_dict.set("Type", Object::Name("XObject".to_string()));
354 main_dict.set("Subtype", Object::Name("Image".to_string()));
355 main_dict.set("Width", Object::Integer(self.width as i64));
356 main_dict.set("Height", Object::Integer(self.height as i64));
357
358 let color_space_name = match self.color_space {
360 ColorSpace::DeviceGray => "DeviceGray",
361 ColorSpace::DeviceRGB => "DeviceRGB",
362 ColorSpace::DeviceCMYK => "DeviceCMYK",
363 };
364 main_dict.set("ColorSpace", Object::Name(color_space_name.to_string()));
365
366 main_dict.set(
368 "BitsPerComponent",
369 Object::Integer(self.bits_per_component as i64),
370 );
371
372 let main_data = match self.format {
374 ImageFormat::Jpeg => {
375 main_dict.set("Filter", Object::Name("DCTDecode".to_string()));
376 self.data.clone()
377 }
378 ImageFormat::Png | ImageFormat::Raw => {
379 main_dict.set("Filter", Object::Name("FlateDecode".to_string()));
381 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
382 encoder.write_all(&self.data).map_err(|e| {
383 PdfError::InvalidImage(format!("Failed to compress image data: {}", e))
384 })?;
385 encoder.finish().map_err(|e| {
386 PdfError::InvalidImage(format!("Failed to finalize image compression: {}", e))
387 })?
388 }
389 ImageFormat::Tiff => {
390 main_dict.set("Filter", Object::Name("FlateDecode".to_string()));
391 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
392 encoder.write_all(&self.data).map_err(|e| {
393 PdfError::InvalidImage(format!("Failed to compress TIFF data: {}", e))
394 })?;
395 encoder.finish().map_err(|e| {
396 PdfError::InvalidImage(format!("Failed to finalize TIFF compression: {}", e))
397 })?
398 }
399 };
400
401 main_dict.set("Length", Object::Integer(main_data.len() as i64));
403
404 let smask_obj = if let Some(mask) = &self.soft_mask {
406 let mut mask_dict = Dictionary::new();
407 mask_dict.set("Type", Object::Name("XObject".to_string()));
408 mask_dict.set("Subtype", Object::Name("Image".to_string()));
409 mask_dict.set("Width", Object::Integer(mask.width as i64));
410 mask_dict.set("Height", Object::Integer(mask.height as i64));
411 mask_dict.set("ColorSpace", Object::Name("DeviceGray".to_string()));
412 mask_dict.set("BitsPerComponent", Object::Integer(8));
413 mask_dict.set("Filter", Object::Name("FlateDecode".to_string()));
414
415 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
417 encoder.write_all(&mask.data).map_err(|e| {
418 PdfError::InvalidImage(format!("Failed to compress alpha channel: {}", e))
419 })?;
420 let compressed_mask_data = encoder.finish().map_err(|e| {
421 PdfError::InvalidImage(format!("Failed to finalize alpha compression: {}", e))
422 })?;
423
424 mask_dict.set("Length", Object::Integer(compressed_mask_data.len() as i64));
426
427 Some(Object::Stream(mask_dict, compressed_mask_data))
428 } else {
429 None
430 };
431
432 Ok((Object::Stream(main_dict, main_data), smask_obj))
436 }
437
438 pub fn has_transparency(&self) -> bool {
440 self.soft_mask.is_some() || self.alpha_data.is_some()
441 }
442
443 pub fn create_stencil_mask(&self, threshold: u8) -> Option<Image> {
446 if let Some(alpha) = &self.alpha_data {
447 let mut mask_data = Vec::new();
449 let mut current_byte = 0u8;
450 let mut bit_count = 0;
451
452 for &alpha_value in alpha.iter() {
453 if alpha_value > threshold {
455 current_byte |= 1 << (7 - bit_count);
456 }
457
458 bit_count += 1;
459 if bit_count == 8 {
460 mask_data.push(current_byte);
461 current_byte = 0;
462 bit_count = 0;
463 }
464 }
465
466 if bit_count > 0 {
468 mask_data.push(current_byte);
469 }
470
471 Some(Image {
472 data: mask_data,
473 format: ImageFormat::Raw,
474 width: self.width,
475 height: self.height,
476 color_space: ColorSpace::DeviceGray,
477 bits_per_component: 1,
478 alpha_data: None,
479 soft_mask: None,
480 })
481 } else {
482 None
483 }
484 }
485
486 pub fn create_mask(&self, mask_type: MaskType, threshold: Option<u8>) -> Option<Image> {
488 match mask_type {
489 MaskType::Soft => self.soft_mask.as_ref().map(|m| m.as_ref().clone()),
490 MaskType::Stencil => self.create_stencil_mask(threshold.unwrap_or(128)),
491 }
492 }
493
494 pub fn with_mask(mut self, mask: Image, mask_type: MaskType) -> Self {
496 match mask_type {
497 MaskType::Soft => {
498 self.soft_mask = Some(Box::new(mask));
499 }
500 MaskType::Stencil => {
501 self.soft_mask = Some(Box::new(mask));
503 }
504 }
505 self
506 }
507
508 pub fn soft_mask(&self) -> Option<&Image> {
510 self.soft_mask.as_ref().map(|m| m.as_ref())
511 }
512
513 pub fn alpha_data(&self) -> Option<&[u8]> {
515 self.alpha_data.as_deref()
516 }
517}
518
519fn parse_jpeg_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
521 if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
522 return Err(PdfError::InvalidImage("Not a valid JPEG file".to_string()));
523 }
524
525 let mut pos = 2;
526 let mut width = 0;
527 let mut height = 0;
528 let mut components = 0;
529
530 while pos < data.len() - 1 {
531 if data[pos] != 0xFF {
532 return Err(PdfError::InvalidImage("Invalid JPEG marker".to_string()));
533 }
534
535 let marker = data[pos + 1];
536 pos += 2;
537
538 if marker == 0xFF {
540 continue;
541 }
542
543 if (0xC0..=0xCF).contains(&marker) && marker != 0xC4 && marker != 0xC8 && marker != 0xCC {
545 if pos + 7 >= data.len() {
547 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
548 }
549
550 pos += 2;
552
553 pos += 1;
555
556 height = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
558 pos += 2;
559 width = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
560 pos += 2;
561
562 components = data[pos];
564 break;
565 } else if marker == 0xD9 {
566 break;
568 } else if marker == 0xD8 || (0xD0..=0xD7).contains(&marker) {
569 continue;
571 } else {
572 if pos + 1 >= data.len() {
574 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
575 }
576 let length = ((data[pos] as usize) << 8) | (data[pos + 1] as usize);
577 pos += length;
578 }
579 }
580
581 if width == 0 || height == 0 {
582 return Err(PdfError::InvalidImage(
583 "Could not find image dimensions".to_string(),
584 ));
585 }
586
587 let color_space = match components {
588 1 => ColorSpace::DeviceGray,
589 3 => ColorSpace::DeviceRGB,
590 4 => ColorSpace::DeviceCMYK,
591 _ => {
592 return Err(PdfError::InvalidImage(format!(
593 "Unsupported number of components: {components}"
594 )))
595 }
596 };
597
598 Ok((width, height, color_space, 8)) }
600
601#[allow(dead_code)]
603fn parse_png_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
604 if data.len() < 8 || &data[0..8] != b"\x89PNG\r\n\x1a\n" {
606 return Err(PdfError::InvalidImage("Not a valid PNG file".to_string()));
607 }
608
609 let mut pos = 8;
611
612 while pos + 8 < data.len() {
613 let chunk_length =
615 u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
616
617 let chunk_type = &data[pos + 4..pos + 8];
619
620 if chunk_type == b"IHDR" {
621 if pos + 8 + chunk_length > data.len() || chunk_length < 13 {
623 return Err(PdfError::InvalidImage("Invalid PNG IHDR chunk".to_string()));
624 }
625
626 let ihdr_data = &data[pos + 8..pos + 8 + chunk_length];
627
628 let width =
630 u32::from_be_bytes([ihdr_data[0], ihdr_data[1], ihdr_data[2], ihdr_data[3]]);
631
632 let height =
633 u32::from_be_bytes([ihdr_data[4], ihdr_data[5], ihdr_data[6], ihdr_data[7]]);
634
635 let bit_depth = ihdr_data[8];
636 let color_type = ihdr_data[9];
637
638 let color_space = match color_type {
640 0 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 3 => ColorSpace::DeviceRGB, 4 => ColorSpace::DeviceGray, 6 => ColorSpace::DeviceRGB, _ => {
646 return Err(PdfError::InvalidImage(format!(
647 "Unsupported PNG color type: {color_type}"
648 )))
649 }
650 };
651
652 return Ok((width, height, color_space, bit_depth));
653 }
654
655 pos += 8 + chunk_length + 4; }
658
659 Err(PdfError::InvalidImage(
660 "PNG IHDR chunk not found".to_string(),
661 ))
662}
663
664fn parse_tiff_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
666 if data.len() < 8 {
667 return Err(PdfError::InvalidImage(
668 "Invalid TIFF file: too short".to_string(),
669 ));
670 }
671
672 let (is_little_endian, offset) = if &data[0..2] == b"II" {
674 (true, 2) } else if &data[0..2] == b"MM" {
676 (false, 2) } else {
678 return Err(PdfError::InvalidImage(
679 "Invalid TIFF byte order".to_string(),
680 ));
681 };
682
683 let magic = if is_little_endian {
685 u16::from_le_bytes([data[offset], data[offset + 1]])
686 } else {
687 u16::from_be_bytes([data[offset], data[offset + 1]])
688 };
689
690 if magic != 42 {
691 return Err(PdfError::InvalidImage(
692 "Invalid TIFF magic number".to_string(),
693 ));
694 }
695
696 let ifd_offset = if is_little_endian {
698 u32::from_le_bytes([
699 data[offset + 2],
700 data[offset + 3],
701 data[offset + 4],
702 data[offset + 5],
703 ])
704 } else {
705 u32::from_be_bytes([
706 data[offset + 2],
707 data[offset + 3],
708 data[offset + 4],
709 data[offset + 5],
710 ])
711 } as usize;
712
713 if ifd_offset + 2 > data.len() {
714 return Err(PdfError::InvalidImage(
715 "Invalid TIFF IFD offset".to_string(),
716 ));
717 }
718
719 let num_entries = if is_little_endian {
721 u16::from_le_bytes([data[ifd_offset], data[ifd_offset + 1]])
722 } else {
723 u16::from_be_bytes([data[ifd_offset], data[ifd_offset + 1]])
724 };
725
726 let mut width = 0u32;
727 let mut height = 0u32;
728 let mut bits_per_sample = 8u16;
729 let mut photometric_interpretation = 0u16;
730
731 for i in 0..num_entries {
733 let entry_offset = ifd_offset + 2 + (i as usize * 12);
734
735 if entry_offset + 12 > data.len() {
736 break;
737 }
738
739 let tag = if is_little_endian {
740 u16::from_le_bytes([data[entry_offset], data[entry_offset + 1]])
741 } else {
742 u16::from_be_bytes([data[entry_offset], data[entry_offset + 1]])
743 };
744
745 let value_offset = entry_offset + 8;
746
747 match tag {
748 256 => {
749 width = if is_little_endian {
751 u32::from_le_bytes([
752 data[value_offset],
753 data[value_offset + 1],
754 data[value_offset + 2],
755 data[value_offset + 3],
756 ])
757 } else {
758 u32::from_be_bytes([
759 data[value_offset],
760 data[value_offset + 1],
761 data[value_offset + 2],
762 data[value_offset + 3],
763 ])
764 };
765 }
766 257 => {
767 height = if is_little_endian {
769 u32::from_le_bytes([
770 data[value_offset],
771 data[value_offset + 1],
772 data[value_offset + 2],
773 data[value_offset + 3],
774 ])
775 } else {
776 u32::from_be_bytes([
777 data[value_offset],
778 data[value_offset + 1],
779 data[value_offset + 2],
780 data[value_offset + 3],
781 ])
782 };
783 }
784 258 => {
785 bits_per_sample = if is_little_endian {
787 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
788 } else {
789 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
790 };
791 }
792 262 => {
793 photometric_interpretation = if is_little_endian {
795 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
796 } else {
797 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
798 };
799 }
800 _ => {} }
802 }
803
804 if width == 0 || height == 0 {
805 return Err(PdfError::InvalidImage(
806 "TIFF dimensions not found".to_string(),
807 ));
808 }
809
810 let color_space = match photometric_interpretation {
812 0 | 1 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 5 => ColorSpace::DeviceCMYK, _ => ColorSpace::DeviceRGB, };
817
818 Ok((width, height, color_space, bits_per_sample as u8))
819}
820
821#[cfg(test)]
822mod tests {
823 use super::*;
824
825 fn create_minimal_png(width: u32, height: u32, color_type: u8) -> Vec<u8> {
827 match color_type {
830 0 => {
831 vec![
833 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,
844 0x01, 0xE2, 0xF9, 0x8C, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
850 }
851 2 => {
852 vec![
854 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,
865 0x01, 0x27, 0x18, 0xAA, 0x61, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
871 }
872 3 => {
873 vec![
875 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,
890 0x01, 0xE5, 0x27, 0xDE, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
896 }
897 6 => {
898 vec![
900 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,
911 0x01, 0x75, 0xAA, 0x50, 0x19, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
917 }
918 _ => {
919 create_minimal_png(width, height, 2)
921 }
922 }
923 }
924
925 #[test]
926 fn test_parse_jpeg_header() {
927 let jpeg_data = vec![
929 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, ];
938
939 let result = parse_jpeg_header(&jpeg_data);
940 assert!(result.is_ok());
941 let (width, height, color_space, bits) = result.unwrap();
942 assert_eq!(width, 200);
943 assert_eq!(height, 100);
944 assert_eq!(color_space, ColorSpace::DeviceRGB);
945 assert_eq!(bits, 8);
946 }
947
948 #[test]
949 fn test_invalid_jpeg() {
950 let invalid_data = vec![0x00, 0x00];
951 let result = parse_jpeg_header(&invalid_data);
952 assert!(result.is_err());
953 }
954
955 #[test]
956 fn test_parse_png_header() {
957 let mut png_data = vec![
959 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, ];
970
971 png_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
973
974 let result = parse_png_header(&png_data);
975 assert!(result.is_ok());
976 let (width, height, color_space, bits) = result.unwrap();
977 assert_eq!(width, 100);
978 assert_eq!(height, 100);
979 assert_eq!(color_space, ColorSpace::DeviceRGB);
980 assert_eq!(bits, 8);
981 }
982
983 #[test]
984 fn test_invalid_png() {
985 let invalid_data = vec![0x00, 0x00];
986 let result = parse_png_header(&invalid_data);
987 assert!(result.is_err());
988 }
989
990 #[test]
991 fn test_parse_tiff_header_little_endian() {
992 let tiff_data = vec![
994 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
1000 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
1002 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1004 0x00, 0x00, ];
1006
1007 let result = parse_tiff_header(&tiff_data);
1008 assert!(result.is_ok());
1009 let (width, height, color_space, bits) = result.unwrap();
1010 assert_eq!(width, 100);
1011 assert_eq!(height, 100);
1012 assert_eq!(color_space, ColorSpace::DeviceGray);
1013 assert_eq!(bits, 8);
1014 }
1015
1016 #[test]
1017 fn test_parse_tiff_header_big_endian() {
1018 let tiff_data = vec![
1020 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
1026 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
1028 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
1030 0x00, 0x00, ];
1032
1033 let result = parse_tiff_header(&tiff_data);
1034 assert!(result.is_ok());
1035 let (width, height, color_space, bits) = result.unwrap();
1036 assert_eq!(width, 100);
1037 assert_eq!(height, 100);
1038 assert_eq!(color_space, ColorSpace::DeviceGray);
1039 assert_eq!(bits, 8);
1040 }
1041
1042 #[test]
1043 fn test_invalid_tiff() {
1044 let invalid_data = vec![0x00, 0x00];
1045 let result = parse_tiff_header(&invalid_data);
1046 assert!(result.is_err());
1047 }
1048
1049 #[test]
1050 fn test_image_format_enum() {
1051 assert_eq!(ImageFormat::Jpeg, ImageFormat::Jpeg);
1052 assert_eq!(ImageFormat::Png, ImageFormat::Png);
1053 assert_eq!(ImageFormat::Tiff, ImageFormat::Tiff);
1054 assert_ne!(ImageFormat::Jpeg, ImageFormat::Png);
1055 }
1056
1057 mod comprehensive_tests {
1059 use super::*;
1060 use std::fs;
1061 use tempfile::TempDir;
1062
1063 #[test]
1064 fn test_image_format_variants() {
1065 let jpeg = ImageFormat::Jpeg;
1067 let png = ImageFormat::Png;
1068 let tiff = ImageFormat::Tiff;
1069
1070 assert_eq!(jpeg, ImageFormat::Jpeg);
1071 assert_eq!(png, ImageFormat::Png);
1072 assert_eq!(tiff, ImageFormat::Tiff);
1073
1074 assert_ne!(jpeg, png);
1075 assert_ne!(png, tiff);
1076 assert_ne!(tiff, jpeg);
1077 }
1078
1079 #[test]
1080 fn test_image_format_debug() {
1081 let jpeg = ImageFormat::Jpeg;
1082 let png = ImageFormat::Png;
1083 let tiff = ImageFormat::Tiff;
1084
1085 assert_eq!(format!("{jpeg:?}"), "Jpeg");
1086 assert_eq!(format!("{png:?}"), "Png");
1087 assert_eq!(format!("{tiff:?}"), "Tiff");
1088 }
1089
1090 #[test]
1091 fn test_image_format_clone_copy() {
1092 let jpeg = ImageFormat::Jpeg;
1093 let jpeg_clone = jpeg;
1094 let jpeg_copy = jpeg;
1095
1096 assert_eq!(jpeg_clone, ImageFormat::Jpeg);
1097 assert_eq!(jpeg_copy, ImageFormat::Jpeg);
1098 }
1099
1100 #[test]
1101 fn test_color_space_variants() {
1102 let gray = ColorSpace::DeviceGray;
1104 let rgb = ColorSpace::DeviceRGB;
1105 let cmyk = ColorSpace::DeviceCMYK;
1106
1107 assert_eq!(gray, ColorSpace::DeviceGray);
1108 assert_eq!(rgb, ColorSpace::DeviceRGB);
1109 assert_eq!(cmyk, ColorSpace::DeviceCMYK);
1110
1111 assert_ne!(gray, rgb);
1112 assert_ne!(rgb, cmyk);
1113 assert_ne!(cmyk, gray);
1114 }
1115
1116 #[test]
1117 fn test_color_space_debug() {
1118 let gray = ColorSpace::DeviceGray;
1119 let rgb = ColorSpace::DeviceRGB;
1120 let cmyk = ColorSpace::DeviceCMYK;
1121
1122 assert_eq!(format!("{gray:?}"), "DeviceGray");
1123 assert_eq!(format!("{rgb:?}"), "DeviceRGB");
1124 assert_eq!(format!("{cmyk:?}"), "DeviceCMYK");
1125 }
1126
1127 #[test]
1128 fn test_color_space_clone_copy() {
1129 let rgb = ColorSpace::DeviceRGB;
1130 let rgb_clone = rgb;
1131 let rgb_copy = rgb;
1132
1133 assert_eq!(rgb_clone, ColorSpace::DeviceRGB);
1134 assert_eq!(rgb_copy, ColorSpace::DeviceRGB);
1135 }
1136
1137 #[test]
1138 fn test_image_from_jpeg_data() {
1139 let jpeg_data = vec![
1141 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1153
1154 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1155
1156 assert_eq!(image.width(), 200);
1157 assert_eq!(image.height(), 100);
1158 assert_eq!(image.format(), ImageFormat::Jpeg);
1159 assert_eq!(image.data(), jpeg_data);
1160 }
1161
1162 #[test]
1163 fn test_image_from_png_data() {
1164 let mut png_data = Vec::new();
1166
1167 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1169
1170 png_data.extend_from_slice(&[
1172 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, ]);
1182 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]);
1184
1185 let raw_data = vec![0x00, 0x00, 0x00, 0x00]; use flate2::write::ZlibEncoder;
1190 use flate2::Compression;
1191 use std::io::Write;
1192
1193 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1194 encoder.write_all(&raw_data).unwrap();
1195 let compressed_data = encoder.finish().unwrap();
1196
1197 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1199 png_data.extend_from_slice(b"IDAT");
1200 png_data.extend_from_slice(&compressed_data);
1201 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); png_data.extend_from_slice(&[
1205 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]);
1209
1210 let image = Image::from_png_data(png_data.clone()).unwrap();
1211
1212 assert_eq!(image.width(), 1);
1213 assert_eq!(image.height(), 1);
1214 assert_eq!(image.format(), ImageFormat::Png);
1215 assert_eq!(image.data(), png_data);
1216 }
1217
1218 #[test]
1219 fn test_image_from_tiff_data() {
1220 let tiff_data = vec![
1222 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
1228 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
1230 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1232 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
1234 0x00, 0x00, ];
1236
1237 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
1238
1239 assert_eq!(image.width(), 128);
1240 assert_eq!(image.height(), 128);
1241 assert_eq!(image.format(), ImageFormat::Tiff);
1242 assert_eq!(image.data(), tiff_data);
1243 }
1244
1245 #[test]
1246 fn test_image_from_jpeg_file() {
1247 let temp_dir = TempDir::new().unwrap();
1248 let file_path = temp_dir.path().join("test.jpg");
1249
1250 let jpeg_data = vec![
1252 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1264
1265 fs::write(&file_path, &jpeg_data).unwrap();
1266
1267 let image = Image::from_jpeg_file(&file_path).unwrap();
1268
1269 assert_eq!(image.width(), 100);
1270 assert_eq!(image.height(), 50);
1271 assert_eq!(image.format(), ImageFormat::Jpeg);
1272 assert_eq!(image.data(), jpeg_data);
1273 }
1274
1275 #[test]
1276 fn test_image_from_png_file() {
1277 let temp_dir = TempDir::new().unwrap();
1278 let file_path = temp_dir.path().join("test.png");
1279
1280 let mut png_data = Vec::new();
1282
1283 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1285
1286 png_data.extend_from_slice(&[
1288 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, ]);
1298 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]); let raw_data = vec![0x00, 0x00, 0x00, 0x00]; use flate2::write::ZlibEncoder;
1304 use flate2::Compression;
1305 use std::io::Write;
1306
1307 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1308 encoder.write_all(&raw_data).unwrap();
1309 let compressed_data = encoder.finish().unwrap();
1310
1311 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1312 png_data.extend_from_slice(b"IDAT");
1313 png_data.extend_from_slice(&compressed_data);
1314 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); png_data.extend_from_slice(&[
1318 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]);
1322
1323 fs::write(&file_path, &png_data).unwrap();
1324
1325 let image = Image::from_png_file(&file_path).unwrap();
1326
1327 assert_eq!(image.width(), 1);
1328 assert_eq!(image.height(), 1);
1329 assert_eq!(image.format(), ImageFormat::Png);
1330 assert_eq!(image.data(), png_data);
1331 }
1332
1333 #[test]
1334 fn test_image_from_tiff_file() {
1335 let temp_dir = TempDir::new().unwrap();
1336 let file_path = temp_dir.path().join("test.tiff");
1337
1338 let tiff_data = vec![
1340 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
1346 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
1348 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1350 0x00, 0x00, ];
1352
1353 fs::write(&file_path, &tiff_data).unwrap();
1354
1355 let image = Image::from_tiff_file(&file_path).unwrap();
1356
1357 assert_eq!(image.width(), 96);
1358 assert_eq!(image.height(), 96);
1359 assert_eq!(image.format(), ImageFormat::Tiff);
1360 assert_eq!(image.data(), tiff_data);
1361 }
1362
1363 #[test]
1364 fn test_image_to_pdf_object_jpeg() {
1365 let jpeg_data = vec![
1366 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1378
1379 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1380 let pdf_obj = image.to_pdf_object();
1381
1382 if let Object::Stream(dict, data) = pdf_obj {
1383 assert_eq!(
1384 dict.get("Type").unwrap(),
1385 &Object::Name("XObject".to_string())
1386 );
1387 assert_eq!(
1388 dict.get("Subtype").unwrap(),
1389 &Object::Name("Image".to_string())
1390 );
1391 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
1392 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
1393 assert_eq!(
1394 dict.get("ColorSpace").unwrap(),
1395 &Object::Name("DeviceRGB".to_string())
1396 );
1397 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1398 assert_eq!(
1399 dict.get("Filter").unwrap(),
1400 &Object::Name("DCTDecode".to_string())
1401 );
1402 assert_eq!(data, jpeg_data);
1403 } else {
1404 panic!("Expected Stream object");
1405 }
1406 }
1407
1408 #[test]
1409 fn test_image_to_pdf_object_png() {
1410 let mut png_data = Vec::new();
1412
1413 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1414 png_data.extend_from_slice(&[
1415 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
1416 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00,
1417 ]);
1418 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]);
1419
1420 let raw_data = vec![0x00, 0x00, 0x00, 0x00];
1421 use flate2::write::ZlibEncoder;
1422 use flate2::Compression;
1423 use std::io::Write;
1424
1425 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1426 encoder.write_all(&raw_data).unwrap();
1427 let compressed_data = encoder.finish().unwrap();
1428
1429 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1430 png_data.extend_from_slice(b"IDAT");
1431 png_data.extend_from_slice(&compressed_data);
1432 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
1433
1434 png_data.extend_from_slice(&[
1435 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
1436 ]);
1437
1438 let image = Image::from_png_data(png_data.clone()).unwrap();
1439 let pdf_obj = image.to_pdf_object();
1440
1441 if let Object::Stream(dict, data) = pdf_obj {
1442 assert_eq!(
1443 dict.get("Type").unwrap(),
1444 &Object::Name("XObject".to_string())
1445 );
1446 assert_eq!(
1447 dict.get("Subtype").unwrap(),
1448 &Object::Name("Image".to_string())
1449 );
1450 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(1));
1451 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(1));
1452 assert_eq!(
1453 dict.get("ColorSpace").unwrap(),
1454 &Object::Name("DeviceRGB".to_string())
1455 );
1456 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1457 assert_eq!(
1458 dict.get("Filter").unwrap(),
1459 &Object::Name("FlateDecode".to_string())
1460 );
1461 assert_eq!(data, png_data);
1462 } else {
1463 panic!("Expected Stream object");
1464 }
1465 }
1466
1467 #[test]
1468 fn test_image_to_pdf_object_tiff() {
1469 let tiff_data = vec![
1470 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
1476 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
1478 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1480 0x00, 0x00, ];
1482
1483 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
1484 let pdf_obj = image.to_pdf_object();
1485
1486 if let Object::Stream(dict, data) = pdf_obj {
1487 assert_eq!(
1488 dict.get("Type").unwrap(),
1489 &Object::Name("XObject".to_string())
1490 );
1491 assert_eq!(
1492 dict.get("Subtype").unwrap(),
1493 &Object::Name("Image".to_string())
1494 );
1495 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(64));
1496 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(64));
1497 assert_eq!(
1498 dict.get("ColorSpace").unwrap(),
1499 &Object::Name("DeviceGray".to_string())
1500 );
1501 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1502 assert_eq!(
1503 dict.get("Filter").unwrap(),
1504 &Object::Name("FlateDecode".to_string())
1505 );
1506 assert_eq!(data, tiff_data);
1507 } else {
1508 panic!("Expected Stream object");
1509 }
1510 }
1511
1512 #[test]
1513 fn test_image_clone() {
1514 let jpeg_data = vec![
1515 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1527
1528 let image1 = Image::from_jpeg_data(jpeg_data).unwrap();
1529 let image2 = image1.clone();
1530
1531 assert_eq!(image1.width(), image2.width());
1532 assert_eq!(image1.height(), image2.height());
1533 assert_eq!(image1.format(), image2.format());
1534 assert_eq!(image1.data(), image2.data());
1535 }
1536
1537 #[test]
1538 fn test_image_debug() {
1539 let jpeg_data = vec![
1540 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1552
1553 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1554 let debug_str = format!("{image:?}");
1555
1556 assert!(debug_str.contains("Image"));
1557 assert!(debug_str.contains("width"));
1558 assert!(debug_str.contains("height"));
1559 assert!(debug_str.contains("format"));
1560 }
1561
1562 #[test]
1563 fn test_jpeg_grayscale_image() {
1564 let jpeg_data = vec![
1565 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x01, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD9, ];
1576
1577 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1578 let pdf_obj = image.to_pdf_object();
1579
1580 if let Object::Stream(dict, _) = pdf_obj {
1581 assert_eq!(
1582 dict.get("ColorSpace").unwrap(),
1583 &Object::Name("DeviceGray".to_string())
1584 );
1585 } else {
1586 panic!("Expected Stream object");
1587 }
1588 }
1589
1590 #[test]
1591 fn test_jpeg_cmyk_image() {
1592 let jpeg_data = vec![
1593 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x04, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1605
1606 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1607 let pdf_obj = image.to_pdf_object();
1608
1609 if let Object::Stream(dict, _) = pdf_obj {
1610 assert_eq!(
1611 dict.get("ColorSpace").unwrap(),
1612 &Object::Name("DeviceCMYK".to_string())
1613 );
1614 } else {
1615 panic!("Expected Stream object");
1616 }
1617 }
1618
1619 #[test]
1620 fn test_png_grayscale_image() {
1621 let png_data = create_minimal_png(1, 1, 0); let image = Image::from_png_data(png_data).unwrap();
1624 let pdf_obj = image.to_pdf_object();
1625
1626 if let Object::Stream(dict, _) = pdf_obj {
1627 assert_eq!(
1628 dict.get("ColorSpace").unwrap(),
1629 &Object::Name("DeviceGray".to_string())
1630 );
1631 } else {
1632 panic!("Expected Stream object");
1633 }
1634 }
1635
1636 #[test]
1637 fn test_png_palette_image_incomplete() {
1638 let png_data = vec![
1640 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, ];
1652
1653 let result = Image::from_png_data(png_data);
1655 assert!(result.is_err(), "Incomplete PNG should return error");
1656 }
1657
1658 #[test]
1659 fn test_tiff_big_endian() {
1660 let tiff_data = vec![
1661 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1667 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1669 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00,
1671 0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
1673 0x00, 0x00, ];
1675
1676 let image = Image::from_tiff_data(tiff_data).unwrap();
1677
1678 assert_eq!(image.width(), 128);
1679 assert_eq!(image.height(), 128);
1680 assert_eq!(image.format(), ImageFormat::Tiff);
1681 }
1682
1683 #[test]
1684 fn test_tiff_cmyk_image() {
1685 let tiff_data = vec![
1686 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1692 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1694 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1696 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
1698 0x00, 0x00, ];
1700
1701 let image = Image::from_tiff_data(tiff_data).unwrap();
1702 let pdf_obj = image.to_pdf_object();
1703
1704 if let Object::Stream(dict, _) = pdf_obj {
1705 assert_eq!(
1706 dict.get("ColorSpace").unwrap(),
1707 &Object::Name("DeviceCMYK".to_string())
1708 );
1709 } else {
1710 panic!("Expected Stream object");
1711 }
1712 }
1713
1714 #[test]
1715 fn test_error_invalid_jpeg() {
1716 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_jpeg_data(invalid_data);
1718 assert!(result.is_err());
1719 }
1720
1721 #[test]
1722 fn test_error_invalid_png() {
1723 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_png_data(invalid_data);
1725 assert!(result.is_err());
1726 }
1727
1728 #[test]
1729 fn test_error_invalid_tiff() {
1730 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_tiff_data(invalid_data);
1732 assert!(result.is_err());
1733 }
1734
1735 #[test]
1736 fn test_error_truncated_jpeg() {
1737 let truncated_data = vec![0xFF, 0xD8, 0xFF]; let result = Image::from_jpeg_data(truncated_data);
1739 assert!(result.is_err());
1740 }
1741
1742 #[test]
1743 fn test_error_truncated_png() {
1744 let truncated_data = vec![0x89, 0x50, 0x4E, 0x47]; let result = Image::from_png_data(truncated_data);
1746 assert!(result.is_err());
1747 }
1748
1749 #[test]
1750 fn test_error_truncated_tiff() {
1751 let truncated_data = vec![0x49, 0x49, 0x2A]; let result = Image::from_tiff_data(truncated_data);
1753 assert!(result.is_err());
1754 }
1755
1756 #[test]
1757 fn test_error_jpeg_unsupported_components() {
1758 let invalid_jpeg = vec![
1759 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x05, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1771
1772 let result = Image::from_jpeg_data(invalid_jpeg);
1773 assert!(result.is_err());
1774 }
1775
1776 #[test]
1777 fn test_error_png_unsupported_color_type() {
1778 let invalid_png = vec![
1779 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, ];
1791
1792 let result = Image::from_png_data(invalid_png);
1793 assert!(result.is_err());
1794 }
1795
1796 #[test]
1797 fn test_error_nonexistent_file() {
1798 let result = Image::from_jpeg_file("/nonexistent/path/image.jpg");
1799 assert!(result.is_err());
1800
1801 let result = Image::from_png_file("/nonexistent/path/image.png");
1802 assert!(result.is_err());
1803
1804 let result = Image::from_tiff_file("/nonexistent/path/image.tiff");
1805 assert!(result.is_err());
1806 }
1807
1808 #[test]
1809 fn test_jpeg_no_dimensions() {
1810 let jpeg_no_dims = vec![
1811 0xFF, 0xD8, 0xFF, 0xD9, ];
1814
1815 let result = Image::from_jpeg_data(jpeg_no_dims);
1816 assert!(result.is_err());
1817 }
1818
1819 #[test]
1820 fn test_png_no_ihdr() {
1821 let png_no_ihdr = vec![
1822 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,
1826 0x72, 0x6E, 0x38, ];
1828
1829 let result = Image::from_png_data(png_no_ihdr);
1830 assert!(result.is_err());
1831 }
1832
1833 #[test]
1834 fn test_tiff_no_dimensions() {
1835 let tiff_no_dims = vec![
1836 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1842 0x00, 0x00, ];
1844
1845 let result = Image::from_tiff_data(tiff_no_dims);
1846 assert!(result.is_err());
1847 }
1848
1849 fn png_crc32(data: &[u8]) -> u32 {
1851 let mut crc = 0xFFFFFFFF_u32;
1853 for &byte in data {
1854 crc ^= byte as u32;
1855 for _ in 0..8 {
1856 if crc & 1 != 0 {
1857 crc = (crc >> 1) ^ 0xEDB88320;
1858 } else {
1859 crc >>= 1;
1860 }
1861 }
1862 }
1863 !crc
1864 }
1865
1866 fn create_valid_png_data(
1868 width: u32,
1869 height: u32,
1870 bit_depth: u8,
1871 color_type: u8,
1872 ) -> Vec<u8> {
1873 use flate2::write::ZlibEncoder;
1874 use flate2::Compression;
1875 use std::io::Write;
1876
1877 let mut png_data = Vec::new();
1878
1879 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1881
1882 let mut ihdr_data = Vec::new();
1884 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();
1894 ihdr_crc_data.extend_from_slice(b"IHDR");
1895 ihdr_crc_data.extend_from_slice(&ihdr_data);
1896 let ihdr_crc = png_crc32(&ihdr_crc_data);
1897
1898 png_data.extend_from_slice(&(ihdr_data.len() as u32).to_be_bytes());
1900 png_data.extend_from_slice(b"IHDR");
1901 png_data.extend_from_slice(&ihdr_data);
1902 png_data.extend_from_slice(&ihdr_crc.to_be_bytes());
1903
1904 let bytes_per_pixel = match (color_type, bit_depth) {
1906 (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,
1912 };
1913
1914 let mut raw_data = Vec::new();
1915 for _y in 0..height {
1916 raw_data.push(0); for _x in 0..width {
1918 for _c in 0..bytes_per_pixel {
1919 raw_data.push(0); }
1921 }
1922 }
1923
1924 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1926 encoder.write_all(&raw_data).unwrap();
1927 let compressed_data = encoder.finish().unwrap();
1928
1929 let mut idat_crc_data = Vec::new();
1931 idat_crc_data.extend_from_slice(b"IDAT");
1932 idat_crc_data.extend_from_slice(&compressed_data);
1933 let idat_crc = png_crc32(&idat_crc_data);
1934
1935 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1937 png_data.extend_from_slice(b"IDAT");
1938 png_data.extend_from_slice(&compressed_data);
1939 png_data.extend_from_slice(&idat_crc.to_be_bytes());
1940
1941 png_data.extend_from_slice(&[0, 0, 0, 0]); png_data.extend_from_slice(b"IEND");
1944 png_data.extend_from_slice(&[0xAE, 0x42, 0x60, 0x82]); png_data
1947 }
1948
1949 #[test]
1950 fn test_different_bit_depths() {
1951 let png_8bit = create_valid_png_data(2, 2, 8, 2); let image_8bit = Image::from_png_data(png_8bit).unwrap();
1956 let pdf_obj_8bit = image_8bit.to_pdf_object();
1957
1958 if let Object::Stream(dict, _) = pdf_obj_8bit {
1959 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1960 } else {
1961 panic!("Expected Stream object");
1962 }
1963
1964 let png_16bit = create_valid_png_data(2, 2, 16, 2); let image_16bit = Image::from_png_data(png_16bit).unwrap();
1967 let pdf_obj_16bit = image_16bit.to_pdf_object();
1968
1969 if let Object::Stream(dict, _) = pdf_obj_16bit {
1970 let bits = dict.get("BitsPerComponent").unwrap();
1972 assert!(matches!(bits, &Object::Integer(8) | &Object::Integer(16)));
1973 } else {
1974 panic!("Expected Stream object");
1975 }
1976 }
1977
1978 #[test]
1979 fn test_performance_large_image_data() {
1980 let mut large_jpeg = vec![
1982 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x04, 0x00, 0x04, 0x00, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, ];
1993
1994 large_jpeg.extend(vec![0x00; 10000]);
1996 large_jpeg.extend(vec![0xFF, 0xD9]); let start = std::time::Instant::now();
1999 let image = Image::from_jpeg_data(large_jpeg.clone()).unwrap();
2000 let duration = start.elapsed();
2001
2002 assert_eq!(image.width(), 1024);
2003 assert_eq!(image.height(), 1024);
2004 assert_eq!(image.data().len(), large_jpeg.len());
2005 assert!(duration.as_millis() < 100); }
2007
2008 #[test]
2009 fn test_memory_efficiency() {
2010 let jpeg_data = vec![
2011 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
2023
2024 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
2025
2026 assert_eq!(image.data().len(), jpeg_data.len());
2028 assert_eq!(image.data(), jpeg_data);
2029
2030 let cloned = image.clone();
2032 assert_eq!(cloned.data(), image.data());
2033 }
2034
2035 #[test]
2036 fn test_complete_workflow() {
2037 let test_cases = vec![
2039 (ImageFormat::Jpeg, "DCTDecode", "DeviceRGB"),
2040 (ImageFormat::Png, "FlateDecode", "DeviceRGB"),
2041 (ImageFormat::Tiff, "FlateDecode", "DeviceGray"),
2042 ];
2043
2044 for (expected_format, expected_filter, expected_color_space) in test_cases {
2045 let data = match expected_format {
2046 ImageFormat::Jpeg => vec![
2047 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ],
2059 ImageFormat::Png => create_valid_png_data(2, 2, 8, 2), ImageFormat::Tiff => vec![
2061 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00,
2067 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
2069 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
2071 0x00, 0x00, 0x00, 0x00, ],
2073 ImageFormat::Raw => Vec::new(), };
2075
2076 let image = match expected_format {
2077 ImageFormat::Jpeg => Image::from_jpeg_data(data.clone()).unwrap(),
2078 ImageFormat::Png => Image::from_png_data(data.clone()).unwrap(),
2079 ImageFormat::Tiff => Image::from_tiff_data(data.clone()).unwrap(),
2080 ImageFormat::Raw => continue, };
2082
2083 assert_eq!(image.format(), expected_format);
2085 if expected_format == ImageFormat::Png {
2087 assert_eq!(image.width(), 2);
2088 assert_eq!(image.height(), 2);
2089 } else if expected_format == ImageFormat::Jpeg {
2090 assert_eq!(image.width(), 200);
2091 assert_eq!(image.height(), 100);
2092 } else if expected_format == ImageFormat::Tiff {
2093 assert_eq!(image.width(), 200);
2094 assert_eq!(image.height(), 100);
2095 }
2096 assert_eq!(image.data(), data);
2097
2098 let pdf_obj = image.to_pdf_object();
2100 if let Object::Stream(dict, stream_data) = pdf_obj {
2101 assert_eq!(
2102 dict.get("Type").unwrap(),
2103 &Object::Name("XObject".to_string())
2104 );
2105 assert_eq!(
2106 dict.get("Subtype").unwrap(),
2107 &Object::Name("Image".to_string())
2108 );
2109 if expected_format == ImageFormat::Png {
2111 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(2));
2112 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(2));
2113 } else {
2114 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
2115 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
2116 }
2117 assert_eq!(
2118 dict.get("ColorSpace").unwrap(),
2119 &Object::Name(expected_color_space.to_string())
2120 );
2121 assert_eq!(
2122 dict.get("Filter").unwrap(),
2123 &Object::Name(expected_filter.to_string())
2124 );
2125 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
2126 assert_eq!(stream_data, data);
2127 } else {
2128 panic!("Expected Stream object for format {expected_format:?}");
2129 }
2130 }
2131 }
2132 }
2133}