1use crate::objects::{Dictionary, Object};
7use crate::{PdfError, Result};
8use std::fs::File;
9use std::io::Read;
10use std::path::Path;
11
12#[derive(Debug, Clone)]
14pub struct Image {
15 data: Vec<u8>,
17 format: ImageFormat,
19 width: u32,
21 height: u32,
23 color_space: ColorSpace,
25 bits_per_component: u8,
27 alpha_data: Option<Vec<u8>>,
29 soft_mask: Option<Box<Image>>,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq)]
35pub enum ImageFormat {
36 Jpeg,
38 Png,
40 Tiff,
42 Raw,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq)]
48pub enum MaskType {
49 Soft,
51 Stencil,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq)]
57pub enum ColorSpace {
58 DeviceGray,
60 DeviceRGB,
62 DeviceCMYK,
64}
65
66impl Image {
67 pub fn from_jpeg_file<P: AsRef<Path>>(path: P) -> Result<Self> {
69 #[cfg(feature = "external-images")]
70 {
71 let mut file = File::open(path)?;
73 let mut data = Vec::new();
74 file.read_to_end(&mut data)?;
75 Self::from_jpeg_data(data)
76 }
77 #[cfg(not(feature = "external-images"))]
78 {
79 let mut file = File::open(path)?;
80 let mut data = Vec::new();
81 file.read_to_end(&mut data)?;
82 Self::from_jpeg_data(data)
83 }
84 }
85
86 pub fn from_jpeg_data(data: Vec<u8>) -> Result<Self> {
88 let (width, height, color_space, bits_per_component) = parse_jpeg_header(&data)?;
90
91 Ok(Image {
92 data,
93 format: ImageFormat::Jpeg,
94 width,
95 height,
96 color_space,
97 bits_per_component,
98 alpha_data: None,
99 soft_mask: None,
100 })
101 }
102
103 pub fn from_png_file<P: AsRef<Path>>(path: P) -> Result<Self> {
105 #[cfg(feature = "external-images")]
106 {
107 let mut file = File::open(path)?;
109 let mut data = Vec::new();
110 file.read_to_end(&mut data)?;
111 Self::from_png_data(data)
112 }
113 #[cfg(not(feature = "external-images"))]
114 {
115 let mut file = File::open(path)?;
116 let mut data = Vec::new();
117 file.read_to_end(&mut data)?;
118 Self::from_png_data(data)
119 }
120 }
121
122 pub fn from_png_data(data: Vec<u8>) -> Result<Self> {
124 use crate::graphics::png_decoder::{decode_png, PngColorType};
125
126 let decoded = decode_png(&data)?;
128
129 let color_space = match decoded.color_type {
131 PngColorType::Grayscale | PngColorType::GrayscaleAlpha => ColorSpace::DeviceGray,
132 PngColorType::Rgb | PngColorType::RgbAlpha | PngColorType::Palette => {
133 ColorSpace::DeviceRGB
134 }
135 };
136
137 let soft_mask = if let Some(alpha) = &decoded.alpha_data {
139 Some(Box::new(Image {
140 data: alpha.clone(),
141 format: ImageFormat::Raw,
142 width: decoded.width,
143 height: decoded.height,
144 color_space: ColorSpace::DeviceGray,
145 bits_per_component: 8,
146 alpha_data: None,
147 soft_mask: None,
148 }))
149 } else {
150 None
151 };
152
153 Ok(Image {
154 data, format: ImageFormat::Png, width: decoded.width,
157 height: decoded.height,
158 color_space,
159 bits_per_component: 8, alpha_data: decoded.alpha_data,
161 soft_mask,
162 })
163 }
164
165 pub fn from_tiff_file<P: AsRef<Path>>(path: P) -> Result<Self> {
167 let mut file = File::open(path)?;
168 let mut data = Vec::new();
169 file.read_to_end(&mut data)?;
170 Self::from_tiff_data(data)
171 }
172
173 pub fn from_tiff_data(data: Vec<u8>) -> Result<Self> {
175 let (width, height, color_space, bits_per_component) = parse_tiff_header(&data)?;
177
178 Ok(Image {
179 data,
180 format: ImageFormat::Tiff,
181 width,
182 height,
183 color_space,
184 bits_per_component,
185 alpha_data: None,
186 soft_mask: None,
187 })
188 }
189
190 pub fn width(&self) -> u32 {
192 self.width
193 }
194
195 pub fn height(&self) -> u32 {
197 self.height
198 }
199
200 pub fn data(&self) -> &[u8] {
202 &self.data
203 }
204
205 pub fn format(&self) -> ImageFormat {
207 self.format
208 }
209
210 pub fn bits_per_component(&self) -> u8 {
212 self.bits_per_component
213 }
214
215 pub fn from_raw_data(
217 data: Vec<u8>,
218 width: u32,
219 height: u32,
220 color_space: ColorSpace,
221 bits_per_component: u8,
222 ) -> Self {
223 Image {
224 data,
225 format: ImageFormat::Raw,
226 width,
227 height,
228 color_space,
229 bits_per_component,
230 alpha_data: None,
231 soft_mask: None,
232 }
233 }
234
235 pub fn from_rgba_data(rgba_data: Vec<u8>, width: u32, height: u32) -> Result<Self> {
237 if rgba_data.len() != (width * height * 4) as usize {
238 return Err(PdfError::InvalidImage(
239 "RGBA data size doesn't match dimensions".to_string(),
240 ));
241 }
242
243 let mut rgb_data = Vec::with_capacity((width * height * 3) as usize);
245 let mut alpha_data = Vec::with_capacity((width * height) as usize);
246
247 for chunk in rgba_data.chunks(4) {
248 rgb_data.push(chunk[0]); rgb_data.push(chunk[1]); rgb_data.push(chunk[2]); alpha_data.push(chunk[3]); }
253
254 let soft_mask = Some(Box::new(Image {
256 data: alpha_data.clone(),
257 format: ImageFormat::Raw,
258 width,
259 height,
260 color_space: ColorSpace::DeviceGray,
261 bits_per_component: 8,
262 alpha_data: None,
263 soft_mask: None,
264 }));
265
266 Ok(Image {
267 data: rgb_data,
268 format: ImageFormat::Raw,
269 width,
270 height,
271 color_space: ColorSpace::DeviceRGB,
272 bits_per_component: 8,
273 alpha_data: Some(alpha_data),
274 soft_mask,
275 })
276 }
277
278 pub fn from_gray_data(gray_data: Vec<u8>, width: u32, height: u32) -> Result<Self> {
280 if gray_data.len() != (width * height) as usize {
281 return Err(PdfError::InvalidImage(
282 "Gray data size doesn't match dimensions".to_string(),
283 ));
284 }
285
286 Ok(Image {
287 data: gray_data,
288 format: ImageFormat::Raw,
289 width,
290 height,
291 color_space: ColorSpace::DeviceGray,
292 bits_per_component: 8,
293 alpha_data: None,
294 soft_mask: None,
295 })
296 }
297
298 #[cfg(feature = "external-images")]
300 pub fn from_file_raw<P: AsRef<Path>>(
301 path: P,
302 width: u32,
303 height: u32,
304 format: ImageFormat,
305 ) -> Result<Self> {
306 let data = std::fs::read(path)
307 .map_err(|e| PdfError::InvalidImage(format!("Failed to read image file: {}", e)))?;
308
309 Ok(Image {
310 data,
311 format,
312 width,
313 height,
314 color_space: ColorSpace::DeviceRGB,
315 bits_per_component: 8,
316 alpha_data: None,
317 soft_mask: None,
318 })
319 }
320
321 pub fn to_pdf_object(&self) -> Object {
323 let mut dict = Dictionary::new();
324
325 dict.set("Type", Object::Name("XObject".to_string()));
327 dict.set("Subtype", Object::Name("Image".to_string()));
328 dict.set("Width", Object::Integer(self.width as i64));
329 dict.set("Height", Object::Integer(self.height as i64));
330
331 let color_space_name = match self.color_space {
333 ColorSpace::DeviceGray => "DeviceGray",
334 ColorSpace::DeviceRGB => "DeviceRGB",
335 ColorSpace::DeviceCMYK => "DeviceCMYK",
336 };
337 dict.set("ColorSpace", Object::Name(color_space_name.to_string()));
338
339 dict.set(
341 "BitsPerComponent",
342 Object::Integer(self.bits_per_component as i64),
343 );
344
345 match self.format {
347 ImageFormat::Jpeg => {
348 dict.set("Filter", Object::Name("DCTDecode".to_string()));
349 }
350 ImageFormat::Png => {
351 dict.set("Filter", Object::Name("FlateDecode".to_string()));
352 }
353 ImageFormat::Tiff => {
354 dict.set("Filter", Object::Name("FlateDecode".to_string()));
356 }
357 ImageFormat::Raw => {
358 }
360 }
361
362 Object::Stream(dict, self.data.clone())
364 }
365
366 pub fn to_pdf_object_with_transparency(&self) -> (Object, Option<Object>) {
368 let mut main_dict = Dictionary::new();
369
370 main_dict.set("Type", Object::Name("XObject".to_string()));
372 main_dict.set("Subtype", Object::Name("Image".to_string()));
373 main_dict.set("Width", Object::Integer(self.width as i64));
374 main_dict.set("Height", Object::Integer(self.height as i64));
375
376 let color_space_name = match self.color_space {
378 ColorSpace::DeviceGray => "DeviceGray",
379 ColorSpace::DeviceRGB => "DeviceRGB",
380 ColorSpace::DeviceCMYK => "DeviceCMYK",
381 };
382 main_dict.set("ColorSpace", Object::Name(color_space_name.to_string()));
383
384 main_dict.set(
386 "BitsPerComponent",
387 Object::Integer(self.bits_per_component as i64),
388 );
389
390 match self.format {
392 ImageFormat::Jpeg => {
393 main_dict.set("Filter", Object::Name("DCTDecode".to_string()));
394 }
395 ImageFormat::Png | ImageFormat::Raw => {
396 main_dict.set("Filter", Object::Name("FlateDecode".to_string()));
398 }
399 ImageFormat::Tiff => {
400 main_dict.set("Filter", Object::Name("FlateDecode".to_string()));
401 }
402 }
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 Some(Object::Stream(mask_dict, mask.data.clone()))
416 } else {
417 None
418 };
419
420 (Object::Stream(main_dict, self.data.clone()), smask_obj)
424 }
425
426 pub fn has_transparency(&self) -> bool {
428 self.soft_mask.is_some() || self.alpha_data.is_some()
429 }
430
431 pub fn create_stencil_mask(&self, threshold: u8) -> Option<Image> {
434 if let Some(alpha) = &self.alpha_data {
435 let mut mask_data = Vec::new();
437 let mut current_byte = 0u8;
438 let mut bit_count = 0;
439
440 for &alpha_value in alpha.iter() {
441 if alpha_value > threshold {
443 current_byte |= 1 << (7 - bit_count);
444 }
445
446 bit_count += 1;
447 if bit_count == 8 {
448 mask_data.push(current_byte);
449 current_byte = 0;
450 bit_count = 0;
451 }
452 }
453
454 if bit_count > 0 {
456 mask_data.push(current_byte);
457 }
458
459 Some(Image {
460 data: mask_data,
461 format: ImageFormat::Raw,
462 width: self.width,
463 height: self.height,
464 color_space: ColorSpace::DeviceGray,
465 bits_per_component: 1,
466 alpha_data: None,
467 soft_mask: None,
468 })
469 } else {
470 None
471 }
472 }
473
474 pub fn create_mask(&self, mask_type: MaskType, threshold: Option<u8>) -> Option<Image> {
476 match mask_type {
477 MaskType::Soft => self.soft_mask.as_ref().map(|m| m.as_ref().clone()),
478 MaskType::Stencil => self.create_stencil_mask(threshold.unwrap_or(128)),
479 }
480 }
481
482 pub fn with_mask(mut self, mask: Image, mask_type: MaskType) -> Self {
484 match mask_type {
485 MaskType::Soft => {
486 self.soft_mask = Some(Box::new(mask));
487 }
488 MaskType::Stencil => {
489 self.soft_mask = Some(Box::new(mask));
491 }
492 }
493 self
494 }
495
496 pub fn soft_mask(&self) -> Option<&Image> {
498 self.soft_mask.as_ref().map(|m| m.as_ref())
499 }
500
501 pub fn alpha_data(&self) -> Option<&[u8]> {
503 self.alpha_data.as_deref()
504 }
505}
506
507fn parse_jpeg_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
509 if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
510 return Err(PdfError::InvalidImage("Not a valid JPEG file".to_string()));
511 }
512
513 let mut pos = 2;
514 let mut width = 0;
515 let mut height = 0;
516 let mut components = 0;
517
518 while pos < data.len() - 1 {
519 if data[pos] != 0xFF {
520 return Err(PdfError::InvalidImage("Invalid JPEG marker".to_string()));
521 }
522
523 let marker = data[pos + 1];
524 pos += 2;
525
526 if marker == 0xFF {
528 continue;
529 }
530
531 if (0xC0..=0xCF).contains(&marker) && marker != 0xC4 && marker != 0xC8 && marker != 0xCC {
533 if pos + 7 >= data.len() {
535 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
536 }
537
538 pos += 2;
540
541 pos += 1;
543
544 height = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
546 pos += 2;
547 width = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
548 pos += 2;
549
550 components = data[pos];
552 break;
553 } else if marker == 0xD9 {
554 break;
556 } else if marker == 0xD8 || (0xD0..=0xD7).contains(&marker) {
557 continue;
559 } else {
560 if pos + 1 >= data.len() {
562 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
563 }
564 let length = ((data[pos] as usize) << 8) | (data[pos + 1] as usize);
565 pos += length;
566 }
567 }
568
569 if width == 0 || height == 0 {
570 return Err(PdfError::InvalidImage(
571 "Could not find image dimensions".to_string(),
572 ));
573 }
574
575 let color_space = match components {
576 1 => ColorSpace::DeviceGray,
577 3 => ColorSpace::DeviceRGB,
578 4 => ColorSpace::DeviceCMYK,
579 _ => {
580 return Err(PdfError::InvalidImage(format!(
581 "Unsupported number of components: {components}"
582 )))
583 }
584 };
585
586 Ok((width, height, color_space, 8)) }
588
589#[allow(dead_code)]
591fn parse_png_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
592 if data.len() < 8 || &data[0..8] != b"\x89PNG\r\n\x1a\n" {
594 return Err(PdfError::InvalidImage("Not a valid PNG file".to_string()));
595 }
596
597 let mut pos = 8;
599
600 while pos + 8 < data.len() {
601 let chunk_length =
603 u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
604
605 let chunk_type = &data[pos + 4..pos + 8];
607
608 if chunk_type == b"IHDR" {
609 if pos + 8 + chunk_length > data.len() || chunk_length < 13 {
611 return Err(PdfError::InvalidImage("Invalid PNG IHDR chunk".to_string()));
612 }
613
614 let ihdr_data = &data[pos + 8..pos + 8 + chunk_length];
615
616 let width =
618 u32::from_be_bytes([ihdr_data[0], ihdr_data[1], ihdr_data[2], ihdr_data[3]]);
619
620 let height =
621 u32::from_be_bytes([ihdr_data[4], ihdr_data[5], ihdr_data[6], ihdr_data[7]]);
622
623 let bit_depth = ihdr_data[8];
624 let color_type = ihdr_data[9];
625
626 let color_space = match color_type {
628 0 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 3 => ColorSpace::DeviceRGB, 4 => ColorSpace::DeviceGray, 6 => ColorSpace::DeviceRGB, _ => {
634 return Err(PdfError::InvalidImage(format!(
635 "Unsupported PNG color type: {color_type}"
636 )))
637 }
638 };
639
640 return Ok((width, height, color_space, bit_depth));
641 }
642
643 pos += 8 + chunk_length + 4; }
646
647 Err(PdfError::InvalidImage(
648 "PNG IHDR chunk not found".to_string(),
649 ))
650}
651
652fn parse_tiff_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
654 if data.len() < 8 {
655 return Err(PdfError::InvalidImage(
656 "Invalid TIFF file: too short".to_string(),
657 ));
658 }
659
660 let (is_little_endian, offset) = if &data[0..2] == b"II" {
662 (true, 2) } else if &data[0..2] == b"MM" {
664 (false, 2) } else {
666 return Err(PdfError::InvalidImage(
667 "Invalid TIFF byte order".to_string(),
668 ));
669 };
670
671 let magic = if is_little_endian {
673 u16::from_le_bytes([data[offset], data[offset + 1]])
674 } else {
675 u16::from_be_bytes([data[offset], data[offset + 1]])
676 };
677
678 if magic != 42 {
679 return Err(PdfError::InvalidImage(
680 "Invalid TIFF magic number".to_string(),
681 ));
682 }
683
684 let ifd_offset = if is_little_endian {
686 u32::from_le_bytes([
687 data[offset + 2],
688 data[offset + 3],
689 data[offset + 4],
690 data[offset + 5],
691 ])
692 } else {
693 u32::from_be_bytes([
694 data[offset + 2],
695 data[offset + 3],
696 data[offset + 4],
697 data[offset + 5],
698 ])
699 } as usize;
700
701 if ifd_offset + 2 > data.len() {
702 return Err(PdfError::InvalidImage(
703 "Invalid TIFF IFD offset".to_string(),
704 ));
705 }
706
707 let num_entries = if is_little_endian {
709 u16::from_le_bytes([data[ifd_offset], data[ifd_offset + 1]])
710 } else {
711 u16::from_be_bytes([data[ifd_offset], data[ifd_offset + 1]])
712 };
713
714 let mut width = 0u32;
715 let mut height = 0u32;
716 let mut bits_per_sample = 8u16;
717 let mut photometric_interpretation = 0u16;
718
719 for i in 0..num_entries {
721 let entry_offset = ifd_offset + 2 + (i as usize * 12);
722
723 if entry_offset + 12 > data.len() {
724 break;
725 }
726
727 let tag = if is_little_endian {
728 u16::from_le_bytes([data[entry_offset], data[entry_offset + 1]])
729 } else {
730 u16::from_be_bytes([data[entry_offset], data[entry_offset + 1]])
731 };
732
733 let value_offset = entry_offset + 8;
734
735 match tag {
736 256 => {
737 width = if is_little_endian {
739 u32::from_le_bytes([
740 data[value_offset],
741 data[value_offset + 1],
742 data[value_offset + 2],
743 data[value_offset + 3],
744 ])
745 } else {
746 u32::from_be_bytes([
747 data[value_offset],
748 data[value_offset + 1],
749 data[value_offset + 2],
750 data[value_offset + 3],
751 ])
752 };
753 }
754 257 => {
755 height = if is_little_endian {
757 u32::from_le_bytes([
758 data[value_offset],
759 data[value_offset + 1],
760 data[value_offset + 2],
761 data[value_offset + 3],
762 ])
763 } else {
764 u32::from_be_bytes([
765 data[value_offset],
766 data[value_offset + 1],
767 data[value_offset + 2],
768 data[value_offset + 3],
769 ])
770 };
771 }
772 258 => {
773 bits_per_sample = if is_little_endian {
775 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
776 } else {
777 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
778 };
779 }
780 262 => {
781 photometric_interpretation = if is_little_endian {
783 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
784 } else {
785 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
786 };
787 }
788 _ => {} }
790 }
791
792 if width == 0 || height == 0 {
793 return Err(PdfError::InvalidImage(
794 "TIFF dimensions not found".to_string(),
795 ));
796 }
797
798 let color_space = match photometric_interpretation {
800 0 | 1 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 5 => ColorSpace::DeviceCMYK, _ => ColorSpace::DeviceRGB, };
805
806 Ok((width, height, color_space, bits_per_sample as u8))
807}
808
809#[cfg(test)]
810mod tests {
811 use super::*;
812
813 fn create_minimal_png(width: u32, height: u32, color_type: u8) -> Vec<u8> {
815 match color_type {
818 0 => {
819 vec![
821 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,
832 0x01, 0xE2, 0xF9, 0x8C, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
838 }
839 2 => {
840 vec![
842 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,
853 0x01, 0x27, 0x18, 0xAA, 0x61, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
859 }
860 3 => {
861 vec![
863 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,
878 0x01, 0xE5, 0x27, 0xDE, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
884 }
885 6 => {
886 vec![
888 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,
899 0x01, 0x75, 0xAA, 0x50, 0x19, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
905 }
906 _ => {
907 create_minimal_png(width, height, 2)
909 }
910 }
911 }
912
913 #[test]
914 fn test_parse_jpeg_header() {
915 let jpeg_data = vec![
917 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, ];
926
927 let result = parse_jpeg_header(&jpeg_data);
928 assert!(result.is_ok());
929 let (width, height, color_space, bits) = result.unwrap();
930 assert_eq!(width, 200);
931 assert_eq!(height, 100);
932 assert_eq!(color_space, ColorSpace::DeviceRGB);
933 assert_eq!(bits, 8);
934 }
935
936 #[test]
937 fn test_invalid_jpeg() {
938 let invalid_data = vec![0x00, 0x00];
939 let result = parse_jpeg_header(&invalid_data);
940 assert!(result.is_err());
941 }
942
943 #[test]
944 fn test_parse_png_header() {
945 let mut png_data = vec![
947 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, ];
958
959 png_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
961
962 let result = parse_png_header(&png_data);
963 assert!(result.is_ok());
964 let (width, height, color_space, bits) = result.unwrap();
965 assert_eq!(width, 100);
966 assert_eq!(height, 100);
967 assert_eq!(color_space, ColorSpace::DeviceRGB);
968 assert_eq!(bits, 8);
969 }
970
971 #[test]
972 fn test_invalid_png() {
973 let invalid_data = vec![0x00, 0x00];
974 let result = parse_png_header(&invalid_data);
975 assert!(result.is_err());
976 }
977
978 #[test]
979 fn test_parse_tiff_header_little_endian() {
980 let tiff_data = vec![
982 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
988 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
990 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
992 0x00, 0x00, ];
994
995 let result = parse_tiff_header(&tiff_data);
996 assert!(result.is_ok());
997 let (width, height, color_space, bits) = result.unwrap();
998 assert_eq!(width, 100);
999 assert_eq!(height, 100);
1000 assert_eq!(color_space, ColorSpace::DeviceGray);
1001 assert_eq!(bits, 8);
1002 }
1003
1004 #[test]
1005 fn test_parse_tiff_header_big_endian() {
1006 let tiff_data = vec![
1008 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
1014 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
1016 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
1018 0x00, 0x00, ];
1020
1021 let result = parse_tiff_header(&tiff_data);
1022 assert!(result.is_ok());
1023 let (width, height, color_space, bits) = result.unwrap();
1024 assert_eq!(width, 100);
1025 assert_eq!(height, 100);
1026 assert_eq!(color_space, ColorSpace::DeviceGray);
1027 assert_eq!(bits, 8);
1028 }
1029
1030 #[test]
1031 fn test_invalid_tiff() {
1032 let invalid_data = vec![0x00, 0x00];
1033 let result = parse_tiff_header(&invalid_data);
1034 assert!(result.is_err());
1035 }
1036
1037 #[test]
1038 fn test_image_format_enum() {
1039 assert_eq!(ImageFormat::Jpeg, ImageFormat::Jpeg);
1040 assert_eq!(ImageFormat::Png, ImageFormat::Png);
1041 assert_eq!(ImageFormat::Tiff, ImageFormat::Tiff);
1042 assert_ne!(ImageFormat::Jpeg, ImageFormat::Png);
1043 }
1044
1045 mod comprehensive_tests {
1047 use super::*;
1048 use std::fs;
1049 use tempfile::TempDir;
1050
1051 #[test]
1052 fn test_image_format_variants() {
1053 let jpeg = ImageFormat::Jpeg;
1055 let png = ImageFormat::Png;
1056 let tiff = ImageFormat::Tiff;
1057
1058 assert_eq!(jpeg, ImageFormat::Jpeg);
1059 assert_eq!(png, ImageFormat::Png);
1060 assert_eq!(tiff, ImageFormat::Tiff);
1061
1062 assert_ne!(jpeg, png);
1063 assert_ne!(png, tiff);
1064 assert_ne!(tiff, jpeg);
1065 }
1066
1067 #[test]
1068 fn test_image_format_debug() {
1069 let jpeg = ImageFormat::Jpeg;
1070 let png = ImageFormat::Png;
1071 let tiff = ImageFormat::Tiff;
1072
1073 assert_eq!(format!("{jpeg:?}"), "Jpeg");
1074 assert_eq!(format!("{png:?}"), "Png");
1075 assert_eq!(format!("{tiff:?}"), "Tiff");
1076 }
1077
1078 #[test]
1079 fn test_image_format_clone_copy() {
1080 let jpeg = ImageFormat::Jpeg;
1081 let jpeg_clone = jpeg;
1082 let jpeg_copy = jpeg;
1083
1084 assert_eq!(jpeg_clone, ImageFormat::Jpeg);
1085 assert_eq!(jpeg_copy, ImageFormat::Jpeg);
1086 }
1087
1088 #[test]
1089 fn test_color_space_variants() {
1090 let gray = ColorSpace::DeviceGray;
1092 let rgb = ColorSpace::DeviceRGB;
1093 let cmyk = ColorSpace::DeviceCMYK;
1094
1095 assert_eq!(gray, ColorSpace::DeviceGray);
1096 assert_eq!(rgb, ColorSpace::DeviceRGB);
1097 assert_eq!(cmyk, ColorSpace::DeviceCMYK);
1098
1099 assert_ne!(gray, rgb);
1100 assert_ne!(rgb, cmyk);
1101 assert_ne!(cmyk, gray);
1102 }
1103
1104 #[test]
1105 fn test_color_space_debug() {
1106 let gray = ColorSpace::DeviceGray;
1107 let rgb = ColorSpace::DeviceRGB;
1108 let cmyk = ColorSpace::DeviceCMYK;
1109
1110 assert_eq!(format!("{gray:?}"), "DeviceGray");
1111 assert_eq!(format!("{rgb:?}"), "DeviceRGB");
1112 assert_eq!(format!("{cmyk:?}"), "DeviceCMYK");
1113 }
1114
1115 #[test]
1116 fn test_color_space_clone_copy() {
1117 let rgb = ColorSpace::DeviceRGB;
1118 let rgb_clone = rgb;
1119 let rgb_copy = rgb;
1120
1121 assert_eq!(rgb_clone, ColorSpace::DeviceRGB);
1122 assert_eq!(rgb_copy, ColorSpace::DeviceRGB);
1123 }
1124
1125 #[test]
1126 fn test_image_from_jpeg_data() {
1127 let jpeg_data = vec![
1129 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1141
1142 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1143
1144 assert_eq!(image.width(), 200);
1145 assert_eq!(image.height(), 100);
1146 assert_eq!(image.format(), ImageFormat::Jpeg);
1147 assert_eq!(image.data(), jpeg_data);
1148 }
1149
1150 #[test]
1151 fn test_image_from_png_data() {
1152 let mut png_data = Vec::new();
1154
1155 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1157
1158 png_data.extend_from_slice(&[
1160 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, ]);
1170 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]);
1172
1173 let raw_data = vec![0x00, 0x00, 0x00, 0x00]; use flate2::write::ZlibEncoder;
1178 use flate2::Compression;
1179 use std::io::Write;
1180
1181 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1182 encoder.write_all(&raw_data).unwrap();
1183 let compressed_data = encoder.finish().unwrap();
1184
1185 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1187 png_data.extend_from_slice(b"IDAT");
1188 png_data.extend_from_slice(&compressed_data);
1189 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); png_data.extend_from_slice(&[
1193 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]);
1197
1198 let image = Image::from_png_data(png_data.clone()).unwrap();
1199
1200 assert_eq!(image.width(), 1);
1201 assert_eq!(image.height(), 1);
1202 assert_eq!(image.format(), ImageFormat::Png);
1203 assert_eq!(image.data(), png_data);
1204 }
1205
1206 #[test]
1207 fn test_image_from_tiff_data() {
1208 let tiff_data = vec![
1210 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
1216 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
1218 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1220 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
1222 0x00, 0x00, ];
1224
1225 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
1226
1227 assert_eq!(image.width(), 128);
1228 assert_eq!(image.height(), 128);
1229 assert_eq!(image.format(), ImageFormat::Tiff);
1230 assert_eq!(image.data(), tiff_data);
1231 }
1232
1233 #[test]
1234 fn test_image_from_jpeg_file() {
1235 let temp_dir = TempDir::new().unwrap();
1236 let file_path = temp_dir.path().join("test.jpg");
1237
1238 let jpeg_data = vec![
1240 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1252
1253 fs::write(&file_path, &jpeg_data).unwrap();
1254
1255 let image = Image::from_jpeg_file(&file_path).unwrap();
1256
1257 assert_eq!(image.width(), 100);
1258 assert_eq!(image.height(), 50);
1259 assert_eq!(image.format(), ImageFormat::Jpeg);
1260 assert_eq!(image.data(), jpeg_data);
1261 }
1262
1263 #[test]
1264 fn test_image_from_png_file() {
1265 let temp_dir = TempDir::new().unwrap();
1266 let file_path = temp_dir.path().join("test.png");
1267
1268 let mut png_data = Vec::new();
1270
1271 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1273
1274 png_data.extend_from_slice(&[
1276 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, ]);
1286 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]); let raw_data = vec![0x00, 0x00, 0x00, 0x00]; use flate2::write::ZlibEncoder;
1292 use flate2::Compression;
1293 use std::io::Write;
1294
1295 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1296 encoder.write_all(&raw_data).unwrap();
1297 let compressed_data = encoder.finish().unwrap();
1298
1299 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1300 png_data.extend_from_slice(b"IDAT");
1301 png_data.extend_from_slice(&compressed_data);
1302 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); png_data.extend_from_slice(&[
1306 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]);
1310
1311 fs::write(&file_path, &png_data).unwrap();
1312
1313 let image = Image::from_png_file(&file_path).unwrap();
1314
1315 assert_eq!(image.width(), 1);
1316 assert_eq!(image.height(), 1);
1317 assert_eq!(image.format(), ImageFormat::Png);
1318 assert_eq!(image.data(), png_data);
1319 }
1320
1321 #[test]
1322 fn test_image_from_tiff_file() {
1323 let temp_dir = TempDir::new().unwrap();
1324 let file_path = temp_dir.path().join("test.tiff");
1325
1326 let tiff_data = vec![
1328 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
1334 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
1336 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1338 0x00, 0x00, ];
1340
1341 fs::write(&file_path, &tiff_data).unwrap();
1342
1343 let image = Image::from_tiff_file(&file_path).unwrap();
1344
1345 assert_eq!(image.width(), 96);
1346 assert_eq!(image.height(), 96);
1347 assert_eq!(image.format(), ImageFormat::Tiff);
1348 assert_eq!(image.data(), tiff_data);
1349 }
1350
1351 #[test]
1352 fn test_image_to_pdf_object_jpeg() {
1353 let jpeg_data = vec![
1354 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1366
1367 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1368 let pdf_obj = image.to_pdf_object();
1369
1370 if let Object::Stream(dict, data) = pdf_obj {
1371 assert_eq!(
1372 dict.get("Type").unwrap(),
1373 &Object::Name("XObject".to_string())
1374 );
1375 assert_eq!(
1376 dict.get("Subtype").unwrap(),
1377 &Object::Name("Image".to_string())
1378 );
1379 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
1380 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
1381 assert_eq!(
1382 dict.get("ColorSpace").unwrap(),
1383 &Object::Name("DeviceRGB".to_string())
1384 );
1385 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1386 assert_eq!(
1387 dict.get("Filter").unwrap(),
1388 &Object::Name("DCTDecode".to_string())
1389 );
1390 assert_eq!(data, jpeg_data);
1391 } else {
1392 panic!("Expected Stream object");
1393 }
1394 }
1395
1396 #[test]
1397 fn test_image_to_pdf_object_png() {
1398 let mut png_data = Vec::new();
1400
1401 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1402 png_data.extend_from_slice(&[
1403 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
1404 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00,
1405 ]);
1406 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]);
1407
1408 let raw_data = vec![0x00, 0x00, 0x00, 0x00];
1409 use flate2::write::ZlibEncoder;
1410 use flate2::Compression;
1411 use std::io::Write;
1412
1413 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1414 encoder.write_all(&raw_data).unwrap();
1415 let compressed_data = encoder.finish().unwrap();
1416
1417 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1418 png_data.extend_from_slice(b"IDAT");
1419 png_data.extend_from_slice(&compressed_data);
1420 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
1421
1422 png_data.extend_from_slice(&[
1423 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
1424 ]);
1425
1426 let image = Image::from_png_data(png_data.clone()).unwrap();
1427 let pdf_obj = image.to_pdf_object();
1428
1429 if let Object::Stream(dict, data) = pdf_obj {
1430 assert_eq!(
1431 dict.get("Type").unwrap(),
1432 &Object::Name("XObject".to_string())
1433 );
1434 assert_eq!(
1435 dict.get("Subtype").unwrap(),
1436 &Object::Name("Image".to_string())
1437 );
1438 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(1));
1439 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(1));
1440 assert_eq!(
1441 dict.get("ColorSpace").unwrap(),
1442 &Object::Name("DeviceRGB".to_string())
1443 );
1444 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1445 assert_eq!(
1446 dict.get("Filter").unwrap(),
1447 &Object::Name("FlateDecode".to_string())
1448 );
1449 assert_eq!(data, png_data);
1450 } else {
1451 panic!("Expected Stream object");
1452 }
1453 }
1454
1455 #[test]
1456 fn test_image_to_pdf_object_tiff() {
1457 let tiff_data = vec![
1458 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
1464 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
1466 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1468 0x00, 0x00, ];
1470
1471 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
1472 let pdf_obj = image.to_pdf_object();
1473
1474 if let Object::Stream(dict, data) = pdf_obj {
1475 assert_eq!(
1476 dict.get("Type").unwrap(),
1477 &Object::Name("XObject".to_string())
1478 );
1479 assert_eq!(
1480 dict.get("Subtype").unwrap(),
1481 &Object::Name("Image".to_string())
1482 );
1483 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(64));
1484 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(64));
1485 assert_eq!(
1486 dict.get("ColorSpace").unwrap(),
1487 &Object::Name("DeviceGray".to_string())
1488 );
1489 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1490 assert_eq!(
1491 dict.get("Filter").unwrap(),
1492 &Object::Name("FlateDecode".to_string())
1493 );
1494 assert_eq!(data, tiff_data);
1495 } else {
1496 panic!("Expected Stream object");
1497 }
1498 }
1499
1500 #[test]
1501 fn test_image_clone() {
1502 let jpeg_data = vec![
1503 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1515
1516 let image1 = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1517 let image2 = image1.clone();
1518
1519 assert_eq!(image1.width(), image2.width());
1520 assert_eq!(image1.height(), image2.height());
1521 assert_eq!(image1.format(), image2.format());
1522 assert_eq!(image1.data(), image2.data());
1523 }
1524
1525 #[test]
1526 fn test_image_debug() {
1527 let jpeg_data = vec![
1528 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1540
1541 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1542 let debug_str = format!("{image:?}");
1543
1544 assert!(debug_str.contains("Image"));
1545 assert!(debug_str.contains("width"));
1546 assert!(debug_str.contains("height"));
1547 assert!(debug_str.contains("format"));
1548 }
1549
1550 #[test]
1551 fn test_jpeg_grayscale_image() {
1552 let jpeg_data = vec![
1553 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x01, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD9, ];
1564
1565 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1566 let pdf_obj = image.to_pdf_object();
1567
1568 if let Object::Stream(dict, _) = pdf_obj {
1569 assert_eq!(
1570 dict.get("ColorSpace").unwrap(),
1571 &Object::Name("DeviceGray".to_string())
1572 );
1573 } else {
1574 panic!("Expected Stream object");
1575 }
1576 }
1577
1578 #[test]
1579 fn test_jpeg_cmyk_image() {
1580 let jpeg_data = vec![
1581 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x04, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1593
1594 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1595 let pdf_obj = image.to_pdf_object();
1596
1597 if let Object::Stream(dict, _) = pdf_obj {
1598 assert_eq!(
1599 dict.get("ColorSpace").unwrap(),
1600 &Object::Name("DeviceCMYK".to_string())
1601 );
1602 } else {
1603 panic!("Expected Stream object");
1604 }
1605 }
1606
1607 #[test]
1608 fn test_png_grayscale_image() {
1609 let png_data = create_minimal_png(1, 1, 0); let image = Image::from_png_data(png_data).unwrap();
1612 let pdf_obj = image.to_pdf_object();
1613
1614 if let Object::Stream(dict, _) = pdf_obj {
1615 assert_eq!(
1616 dict.get("ColorSpace").unwrap(),
1617 &Object::Name("DeviceGray".to_string())
1618 );
1619 } else {
1620 panic!("Expected Stream object");
1621 }
1622 }
1623
1624 #[test]
1625 #[ignore = "Palette PNG not yet fully supported - see PNG_DECODER_ISSUES.md"]
1626 fn test_png_palette_image() {
1627 let png_data = vec![
1628 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, ];
1640
1641 let image = Image::from_png_data(png_data).unwrap();
1642 let pdf_obj = image.to_pdf_object();
1643
1644 if let Object::Stream(dict, _) = pdf_obj {
1645 assert_eq!(
1647 dict.get("ColorSpace").unwrap(),
1648 &Object::Name("DeviceRGB".to_string())
1649 );
1650 } else {
1651 panic!("Expected Stream object");
1652 }
1653 }
1654
1655 #[test]
1656 fn test_tiff_big_endian() {
1657 let tiff_data = vec![
1658 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1664 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1666 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00,
1668 0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
1670 0x00, 0x00, ];
1672
1673 let image = Image::from_tiff_data(tiff_data).unwrap();
1674
1675 assert_eq!(image.width(), 128);
1676 assert_eq!(image.height(), 128);
1677 assert_eq!(image.format(), ImageFormat::Tiff);
1678 }
1679
1680 #[test]
1681 fn test_tiff_cmyk_image() {
1682 let tiff_data = vec![
1683 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1689 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1691 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1693 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
1695 0x00, 0x00, ];
1697
1698 let image = Image::from_tiff_data(tiff_data).unwrap();
1699 let pdf_obj = image.to_pdf_object();
1700
1701 if let Object::Stream(dict, _) = pdf_obj {
1702 assert_eq!(
1703 dict.get("ColorSpace").unwrap(),
1704 &Object::Name("DeviceCMYK".to_string())
1705 );
1706 } else {
1707 panic!("Expected Stream object");
1708 }
1709 }
1710
1711 #[test]
1712 fn test_error_invalid_jpeg() {
1713 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_jpeg_data(invalid_data);
1715 assert!(result.is_err());
1716 }
1717
1718 #[test]
1719 fn test_error_invalid_png() {
1720 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_png_data(invalid_data);
1722 assert!(result.is_err());
1723 }
1724
1725 #[test]
1726 fn test_error_invalid_tiff() {
1727 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_tiff_data(invalid_data);
1729 assert!(result.is_err());
1730 }
1731
1732 #[test]
1733 fn test_error_truncated_jpeg() {
1734 let truncated_data = vec![0xFF, 0xD8, 0xFF]; let result = Image::from_jpeg_data(truncated_data);
1736 assert!(result.is_err());
1737 }
1738
1739 #[test]
1740 fn test_error_truncated_png() {
1741 let truncated_data = vec![0x89, 0x50, 0x4E, 0x47]; let result = Image::from_png_data(truncated_data);
1743 assert!(result.is_err());
1744 }
1745
1746 #[test]
1747 fn test_error_truncated_tiff() {
1748 let truncated_data = vec![0x49, 0x49, 0x2A]; let result = Image::from_tiff_data(truncated_data);
1750 assert!(result.is_err());
1751 }
1752
1753 #[test]
1754 fn test_error_jpeg_unsupported_components() {
1755 let invalid_jpeg = vec![
1756 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x05, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1768
1769 let result = Image::from_jpeg_data(invalid_jpeg);
1770 assert!(result.is_err());
1771 }
1772
1773 #[test]
1774 fn test_error_png_unsupported_color_type() {
1775 let invalid_png = vec![
1776 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, ];
1788
1789 let result = Image::from_png_data(invalid_png);
1790 assert!(result.is_err());
1791 }
1792
1793 #[test]
1794 fn test_error_nonexistent_file() {
1795 let result = Image::from_jpeg_file("/nonexistent/path/image.jpg");
1796 assert!(result.is_err());
1797
1798 let result = Image::from_png_file("/nonexistent/path/image.png");
1799 assert!(result.is_err());
1800
1801 let result = Image::from_tiff_file("/nonexistent/path/image.tiff");
1802 assert!(result.is_err());
1803 }
1804
1805 #[test]
1806 fn test_jpeg_no_dimensions() {
1807 let jpeg_no_dims = vec![
1808 0xFF, 0xD8, 0xFF, 0xD9, ];
1811
1812 let result = Image::from_jpeg_data(jpeg_no_dims);
1813 assert!(result.is_err());
1814 }
1815
1816 #[test]
1817 fn test_png_no_ihdr() {
1818 let png_no_ihdr = vec![
1819 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,
1823 0x72, 0x6E, 0x38, ];
1825
1826 let result = Image::from_png_data(png_no_ihdr);
1827 assert!(result.is_err());
1828 }
1829
1830 #[test]
1831 fn test_tiff_no_dimensions() {
1832 let tiff_no_dims = vec![
1833 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1839 0x00, 0x00, ];
1841
1842 let result = Image::from_tiff_data(tiff_no_dims);
1843 assert!(result.is_err());
1844 }
1845
1846 fn png_crc32(data: &[u8]) -> u32 {
1848 let mut crc = 0xFFFFFFFF_u32;
1850 for &byte in data {
1851 crc ^= byte as u32;
1852 for _ in 0..8 {
1853 if crc & 1 != 0 {
1854 crc = (crc >> 1) ^ 0xEDB88320;
1855 } else {
1856 crc >>= 1;
1857 }
1858 }
1859 }
1860 !crc
1861 }
1862
1863 fn create_valid_png_data(
1865 width: u32,
1866 height: u32,
1867 bit_depth: u8,
1868 color_type: u8,
1869 ) -> Vec<u8> {
1870 use flate2::write::ZlibEncoder;
1871 use flate2::Compression;
1872 use std::io::Write;
1873
1874 let mut png_data = Vec::new();
1875
1876 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1878
1879 let mut ihdr_data = Vec::new();
1881 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();
1891 ihdr_crc_data.extend_from_slice(b"IHDR");
1892 ihdr_crc_data.extend_from_slice(&ihdr_data);
1893 let ihdr_crc = png_crc32(&ihdr_crc_data);
1894
1895 png_data.extend_from_slice(&(ihdr_data.len() as u32).to_be_bytes());
1897 png_data.extend_from_slice(b"IHDR");
1898 png_data.extend_from_slice(&ihdr_data);
1899 png_data.extend_from_slice(&ihdr_crc.to_be_bytes());
1900
1901 let bytes_per_pixel = match (color_type, bit_depth) {
1903 (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,
1909 };
1910
1911 let mut raw_data = Vec::new();
1912 for _y in 0..height {
1913 raw_data.push(0); for _x in 0..width {
1915 for _c in 0..bytes_per_pixel {
1916 raw_data.push(0); }
1918 }
1919 }
1920
1921 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1923 encoder.write_all(&raw_data).unwrap();
1924 let compressed_data = encoder.finish().unwrap();
1925
1926 let mut idat_crc_data = Vec::new();
1928 idat_crc_data.extend_from_slice(b"IDAT");
1929 idat_crc_data.extend_from_slice(&compressed_data);
1930 let idat_crc = png_crc32(&idat_crc_data);
1931
1932 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1934 png_data.extend_from_slice(b"IDAT");
1935 png_data.extend_from_slice(&compressed_data);
1936 png_data.extend_from_slice(&idat_crc.to_be_bytes());
1937
1938 png_data.extend_from_slice(&[0, 0, 0, 0]); png_data.extend_from_slice(b"IEND");
1941 png_data.extend_from_slice(&[0xAE, 0x42, 0x60, 0x82]); png_data
1944 }
1945
1946 #[test]
1947 fn test_different_bit_depths() {
1948 let png_8bit = create_valid_png_data(2, 2, 8, 2); let image_8bit = Image::from_png_data(png_8bit).unwrap();
1953 let pdf_obj_8bit = image_8bit.to_pdf_object();
1954
1955 if let Object::Stream(dict, _) = pdf_obj_8bit {
1956 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1957 } else {
1958 panic!("Expected Stream object");
1959 }
1960
1961 let png_16bit = create_valid_png_data(2, 2, 16, 2); let image_16bit = Image::from_png_data(png_16bit).unwrap();
1964 let pdf_obj_16bit = image_16bit.to_pdf_object();
1965
1966 if let Object::Stream(dict, _) = pdf_obj_16bit {
1967 let bits = dict.get("BitsPerComponent").unwrap();
1969 assert!(matches!(bits, &Object::Integer(8) | &Object::Integer(16)));
1970 } else {
1971 panic!("Expected Stream object");
1972 }
1973 }
1974
1975 #[test]
1976 fn test_performance_large_image_data() {
1977 let mut large_jpeg = vec![
1979 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x04, 0x00, 0x04, 0x00, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, ];
1990
1991 large_jpeg.extend(vec![0x00; 10000]);
1993 large_jpeg.extend(vec![0xFF, 0xD9]); let start = std::time::Instant::now();
1996 let image = Image::from_jpeg_data(large_jpeg.clone()).unwrap();
1997 let duration = start.elapsed();
1998
1999 assert_eq!(image.width(), 1024);
2000 assert_eq!(image.height(), 1024);
2001 assert_eq!(image.data().len(), large_jpeg.len());
2002 assert!(duration.as_millis() < 100); }
2004
2005 #[test]
2006 fn test_memory_efficiency() {
2007 let jpeg_data = vec![
2008 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
2020
2021 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
2022
2023 assert_eq!(image.data().len(), jpeg_data.len());
2025 assert_eq!(image.data(), jpeg_data);
2026
2027 let cloned = image.clone();
2029 assert_eq!(cloned.data(), image.data());
2030 }
2031
2032 #[test]
2033 fn test_complete_workflow() {
2034 let test_cases = vec![
2036 (ImageFormat::Jpeg, "DCTDecode", "DeviceRGB"),
2037 (ImageFormat::Png, "FlateDecode", "DeviceRGB"),
2038 (ImageFormat::Tiff, "FlateDecode", "DeviceGray"),
2039 ];
2040
2041 for (expected_format, expected_filter, expected_color_space) in test_cases {
2042 let data = match expected_format {
2043 ImageFormat::Jpeg => vec![
2044 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ],
2056 ImageFormat::Png => create_valid_png_data(2, 2, 8, 2), ImageFormat::Tiff => vec![
2058 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00,
2064 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
2066 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
2068 0x00, 0x00, 0x00, 0x00, ],
2070 ImageFormat::Raw => Vec::new(), };
2072
2073 let image = match expected_format {
2074 ImageFormat::Jpeg => Image::from_jpeg_data(data.clone()).unwrap(),
2075 ImageFormat::Png => Image::from_png_data(data.clone()).unwrap(),
2076 ImageFormat::Tiff => Image::from_tiff_data(data.clone()).unwrap(),
2077 ImageFormat::Raw => continue, };
2079
2080 assert_eq!(image.format(), expected_format);
2082 if expected_format == ImageFormat::Png {
2084 assert_eq!(image.width(), 2);
2085 assert_eq!(image.height(), 2);
2086 } else if expected_format == ImageFormat::Jpeg {
2087 assert_eq!(image.width(), 200);
2088 assert_eq!(image.height(), 100);
2089 } else if expected_format == ImageFormat::Tiff {
2090 assert_eq!(image.width(), 200);
2091 assert_eq!(image.height(), 100);
2092 }
2093 assert_eq!(image.data(), data);
2094
2095 let pdf_obj = image.to_pdf_object();
2097 if let Object::Stream(dict, stream_data) = pdf_obj {
2098 assert_eq!(
2099 dict.get("Type").unwrap(),
2100 &Object::Name("XObject".to_string())
2101 );
2102 assert_eq!(
2103 dict.get("Subtype").unwrap(),
2104 &Object::Name("Image".to_string())
2105 );
2106 if expected_format == ImageFormat::Png {
2108 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(2));
2109 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(2));
2110 } else {
2111 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
2112 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
2113 }
2114 assert_eq!(
2115 dict.get("ColorSpace").unwrap(),
2116 &Object::Name(expected_color_space.to_string())
2117 );
2118 assert_eq!(
2119 dict.get("Filter").unwrap(),
2120 &Object::Name(expected_filter.to_string())
2121 );
2122 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
2123 assert_eq!(stream_data, data);
2124 } else {
2125 panic!("Expected Stream object for format {expected_format:?}");
2126 }
2127 }
2128 }
2129 }
2130}