1 | use crate::util::*; |
2 | use crate::{ImageResult, ImageSize}; |
3 | |
4 | use std::io::{BufRead, Cursor, Seek, SeekFrom}; |
5 | |
6 | pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> { |
7 | reader.seek(SeekFrom::Start(0))?; |
8 | |
9 | let mut endian_marker = [0; 2]; |
10 | reader.read_exact(&mut endian_marker)?; |
11 | |
12 | // Get the endianness which determines how we read the input |
13 | let endianness = if &endian_marker[0..2] == b"II" { |
14 | Endian::Little |
15 | } else if &endian_marker[0..2] == b"MM" { |
16 | Endian::Big |
17 | } else { |
18 | // Shouldn't get here by normal means, but handle invalid header anyway |
19 | return Err( |
20 | std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid TIFF header" ).into(), |
21 | ); |
22 | }; |
23 | |
24 | // Read the IFD offset from the header |
25 | reader.seek(SeekFrom::Start(4))?; |
26 | let ifd_offset = read_u32(reader, &endianness)?; |
27 | |
28 | // IFD offset cannot be 0 |
29 | if ifd_offset == 0 { |
30 | return Err( |
31 | std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid IFD offset" ).into(), |
32 | ); |
33 | } |
34 | |
35 | // Jump to the IFD offset |
36 | reader.seek(SeekFrom::Start(ifd_offset.into()))?; |
37 | |
38 | // Read how many IFD records there are |
39 | let ifd_count = read_u16(reader, &endianness)?; |
40 | let mut width = None; |
41 | let mut height = None; |
42 | |
43 | for _ifd in 0..ifd_count { |
44 | let tag = read_u16(reader, &endianness)?; |
45 | let kind = read_u16(reader, &endianness)?; |
46 | let _count = read_u32(reader, &endianness)?; |
47 | |
48 | let value_bytes = match kind { |
49 | // BYTE | ASCII | SBYTE | UNDEFINED |
50 | 1 | 2 | 6 | 7 => 1, |
51 | // SHORT | SSHORT |
52 | 3 | 8 => 2, |
53 | // LONG | SLONG | FLOAT | IFD |
54 | 4 | 9 | 11 | 13 => 4, |
55 | // RATIONAL | SRATIONAL |
56 | 5 | 10 => 4 * 2, |
57 | // DOUBLE | LONG8 | SLONG8 | IFD8 |
58 | 12 | 16 | 17 | 18 => 8, |
59 | // Anything else is invalid |
60 | _ => { |
61 | return Err(std::io::Error::new( |
62 | std::io::ErrorKind::InvalidData, |
63 | "Invalid IFD type" , |
64 | ) |
65 | .into()) |
66 | } |
67 | }; |
68 | |
69 | let mut value_buffer = [0; 4]; |
70 | reader.read_exact(&mut value_buffer)?; |
71 | |
72 | let mut r = Cursor::new(&value_buffer[..]); |
73 | let value = match value_bytes { |
74 | 2 => Some(read_u16(&mut r, &endianness)? as u32), |
75 | 4 => Some(read_u32(&mut r, &endianness)?), |
76 | _ => None, |
77 | }; |
78 | |
79 | // Tag 0x100 is the image width, 0x101 is image height |
80 | if tag == 0x100 { |
81 | width = value; |
82 | } else if tag == 0x101 { |
83 | height = value; |
84 | } |
85 | |
86 | // If we've read both values we need, return the data |
87 | if let (Some(width), Some(height)) = (width, height) { |
88 | return Ok(ImageSize { |
89 | width: width as usize, |
90 | height: height as usize, |
91 | }); |
92 | } |
93 | } |
94 | |
95 | // If no width/height pair was found return invalid data |
96 | Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "No dimensions in IFD tags" ).into()) |
97 | } |
98 | |
99 | pub fn matches(header: &[u8]) -> bool { |
100 | header.starts_with(needle:b"II \x2A\x00" ) || header.starts_with(needle:b"MM \x00\x2A" ) |
101 | } |
102 | |