1use crate::util::*;
2use crate::{ImageError, ImageResult, ImageSize};
3
4use std::io::{BufRead, Seek, SeekFrom};
5
6pub 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
78pub 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
108fn 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