imagesize/formats/
tiff.rs1use 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 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 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 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 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 if ifd_offset == 0 {
69 return Err(
70 std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid IFD offset").into(),
71 );
72 }
73
74 reader.seek(SeekFrom::Start(ifd_offset))?;
76
77 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 1 | 2 | 6 | 7 => 1,
99 3 | 8 => 2,
101 4 | 9 | 11 | 13 => 4,
103 5 | 10 => 4 * 2,
105 12 => 8,
107 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 _ => {
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 if tag == 0x100 {
149 width = value;
150 } else if tag == 0x101 {
151 height = value;
152 }
153
154 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 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}