imsz/
lib.rs

1//! Get image width and height reading as few bytes as possible.
2//! 
3//! [GitHub](https://github.com/panzi/imsz) – [C API Reference](https://panzi.github.io/imsz/c)
4//! 
5//! Example:
6//! ```
7//! # use std::io::BufReader;
8//! # use std::fs::File;
9//! # fn main() -> imsz::ImResult<()> {
10//! use imsz::imsz;
11//! 
12//! let fname = "testdata/image.gif";
13//! let info = imsz(fname)?;
14//! println!("{}: {}, {} x {}", fname, info.format, info.width, info.height);
15//! // testdata/image.gif: GIF, 32 x 16
16//! 
17//! // alternatively if you have something implementing std::io::Read and std::io::Seek:
18//! use imsz::imsz_from_reader;
19//! 
20//! let mut file = BufReader::new(File::open(fname)?);
21//! let info = imsz_from_reader(&mut file)?;
22//! # Ok(())
23//! # }
24//! ```
25
26use std::fs::File;
27use std::io::{Read, Seek, SeekFrom, BufReader};
28
29#[non_exhaustive]
30#[derive(Debug, Clone, Copy, PartialEq)]
31pub enum ImFormat {
32    /// Graphics Interchange Format files in version GIF87a or GIF89a.
33    GIF     =  1,
34
35    /// Portable Network Graphics files. Requires the first chunk to be `IHDR`.
36    PNG     =  2,
37
38    /// Windows Bitmap, both for Windows 2.0 (BITMAPCOREHEADER) and for newer
39    /// versions (BITMAPINFOHEADER).
40    BMP     =  3,
41
42    /// Joint Photographic Experts Group files.
43    JPEG    =  4,
44
45    /// WebP files. Supported sub-formats: `VP8 `, `VP8L`, `VP8X`.
46    WEBP    =  5,
47
48    /// Quite OK Image format files.
49    QOI     =  6,
50
51    /// Adobe Photoshop files.
52    PSD     =  7,
53
54    /// GIMP files.
55    XCF     =  8,
56
57    /// ICO files can contain multiple images. This returns the dimensions of
58    /// the biggest image in the file.
59    ICO     =  9,
60
61    /// AV1 Image File Format.
62    AVIF    = 10,
63
64    /// Tag Image File Format. Supports big endian and little endian TIFF files.
65    TIFF    = 11,
66
67    /// OpenEXR files.
68    OpenEXR = 12,
69
70    /// PiCture eXchange files.
71    PCX     = 13,
72
73    /// TARGA (Truevision Advanced Raster Graphics Adapter) files.
74    /// 
75    /// Only if the file ends in `b"TRUEVISION-XFILE.\0"` since otherwise there
76    /// is no good way to detect TGA files. Note that this string is optional
77    /// to this file format and thus there can be TGA files that aren't supported
78    /// by this library.
79    TGA     = 14,
80
81    /// DirectDraw Surface files.
82    DDS     = 15,
83
84    /// HEIC/HEIF files. These are extremely similar to AVIF and use the same
85    /// parsing code.
86    HEIF    = 16,
87
88    /// JPEG 2000 files.
89    JP2K    = 17,
90
91    /// Device-Independent Bitmap files.
92    DIB     = 18,
93
94    /// Valve Texture Format.
95    VTF     = 19,
96
97    /// Interleaved Bitmap files, including Planar Bitmap variant.
98    ILBM    = 20,
99}
100
101impl ImFormat {
102    pub const fn name(&self) -> &'static str {
103        match self {
104            Self::GIF     => "GIF",
105            Self::PNG     => "PNG",
106            Self::BMP     => "BMP",
107            Self::JPEG    => "JPEG",
108            Self::WEBP    => "WebP",
109            Self::QOI     => "QOI",
110            Self::PSD     => "PSD",
111            Self::XCF     => "XCF",
112            Self::ICO     => "ICO",
113            Self::AVIF    => "AVIF",
114            Self::TIFF    => "TIFF",
115            Self::OpenEXR => "OpenEXR",
116            Self::PCX     => "PCX",
117            Self::TGA     => "TGA",
118            Self::DDS     => "DDS",
119            Self::HEIF    => "HEIF",
120            Self::JP2K    => "JPEG 2000",
121            Self::DIB     => "DIB",
122            Self::VTF     => "VTF",
123            Self::ILBM    => "ILBM",
124        }
125    }
126}
127
128impl std::fmt::Display for ImFormat {
129    #[inline]
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        self.name().fmt(f)
132    }
133}
134
135/// The width, height and format of an image.
136#[derive(Debug, Clone)]
137pub struct ImInfo {
138    pub width:  u64,
139    pub height: u64,
140    pub format: ImFormat,
141}
142
143#[derive(Debug)]
144pub enum ImError {
145    /// If there was an IO error reading the image file this error is returend.
146    IO(std::io::Error),
147
148    /// If the image format couldn't be detected this error is returend.
149    UnknownFormat,
150
151    /// If the image format was detected, but then something went wrong parsing
152    /// the file this error is returned.
153    ParserError(ImFormat),
154}
155
156impl std::fmt::Display for ImError {
157    #[inline]
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        match self {
160            Self::IO(error) => error.fmt(f),
161            Self::UnknownFormat => "Unknown Format".fmt(f),
162            Self::ParserError(format) => write!(f, "Error parsing {format} image")
163        }
164    }
165}
166
167impl std::error::Error for ImError {
168    #[inline]
169    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
170        match self {
171            Self::IO(error) => Some(error),
172            _ => None
173        }
174    }
175}
176
177impl From<std::io::Error> for ImError {
178    #[inline]
179    fn from(error: std::io::Error) -> Self {
180        ImError::IO(error)
181    }
182}
183
184pub type ImResult<T> = std::result::Result<T, ImError>;
185
186trait Ratio<T: Sized> {
187    fn value<R>(&self) -> R::Output
188    where R: Sized, R: std::ops::Div, R: From<T>;
189}
190
191impl Ratio<u32> for (u32, u32) {
192    #[inline]
193    fn value<R>(&self) -> R::Output
194    where R: Sized, R: std::ops::Div, R: From<u32> {
195        let (a, b) = *self;
196        let x: R = a.into();
197        let y: R = b.into();
198        x / y
199    }
200}
201
202impl Ratio<i32> for (i32, i32) {
203    #[inline]
204    fn value<R>(&self) -> R::Output
205    where R: Sized, R: std::ops::Div, R: From<i32> {
206        let (a, b) = *self;
207        let x: R = a.into();
208        let y: R = b.into();
209        x / y
210    }
211}
212
213trait BinaryReader {
214    #[inline]
215    fn read_u8(reader: &mut impl Read) -> std::io::Result<u8> {
216        let mut buf = [0u8];
217        reader.read_exact(&mut buf)?;
218        return Ok(buf[0]);
219    }
220
221    #[inline]
222    fn read_uchar(reader: &mut impl Read) -> std::io::Result<u8> {
223        let mut buf = [0u8];
224        reader.read_exact(&mut buf)?;
225        return Ok(buf[0]);
226    }
227
228    #[inline]
229    fn read_i8(reader: &mut impl Read) -> std::io::Result<i8> {
230        let mut buf = [0u8];
231        reader.read_exact(&mut buf)?;
232        return Ok(buf[0] as i8);
233    }
234
235    #[inline]
236    fn read_ichar(reader: &mut impl Read) -> std::io::Result<i8> {
237        let mut buf = [0u8];
238        reader.read_exact(&mut buf)?;
239        return Ok(buf[0] as i8);
240    }
241
242    fn get_u32(buf: [u8; 4]) -> u32;
243
244    fn read_u16(reader: &mut impl Read) -> std::io::Result<u16>;
245    fn read_u32(reader: &mut impl Read) -> std::io::Result<u32>;
246    fn read_uratio(reader: &mut impl Read) -> std::io::Result<(u32, u32)>;
247
248    fn read_i16(reader: &mut impl Read) -> std::io::Result<i16>;
249    fn read_i32(reader: &mut impl Read) -> std::io::Result<i32>;
250    fn read_iratio(reader: &mut impl Read) -> std::io::Result<(i32, i32)>;
251
252    fn read_f32(reader: &mut impl Read) -> std::io::Result<f32>;
253    fn read_f64(reader: &mut impl Read) -> std::io::Result<f64>;
254}
255
256struct LittleEndianReader;
257struct BigEndianReader;
258
259impl BinaryReader for LittleEndianReader {
260    #[inline]
261    fn get_u32(buf: [u8; 4]) -> u32 {
262        return u32::from_le_bytes(buf);
263    }
264
265    #[inline]
266    fn read_u16(reader: &mut impl Read) -> std::io::Result<u16> {
267        let mut buf = [0u8; 2];
268        reader.read_exact(&mut buf)?;
269        return Ok(u16::from_le_bytes(buf));
270    }
271
272    #[inline]
273    fn read_u32(reader: &mut impl Read) -> std::io::Result<u32> {
274        let mut buf = [0u8; 4];
275        reader.read_exact(&mut buf)?;
276        return Ok(u32::from_le_bytes(buf));
277    }
278
279    #[inline]
280    fn read_uratio(reader: &mut impl Read) -> std::io::Result<(u32, u32)> {
281        let mut buf = [0u8; 8];
282        reader.read_exact(&mut buf)?;
283        return Ok((
284            u32::from_le_bytes([ buf[0], buf[1], buf[2], buf[3] ]),
285            u32::from_le_bytes([ buf[4], buf[5], buf[6], buf[7] ]),
286        ));
287    }
288
289    #[inline]
290    fn read_i16(reader: &mut impl Read) -> std::io::Result<i16> {
291        let mut buf = [0u8; 2];
292        reader.read_exact(&mut buf)?;
293        return Ok(i16::from_le_bytes(buf));
294    }
295
296    #[inline]
297    fn read_i32(reader: &mut impl Read) -> std::io::Result<i32> {
298        let mut buf = [0u8; 4];
299        reader.read_exact(&mut buf)?;
300        return Ok(i32::from_le_bytes(buf));
301    }
302
303    #[inline]
304    fn read_iratio(reader: &mut impl Read) -> std::io::Result<(i32, i32)> {
305        let mut buf = [0u8; 8];
306        reader.read_exact(&mut buf)?;
307        return Ok((
308            i32::from_le_bytes([ buf[0], buf[1], buf[2], buf[3] ]),
309            i32::from_le_bytes([ buf[4], buf[5], buf[6], buf[7] ]),
310        ));
311    }
312
313    #[inline]
314    fn read_f32(reader: &mut impl Read) -> std::io::Result<f32> {
315        let mut buf = [0u8; 4];
316        reader.read_exact(&mut buf)?;
317        return Ok(f32::from_le_bytes(buf));
318    }
319
320    #[inline]
321    fn read_f64(reader: &mut impl Read) -> std::io::Result<f64> {
322        let mut buf = [0u8; 8];
323        reader.read_exact(&mut buf)?;
324        return Ok(f64::from_le_bytes(buf));
325    }
326}
327
328impl BinaryReader for BigEndianReader {
329    #[inline]
330    fn get_u32(buf: [u8; 4]) -> u32 {
331        return u32::from_be_bytes(buf);
332    }
333
334    #[inline]
335    fn read_u16(reader: &mut impl Read) -> std::io::Result<u16> {
336        let mut buf = [0u8; 2];
337        reader.read_exact(&mut buf)?;
338        return Ok(u16::from_be_bytes(buf));
339    }
340
341    #[inline]
342    fn read_u32(reader: &mut impl Read) -> std::io::Result<u32> {
343        let mut buf = [0u8; 4];
344        reader.read_exact(&mut buf)?;
345        return Ok(u32::from_be_bytes(buf));
346    }
347
348    #[inline]
349    fn read_uratio(reader: &mut impl Read) -> std::io::Result<(u32, u32)> {
350        let mut buf = [0u8; 8];
351        reader.read_exact(&mut buf)?;
352        return Ok((
353            u32::from_be_bytes([ buf[0], buf[1], buf[2], buf[3] ]),
354            u32::from_be_bytes([ buf[4], buf[5], buf[6], buf[7] ]),
355        ));
356    }
357
358    #[inline]
359    fn read_i16(reader: &mut impl Read) -> std::io::Result<i16> {
360        let mut buf = [0u8; 2];
361        reader.read_exact(&mut buf)?;
362        return Ok(i16::from_be_bytes(buf));
363    }
364
365    #[inline]
366    fn read_i32(reader: &mut impl Read) -> std::io::Result<i32> {
367        let mut buf = [0u8; 4];
368        reader.read_exact(&mut buf)?;
369        return Ok(i32::from_be_bytes(buf));
370    }
371
372    #[inline]
373    fn read_iratio(reader: &mut impl Read) -> std::io::Result<(i32, i32)> {
374        let mut buf = [0u8; 8];
375        reader.read_exact(&mut buf)?;
376        return Ok((
377            i32::from_be_bytes([ buf[0], buf[1], buf[2], buf[3] ]),
378            i32::from_be_bytes([ buf[4], buf[5], buf[6], buf[7] ]),
379        ));
380    }
381
382    #[inline]
383    fn read_f32(reader: &mut impl Read) -> std::io::Result<f32> {
384        let mut buf = [0u8; 4];
385        reader.read_exact(&mut buf)?;
386        return Ok(f32::from_be_bytes(buf));
387    }
388
389    #[inline]
390    fn read_f64(reader: &mut impl Read) -> std::io::Result<f64> {
391        let mut buf = [0u8; 8];
392        reader.read_exact(&mut buf)?;
393        return Ok(f64::from_be_bytes(buf));
394    }
395}
396
397macro_rules! array2 {
398    ($data:expr, $offset:expr) => {
399        [ $data[$offset], $data[$offset + 1] ]
400    };
401}
402
403macro_rules! array4 {
404    ($data:expr, $offset:expr) => {
405        [ $data[$offset], $data[$offset + 1], $data[$offset + 2], $data[$offset + 3] ]
406    };
407}
408
409macro_rules! map_err {
410    ($fmt:expr, $expr:expr) => {
411        if let Err(_) = $expr {
412            return Err(ImError::ParserError($fmt));
413        }
414    };
415
416    ($fmt:ident $expr:expr) => {
417        map_err!(ImFormat::$fmt, $expr);
418    };
419}
420
421macro_rules! map_expr {
422    ($fmt:ident $expr:expr) => {
423        match $expr {
424            Err(_) => return Err(ImError::ParserError(ImFormat::$fmt)),
425            Ok(value) => value
426        }
427    };
428}
429
430fn find_riff_chunk<R>(reader: &mut R, name: &[u8; 4], chunk_size: u64, format: ImFormat) -> ImResult<u64>
431where R: Read, R: Seek {
432    let mut sub_chunk_size;
433    let mut buf = [0u8; 8];
434    let mut offset = 0;
435
436    loop {
437        if offset > chunk_size {
438            return Err(ImError::ParserError(format));
439        }
440        if let Err(_) = reader.read_exact(&mut buf) {
441            return Err(ImError::ParserError(format));
442        }
443        sub_chunk_size = u32::from_be_bytes(array4!(&buf, 0)) as u64;
444        if sub_chunk_size < 8 {
445            return Err(ImError::ParserError(format));
446        }
447        if &buf[4..8] == name {
448            break;
449        }
450        offset += sub_chunk_size;
451        if let Err(_) = reader.seek(SeekFrom::Current(sub_chunk_size as i64 - 8)) {
452            return Err(ImError::ParserError(format));
453        }
454    }
455
456    return Ok(sub_chunk_size);
457}
458
459fn parse_tiff<BR, R>(reader: &mut R, preamble: &[u8]) -> ImResult<ImInfo>
460where BR: BinaryReader, R: Read, R: Seek {
461    let ifd_offset = BR::get_u32(array4!(preamble, 4));
462    map_err!(TIFF reader.seek(SeekFrom::Start(ifd_offset as u64)));
463
464    let ifd_entry_count = map_expr!(TIFF BR::read_u16(reader)) as u32;
465    // 2 bytes: TagId + 2 bytes: type + 4 bytes: count of values + 4
466    // bytes: value offset
467    let mut width:  Option<u64> = None;
468    let mut height: Option<u64> = None;
469
470    for index in 0..ifd_entry_count {
471        // sizeof ifd_entry_count = 2
472        let entry_offset = ifd_offset + 2 + index * 12;
473        map_err!(TIFF reader.seek(SeekFrom::Start(entry_offset as u64)));
474        let tag = map_expr!(TIFF BR::read_u16(reader));
475
476        // 256 ... width
477        // 257 ... height
478        if tag == 256 || tag == 257 {
479            // if type indicates that value fits into 4 bytes, value
480            // offset is not an offset but value itself
481            let ftype = map_expr!(TIFF BR::read_u16(reader));
482            map_err!(TIFF reader.seek(SeekFrom::Start(entry_offset as u64 + 8)));
483            let value: u64 = match ftype {
484                 1 => map_expr!(TIFF BR::read_u8(reader)).into(),
485                 2 => map_expr!(TIFF BR::read_uchar(reader)).into(),
486                 3 => map_expr!(TIFF BR::read_u16(reader)).into(),
487                 4 => map_expr!(TIFF BR::read_u32(reader)).into(),
488                 5 => map_expr!(TIFF BR::read_uratio(reader)).value::<u64>(),
489                 6 => map_expr!(TIFF BR::read_i8(reader)).max(0) as u64,
490                 7 => map_expr!(TIFF BR::read_ichar(reader)).max(0) as u64,
491                 8 => map_expr!(TIFF BR::read_i16(reader)).max(0) as u64,
492                 9 => map_expr!(TIFF BR::read_i32(reader)).max(0) as u64,
493                10 => map_expr!(TIFF BR::read_iratio(reader)).value::<i64>().max(0) as u64,
494                11 => map_expr!(TIFF BR::read_f32(reader)) as u64,
495                12 => map_expr!(TIFF BR::read_f64(reader)) as u64,
496                _ => return Err(ImError::ParserError(ImFormat::TIFF))
497            };
498
499            if tag == 256 {
500                if let Some(height) = height {
501                    return Ok(ImInfo {
502                        format: ImFormat::TIFF,
503                        width: value,
504                        height,
505                    });
506                }
507                width = Some(value);
508            } else {
509                if let Some(width) = width {
510                    return Ok(ImInfo {
511                        format: ImFormat::TIFF,
512                        width,
513                        height: value,
514                    });
515                }
516                height = Some(value);
517            }
518        }
519    }
520
521    return Err(ImError::ParserError(ImFormat::TIFF));
522}
523
524#[inline]
525fn is_tga<R>(file: &mut R) -> std::io::Result<bool>
526where R: Read, R: Seek {
527    file.seek(SeekFrom::End(-18))?;
528    let mut buf = [0u8; 18];
529    file.read_exact(&mut buf)?;
530    return Ok(&buf == b"TRUEVISION-XFILE.\0");
531}
532
533/// Trait to provide generic [imsz()] function for paths, buffers, and readers.
534pub trait Imsz {
535    fn imsz(self) -> ImResult<ImInfo>;
536}
537
538impl Imsz for &str {
539    #[inline]
540    fn imsz(self) -> ImResult<ImInfo> {
541        return imsz_from_path(self);
542    }
543}
544
545impl Imsz for &String {
546    #[inline]
547    fn imsz(self) -> ImResult<ImInfo> {
548        return imsz_from_path(self.as_str());
549    }
550}
551
552impl Imsz for String {
553    #[inline]
554    fn imsz(self) -> ImResult<ImInfo> {
555        return imsz_from_path(self.as_str());
556    }
557}
558
559impl Imsz for &std::ffi::OsStr {
560    #[inline]
561    fn imsz(self) -> ImResult<ImInfo> {
562        return imsz_from_path(self);
563    }
564}
565
566impl Imsz for &std::ffi::OsString {
567    #[inline]
568    fn imsz(self) -> ImResult<ImInfo> {
569        return imsz_from_path(self.as_os_str());
570    }
571}
572
573impl Imsz for std::ffi::OsString {
574    #[inline]
575    fn imsz(self) -> ImResult<ImInfo> {
576        return imsz_from_path(self.as_os_str());
577    }
578}
579
580impl Imsz for &std::path::Path {
581    #[inline]
582    fn imsz(self) -> ImResult<ImInfo> {
583        return imsz_from_path(self);
584    }
585}
586
587impl Imsz for &std::path::PathBuf {
588    #[inline]
589    fn imsz(self) -> ImResult<ImInfo> {
590        return imsz_from_path(self.as_path());
591    }
592}
593
594impl Imsz for std::path::PathBuf {
595    #[inline]
596    fn imsz(self) -> ImResult<ImInfo> {
597        return imsz_from_path(self.as_path());
598    }
599}
600
601impl Imsz for &[u8] {
602    #[inline]
603    fn imsz(self) -> ImResult<ImInfo> {
604        return imsz_from_reader(&mut std::io::Cursor::new(self));
605    }
606}
607
608impl<const LEN: usize> Imsz for [u8; LEN] {
609    #[inline]
610    fn imsz(self) -> ImResult<ImInfo> {
611        return imsz_from_reader(&mut std::io::Cursor::new(&self[..]));
612    }
613}
614
615impl<const LEN: usize> Imsz for &[u8; LEN] {
616    #[inline]
617    fn imsz(self) -> ImResult<ImInfo> {
618        return imsz_from_reader(&mut std::io::Cursor::new(&self[..]));
619    }
620}
621
622impl Imsz for &mut std::fs::File {
623    #[inline]
624    fn imsz(self) -> ImResult<ImInfo> {
625        return imsz_from_reader(&mut BufReader::new(self));
626    }
627}
628
629impl Imsz for std::fs::File {
630    #[inline]
631    fn imsz(mut self) -> ImResult<ImInfo> {
632        return imsz_from_reader(&mut BufReader::new(&mut self));
633    }
634}
635
636impl Imsz for std::io::Stdin {
637    #[inline]
638    fn imsz(self) -> ImResult<ImInfo> {
639        return (&self).imsz();
640    }
641}
642
643#[cfg(any(target_family="unix", target_family="windows"))]
644impl Imsz for &std::io::Stdin {
645    /// WARNING: This looses already buffered input!
646    #[inline]
647    fn imsz(self) -> ImResult<ImInfo> {
648        let lock = self.lock();
649
650        #[cfg(target_family="unix")]
651        let mut seekable_stdin = unsafe {
652            use std::os::unix::io::{AsRawFd, FromRawFd};
653            std::fs::File::from_raw_fd(lock.as_raw_fd())
654        };
655
656        #[cfg(target_family="windows")]
657        let mut seekable_stdin = unsafe {
658            use std::os::windows::io::{AsRawHandle, FromRawHandle};
659            std::fs::File::from_raw_handle(lock.as_raw_handle())
660        };
661
662        let result = imsz_from_reader(&mut BufReader::new(&mut seekable_stdin));
663
664        // Be sure the lock is released *after* all my IO happened.
665        drop(lock);
666
667        return result;
668    }
669}
670
671impl Imsz for &mut std::io::Cursor<&[u8]> {
672    #[inline]
673    fn imsz(self) -> ImResult<ImInfo> {
674        return imsz_from_reader(self);
675    }
676}
677
678impl Imsz for std::io::Cursor<&[u8]> {
679    #[inline]
680    fn imsz(mut self) -> ImResult<ImInfo> {
681        return imsz_from_reader(&mut self);
682    }
683}
684
685impl<const LEN: usize> Imsz for std::io::Cursor<&[u8; LEN]> {
686    #[inline]
687    fn imsz(mut self) -> ImResult<ImInfo> {
688        return imsz_from_reader(&mut self);
689    }
690}
691
692impl<const LEN: usize> Imsz for std::io::Cursor<[u8; LEN]> {
693    #[inline]
694    fn imsz(mut self) -> ImResult<ImInfo> {
695        return imsz_from_reader(&mut self);
696    }
697}
698
699impl<R> Imsz for &mut std::io::BufReader<R> where R: Read, R: Seek {
700    #[inline]
701    fn imsz(self) -> ImResult<ImInfo> {
702        return imsz_from_reader(self);
703    }
704}
705
706impl<R> Imsz for std::io::BufReader<R> where R: Read, R: Seek {
707    #[inline]
708    fn imsz(mut self) -> ImResult<ImInfo> {
709        return imsz_from_reader(&mut self);
710    }
711}
712
713/// Read width and height of an image.
714/// 
715/// `input` can be a file path, a byte buffer, a file reader, or a buffered reader.
716#[inline]
717pub fn imsz(input: impl Imsz) -> ImResult<ImInfo> {
718    return input.imsz();
719}
720
721/// Read width and height of an image.
722#[inline]
723pub fn imsz_from_path(path: impl AsRef<std::path::Path>) -> ImResult<ImInfo> {
724    let mut reader = BufReader::new(File::open(path)?);
725    return imsz_from_reader(&mut reader);
726}
727
728/// Read width and height of an image.
729/// 
730/// Some file formats (like JPEG) need repeated small reads, so passing a
731/// `std::io::BufReader` is recommended.
732pub fn imsz_from_reader<R>(file: &mut R) -> ImResult<ImInfo>
733where R: Read, R: Seek {
734    let mut preamble = [0u8; 30];
735
736    let size = file.read(&mut preamble)?;
737
738    if size >= 6 && (&preamble[..6] == b"GIF87a" || &preamble[..6] == b"GIF89a") {
739        // GIF
740        if size < 10 {
741            return Err(ImError::ParserError(ImFormat::GIF));
742        }
743        let w = u16::from_le_bytes(array2!(preamble, 6));
744        let h = u16::from_le_bytes(array2!(preamble, 8));
745
746        return Ok(ImInfo {
747            format: ImFormat::GIF,
748            width:  w as u64,
749            height: h as u64,
750        });
751    } else if size >= 8 && preamble.starts_with(b"\x89PNG\r\n\x1a\n") {
752        // PNG
753        if size < 24 {
754            return Err(ImError::ParserError(ImFormat::PNG));
755        }
756
757        let chunk_size = u32::from_be_bytes(array4!(preamble, 8));
758        if chunk_size < 8 || &preamble[12..16] != b"IHDR" {
759            return Err(ImError::ParserError(ImFormat::PNG));
760        }
761
762        let w = u32::from_be_bytes(array4!(preamble, 16));
763        let h = u32::from_be_bytes(array4!(preamble, 20));
764
765        return Ok(ImInfo {
766            format: ImFormat::PNG,
767            width:  w as u64,
768            height: h as u64,
769        });
770    } else if size >= 10 && preamble.starts_with(b"BM") && &preamble[6..10] == b"\0\0\0\0" {
771        // BMP
772        let file_size = u32::from_le_bytes(array4!(preamble, 2));
773        let min_size = (file_size as usize).min(size);
774        if min_size < 22 {
775            return Err(ImError::ParserError(ImFormat::BMP));
776        }
777
778        let header_size = u32::from_le_bytes(array4!(preamble, 14));
779        if header_size == 12 {
780            // Windows 2.0 BITMAPCOREHEADER
781            let w = i16::from_le_bytes(array2!(preamble, 18));
782            let h = i16::from_le_bytes(array2!(preamble, 20));
783
784            return Ok(ImInfo {
785                format: ImFormat::BMP,
786                width:  w as u64,
787                // h is negative when stored upside down
788                height: h.abs() as u64,
789            });
790        } else {
791            if min_size < 26 || header_size <= 12 {
792                return Err(ImError::ParserError(ImFormat::BMP));
793            }
794            let w = i32::from_le_bytes(array4!(preamble, 18));
795            let h = i32::from_le_bytes(array4!(preamble, 22));
796
797            return Ok(ImInfo {
798                format: ImFormat::BMP,
799                width:  w as u64,
800                // h is negative when stored upside down
801                height: h.abs() as u64
802            });
803        }
804    } else if size >= 3 && &preamble[..2] == b"\xff\xd8" {
805        // JPEG
806        map_err!(JPEG file.seek(SeekFrom::Start(3)));
807        let mut buf1: [u8; 1] = [ preamble[2] ];
808        let mut buf2: [u8; 2] = [0; 2];
809        let mut buf4: [u8; 4] = [0; 4];
810        while buf1[0] != b'\xda' && buf1[0] != 0 {
811            while buf1[0] != b'\xff' {
812                map_err!(JPEG file.read_exact(&mut buf1));
813            }
814            while buf1[0] == b'\xff' {
815                map_err!(JPEG file.read_exact(&mut buf1));
816            }
817            if buf1[0] >= 0xc0 && buf1[0] <= 0xc3 {
818                map_err!(JPEG file.seek(SeekFrom::Current(3)));
819                map_err!(JPEG file.read_exact(&mut buf4));
820                let h = u16::from_be_bytes(array2!(buf4, 0));
821                let w = u16::from_be_bytes(array2!(buf4, 2));
822
823                return Ok(ImInfo {
824                    format: ImFormat::JPEG,
825                    width:  w as u64,
826                    height: h as u64,
827                });
828            }
829            map_err!(JPEG file.read_exact(&mut buf2));
830            let b = u16::from_be_bytes(buf2);
831            let offset = (b - 2) as i64;
832            map_err!(JPEG file.seek(SeekFrom::Current(offset)));
833            map_err!(JPEG file.read_exact(&mut buf1));
834        }
835        return Err(ImError::ParserError(ImFormat::JPEG));
836    } else if size >= 30 && preamble.starts_with(b"RIFF") && &preamble[8..12] == b"WEBP" {
837        // WEBP
838        let hdr = &preamble[12..16];
839        if hdr == b"VP8L" {
840            let b0 = preamble[21];
841            let b1 = preamble[22];
842            let b2 = preamble[23];
843            let b3 = preamble[24];
844
845            let w = 1u32 + ((((b1 & 0x3F) as u32) << 8) | b0 as u32);
846            let h = 1u32 + ((((b3 & 0xF) as u32) << 10) | ((b2 as u32) << 2) | ((b1 & 0xC0) as u32 >> 6));
847
848            return Ok(ImInfo {
849                format: ImFormat::WEBP,
850                width:  w as u64,
851                height: h as u64,
852            });
853        } else if hdr == b"VP8 " {
854            let b0 = preamble[23];
855            let b1 = preamble[24];
856            let b2 = preamble[25];
857            if b0 != 0x9d || b1 != 0x01 || b2 != 0x2a {
858                return Err(ImError::ParserError(ImFormat::WEBP));
859            }
860            let w = u16::from_le_bytes(array2!(preamble, 26));
861            let h = u16::from_le_bytes(array2!(preamble, 28));
862            return Ok(ImInfo {
863                format: ImFormat::WEBP,
864                width:  w as u64 & 0x3ffff,
865                height: h as u64 & 0x3ffff,
866            });
867        } else if hdr == b"VP8X" {
868            let w1 = preamble[24] as u32;
869            let w2 = preamble[25] as u32;
870            let w3 = preamble[26] as u32;
871            let h1 = preamble[27] as u32;
872            let h2 = preamble[28] as u32;
873            let h3 = preamble[29] as u32;
874
875            let width  = (w1 | w2 << 8 | w3 << 16) + 1;
876            let height = (h1 | h2 << 8 | h3 << 16) + 1;
877
878            return Ok(ImInfo {
879                format: ImFormat::WEBP,
880                width:  width  as u64,
881                height: height as u64,
882            });
883        }
884        return Err(ImError::ParserError(ImFormat::WEBP));
885    } else if size >= 12 && (&preamble[4..12] == b"ftypavif" || &preamble[4..12] == b"ftypheic") {
886        // AVIF and HEIF
887        let format = if &preamble[8..12] == b"avif" {
888            ImFormat::AVIF
889        } else {
890            ImFormat::HEIF
891        };
892
893        let ftype_size = u32::from_be_bytes(array4!(preamble, 0));
894        if ftype_size < 12 {
895            return Err(ImError::ParserError(format));
896        }
897        map_err!(format, file.seek(SeekFrom::Start(ftype_size as u64)));
898
899        // chunk nesting: meta > iprp > ipco > ispe
900        let chunk_size = find_riff_chunk(file, b"meta", u64::MAX, format)?;
901        if chunk_size < 12 {
902            return Err(ImError::ParserError(format));
903        }
904        map_err!(format, file.seek(SeekFrom::Current(4)));
905        let chunk_size = find_riff_chunk(file, b"iprp", chunk_size - 12, format)?;
906        let chunk_size = find_riff_chunk(file, b"ipco", chunk_size -  8, format)?;
907        let chunk_size = find_riff_chunk(file, b"ispe", chunk_size -  8, format)?;
908
909        if chunk_size < 12 {
910            return Err(ImError::ParserError(format));
911        }
912
913        let mut buf = [0u8; 12];
914        map_err!(format, file.read_exact(&mut buf));
915
916        let w = u32::from_be_bytes(array4!(buf, 4));
917        let h = u32::from_be_bytes(array4!(buf, 8));
918
919        return Ok(ImInfo {
920            format,
921            width:  w as u64,
922            height: h as u64,
923        });
924    } else if size >= 24 && preamble.starts_with(b"\0\0\0\x0CjP  ") && &preamble[16..24] == b"ftypjp2 " {
925        // JPEG 2000
926        let chunk_size = u32::from_be_bytes(array4!(preamble, 12));
927        map_err!(JP2K file.seek(SeekFrom::Start(12 + chunk_size as u64)));
928        let chunk_size = find_riff_chunk(file, b"jp2h", u64::MAX, ImFormat::JP2K)?;
929        let chunk_size = find_riff_chunk(file, b"ihdr", chunk_size, ImFormat::JP2K)?;
930
931        if chunk_size < 8 {
932            return Err(ImError::ParserError(ImFormat::JP2K));
933        }
934
935        let mut buf = [0u8; 8];
936        map_err!(JP2K file.read_exact(&mut buf));
937
938        let h = u32::from_be_bytes(array4!(buf, 0));
939        let w = u32::from_be_bytes(array4!(buf, 4));
940
941        return Ok(ImInfo {
942            format: ImFormat::JP2K,
943            width:  w as u64,
944            height: h as u64,
945        });
946    } else if size >= 8 && (preamble.starts_with(b"II*\0") || preamble.starts_with(b"MM\0*")) {
947        // TIFF
948        if preamble.starts_with(b"MM") {
949            // big endian
950            return parse_tiff::<BigEndianReader, R>(file, &preamble[..size]);
951        } else {
952            // little endian
953            return parse_tiff::<LittleEndianReader, R>(file, &preamble[..size]);
954        }
955    } else if size >= 14 && preamble.starts_with(b"qoif") {
956        // QOI
957        let w = u32::from_be_bytes(array4!(preamble, 4));
958        let h = u32::from_be_bytes(array4!(preamble, 8));
959
960        return Ok(ImInfo {
961            format: ImFormat::QOI,
962            width:  w as u64,
963            height: h as u64,
964        });
965    } else if size >= 22 && preamble.starts_with(b"8BPS\0\x01\0\0\0\0\0\0") {
966        // PSD
967        let h = u32::from_be_bytes(array4!(preamble, 14));
968        let w = u32::from_be_bytes(array4!(preamble, 18));
969
970        return Ok(ImInfo {
971            format: ImFormat::PSD,
972            width:  w as u64,
973            height: h as u64,
974        });
975    } else if size >= 22 && preamble.starts_with(b"gimp xcf ") && preamble[13] == 0 {
976        // XCF
977        let w = u32::from_be_bytes(array4!(preamble, 14));
978        let h = u32::from_be_bytes(array4!(preamble, 18));
979
980        return Ok(ImInfo {
981            format: ImFormat::XCF,
982            width:  w as u64,
983            height: h as u64,
984        });
985    } else if size >= 6 && preamble.starts_with(b"\0\0\x01\0") {
986        // ICO
987        let count = u16::from_le_bytes(array2!(preamble, 4));
988        map_err!(ICO file.seek(SeekFrom::Start(6)));
989
990        let mut buf = [0u8; 16];
991        let mut width:  u32 = 0;
992        let mut height: u32 = 0;
993        for _ in 0..count {
994            map_err!(ICO file.read_exact(&mut buf));
995            let w = buf[0] as u32;
996            let h = buf[1] as u32;
997            if w >= width && h >= height {
998                width  = w;
999                height = h;
1000            }
1001        }
1002
1003        return Ok(ImInfo {
1004            format: ImFormat::ICO,
1005            width:  width  as u64,
1006            height: height as u64,
1007        });
1008    } else if size > 8 && preamble.starts_with(b"\x76\x2f\x31\x01") && (preamble[4] == 0x01 || preamble[4] == 0x02) {
1009        // OpenEXR
1010        // https://www.openexr.com/documentation/openexrfilelayout.pdf
1011        map_err!(OpenEXR file.seek(SeekFrom::Start(8)));
1012
1013        let mut name_buf = Vec::new();
1014        let mut type_buf = Vec::new();
1015        let mut buf1 = [0u8];
1016        let mut buf4 = [0u8; 4];
1017
1018        loop {
1019            name_buf.clear();
1020            loop {
1021                map_err!(OpenEXR file.read_exact(&mut buf1));
1022                let byte = buf1[0];
1023                if byte == 0 {
1024                    break;
1025                }
1026                name_buf.push(byte);
1027            }
1028
1029            if name_buf.is_empty() {
1030                break;
1031            }
1032
1033            type_buf.clear();
1034            loop {
1035                map_err!(OpenEXR file.read_exact(&mut buf1));
1036                let byte = buf1[0];
1037                if byte == 0 {
1038                    break;
1039                }
1040                type_buf.push(byte);
1041            }
1042
1043            map_err!(OpenEXR file.read_exact(&mut buf4));
1044            let size = u32::from_le_bytes(buf4);
1045
1046            if &name_buf == b"displayWindow" {
1047                if &type_buf != b"box2i" || size != 16 {
1048                    return Err(ImError::ParserError(ImFormat::OpenEXR));
1049                }
1050
1051                let mut box_buf = [0u8; 16];
1052                map_err!(OpenEXR file.read_exact(&mut box_buf));
1053
1054                let x1 = i32::from_le_bytes(array4!(box_buf,  0)) as i64;
1055                let y1 = i32::from_le_bytes(array4!(box_buf,  4)) as i64;
1056                let x2 = i32::from_le_bytes(array4!(box_buf,  8)) as i64;
1057                let y2 = i32::from_le_bytes(array4!(box_buf, 12)) as i64;
1058
1059                let width  = x2 - x1 + 1;
1060                let height = y2 - y1 + 1;
1061
1062                if width <= 0 || height <= 0 {
1063                    return Err(ImError::ParserError(ImFormat::OpenEXR));
1064                }
1065
1066                return Ok(ImInfo {
1067                    format: ImFormat::OpenEXR,
1068                    width:  width  as u64,
1069                    height: height as u64,
1070                });
1071            } else {
1072                map_err!(OpenEXR file.seek(SeekFrom::Current(size as i64)));
1073            }
1074        }
1075
1076        return Err(ImError::ParserError(ImFormat::OpenEXR));
1077    } else if size >= 30 && preamble[0] == 0x0A && preamble[1] < 6 && (preamble[3] == 1 || preamble[3] == 2 || preamble[3] == 4 || preamble[3] == 8) {
1078        // PCX
1079        let x1 = u16::from_le_bytes(array2!(preamble,  4)) as i64;
1080        let y1 = u16::from_le_bytes(array2!(preamble,  6)) as i64;
1081        let x2 = u16::from_le_bytes(array2!(preamble,  8)) as i64;
1082        let y2 = u16::from_le_bytes(array2!(preamble, 10)) as i64;
1083
1084        let width  = x2 - x1 + 1;
1085        let height = y2 - y1 + 1;
1086
1087        if width <= 0 || height <= 0 {
1088            return Err(ImError::ParserError(ImFormat::PCX));
1089        }
1090
1091        return Ok(ImInfo {
1092            format: ImFormat::PCX,
1093            width:  width  as u64,
1094            height: height as u64,
1095        });
1096    } else if size >= 30 && preamble.starts_with(b"DDS \x7C\0\0\0") && (u32::from_le_bytes(array4!(preamble, 8)) & 0x1007) != 0 {
1097        // DDS
1098        // http://doc.51windows.net/directx9_sdk/graphics/reference/DDSFileReference/ddsfileformat.htm
1099        // https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dds-header
1100
1101        let h = u32::from_le_bytes(array4!(preamble, 12));
1102        let w = u32::from_le_bytes(array4!(preamble, 16));
1103
1104        return Ok(ImInfo {
1105            format: ImFormat::DDS,
1106            width:  w as u64,
1107            height: h as u64,
1108        });
1109    } else if size >= 14 && preamble.starts_with(b"\x28\0\0\0") && &preamble[12..14] == b"\x01\0" && preamble[15] == 0 {
1110        // DIB
1111        let w = i32::from_le_bytes(array4!(preamble, 4));
1112        let h = i32::from_le_bytes(array4!(preamble, 8));
1113
1114        return Ok(ImInfo {
1115            format: ImFormat::DIB,
1116            width:  w as u64,
1117            height: h.abs() as u64,
1118        });
1119    } else if size >= 20 && preamble.starts_with(b"VTF\0") {
1120        // VTF
1121        let header_size = u32::from_le_bytes(array4!(preamble, 12));
1122        let w = u16::from_le_bytes(array2!(preamble, 16));
1123        let h = u16::from_le_bytes(array2!(preamble, 18));
1124        if header_size < 10 {
1125            return Err(ImError::ParserError(ImFormat::VTF));
1126        }
1127
1128        return Ok(ImInfo {
1129            format: ImFormat::VTF,
1130            width:  w as u64,
1131            height: h as u64,
1132        });
1133    } else if size >= 24 && preamble.starts_with(b"FORM") && matches!(&preamble[8..12], b"ILBM"|b"PBM ") && &preamble[12..16] == b"BMHD" {
1134        let chunk_len = u32::from_be_bytes(array4!(preamble, 4));
1135        if chunk_len < 32 {
1136            // need at least room for full header chunk
1137            return Err(ImError::ParserError(ImFormat::ILBM));
1138        }
1139
1140        let bmhd_chunk_len = u32::from_be_bytes(array4!(preamble, 16));
1141        if bmhd_chunk_len < 20 {
1142            // need at least room for full header data
1143            return Err(ImError::ParserError(ImFormat::ILBM));
1144        }
1145
1146        let w = u16::from_be_bytes(array2!(preamble, 20));
1147        let h = u16::from_be_bytes(array2!(preamble, 22));
1148
1149        return Ok(ImInfo {
1150            format: ImFormat::ILBM,
1151            width:  w as u64,
1152            height: h as u64,
1153        })
1154    } else if size >= 30 && preamble[1] < 2 && preamble[2] < 12 && is_tga(file)? {
1155        // TGA
1156        let w = u16::from_le_bytes(array2!(preamble, 12));
1157        let h = u16::from_le_bytes(array2!(preamble, 14));
1158
1159        return Ok(ImInfo {
1160            format: ImFormat::TGA,
1161            width:  w as u64,
1162            height: h as u64,
1163        });
1164    }
1165    return Err(ImError::UnknownFormat);
1166}