oxidize_pdf/graphics/
image.rs

1//! Image support for PDF generation
2//!
3//! Currently supports:
4//! - JPEG images
5
6use crate::objects::{Dictionary, Object};
7use crate::{PdfError, Result};
8use std::fs::File;
9use std::io::Read;
10use std::path::Path;
11
12/// Represents an image that can be embedded in a PDF
13#[derive(Debug, Clone)]
14pub struct Image {
15    /// Image data
16    data: Vec<u8>,
17    /// Image format
18    format: ImageFormat,
19    /// Width in pixels
20    width: u32,
21    /// Height in pixels
22    height: u32,
23    /// Color space
24    color_space: ColorSpace,
25    /// Bits per component
26    bits_per_component: u8,
27}
28
29/// Supported image formats
30#[derive(Debug, Clone, Copy, PartialEq)]
31pub enum ImageFormat {
32    /// JPEG format
33    Jpeg,
34    /// PNG format
35    Png,
36    /// TIFF format
37    Tiff,
38}
39
40/// Color spaces for images
41#[derive(Debug, Clone, Copy, PartialEq)]
42pub enum ColorSpace {
43    /// Grayscale
44    DeviceGray,
45    /// RGB color
46    DeviceRGB,
47    /// CMYK color
48    DeviceCMYK,
49}
50
51impl Image {
52    /// Load a JPEG image from a file
53    pub fn from_jpeg_file<P: AsRef<Path>>(path: P) -> Result<Self> {
54        let mut file = File::open(path)?;
55        let mut data = Vec::new();
56        file.read_to_end(&mut data)?;
57        Self::from_jpeg_data(data)
58    }
59
60    /// Create an image from JPEG data
61    pub fn from_jpeg_data(data: Vec<u8>) -> Result<Self> {
62        // Parse JPEG header to get dimensions and color info
63        let (width, height, color_space, bits_per_component) = parse_jpeg_header(&data)?;
64
65        Ok(Image {
66            data,
67            format: ImageFormat::Jpeg,
68            width,
69            height,
70            color_space,
71            bits_per_component,
72        })
73    }
74
75    /// Load a PNG image from a file
76    pub fn from_png_file<P: AsRef<Path>>(path: P) -> Result<Self> {
77        let mut file = File::open(path)?;
78        let mut data = Vec::new();
79        file.read_to_end(&mut data)?;
80        Self::from_png_data(data)
81    }
82
83    /// Create an image from PNG data
84    pub fn from_png_data(data: Vec<u8>) -> Result<Self> {
85        // Parse PNG header to get dimensions and color info
86        let (width, height, color_space, bits_per_component) = parse_png_header(&data)?;
87
88        Ok(Image {
89            data,
90            format: ImageFormat::Png,
91            width,
92            height,
93            color_space,
94            bits_per_component,
95        })
96    }
97
98    /// Load a TIFF image from a file
99    pub fn from_tiff_file<P: AsRef<Path>>(path: P) -> Result<Self> {
100        let mut file = File::open(path)?;
101        let mut data = Vec::new();
102        file.read_to_end(&mut data)?;
103        Self::from_tiff_data(data)
104    }
105
106    /// Create an image from TIFF data
107    pub fn from_tiff_data(data: Vec<u8>) -> Result<Self> {
108        // Parse TIFF header to get dimensions and color info
109        let (width, height, color_space, bits_per_component) = parse_tiff_header(&data)?;
110
111        Ok(Image {
112            data,
113            format: ImageFormat::Tiff,
114            width,
115            height,
116            color_space,
117            bits_per_component,
118        })
119    }
120
121    /// Get image width in pixels
122    pub fn width(&self) -> u32 {
123        self.width
124    }
125
126    /// Get image height in pixels
127    pub fn height(&self) -> u32 {
128        self.height
129    }
130
131    /// Get image data
132    pub fn data(&self) -> &[u8] {
133        &self.data
134    }
135
136    /// Get image format
137    pub fn format(&self) -> ImageFormat {
138        self.format
139    }
140
141    /// Convert to PDF XObject
142    pub fn to_pdf_object(&self) -> Object {
143        let mut dict = Dictionary::new();
144
145        // Required entries for image XObject
146        dict.set("Type", Object::Name("XObject".to_string()));
147        dict.set("Subtype", Object::Name("Image".to_string()));
148        dict.set("Width", Object::Integer(self.width as i64));
149        dict.set("Height", Object::Integer(self.height as i64));
150
151        // Color space
152        let color_space_name = match self.color_space {
153            ColorSpace::DeviceGray => "DeviceGray",
154            ColorSpace::DeviceRGB => "DeviceRGB",
155            ColorSpace::DeviceCMYK => "DeviceCMYK",
156        };
157        dict.set("ColorSpace", Object::Name(color_space_name.to_string()));
158
159        // Bits per component
160        dict.set(
161            "BitsPerComponent",
162            Object::Integer(self.bits_per_component as i64),
163        );
164
165        // Filter based on image format
166        match self.format {
167            ImageFormat::Jpeg => {
168                dict.set("Filter", Object::Name("DCTDecode".to_string()));
169            }
170            ImageFormat::Png => {
171                dict.set("Filter", Object::Name("FlateDecode".to_string()));
172            }
173            ImageFormat::Tiff => {
174                // TIFF can use various filters, but commonly LZW or FlateDecode
175                dict.set("Filter", Object::Name("FlateDecode".to_string()));
176            }
177        }
178
179        // Create stream with image data
180        Object::Stream(dict, self.data.clone())
181    }
182}
183
184/// Parse JPEG header to extract image information
185fn parse_jpeg_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
186    if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
187        return Err(PdfError::InvalidImage("Not a valid JPEG file".to_string()));
188    }
189
190    let mut pos = 2;
191    let mut width = 0;
192    let mut height = 0;
193    let mut components = 0;
194
195    while pos < data.len() - 1 {
196        if data[pos] != 0xFF {
197            return Err(PdfError::InvalidImage("Invalid JPEG marker".to_string()));
198        }
199
200        let marker = data[pos + 1];
201        pos += 2;
202
203        // Skip padding bytes
204        if marker == 0xFF {
205            continue;
206        }
207
208        // Check for SOF markers (Start of Frame)
209        if (0xC0..=0xCF).contains(&marker) && marker != 0xC4 && marker != 0xC8 && marker != 0xCC {
210            // This is a SOF marker
211            if pos + 7 >= data.len() {
212                return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
213            }
214
215            // Skip length
216            pos += 2;
217
218            // Skip precision
219            pos += 1;
220
221            // Read height and width
222            height = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
223            pos += 2;
224            width = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
225            pos += 2;
226
227            // Read number of components
228            components = data[pos];
229            break;
230        } else if marker == 0xD9 {
231            // End of image
232            break;
233        } else if marker == 0xD8 || (0xD0..=0xD7).contains(&marker) {
234            // No length field for these markers
235            continue;
236        } else {
237            // Read length and skip segment
238            if pos + 1 >= data.len() {
239                return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
240            }
241            let length = ((data[pos] as usize) << 8) | (data[pos + 1] as usize);
242            pos += length;
243        }
244    }
245
246    if width == 0 || height == 0 {
247        return Err(PdfError::InvalidImage(
248            "Could not find image dimensions".to_string(),
249        ));
250    }
251
252    let color_space = match components {
253        1 => ColorSpace::DeviceGray,
254        3 => ColorSpace::DeviceRGB,
255        4 => ColorSpace::DeviceCMYK,
256        _ => {
257            return Err(PdfError::InvalidImage(format!(
258                "Unsupported number of components: {components}"
259            )))
260        }
261    };
262
263    Ok((width, height, color_space, 8)) // JPEG typically uses 8 bits per component
264}
265
266/// Parse PNG header to extract image information
267fn parse_png_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
268    // PNG signature: 8 bytes
269    if data.len() < 8 || &data[0..8] != b"\x89PNG\r\n\x1a\n" {
270        return Err(PdfError::InvalidImage("Not a valid PNG file".to_string()));
271    }
272
273    // Find IHDR chunk (should be first chunk after signature)
274    let mut pos = 8;
275
276    while pos + 8 < data.len() {
277        // Read chunk length (4 bytes, big-endian)
278        let chunk_length =
279            u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
280
281        // Read chunk type (4 bytes)
282        let chunk_type = &data[pos + 4..pos + 8];
283
284        if chunk_type == b"IHDR" {
285            // IHDR chunk found
286            if pos + 8 + chunk_length > data.len() || chunk_length < 13 {
287                return Err(PdfError::InvalidImage("Invalid PNG IHDR chunk".to_string()));
288            }
289
290            let ihdr_data = &data[pos + 8..pos + 8 + chunk_length];
291
292            // Parse IHDR data
293            let width =
294                u32::from_be_bytes([ihdr_data[0], ihdr_data[1], ihdr_data[2], ihdr_data[3]]);
295
296            let height =
297                u32::from_be_bytes([ihdr_data[4], ihdr_data[5], ihdr_data[6], ihdr_data[7]]);
298
299            let bit_depth = ihdr_data[8];
300            let color_type = ihdr_data[9];
301
302            // Map PNG color types to PDF color spaces
303            let color_space = match color_type {
304                0 => ColorSpace::DeviceGray, // Grayscale
305                2 => ColorSpace::DeviceRGB,  // RGB
306                3 => ColorSpace::DeviceRGB,  // Palette (treated as RGB)
307                4 => ColorSpace::DeviceGray, // Grayscale + Alpha
308                6 => ColorSpace::DeviceRGB,  // RGB + Alpha
309                _ => {
310                    return Err(PdfError::InvalidImage(format!(
311                        "Unsupported PNG color type: {color_type}"
312                    )))
313                }
314            };
315
316            return Ok((width, height, color_space, bit_depth));
317        }
318
319        // Skip to next chunk
320        pos += 8 + chunk_length + 4; // header + data + CRC
321    }
322
323    Err(PdfError::InvalidImage(
324        "PNG IHDR chunk not found".to_string(),
325    ))
326}
327
328/// Parse TIFF header to extract image information
329fn parse_tiff_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
330    if data.len() < 8 {
331        return Err(PdfError::InvalidImage(
332            "Invalid TIFF file: too short".to_string(),
333        ));
334    }
335
336    // Check byte order (first 2 bytes)
337    let (is_little_endian, offset) = if &data[0..2] == b"II" {
338        (true, 2) // Little endian
339    } else if &data[0..2] == b"MM" {
340        (false, 2) // Big endian
341    } else {
342        return Err(PdfError::InvalidImage(
343            "Invalid TIFF byte order".to_string(),
344        ));
345    };
346
347    // Check magic number (should be 42)
348    let magic = if is_little_endian {
349        u16::from_le_bytes([data[offset], data[offset + 1]])
350    } else {
351        u16::from_be_bytes([data[offset], data[offset + 1]])
352    };
353
354    if magic != 42 {
355        return Err(PdfError::InvalidImage(
356            "Invalid TIFF magic number".to_string(),
357        ));
358    }
359
360    // Get offset to first IFD (Image File Directory)
361    let ifd_offset = if is_little_endian {
362        u32::from_le_bytes([
363            data[offset + 2],
364            data[offset + 3],
365            data[offset + 4],
366            data[offset + 5],
367        ])
368    } else {
369        u32::from_be_bytes([
370            data[offset + 2],
371            data[offset + 3],
372            data[offset + 4],
373            data[offset + 5],
374        ])
375    } as usize;
376
377    if ifd_offset + 2 > data.len() {
378        return Err(PdfError::InvalidImage(
379            "Invalid TIFF IFD offset".to_string(),
380        ));
381    }
382
383    // Read number of directory entries
384    let num_entries = if is_little_endian {
385        u16::from_le_bytes([data[ifd_offset], data[ifd_offset + 1]])
386    } else {
387        u16::from_be_bytes([data[ifd_offset], data[ifd_offset + 1]])
388    };
389
390    let mut width = 0u32;
391    let mut height = 0u32;
392    let mut bits_per_sample = 8u16;
393    let mut photometric_interpretation = 0u16;
394
395    // Read directory entries
396    for i in 0..num_entries {
397        let entry_offset = ifd_offset + 2 + (i as usize * 12);
398
399        if entry_offset + 12 > data.len() {
400            break;
401        }
402
403        let tag = if is_little_endian {
404            u16::from_le_bytes([data[entry_offset], data[entry_offset + 1]])
405        } else {
406            u16::from_be_bytes([data[entry_offset], data[entry_offset + 1]])
407        };
408
409        let value_offset = entry_offset + 8;
410
411        match tag {
412            256 => {
413                // ImageWidth
414                width = if is_little_endian {
415                    u32::from_le_bytes([
416                        data[value_offset],
417                        data[value_offset + 1],
418                        data[value_offset + 2],
419                        data[value_offset + 3],
420                    ])
421                } else {
422                    u32::from_be_bytes([
423                        data[value_offset],
424                        data[value_offset + 1],
425                        data[value_offset + 2],
426                        data[value_offset + 3],
427                    ])
428                };
429            }
430            257 => {
431                // ImageHeight
432                height = if is_little_endian {
433                    u32::from_le_bytes([
434                        data[value_offset],
435                        data[value_offset + 1],
436                        data[value_offset + 2],
437                        data[value_offset + 3],
438                    ])
439                } else {
440                    u32::from_be_bytes([
441                        data[value_offset],
442                        data[value_offset + 1],
443                        data[value_offset + 2],
444                        data[value_offset + 3],
445                    ])
446                };
447            }
448            258 => {
449                // BitsPerSample
450                bits_per_sample = if is_little_endian {
451                    u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
452                } else {
453                    u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
454                };
455            }
456            262 => {
457                // PhotometricInterpretation
458                photometric_interpretation = if is_little_endian {
459                    u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
460                } else {
461                    u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
462                };
463            }
464            _ => {} // Skip unknown tags
465        }
466    }
467
468    if width == 0 || height == 0 {
469        return Err(PdfError::InvalidImage(
470            "TIFF dimensions not found".to_string(),
471        ));
472    }
473
474    // Map TIFF photometric interpretation to PDF color space
475    let color_space = match photometric_interpretation {
476        0 | 1 => ColorSpace::DeviceGray, // White is zero | Black is zero
477        2 => ColorSpace::DeviceRGB,      // RGB
478        5 => ColorSpace::DeviceCMYK,     // CMYK
479        _ => ColorSpace::DeviceRGB,      // Default to RGB
480    };
481
482    Ok((width, height, color_space, bits_per_sample as u8))
483}
484
485#[cfg(test)]
486mod tests {
487    use super::*;
488
489    #[test]
490    fn test_parse_jpeg_header() {
491        // Minimal JPEG header for testing
492        let jpeg_data = vec![
493            0xFF, 0xD8, // SOI marker
494            0xFF, 0xC0, // SOF0 marker
495            0x00, 0x11, // Length (17 bytes)
496            0x08, // Precision (8 bits)
497            0x00, 0x64, // Height (100)
498            0x00, 0xC8, // Width (200)
499            0x03, // Components (3 = RGB)
500                  // ... rest of data
501        ];
502
503        let result = parse_jpeg_header(&jpeg_data);
504        assert!(result.is_ok());
505        let (width, height, color_space, bits) = result.unwrap();
506        assert_eq!(width, 200);
507        assert_eq!(height, 100);
508        assert_eq!(color_space, ColorSpace::DeviceRGB);
509        assert_eq!(bits, 8);
510    }
511
512    #[test]
513    fn test_invalid_jpeg() {
514        let invalid_data = vec![0x00, 0x00];
515        let result = parse_jpeg_header(&invalid_data);
516        assert!(result.is_err());
517    }
518
519    #[test]
520    fn test_parse_png_header() {
521        // Minimal PNG header for testing
522        let mut png_data = vec![
523            0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature
524            0x00, 0x00, 0x00, 0x0D, // IHDR chunk length (13)
525            0x49, 0x48, 0x44, 0x52, // IHDR chunk type
526            0x00, 0x00, 0x00, 0x64, // Width (100)
527            0x00, 0x00, 0x00, 0x64, // Height (100)
528            0x08, // Bit depth (8)
529            0x02, // Color type (2 = RGB)
530            0x00, // Compression method
531            0x00, // Filter method
532            0x00, // Interlace method
533        ];
534
535        // Add CRC (simplified - just 4 bytes)
536        png_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
537
538        let result = parse_png_header(&png_data);
539        assert!(result.is_ok());
540        let (width, height, color_space, bits) = result.unwrap();
541        assert_eq!(width, 100);
542        assert_eq!(height, 100);
543        assert_eq!(color_space, ColorSpace::DeviceRGB);
544        assert_eq!(bits, 8);
545    }
546
547    #[test]
548    fn test_invalid_png() {
549        let invalid_data = vec![0x00, 0x00];
550        let result = parse_png_header(&invalid_data);
551        assert!(result.is_err());
552    }
553
554    #[test]
555    fn test_parse_tiff_header_little_endian() {
556        // Minimal TIFF header for testing (little endian)
557        let tiff_data = vec![
558            0x49, 0x49, // Little endian byte order
559            0x2A, 0x00, // Magic number (42)
560            0x08, 0x00, 0x00, 0x00, // Offset to first IFD
561            0x03, 0x00, // Number of directory entries
562            // ImageWidth tag (256)
563            0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
564            // ImageHeight tag (257)
565            0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
566            // BitsPerSample tag (258)
567            0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
568            0x00, 0x00, // Next IFD offset (0 = none)
569        ];
570
571        let result = parse_tiff_header(&tiff_data);
572        assert!(result.is_ok());
573        let (width, height, color_space, bits) = result.unwrap();
574        assert_eq!(width, 100);
575        assert_eq!(height, 100);
576        assert_eq!(color_space, ColorSpace::DeviceGray);
577        assert_eq!(bits, 8);
578    }
579
580    #[test]
581    fn test_parse_tiff_header_big_endian() {
582        // Minimal TIFF header for testing (big endian)
583        let tiff_data = vec![
584            0x4D, 0x4D, // Big endian byte order
585            0x00, 0x2A, // Magic number (42)
586            0x00, 0x00, 0x00, 0x08, // Offset to first IFD
587            0x00, 0x03, // Number of directory entries
588            // ImageWidth tag (256)
589            0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
590            // ImageHeight tag (257)
591            0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
592            // BitsPerSample tag (258)
593            0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
594            0x00, 0x00, // Next IFD offset (0 = none)
595        ];
596
597        let result = parse_tiff_header(&tiff_data);
598        assert!(result.is_ok());
599        let (width, height, color_space, bits) = result.unwrap();
600        assert_eq!(width, 100);
601        assert_eq!(height, 100);
602        assert_eq!(color_space, ColorSpace::DeviceGray);
603        assert_eq!(bits, 8);
604    }
605
606    #[test]
607    fn test_invalid_tiff() {
608        let invalid_data = vec![0x00, 0x00];
609        let result = parse_tiff_header(&invalid_data);
610        assert!(result.is_err());
611    }
612
613    #[test]
614    fn test_image_format_enum() {
615        assert_eq!(ImageFormat::Jpeg, ImageFormat::Jpeg);
616        assert_eq!(ImageFormat::Png, ImageFormat::Png);
617        assert_eq!(ImageFormat::Tiff, ImageFormat::Tiff);
618        assert_ne!(ImageFormat::Jpeg, ImageFormat::Png);
619    }
620
621    // Comprehensive tests for all image types and their methods
622    mod comprehensive_tests {
623        use super::*;
624        use std::fs;
625        use tempfile::TempDir;
626
627        #[test]
628        fn test_image_format_variants() {
629            // Test all ImageFormat variants
630            let jpeg = ImageFormat::Jpeg;
631            let png = ImageFormat::Png;
632            let tiff = ImageFormat::Tiff;
633            
634            assert_eq!(jpeg, ImageFormat::Jpeg);
635            assert_eq!(png, ImageFormat::Png);
636            assert_eq!(tiff, ImageFormat::Tiff);
637            
638            assert_ne!(jpeg, png);
639            assert_ne!(png, tiff);
640            assert_ne!(tiff, jpeg);
641        }
642
643        #[test]
644        fn test_image_format_debug() {
645            let jpeg = ImageFormat::Jpeg;
646            let png = ImageFormat::Png;
647            let tiff = ImageFormat::Tiff;
648            
649            assert_eq!(format!("{:?}", jpeg), "Jpeg");
650            assert_eq!(format!("{:?}", png), "Png");
651            assert_eq!(format!("{:?}", tiff), "Tiff");
652        }
653
654        #[test]
655        fn test_image_format_clone_copy() {
656            let jpeg = ImageFormat::Jpeg;
657            let jpeg_clone = jpeg.clone();
658            let jpeg_copy = jpeg;
659            
660            assert_eq!(jpeg_clone, ImageFormat::Jpeg);
661            assert_eq!(jpeg_copy, ImageFormat::Jpeg);
662        }
663
664        #[test]
665        fn test_color_space_variants() {
666            // Test all ColorSpace variants
667            let gray = ColorSpace::DeviceGray;
668            let rgb = ColorSpace::DeviceRGB;
669            let cmyk = ColorSpace::DeviceCMYK;
670            
671            assert_eq!(gray, ColorSpace::DeviceGray);
672            assert_eq!(rgb, ColorSpace::DeviceRGB);
673            assert_eq!(cmyk, ColorSpace::DeviceCMYK);
674            
675            assert_ne!(gray, rgb);
676            assert_ne!(rgb, cmyk);
677            assert_ne!(cmyk, gray);
678        }
679
680        #[test]
681        fn test_color_space_debug() {
682            let gray = ColorSpace::DeviceGray;
683            let rgb = ColorSpace::DeviceRGB;
684            let cmyk = ColorSpace::DeviceCMYK;
685            
686            assert_eq!(format!("{:?}", gray), "DeviceGray");
687            assert_eq!(format!("{:?}", rgb), "DeviceRGB");
688            assert_eq!(format!("{:?}", cmyk), "DeviceCMYK");
689        }
690
691        #[test]
692        fn test_color_space_clone_copy() {
693            let rgb = ColorSpace::DeviceRGB;
694            let rgb_clone = rgb.clone();
695            let rgb_copy = rgb;
696            
697            assert_eq!(rgb_clone, ColorSpace::DeviceRGB);
698            assert_eq!(rgb_copy, ColorSpace::DeviceRGB);
699        }
700
701        #[test]
702        fn test_image_from_jpeg_data() {
703            // Create a minimal valid JPEG with SOF0 header
704            let jpeg_data = vec![
705                0xFF, 0xD8, // SOI marker
706                0xFF, 0xC0, // SOF0 marker
707                0x00, 0x11, // Length (17 bytes)
708                0x08, // Precision (8 bits)
709                0x00, 0x64, // Height (100)
710                0x00, 0xC8, // Width (200)
711                0x03, // Components (3 = RGB)
712                0x01, 0x11, 0x00, // Component 1
713                0x02, 0x11, 0x01, // Component 2
714                0x03, 0x11, 0x01, // Component 3
715                0xFF, 0xD9, // EOI marker
716            ];
717
718            let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
719            
720            assert_eq!(image.width(), 200);
721            assert_eq!(image.height(), 100);
722            assert_eq!(image.format(), ImageFormat::Jpeg);
723            assert_eq!(image.data(), jpeg_data);
724        }
725
726        #[test]
727        fn test_image_from_png_data() {
728            // Create a minimal valid PNG with IHDR chunk
729            let png_data = vec![
730                0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature
731                0x00, 0x00, 0x00, 0x0D, // IHDR chunk length (13)
732                0x49, 0x48, 0x44, 0x52, // IHDR chunk type
733                0x00, 0x00, 0x01, 0x00, // Width (256)
734                0x00, 0x00, 0x01, 0x00, // Height (256)
735                0x08, // Bit depth (8)
736                0x02, // Color type (2 = RGB)
737                0x00, // Compression method
738                0x00, // Filter method
739                0x00, // Interlace method
740                0x5C, 0x72, 0x6E, 0x38, // CRC
741            ];
742
743            let image = Image::from_png_data(png_data.clone()).unwrap();
744            
745            assert_eq!(image.width(), 256);
746            assert_eq!(image.height(), 256);
747            assert_eq!(image.format(), ImageFormat::Png);
748            assert_eq!(image.data(), png_data);
749        }
750
751        #[test]
752        fn test_image_from_tiff_data() {
753            // Create a minimal valid TIFF (little endian)
754            let tiff_data = vec![
755                0x49, 0x49, // Little endian byte order
756                0x2A, 0x00, // Magic number (42)
757                0x08, 0x00, 0x00, 0x00, // Offset to first IFD
758                0x04, 0x00, // Number of directory entries
759                // ImageWidth tag (256)
760                0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
761                // ImageHeight tag (257)
762                0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
763                // BitsPerSample tag (258)
764                0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
765                // PhotometricInterpretation tag (262)
766                0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
767                0x00, 0x00, 0x00, 0x00, // Next IFD offset (0 = none)
768            ];
769
770            let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
771            
772            assert_eq!(image.width(), 128);
773            assert_eq!(image.height(), 128);
774            assert_eq!(image.format(), ImageFormat::Tiff);
775            assert_eq!(image.data(), tiff_data);
776        }
777
778        #[test]
779        fn test_image_from_jpeg_file() {
780            let temp_dir = TempDir::new().unwrap();
781            let file_path = temp_dir.path().join("test.jpg");
782            
783            // Create a minimal valid JPEG file
784            let jpeg_data = vec![
785                0xFF, 0xD8, // SOI marker
786                0xFF, 0xC0, // SOF0 marker
787                0x00, 0x11, // Length (17 bytes)
788                0x08, // Precision (8 bits)
789                0x00, 0x32, // Height (50)
790                0x00, 0x64, // Width (100)
791                0x03, // Components (3 = RGB)
792                0x01, 0x11, 0x00, // Component 1
793                0x02, 0x11, 0x01, // Component 2
794                0x03, 0x11, 0x01, // Component 3
795                0xFF, 0xD9, // EOI marker
796            ];
797            
798            fs::write(&file_path, &jpeg_data).unwrap();
799            
800            let image = Image::from_jpeg_file(&file_path).unwrap();
801            
802            assert_eq!(image.width(), 100);
803            assert_eq!(image.height(), 50);
804            assert_eq!(image.format(), ImageFormat::Jpeg);
805            assert_eq!(image.data(), jpeg_data);
806        }
807
808        #[test]
809        fn test_image_from_png_file() {
810            let temp_dir = TempDir::new().unwrap();
811            let file_path = temp_dir.path().join("test.png");
812            
813            // Create a minimal valid PNG file
814            let png_data = vec![
815                0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature
816                0x00, 0x00, 0x00, 0x0D, // IHDR chunk length (13)
817                0x49, 0x48, 0x44, 0x52, // IHDR chunk type
818                0x00, 0x00, 0x00, 0x50, // Width (80)
819                0x00, 0x00, 0x00, 0x50, // Height (80)
820                0x08, // Bit depth (8)
821                0x02, // Color type (2 = RGB)
822                0x00, // Compression method
823                0x00, // Filter method
824                0x00, // Interlace method
825                0x5C, 0x72, 0x6E, 0x38, // CRC
826            ];
827            
828            fs::write(&file_path, &png_data).unwrap();
829            
830            let image = Image::from_png_file(&file_path).unwrap();
831            
832            assert_eq!(image.width(), 80);
833            assert_eq!(image.height(), 80);
834            assert_eq!(image.format(), ImageFormat::Png);
835            assert_eq!(image.data(), png_data);
836        }
837
838        #[test]
839        fn test_image_from_tiff_file() {
840            let temp_dir = TempDir::new().unwrap();
841            let file_path = temp_dir.path().join("test.tiff");
842            
843            // Create a minimal valid TIFF file
844            let tiff_data = vec![
845                0x49, 0x49, // Little endian byte order
846                0x2A, 0x00, // Magic number (42)
847                0x08, 0x00, 0x00, 0x00, // Offset to first IFD
848                0x03, 0x00, // Number of directory entries
849                // ImageWidth tag (256)
850                0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
851                // ImageHeight tag (257)
852                0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
853                // BitsPerSample tag (258)
854                0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
855                0x00, 0x00, 0x00, 0x00, // Next IFD offset (0 = none)
856            ];
857            
858            fs::write(&file_path, &tiff_data).unwrap();
859            
860            let image = Image::from_tiff_file(&file_path).unwrap();
861            
862            assert_eq!(image.width(), 96);
863            assert_eq!(image.height(), 96);
864            assert_eq!(image.format(), ImageFormat::Tiff);
865            assert_eq!(image.data(), tiff_data);
866        }
867
868        #[test]
869        fn test_image_to_pdf_object_jpeg() {
870            let jpeg_data = vec![
871                0xFF, 0xD8, // SOI marker
872                0xFF, 0xC0, // SOF0 marker
873                0x00, 0x11, // Length (17 bytes)
874                0x08, // Precision (8 bits)
875                0x00, 0x64, // Height (100)
876                0x00, 0xC8, // Width (200)
877                0x03, // Components (3 = RGB)
878                0x01, 0x11, 0x00, // Component 1
879                0x02, 0x11, 0x01, // Component 2
880                0x03, 0x11, 0x01, // Component 3
881                0xFF, 0xD9, // EOI marker
882            ];
883
884            let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
885            let pdf_obj = image.to_pdf_object();
886            
887            if let Object::Stream(dict, data) = pdf_obj {
888                assert_eq!(dict.get("Type").unwrap(), &Object::Name("XObject".to_string()));
889                assert_eq!(dict.get("Subtype").unwrap(), &Object::Name("Image".to_string()));
890                assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
891                assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
892                assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceRGB".to_string()));
893                assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
894                assert_eq!(dict.get("Filter").unwrap(), &Object::Name("DCTDecode".to_string()));
895                assert_eq!(data, jpeg_data);
896            } else {
897                panic!("Expected Stream object");
898            }
899        }
900
901        #[test]
902        fn test_image_to_pdf_object_png() {
903            let png_data = vec![
904                0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature
905                0x00, 0x00, 0x00, 0x0D, // IHDR chunk length (13)
906                0x49, 0x48, 0x44, 0x52, // IHDR chunk type
907                0x00, 0x00, 0x00, 0x50, // Width (80)
908                0x00, 0x00, 0x00, 0x50, // Height (80)
909                0x08, // Bit depth (8)
910                0x06, // Color type (6 = RGB + Alpha)
911                0x00, // Compression method
912                0x00, // Filter method
913                0x00, // Interlace method
914                0x5C, 0x72, 0x6E, 0x38, // CRC
915            ];
916
917            let image = Image::from_png_data(png_data.clone()).unwrap();
918            let pdf_obj = image.to_pdf_object();
919            
920            if let Object::Stream(dict, data) = pdf_obj {
921                assert_eq!(dict.get("Type").unwrap(), &Object::Name("XObject".to_string()));
922                assert_eq!(dict.get("Subtype").unwrap(), &Object::Name("Image".to_string()));
923                assert_eq!(dict.get("Width").unwrap(), &Object::Integer(80));
924                assert_eq!(dict.get("Height").unwrap(), &Object::Integer(80));
925                assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceRGB".to_string()));
926                assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
927                assert_eq!(dict.get("Filter").unwrap(), &Object::Name("FlateDecode".to_string()));
928                assert_eq!(data, png_data);
929            } else {
930                panic!("Expected Stream object");
931            }
932        }
933
934        #[test]
935        fn test_image_to_pdf_object_tiff() {
936            let tiff_data = vec![
937                0x49, 0x49, // Little endian byte order
938                0x2A, 0x00, // Magic number (42)
939                0x08, 0x00, 0x00, 0x00, // Offset to first IFD
940                0x03, 0x00, // Number of directory entries
941                // ImageWidth tag (256)
942                0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
943                // ImageHeight tag (257)
944                0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
945                // BitsPerSample tag (258)
946                0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
947                0x00, 0x00, 0x00, 0x00, // Next IFD offset (0 = none)
948            ];
949
950            let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
951            let pdf_obj = image.to_pdf_object();
952            
953            if let Object::Stream(dict, data) = pdf_obj {
954                assert_eq!(dict.get("Type").unwrap(), &Object::Name("XObject".to_string()));
955                assert_eq!(dict.get("Subtype").unwrap(), &Object::Name("Image".to_string()));
956                assert_eq!(dict.get("Width").unwrap(), &Object::Integer(64));
957                assert_eq!(dict.get("Height").unwrap(), &Object::Integer(64));
958                assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceGray".to_string()));
959                assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
960                assert_eq!(dict.get("Filter").unwrap(), &Object::Name("FlateDecode".to_string()));
961                assert_eq!(data, tiff_data);
962            } else {
963                panic!("Expected Stream object");
964            }
965        }
966
967        #[test]
968        fn test_image_clone() {
969            let jpeg_data = vec![
970                0xFF, 0xD8, // SOI marker
971                0xFF, 0xC0, // SOF0 marker
972                0x00, 0x11, // Length (17 bytes)
973                0x08, // Precision (8 bits)
974                0x00, 0x32, // Height (50)
975                0x00, 0x64, // Width (100)
976                0x03, // Components (3 = RGB)
977                0x01, 0x11, 0x00, // Component 1
978                0x02, 0x11, 0x01, // Component 2
979                0x03, 0x11, 0x01, // Component 3
980                0xFF, 0xD9, // EOI marker
981            ];
982
983            let image1 = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
984            let image2 = image1.clone();
985            
986            assert_eq!(image1.width(), image2.width());
987            assert_eq!(image1.height(), image2.height());
988            assert_eq!(image1.format(), image2.format());
989            assert_eq!(image1.data(), image2.data());
990        }
991
992        #[test]
993        fn test_image_debug() {
994            let jpeg_data = vec![
995                0xFF, 0xD8, // SOI marker
996                0xFF, 0xC0, // SOF0 marker
997                0x00, 0x11, // Length (17 bytes)
998                0x08, // Precision (8 bits)
999                0x00, 0x32, // Height (50)
1000                0x00, 0x64, // Width (100)
1001                0x03, // Components (3 = RGB)
1002                0x01, 0x11, 0x00, // Component 1
1003                0x02, 0x11, 0x01, // Component 2
1004                0x03, 0x11, 0x01, // Component 3
1005                0xFF, 0xD9, // EOI marker
1006            ];
1007
1008            let image = Image::from_jpeg_data(jpeg_data).unwrap();
1009            let debug_str = format!("{:?}", image);
1010            
1011            assert!(debug_str.contains("Image"));
1012            assert!(debug_str.contains("width"));
1013            assert!(debug_str.contains("height"));
1014            assert!(debug_str.contains("format"));
1015        }
1016
1017        #[test]
1018        fn test_jpeg_grayscale_image() {
1019            let jpeg_data = vec![
1020                0xFF, 0xD8, // SOI marker
1021                0xFF, 0xC0, // SOF0 marker
1022                0x00, 0x11, // Length (17 bytes)
1023                0x08, // Precision (8 bits)
1024                0x00, 0x32, // Height (50)
1025                0x00, 0x64, // Width (100)
1026                0x01, // Components (1 = Grayscale)
1027                0x01, 0x11, 0x00, // Component 1
1028                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Padding
1029                0xFF, 0xD9, // EOI marker
1030            ];
1031
1032            let image = Image::from_jpeg_data(jpeg_data).unwrap();
1033            let pdf_obj = image.to_pdf_object();
1034            
1035            if let Object::Stream(dict, _) = pdf_obj {
1036                assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceGray".to_string()));
1037            } else {
1038                panic!("Expected Stream object");
1039            }
1040        }
1041
1042        #[test]
1043        fn test_jpeg_cmyk_image() {
1044            let jpeg_data = vec![
1045                0xFF, 0xD8, // SOI marker
1046                0xFF, 0xC0, // SOF0 marker
1047                0x00, 0x11, // Length (17 bytes)
1048                0x08, // Precision (8 bits)
1049                0x00, 0x32, // Height (50)
1050                0x00, 0x64, // Width (100)
1051                0x04, // Components (4 = CMYK)
1052                0x01, 0x11, 0x00, // Component 1
1053                0x02, 0x11, 0x01, // Component 2
1054                0x03, 0x11, 0x01, // Component 3
1055                0xFF, 0xD9, // EOI marker
1056            ];
1057
1058            let image = Image::from_jpeg_data(jpeg_data).unwrap();
1059            let pdf_obj = image.to_pdf_object();
1060            
1061            if let Object::Stream(dict, _) = pdf_obj {
1062                assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceCMYK".to_string()));
1063            } else {
1064                panic!("Expected Stream object");
1065            }
1066        }
1067
1068        #[test]
1069        fn test_png_grayscale_image() {
1070            let png_data = vec![
1071                0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature
1072                0x00, 0x00, 0x00, 0x0D, // IHDR chunk length (13)
1073                0x49, 0x48, 0x44, 0x52, // IHDR chunk type
1074                0x00, 0x00, 0x00, 0x50, // Width (80)
1075                0x00, 0x00, 0x00, 0x50, // Height (80)
1076                0x08, // Bit depth (8)
1077                0x00, // Color type (0 = Grayscale)
1078                0x00, // Compression method
1079                0x00, // Filter method
1080                0x00, // Interlace method
1081                0x5C, 0x72, 0x6E, 0x38, // CRC
1082            ];
1083
1084            let image = Image::from_png_data(png_data).unwrap();
1085            let pdf_obj = image.to_pdf_object();
1086            
1087            if let Object::Stream(dict, _) = pdf_obj {
1088                assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceGray".to_string()));
1089            } else {
1090                panic!("Expected Stream object");
1091            }
1092        }
1093
1094        #[test]
1095        fn test_png_palette_image() {
1096            let png_data = vec![
1097                0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature
1098                0x00, 0x00, 0x00, 0x0D, // IHDR chunk length (13)
1099                0x49, 0x48, 0x44, 0x52, // IHDR chunk type
1100                0x00, 0x00, 0x00, 0x50, // Width (80)
1101                0x00, 0x00, 0x00, 0x50, // Height (80)
1102                0x08, // Bit depth (8)
1103                0x03, // Color type (3 = Palette)
1104                0x00, // Compression method
1105                0x00, // Filter method
1106                0x00, // Interlace method
1107                0x5C, 0x72, 0x6E, 0x38, // CRC
1108            ];
1109
1110            let image = Image::from_png_data(png_data).unwrap();
1111            let pdf_obj = image.to_pdf_object();
1112            
1113            if let Object::Stream(dict, _) = pdf_obj {
1114                // Palette images are treated as RGB in PDF
1115                assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceRGB".to_string()));
1116            } else {
1117                panic!("Expected Stream object");
1118            }
1119        }
1120
1121        #[test]
1122        fn test_tiff_big_endian() {
1123            let tiff_data = vec![
1124                0x4D, 0x4D, // Big endian byte order
1125                0x00, 0x2A, // Magic number (42)
1126                0x00, 0x00, 0x00, 0x08, // Offset to first IFD
1127                0x00, 0x04, // Number of directory entries
1128                // ImageWidth tag (256)
1129                0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1130                // ImageHeight tag (257)
1131                0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1132                // BitsPerSample tag (258)
1133                0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00,
1134                // PhotometricInterpretation tag (262)
1135                0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00,
1136                0x00, 0x00, 0x00, 0x00, // Next IFD offset (0 = none)
1137            ];
1138
1139            let image = Image::from_tiff_data(tiff_data).unwrap();
1140            
1141            assert_eq!(image.width(), 128);
1142            assert_eq!(image.height(), 128);
1143            assert_eq!(image.format(), ImageFormat::Tiff);
1144        }
1145
1146        #[test]
1147        fn test_tiff_cmyk_image() {
1148            let tiff_data = vec![
1149                0x49, 0x49, // Little endian byte order
1150                0x2A, 0x00, // Magic number (42)
1151                0x08, 0x00, 0x00, 0x00, // Offset to first IFD
1152                0x04, 0x00, // Number of directory entries
1153                // ImageWidth tag (256)
1154                0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1155                // ImageHeight tag (257)
1156                0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1157                // BitsPerSample tag (258)
1158                0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1159                // PhotometricInterpretation tag (262) - CMYK
1160                0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
1161                0x00, 0x00, 0x00, 0x00, // Next IFD offset (0 = none)
1162            ];
1163
1164            let image = Image::from_tiff_data(tiff_data).unwrap();
1165            let pdf_obj = image.to_pdf_object();
1166            
1167            if let Object::Stream(dict, _) = pdf_obj {
1168                assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name("DeviceCMYK".to_string()));
1169            } else {
1170                panic!("Expected Stream object");
1171            }
1172        }
1173
1174        #[test]
1175        fn test_error_invalid_jpeg() {
1176            let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; // Not a JPEG
1177            let result = Image::from_jpeg_data(invalid_data);
1178            assert!(result.is_err());
1179        }
1180
1181        #[test]
1182        fn test_error_invalid_png() {
1183            let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; // Not a PNG
1184            let result = Image::from_png_data(invalid_data);
1185            assert!(result.is_err());
1186        }
1187
1188        #[test]
1189        fn test_error_invalid_tiff() {
1190            let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; // Not a TIFF
1191            let result = Image::from_tiff_data(invalid_data);
1192            assert!(result.is_err());
1193        }
1194
1195        #[test]
1196        fn test_error_truncated_jpeg() {
1197            let truncated_data = vec![0xFF, 0xD8, 0xFF]; // Truncated JPEG
1198            let result = Image::from_jpeg_data(truncated_data);
1199            assert!(result.is_err());
1200        }
1201
1202        #[test]
1203        fn test_error_truncated_png() {
1204            let truncated_data = vec![0x89, 0x50, 0x4E, 0x47]; // Truncated PNG
1205            let result = Image::from_png_data(truncated_data);
1206            assert!(result.is_err());
1207        }
1208
1209        #[test]
1210        fn test_error_truncated_tiff() {
1211            let truncated_data = vec![0x49, 0x49, 0x2A]; // Truncated TIFF
1212            let result = Image::from_tiff_data(truncated_data);
1213            assert!(result.is_err());
1214        }
1215
1216        #[test]
1217        fn test_error_jpeg_unsupported_components() {
1218            let invalid_jpeg = vec![
1219                0xFF, 0xD8, // SOI marker
1220                0xFF, 0xC0, // SOF0 marker
1221                0x00, 0x11, // Length (17 bytes)
1222                0x08, // Precision (8 bits)
1223                0x00, 0x32, // Height (50)
1224                0x00, 0x64, // Width (100)
1225                0x05, // Components (5 = unsupported)
1226                0x01, 0x11, 0x00, // Component 1
1227                0x02, 0x11, 0x01, // Component 2
1228                0x03, 0x11, 0x01, // Component 3
1229                0xFF, 0xD9, // EOI marker
1230            ];
1231
1232            let result = Image::from_jpeg_data(invalid_jpeg);
1233            assert!(result.is_err());
1234        }
1235
1236        #[test]
1237        fn test_error_png_unsupported_color_type() {
1238            let invalid_png = vec![
1239                0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature
1240                0x00, 0x00, 0x00, 0x0D, // IHDR chunk length (13)
1241                0x49, 0x48, 0x44, 0x52, // IHDR chunk type
1242                0x00, 0x00, 0x00, 0x50, // Width (80)
1243                0x00, 0x00, 0x00, 0x50, // Height (80)
1244                0x08, // Bit depth (8)
1245                0x07, // Color type (7 = unsupported)
1246                0x00, // Compression method
1247                0x00, // Filter method
1248                0x00, // Interlace method
1249                0x5C, 0x72, 0x6E, 0x38, // CRC
1250            ];
1251
1252            let result = Image::from_png_data(invalid_png);
1253            assert!(result.is_err());
1254        }
1255
1256        #[test]
1257        fn test_error_nonexistent_file() {
1258            let result = Image::from_jpeg_file("/nonexistent/path/image.jpg");
1259            assert!(result.is_err());
1260            
1261            let result = Image::from_png_file("/nonexistent/path/image.png");
1262            assert!(result.is_err());
1263            
1264            let result = Image::from_tiff_file("/nonexistent/path/image.tiff");
1265            assert!(result.is_err());
1266        }
1267
1268        #[test]
1269        fn test_jpeg_no_dimensions() {
1270            let jpeg_no_dims = vec![
1271                0xFF, 0xD8, // SOI marker
1272                0xFF, 0xD9, // EOI marker (no SOF)
1273            ];
1274
1275            let result = Image::from_jpeg_data(jpeg_no_dims);
1276            assert!(result.is_err());
1277        }
1278
1279        #[test]
1280        fn test_png_no_ihdr() {
1281            let png_no_ihdr = vec![
1282                0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature
1283                0x00, 0x00, 0x00, 0x0D, // Chunk length (13)
1284                0x49, 0x45, 0x4E, 0x44, // IEND chunk type (not IHDR)
1285                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1286                0x5C, 0x72, 0x6E, 0x38, // CRC
1287            ];
1288
1289            let result = Image::from_png_data(png_no_ihdr);
1290            assert!(result.is_err());
1291        }
1292
1293        #[test]
1294        fn test_tiff_no_dimensions() {
1295            let tiff_no_dims = vec![
1296                0x49, 0x49, // Little endian byte order
1297                0x2A, 0x00, // Magic number (42)
1298                0x08, 0x00, 0x00, 0x00, // Offset to first IFD
1299                0x01, 0x00, // Number of directory entries
1300                // BitsPerSample tag (258) - no width/height
1301                0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1302                0x00, 0x00, 0x00, 0x00, // Next IFD offset (0 = none)
1303            ];
1304
1305            let result = Image::from_tiff_data(tiff_no_dims);
1306            assert!(result.is_err());
1307        }
1308
1309        #[test]
1310        fn test_different_bit_depths() {
1311            // Test PNG with different bit depths
1312            let png_16bit = vec![
1313                0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature
1314                0x00, 0x00, 0x00, 0x0D, // IHDR chunk length (13)
1315                0x49, 0x48, 0x44, 0x52, // IHDR chunk type
1316                0x00, 0x00, 0x00, 0x50, // Width (80)
1317                0x00, 0x00, 0x00, 0x50, // Height (80)
1318                0x10, // Bit depth (16)
1319                0x02, // Color type (2 = RGB)
1320                0x00, // Compression method
1321                0x00, // Filter method
1322                0x00, // Interlace method
1323                0x5C, 0x72, 0x6E, 0x38, // CRC
1324            ];
1325
1326            let image = Image::from_png_data(png_16bit).unwrap();
1327            let pdf_obj = image.to_pdf_object();
1328            
1329            if let Object::Stream(dict, _) = pdf_obj {
1330                assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(16));
1331            } else {
1332                panic!("Expected Stream object");
1333            }
1334        }
1335
1336        #[test]
1337        fn test_performance_large_image_data() {
1338            // Test with larger image data to ensure performance
1339            let mut large_jpeg = vec![
1340                0xFF, 0xD8, // SOI marker
1341                0xFF, 0xC0, // SOF0 marker
1342                0x00, 0x11, // Length (17 bytes)
1343                0x08, // Precision (8 bits)
1344                0x04, 0x00, // Height (1024)
1345                0x04, 0x00, // Width (1024)
1346                0x03, // Components (3 = RGB)
1347                0x01, 0x11, 0x00, // Component 1
1348                0x02, 0x11, 0x01, // Component 2
1349                0x03, 0x11, 0x01, // Component 3
1350            ];
1351            
1352            // Add some dummy data to make it larger
1353            large_jpeg.extend(vec![0x00; 10000]);
1354            large_jpeg.extend(vec![0xFF, 0xD9]); // EOI marker
1355            
1356            let start = std::time::Instant::now();
1357            let image = Image::from_jpeg_data(large_jpeg.clone()).unwrap();
1358            let duration = start.elapsed();
1359            
1360            assert_eq!(image.width(), 1024);
1361            assert_eq!(image.height(), 1024);
1362            assert_eq!(image.data().len(), large_jpeg.len());
1363            assert!(duration.as_millis() < 100); // Should be fast
1364        }
1365
1366        #[test]
1367        fn test_memory_efficiency() {
1368            let jpeg_data = vec![
1369                0xFF, 0xD8, // SOI marker
1370                0xFF, 0xC0, // SOF0 marker
1371                0x00, 0x11, // Length (17 bytes)
1372                0x08, // Precision (8 bits)
1373                0x00, 0x64, // Height (100)
1374                0x00, 0xC8, // Width (200)
1375                0x03, // Components (3 = RGB)
1376                0x01, 0x11, 0x00, // Component 1
1377                0x02, 0x11, 0x01, // Component 2
1378                0x03, 0x11, 0x01, // Component 3
1379                0xFF, 0xD9, // EOI marker
1380            ];
1381
1382            let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1383            
1384            // Test that the image stores the data efficiently
1385            assert_eq!(image.data().len(), jpeg_data.len());
1386            assert_eq!(image.data(), jpeg_data);
1387            
1388            // Test that cloning doesn't affect the original
1389            let cloned = image.clone();
1390            assert_eq!(cloned.data(), image.data());
1391        }
1392
1393        #[test]
1394        fn test_complete_workflow() {
1395            // Test complete workflow: create image -> PDF object -> verify structure
1396            let test_cases = vec![
1397                (ImageFormat::Jpeg, "DCTDecode", "DeviceRGB"),
1398                (ImageFormat::Png, "FlateDecode", "DeviceRGB"),
1399                (ImageFormat::Tiff, "FlateDecode", "DeviceGray"),
1400            ];
1401
1402            for (expected_format, expected_filter, expected_color_space) in test_cases {
1403                let data = match expected_format {
1404                    ImageFormat::Jpeg => vec![
1405                        0xFF, 0xD8, // SOI marker
1406                        0xFF, 0xC0, // SOF0 marker
1407                        0x00, 0x11, // Length (17 bytes)
1408                        0x08, // Precision (8 bits)
1409                        0x00, 0x64, // Height (100)
1410                        0x00, 0xC8, // Width (200)
1411                        0x03, // Components (3 = RGB)
1412                        0x01, 0x11, 0x00, // Component 1
1413                        0x02, 0x11, 0x01, // Component 2
1414                        0x03, 0x11, 0x01, // Component 3
1415                        0xFF, 0xD9, // EOI marker
1416                    ],
1417                    ImageFormat::Png => vec![
1418                        0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature
1419                        0x00, 0x00, 0x00, 0x0D, // IHDR chunk length (13)
1420                        0x49, 0x48, 0x44, 0x52, // IHDR chunk type
1421                        0x00, 0x00, 0x00, 0xC8, // Width (200)
1422                        0x00, 0x00, 0x00, 0x64, // Height (100)
1423                        0x08, // Bit depth (8)
1424                        0x02, // Color type (2 = RGB)
1425                        0x00, // Compression method
1426                        0x00, // Filter method
1427                        0x00, // Interlace method
1428                        0x5C, 0x72, 0x6E, 0x38, // CRC
1429                    ],
1430                    ImageFormat::Tiff => vec![
1431                        0x49, 0x49, // Little endian byte order
1432                        0x2A, 0x00, // Magic number (42)
1433                        0x08, 0x00, 0x00, 0x00, // Offset to first IFD
1434                        0x03, 0x00, // Number of directory entries
1435                        // ImageWidth tag (256)
1436                        0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00,
1437                        // ImageHeight tag (257)
1438                        0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
1439                        // BitsPerSample tag (258)
1440                        0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1441                        0x00, 0x00, 0x00, 0x00, // Next IFD offset (0 = none)
1442                    ],
1443                };
1444
1445                let image = match expected_format {
1446                    ImageFormat::Jpeg => Image::from_jpeg_data(data.clone()).unwrap(),
1447                    ImageFormat::Png => Image::from_png_data(data.clone()).unwrap(),
1448                    ImageFormat::Tiff => Image::from_tiff_data(data.clone()).unwrap(),
1449                };
1450
1451                // Verify image properties
1452                assert_eq!(image.format(), expected_format);
1453                assert_eq!(image.width(), 200);
1454                assert_eq!(image.height(), 100);
1455                assert_eq!(image.data(), data);
1456
1457                // Verify PDF object conversion
1458                let pdf_obj = image.to_pdf_object();
1459                if let Object::Stream(dict, stream_data) = pdf_obj {
1460                    assert_eq!(dict.get("Type").unwrap(), &Object::Name("XObject".to_string()));
1461                    assert_eq!(dict.get("Subtype").unwrap(), &Object::Name("Image".to_string()));
1462                    assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
1463                    assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
1464                    assert_eq!(dict.get("ColorSpace").unwrap(), &Object::Name(expected_color_space.to_string()));
1465                    assert_eq!(dict.get("Filter").unwrap(), &Object::Name(expected_filter.to_string()));
1466                    assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1467                    assert_eq!(stream_data, data);
1468                } else {
1469                    panic!("Expected Stream object for format {:?}", expected_format);
1470                }
1471            }
1472        }
1473    }
1474}