1 | use crate::util::*; |
2 | use crate::{ImageError, ImageResult, ImageSize}; |
3 | |
4 | use std::io::{BufRead, Seek, SeekFrom}; |
5 | |
6 | pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> { |
7 | reader.seek(SeekFrom::Start(0))?; |
8 | // Read the ftyp header size |
9 | let ftyp_size = read_u32(reader, &Endian::Big)?; |
10 | |
11 | // Jump to the first actual box offset |
12 | reader.seek(SeekFrom::Start(ftyp_size.into()))?; |
13 | |
14 | // Skip to meta tag which contains all the metadata |
15 | skip_to_tag(reader, b"meta" )?; |
16 | read_u32(reader, &Endian::Big)?; // Meta has a junk value after it |
17 | skip_to_tag(reader, b"iprp" )?; // Find iprp tag |
18 | |
19 | let mut ipco_size = skip_to_tag(reader, b"ipco" )? as usize; // Find ipco tag |
20 | |
21 | // Keep track of the max size of ipco tag |
22 | let mut max_width = 0usize; |
23 | let mut max_height = 0usize; |
24 | let mut found_ispe = false; |
25 | let mut rotation = 0u8; |
26 | |
27 | while let Ok((tag, size)) = read_tag(reader) { |
28 | // Size of tag length + tag cannot be under 8 (4 bytes each) |
29 | if size < 8 { |
30 | return Err(ImageError::CorruptedImage); |
31 | } |
32 | |
33 | // ispe tag has a junk value followed by width and height as u32 |
34 | if tag == "ispe" { |
35 | found_ispe = true; |
36 | read_u32(reader, &Endian::Big)?; // Discard junk value |
37 | let width = read_u32(reader, &Endian::Big)? as usize; |
38 | let height = read_u32(reader, &Endian::Big)? as usize; |
39 | |
40 | // Assign new largest size by area |
41 | if width * height > max_width * max_height { |
42 | max_width = width; |
43 | max_height = height; |
44 | } |
45 | } else if tag == "irot" { |
46 | // irot is 9 bytes total: size, tag, 1 byte for rotation (0-3) |
47 | rotation = read_u8(reader)?; |
48 | } else if size >= ipco_size { |
49 | // If we've gone past the ipco boundary, then break |
50 | break; |
51 | } else { |
52 | // If we're still inside ipco, consume all bytes for |
53 | // the current tag, minus the bytes already read in `read_tag` |
54 | ipco_size -= size; |
55 | reader.seek(SeekFrom::Current(size as i64 - 8))?; |
56 | } |
57 | } |
58 | |
59 | // If no ispe found, then we have no actual dimension data to use |
60 | if !found_ispe { |
61 | return Err( |
62 | std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "Not enough data" ).into(), |
63 | ); |
64 | } |
65 | |
66 | // Rotation can only be 0-3. 1 and 3 are 90 and 270 degrees respectively (anti-clockwise) |
67 | // If we have 90 or 270 rotation, flip width and height |
68 | if rotation == 1 || rotation == 3 { |
69 | std::mem::swap(&mut max_width, &mut max_height); |
70 | } |
71 | |
72 | Ok(ImageSize { |
73 | width: max_width, |
74 | height: max_height, |
75 | }) |
76 | } |
77 | |
78 | pub fn matches(header: &[u8]) -> bool { |
79 | if header.len() < 12 || &header[4..8] != b"ftyp" { |
80 | return false; |
81 | } |
82 | |
83 | let header_brand = &header[8..12]; |
84 | |
85 | // Since other non-heif files may contain ftype in the header |
86 | // we try to use brands to distinguish image files specifically. |
87 | // List of brands from here: https://mp4ra.org/#/brands |
88 | let valid_brands = [ |
89 | // HEIF specific |
90 | b"avci" , b"avcs" , b"heic" , b"heim" , |
91 | b"heis" , b"heix" , b"hevc" , b"hevm" , |
92 | b"hevs" , b"hevx" , b"jpeg" , b"jpgs" , |
93 | b"mif1" , b"msf1" , b"mif2" , b"pred" , |
94 | // AVIF specific |
95 | b"avif" , b"avio" , b"avis" , b"MA1A" , |
96 | b"MA1B" , |
97 | ]; |
98 | |
99 | for brand in valid_brands { |
100 | if brand == header_brand { |
101 | return true; |
102 | } |
103 | } |
104 | |
105 | false |
106 | } |
107 | |
108 | fn skip_to_tag<R: BufRead + Seek>(reader: &mut R, tag: &[u8]) -> ImageResult<u32> { |
109 | let mut tag_buf: [u8; 4] = [0; 4]; |
110 | |
111 | loop { |
112 | let size: u32 = read_u32(reader, &Endian::Big)?; |
113 | reader.read_exact(&mut tag_buf)?; |
114 | |
115 | if tag_buf == tag { |
116 | return Ok(size); |
117 | } |
118 | |
119 | if size >= 8 { |
120 | reader.seek(pos:SeekFrom::Current(size as i64 - 8))?; |
121 | } else { |
122 | return Err(stdError::io::Error::new( |
123 | kind:std::io::ErrorKind::InvalidData, |
124 | error:format!("Invalid heif box size: {}" , size), |
125 | ) |
126 | .into()); |
127 | } |
128 | } |
129 | } |
130 | |