ai_imagesize/container/
heif.rs1use crate::util::*;
2use crate::{ImageError, ImageResult, ImageSize};
3
4use no_std_io::io::{BufRead, Seek, SeekFrom};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
8pub enum Compression {
9 Av1,
10 Hevc,
11 Jpeg,
12 Unknown,
13 }
18
19pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
20 reader.seek(SeekFrom::Start(0))?;
21 let ftyp_size = read_u32(reader, &Endian::Big)?;
23
24 reader.seek(SeekFrom::Start(ftyp_size.into()))?;
26
27 skip_to_tag(reader, b"meta")?;
29 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;
36 let mut max_height = 0usize;
37 let mut found_ispe = false;
38 let mut rotation = 0u8;
39
40 while let Ok((tag, size)) = read_tag(reader) {
41 if size < 8 {
43 return Err(ImageError::CorruptedImage);
44 }
45
46 if tag == "ispe" {
48 found_ispe = true;
49 read_u32(reader, &Endian::Big)?; let width = read_u32(reader, &Endian::Big)? as usize;
51 let height = read_u32(reader, &Endian::Big)? as usize;
52
53 if width * height > max_width * max_height {
55 max_width = width;
56 max_height = height;
57 }
58 } else if tag == "irot" {
59 rotation = read_u8(reader)?;
61 } else if size >= ipco_size {
62 break;
64 } else {
65 ipco_size -= size;
68 reader.seek(SeekFrom::Current(size as i64 - 8))?;
69 }
70 }
71
72 if !found_ispe {
74 return Err(
75 no_std_io::io::Error::new(no_std_io::io::ErrorKind::UnexpectedEof, "Not enough data").into(),
76 );
77 }
78
79 if rotation == 1 || rotation == 3 {
82 core::mem::swap(&mut max_width, &mut max_height);
83 }
84
85 Ok(ImageSize {
86 width: max_width,
87 height: max_height,
88 })
89}
90
91pub fn matches<R: BufRead + Seek>(header: &[u8], reader: &mut R) -> Option<Compression> {
92 if header.len() < 12 || &header[4..8] != b"ftyp" {
93 return None;
94 }
95
96 let brand: [u8; 4] = header[8..12].try_into().unwrap();
97
98 if let Some(compression) = inner_matches(&brand) {
99 return Some(compression);
101 }
102
103 let brands = [b"mif1", b"msf1", b"mif2", b"miaf"];
105
106 if brands.contains(&&brand) {
107 let mut buf = [0; 12];
108
109 if reader.read_exact(&mut buf).is_err() {
110 return Some(Compression::Unknown);
111 }
112
113 let brand2: [u8; 4] = buf[4..8].try_into().unwrap();
114
115 if let Some(compression) = inner_matches(&brand2) {
116 return Some(compression);
119 }
120
121 if brands.contains(&&brand2) {
122 let brand3: [u8; 4] = buf[8..12].try_into().unwrap();
125
126 if let Some(compression) = inner_matches(&brand3) {
127 return Some(compression);
128 }
129 }
130 }
131
132 Some(Compression::Unknown)
133}
134
135fn inner_matches(brand: &[u8; 4]) -> Option<Compression> {
136 let hevc_brands = [
140 b"heic", b"heix", b"heis", b"hevs", b"heim", b"hevm", b"hevc", b"hevx",
141 ];
142 let av1_brands = [
143 b"avif", b"avio", b"avis",
144 b"MA1A", b"MA1B",
147 ];
148 let jpeg_brands = [b"jpeg", b"jpgs"];
149
150 if hevc_brands.contains(&brand) {
161 return Some(Compression::Hevc);
162 }
163
164 if av1_brands.contains(&brand) {
165 return Some(Compression::Av1);
166 }
167
168 if jpeg_brands.contains(&brand) {
169 return Some(Compression::Jpeg);
170 }
171
172 None
173}
174
175fn skip_to_tag<R: BufRead + Seek>(reader: &mut R, tag: &[u8]) -> ImageResult<u32> {
176 let mut tag_buf = [0; 4];
177
178 loop {
179 let size = read_u32(reader, &Endian::Big)?;
180 reader.read_exact(&mut tag_buf)?;
181
182 if tag_buf == tag {
183 return Ok(size);
184 }
185
186 if size >= 8 {
187 reader.seek(SeekFrom::Current(size as i64 - 8))?;
188 } else {
189 return Err(no_std_io::io::Error::new(
190 no_std_io::io::ErrorKind::InvalidData,
191 "Invalid heif box size",
192 )
193 .into());
194 }
195 }
196}