use crate::util::*;
use crate::{ImageError, ImageResult, ImageSize};
use std::convert::TryInto;
use std::io::{BufRead, Seek, SeekFrom};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Compression {
Av1,
Hevc,
Jpeg,
Unknown,
}
pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
reader.seek(SeekFrom::Start(0))?;
let ftyp_size = read_u32(reader, &Endian::Big)?;
reader.seek(SeekFrom::Start(ftyp_size.into()))?;
skip_to_tag(reader, b"meta")?;
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;
let mut max_height = 0usize;
let mut found_ispe = false;
let mut rotation = 0u8;
while let Ok((tag, size)) = read_tag(reader) {
if size < 8 {
return Err(ImageError::CorruptedImage);
}
if tag == "ispe" {
found_ispe = true;
read_u32(reader, &Endian::Big)?; let width = read_u32(reader, &Endian::Big)? as usize;
let height = read_u32(reader, &Endian::Big)? as usize;
if width * height > max_width * max_height {
max_width = width;
max_height = height;
}
} else if tag == "irot" {
rotation = read_u8(reader)?;
} else if size >= ipco_size {
break;
} else {
ipco_size -= size;
reader.seek(SeekFrom::Current(size as i64 - 8))?;
}
}
if !found_ispe {
return Err(
std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "Not enough data").into(),
);
}
if rotation == 1 || rotation == 3 {
std::mem::swap(&mut max_width, &mut max_height);
}
Ok(ImageSize {
width: max_width,
height: max_height,
})
}
pub fn matches<R: BufRead + Seek>(header: &[u8], reader: &mut R) -> Option<Compression> {
if header.len() < 12 || &header[4..8] != b"ftyp" {
return None;
}
let brand: [u8; 4] = header[8..12].try_into().unwrap();
if let Some(compression) = inner_matches(&brand) {
return Some(compression);
}
let brands = [b"mif1", b"msf1", b"mif2", b"miaf"];
if brands.contains(&&brand) {
let mut buf = [0; 12];
if reader.read_exact(&mut buf).is_err() {
return Some(Compression::Unknown);
}
let brand2: [u8; 4] = buf[4..8].try_into().unwrap();
if let Some(compression) = inner_matches(&brand2) {
return Some(compression);
}
if brands.contains(&&brand2) {
let brand3: [u8; 4] = buf[8..12].try_into().unwrap();
if let Some(compression) = inner_matches(&brand3) {
return Some(compression);
}
}
}
Some(Compression::Unknown)
}
fn inner_matches(brand: &[u8; 4]) -> Option<Compression> {
let hevc_brands = [
b"heic", b"heix", b"heis", b"hevs", b"heim", b"hevm", b"hevc", b"hevx",
];
let av1_brands = [
b"avif", b"avio", b"avis",
b"MA1A", b"MA1B",
];
let jpeg_brands = [b"jpeg", b"jpgs"];
if hevc_brands.contains(&brand) {
return Some(Compression::Hevc);
}
if av1_brands.contains(&brand) {
return Some(Compression::Av1);
}
if jpeg_brands.contains(&brand) {
return Some(Compression::Jpeg);
}
None
}
fn skip_to_tag<R: BufRead + Seek>(reader: &mut R, tag: &[u8]) -> ImageResult<u32> {
let mut tag_buf = [0; 4];
loop {
let size = read_u32(reader, &Endian::Big)?;
reader.read_exact(&mut tag_buf)?;
if tag_buf == tag {
return Ok(size);
}
if size >= 8 {
reader.seek(SeekFrom::Current(size as i64 - 8))?;
} else {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid heif box size: {}", size),
)
.into());
}
}
}