Skip to main content

imagesize/formats/
tiff.rs

1use crate::util::*;
2use crate::{ImageResult, ImageSize};
3
4use std::io::{BufRead, Cursor, Read, Seek, SeekFrom};
5
6#[derive(Debug, PartialEq)]
7enum Type {
8    Tiff,
9    BigTiff,
10}
11
12pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
13    reader.seek(SeekFrom::Start(0))?;
14
15    let mut endian_marker = [0; 2];
16    reader.read_exact(&mut endian_marker)?;
17
18    //  Get the endianness which determines how we read the input
19    let endianness = if &endian_marker[0..2] == b"II" {
20        Endian::Little
21    } else if &endian_marker[0..2] == b"MM" {
22        Endian::Big
23    } else {
24        //  Shouldn't get here by normal means, but handle invalid header anyway
25        return Err(
26            std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid TIFF header").into(),
27        );
28    };
29    let type_marker = read_u16(reader, &endianness)?;
30    let tiff_type = if type_marker == 42 {
31        Type::Tiff
32    } else if type_marker == 43 {
33        Type::BigTiff
34    } else {
35        return Err(
36            std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid TIFF header").into(),
37        );
38    };
39
40    if tiff_type == Type::BigTiff {
41        // http://bigtiff.org/ describes the BigTIFF header additions as constants 8 and 0.
42        let offset_bytesize = read_u16(reader, &endianness)?;
43        if offset_bytesize != 8 {
44            return Err(std::io::Error::new(
45                std::io::ErrorKind::InvalidData,
46                "Unrecognised BigTiff offset size",
47            )
48            .into());
49        }
50        let extra_field = read_u16(reader, &endianness)?;
51        if extra_field != 0 {
52            return Err(std::io::Error::new(
53                std::io::ErrorKind::InvalidData,
54                "Invalid BigTiff header",
55            )
56            .into());
57        }
58    }
59
60    //  Read the IFD offset from the header
61    let ifd_offset = if tiff_type == Type::Tiff {
62        read_u32(reader, &endianness)? as u64
63    } else {
64        read_u64(reader, &endianness)?
65    };
66
67    //  IFD offset cannot be 0
68    if ifd_offset == 0 {
69        return Err(
70            std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid IFD offset").into(),
71        );
72    }
73
74    //  Jump to the IFD offset
75    reader.seek(SeekFrom::Start(ifd_offset))?;
76
77    //  Read how many IFD records there are
78    let ifd_count = if tiff_type == Type::Tiff {
79        read_u16(reader, &endianness)? as u64
80    } else {
81        read_u64(reader, &endianness)?
82    };
83
84    let mut width = None;
85    let mut height = None;
86
87    for _ifd in 0..ifd_count {
88        let tag = read_u16(reader, &endianness)?;
89        let kind = read_u16(reader, &endianness)?;
90        let _count = if tiff_type == Type::Tiff {
91            read_u32(reader, &endianness)? as u64
92        } else {
93            read_u64(reader, &endianness)?
94        };
95
96        let value_bytes = match kind {
97            // BYTE | ASCII | SBYTE | UNDEFINED
98            1 | 2 | 6 | 7 => 1,
99            // SHORT | SSHORT
100            3 | 8 => 2,
101            // LONG | SLONG | FLOAT | IFD
102            4 | 9 | 11 | 13 => 4,
103            // RATIONAL | SRATIONAL
104            5 | 10 => 4 * 2,
105            // DOUBLE
106            12 => 8,
107            // BigTiff only: LONG8 | SLONG8 | IFD8
108            16..=18 => {
109                if tiff_type == Type::Tiff {
110                    return Err(std::io::Error::new(
111                        std::io::ErrorKind::InvalidData,
112                        "Invalid IFD type for standard TIFF",
113                    )
114                    .into());
115                }
116                8
117            }
118            // Anything else is invalid
119            _ => {
120                return Err(std::io::Error::new(
121                    std::io::ErrorKind::InvalidData,
122                    "Invalid IFD type",
123                )
124                .into())
125            }
126        };
127
128        let mut value_buffer = [0; 8];
129        let ifd_value_length = if tiff_type == Type::Tiff { 4 } else { 8 };
130        let mut handle = reader.take(ifd_value_length);
131        let bytes_loaded = handle.read(&mut value_buffer)?;
132        if bytes_loaded != ifd_value_length as usize {
133            return Err(std::io::Error::new(
134                std::io::ErrorKind::InvalidData,
135                "Invalid IFD value length",
136            )
137            .into());
138        }
139
140        let mut r = Cursor::new(&value_buffer[..]);
141        let value = match value_bytes {
142            2 => Some(read_u16(&mut r, &endianness)? as u32),
143            4 => Some(read_u32(&mut r, &endianness)?),
144            _ => None,
145        };
146
147        //  Tag 0x100 is the image width, 0x101 is image height
148        if tag == 0x100 {
149            width = value;
150        } else if tag == 0x101 {
151            height = value;
152        }
153
154        //  If we've read both values we need, return the data
155        if let (Some(width), Some(height)) = (width, height) {
156            return Ok(ImageSize {
157                width: width as usize,
158                height: height as usize,
159            });
160        }
161    }
162
163    //  If no width/height pair was found return invalid data
164    Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "No dimensions in IFD tags").into())
165}
166
167pub fn matches(header: &[u8]) -> bool {
168    const TYPE_MARKERS: [u8; 2] = [b'\x2A', b'\x2B'];
169    (header.starts_with(b"II") && TYPE_MARKERS.contains(&header[2]) && header[3] == 0)
170        || (header.starts_with(b"MM\x00") && TYPE_MARKERS.contains(&header[3]))
171}