imagesize/container/
heif.rs1use crate::util::*;
2use crate::{ImageError, ImageResult, ImageSize};
3
4use std::convert::TryInto;
5use std::io::{BufRead, Seek, SeekFrom};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub enum Compression {
10 Av1,
11 Hevc,
12 Jpeg,
13 Unknown,
14 }
19
20pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
21 reader.seek(SeekFrom::Start(0))?;
22 let ftyp_size = read_u32(reader, &Endian::Big)?;
24
25 reader.seek(SeekFrom::Start(ftyp_size.into()))?;
27
28 skip_to_tag(reader, b"meta")?;
30 read_u32(reader, &Endian::Big)?; skip_to_tag(reader, b"iprp")?; let mut ipco_size = skip_to_tag(reader, b"ipco")? as usize; let mut max_width = 0usize;
37 let mut max_height = 0usize;
38 let mut found_ispe = false;
39 let mut rotation = 0u8;
40
41 while let Ok((tag, size)) = read_tag(reader) {
42 if size < 8 {
44 if tag == "mdat" {
45 break;
50 }
51 return Err(ImageError::CorruptedImage);
52 }
53
54 if tag == "ispe" {
56 found_ispe = true;
57 read_u32(reader, &Endian::Big)?; let width = read_u32(reader, &Endian::Big)? as usize;
59 let height = read_u32(reader, &Endian::Big)? as usize;
60
61 if width * height > max_width * max_height {
63 max_width = width;
64 max_height = height;
65 }
66 } else if tag == "irot" {
67 rotation = read_u8(reader)?;
69 } else if size >= ipco_size {
70 break;
72 } else {
73 ipco_size -= size;
76 reader.seek(SeekFrom::Current(size as i64 - 8))?;
77 }
78 }
79
80 if !found_ispe {
82 return Err(
83 std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "Not enough data").into(),
84 );
85 }
86
87 if rotation == 1 || rotation == 3 {
90 std::mem::swap(&mut max_width, &mut max_height);
91 }
92
93 Ok(ImageSize {
94 width: max_width,
95 height: max_height,
96 })
97}
98
99pub fn matches<R: BufRead + Seek>(header: &[u8], reader: &mut R) -> Option<Compression> {
100 if header.len() < 12 || &header[4..8] != b"ftyp" {
101 return None;
102 }
103
104 let brand: [u8; 4] = header[8..12].try_into().unwrap();
105
106 if let Some(compression) = inner_matches(&brand) {
107 return Some(compression);
109 }
110
111 let brands = [b"mif1", b"msf1", b"mif2", b"miaf"];
113
114 if brands.contains(&&brand) {
115 let mut buf = [0; 12];
116
117 if reader.read_exact(&mut buf).is_err() {
118 return Some(Compression::Unknown);
119 }
120
121 let brand2: [u8; 4] = buf[4..8].try_into().unwrap();
122
123 if let Some(compression) = inner_matches(&brand2) {
124 return Some(compression);
127 }
128
129 if brands.contains(&&brand2) {
130 let brand3: [u8; 4] = buf[8..12].try_into().unwrap();
133
134 if let Some(compression) = inner_matches(&brand3) {
135 return Some(compression);
136 }
137 }
138 }
139
140 Some(Compression::Unknown)
141}
142
143fn inner_matches(brand: &[u8; 4]) -> Option<Compression> {
144 let hevc_brands = [
148 b"heic", b"heix", b"heis", b"hevs", b"heim", b"hevm", b"hevc", b"hevx",
149 ];
150 let av1_brands = [
151 b"avif", b"avio", b"avis",
152 b"MA1A", b"MA1B",
155 ];
156 let jpeg_brands = [b"jpeg", b"jpgs"];
157
158 if hevc_brands.contains(&brand) {
169 return Some(Compression::Hevc);
170 }
171
172 if av1_brands.contains(&brand) {
173 return Some(Compression::Av1);
174 }
175
176 if jpeg_brands.contains(&brand) {
177 return Some(Compression::Jpeg);
178 }
179
180 None
181}
182
183fn skip_to_tag<R: BufRead + Seek>(reader: &mut R, tag: &[u8]) -> ImageResult<u32> {
184 let mut tag_buf = [0; 4];
185
186 loop {
187 let size = read_u32(reader, &Endian::Big)?;
188 reader.read_exact(&mut tag_buf)?;
189
190 if tag_buf == tag {
191 return Ok(size);
192 }
193
194 if size >= 8 {
195 reader.seek(SeekFrom::Current(size as i64 - 8))?;
196 } else {
197 return Err(std::io::Error::new(
198 std::io::ErrorKind::InvalidData,
199 format!("Invalid heif box size: {size}"),
200 )
201 .into());
202 }
203 }
204}