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            return Err(ImageError::CorruptedImage);
45        }
46
47        if tag == "ispe" {
49            found_ispe = true;
50            read_u32(reader, &Endian::Big)?; let width = read_u32(reader, &Endian::Big)? as usize;
52            let height = read_u32(reader, &Endian::Big)? as usize;
53
54            if width * height > max_width * max_height {
56                max_width = width;
57                max_height = height;
58            }
59        } else if tag == "irot" {
60            rotation = read_u8(reader)?;
62        } else if size >= ipco_size {
63            break;
65        } else {
66            ipco_size -= size;
69            reader.seek(SeekFrom::Current(size as i64 - 8))?;
70        }
71    }
72
73    if !found_ispe {
75        return Err(
76            std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "Not enough data").into(),
77        );
78    }
79
80    if rotation == 1 || rotation == 3 {
83        std::mem::swap(&mut max_width, &mut max_height);
84    }
85
86    Ok(ImageSize {
87        width: max_width,
88        height: max_height,
89    })
90}
91
92pub fn matches<R: BufRead + Seek>(header: &[u8], reader: &mut R) -> Option<Compression> {
93    if header.len() < 12 || &header[4..8] != b"ftyp" {
94        return None;
95    }
96
97    let brand: [u8; 4] = header[8..12].try_into().unwrap();
98
99    if let Some(compression) = inner_matches(&brand) {
100        return Some(compression);
102    }
103
104    let brands = [b"mif1", b"msf1", b"mif2", b"miaf"];
106
107    if brands.contains(&&brand) {
108        let mut buf = [0; 12];
109
110        if reader.read_exact(&mut buf).is_err() {
111            return Some(Compression::Unknown);
112        }
113
114        let brand2: [u8; 4] = buf[4..8].try_into().unwrap();
115
116        if let Some(compression) = inner_matches(&brand2) {
117            return Some(compression);
120        }
121
122        if brands.contains(&&brand2) {
123            let brand3: [u8; 4] = buf[8..12].try_into().unwrap();
126
127            if let Some(compression) = inner_matches(&brand3) {
128                return Some(compression);
129            }
130        }
131    }
132
133    Some(Compression::Unknown)
134}
135
136fn inner_matches(brand: &[u8; 4]) -> Option<Compression> {
137    let hevc_brands = [
141        b"heic", b"heix", b"heis", b"hevs", b"heim", b"hevm", b"hevc", b"hevx",
142    ];
143    let av1_brands = [
144        b"avif", b"avio", b"avis",
145        b"MA1A", b"MA1B",
148    ];
149    let jpeg_brands = [b"jpeg", b"jpgs"];
150
151    if hevc_brands.contains(&brand) {
162        return Some(Compression::Hevc);
163    }
164
165    if av1_brands.contains(&brand) {
166        return Some(Compression::Av1);
167    }
168
169    if jpeg_brands.contains(&brand) {
170        return Some(Compression::Jpeg);
171    }
172
173    None
174}
175
176fn skip_to_tag<R: BufRead + Seek>(reader: &mut R, tag: &[u8]) -> ImageResult<u32> {
177    let mut tag_buf = [0; 4];
178
179    loop {
180        let size = read_u32(reader, &Endian::Big)?;
181        reader.read_exact(&mut tag_buf)?;
182
183        if tag_buf == tag {
184            return Ok(size);
185        }
186
187        if size >= 8 {
188            reader.seek(SeekFrom::Current(size as i64 - 8))?;
189        } else {
190            return Err(std::io::Error::new(
191                std::io::ErrorKind::InvalidData,
192                format!("Invalid heif box size: {}", size),
193            )
194            .into());
195        }
196    }
197}