use std::error::Error;
use std::fmt;
use std::fs::File;
use std::path::Path;
use std::io::{BufRead, BufReader, Cursor, Read, Seek, SeekFrom};
#[derive(Debug)]
pub enum ImageError {
NotSupported,
CorruptedImage,
IoError(std::io::Error),
}
impl Error for ImageError {}
impl fmt::Display for ImageError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::ImageError::*;
match self {
NotSupported => f.write_str("Could not decode image"),
CorruptedImage => f.write_str("Hit end of file before finding size"),
IoError(error) => error.fmt(f),
}
}
}
impl From<std::io::Error> for ImageError {
fn from(err: std::io::Error) -> ImageError {
ImageError::IoError(err)
}
}
pub type ImageResult<T> = Result<T, ImageError>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ImageType {
Bmp,
Gif,
Heif,
Jpeg,
Png,
Psd,
Tiff,
Webp,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ImageSize {
pub width: usize,
pub height: usize,
}
enum Endian {
Little,
Big,
}
pub fn image_type(header: &[u8]) -> ImageResult<ImageType> {
if header.len() >= 2 {
if &header[0..2] == b"\x42\x4D" {
return Ok(ImageType::Bmp);
} else if header.len() >= 3 && &header[0..3] == b"\xFF\xD8\xFF" {
return Ok(ImageType::Jpeg);
} else if header.len() >= 4 && &header[0..4] == b"\x89PNG" {
return Ok(ImageType::Png);
} else if header.len() >= 4 && &header[0..4] == b"GIF8" {
return Ok(ImageType::Gif);
} else if header.len() >= 4 && (&header[0..4] == b"II\x2A\x00" || &header[0..4] == b"MM\x00\x2A") {
return Ok(ImageType::Tiff);
} else if header.len() >= 4 && &header[0..4] == b"8BPS" {
return Ok(ImageType::Psd);
} else if header.len() >= 8 &&
&header[4..8] == b"ftyp" {
return Ok(ImageType::Heif);
} else if header.len() >= 12 &&
&header[0..4] == b"RIFF" &&
&header[8..12] == b"WEBP"{
return Ok(ImageType::Webp);
} else {
return Err(ImageError::NotSupported);
}
}
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "Not enough data").into())
}
pub fn size<P>(path: P) -> ImageResult<ImageSize> where P: AsRef<Path> {
let file = File::open(path)?;
let mut reader = BufReader::new(file);
let mut header = [0; 12];
reader.read_exact(&mut header)?;
dispatch_header(&mut reader, &header)
}
pub fn blob_size(data: &[u8]) -> ImageResult<ImageSize> {
let mut reader = Cursor::new(&data[..]);
let mut header = [0; 12];
reader.read_exact(&mut header)?;
dispatch_header(&mut reader, &header)
}
fn dispatch_header<R: BufRead + Seek>(reader: &mut R, header: &[u8]) -> ImageResult<ImageSize> {
match image_type(&header)? {
ImageType::Bmp => bmp_size(reader),
ImageType::Gif => gif_size(header),
ImageType::Heif => heif_size(reader),
ImageType::Jpeg => jpeg_size(reader),
ImageType::Png => png_size(reader),
ImageType::Psd => psd_size(reader),
ImageType::Tiff => tiff_size(reader),
ImageType::Webp => webp_size(reader),
}
}
fn bmp_size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
reader.seek(SeekFrom::Start(0x12))?;
Ok(ImageSize {
width: read_u32(reader, &Endian::Little)? as usize,
height: read_u32(reader, &Endian::Little)? as usize,
})
}
fn gif_size(header: &[u8]) -> ImageResult<ImageSize> {
Ok(ImageSize {
width: ((header[6] as usize) | ((header[7] as usize) << 8)),
height: ((header[8] as usize) | ((header[9] as usize) << 8))
})
}
fn heif_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)) = next_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
})
}
fn next_tag<R: BufRead + Seek>(reader: &mut R) -> ImageResult<(String, usize)> {
let mut tag_buf = [0; 4];
let size = read_u32(reader, &Endian::Big)? as usize;
reader.read_exact(&mut tag_buf)?;
Ok((String::from_utf8_lossy(&tag_buf).into_owned(), size))
}
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 {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, format!("Invalid heif box size: {}", size)).into());
}
reader.seek(SeekFrom::Current(size as i64 - 8))?;
}
}
fn jpeg_size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
let mut marker = [0; 2];
let mut depth = 0i32;
reader.seek(SeekFrom::Start(2))?;
loop {
reader.read_exact(&mut marker)?;
if marker[0] != 0xFF {
return Err(ImageError::CorruptedImage);
}
let page = marker[1];
if (page >= 0xC0 && page <= 0xC3) || (page >= 0xC5 && page <= 0xC7) ||
(page >= 0xC9 && page <= 0xCB) || (page >= 0xCD && page <= 0xCF) {
if depth == 0 {
reader.seek(SeekFrom::Current(3))?;
break;
}
} else if page == 0xD8 {
depth += 1;
} else if page == 0xD9 {
depth -= 1;
if depth < 0 {
return Err(ImageError::CorruptedImage);
}
}
let page_size = read_u16(reader, &Endian::Big)? as i64;
reader.seek(SeekFrom::Current(page_size - 2))?;
}
Ok(ImageSize {
height: read_u16(reader, &Endian::Big)? as usize,
width: read_u16(reader, &Endian::Big)? as usize,
})
}
fn png_size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
reader.seek(SeekFrom::Start(0x10))?;
Ok(ImageSize {
width: read_u32(reader, &Endian::Big)? as usize,
height: read_u32(reader, &Endian::Big)? as usize,
})
}
fn psd_size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
reader.seek(SeekFrom::Start(0x0E))?;
Ok(ImageSize {
height: read_u32(reader, &Endian::Big)? as usize,
width: read_u32(reader, &Endian::Big)? as usize,
})
}
fn tiff_size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
reader.seek(SeekFrom::Start(0))?;
let mut endian_marker = [0; 2];
reader.read_exact(&mut endian_marker)?;
let endianness = if &endian_marker[0..2] == b"II" {
Endian::Little
} else if &endian_marker[0..2] == b"MM" {
Endian::Big
} else {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid TIFF header").into())
};
reader.seek(SeekFrom::Start(4))?;
let ifd_offset = read_u32(reader, &endianness)?;
if ifd_offset == 0 {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid IFD offset").into())
}
reader.seek(SeekFrom::Start(ifd_offset.into()))?;
let ifd_count = read_u16(reader, &endianness)?;
let mut width = None;
let mut height = None;
for _ifd in 0..ifd_count {
let tag = read_u16(reader, &endianness)?;
if tag == 0x100 {
reader.seek(SeekFrom::Current(6))?;
width = Some(read_u32(reader, &endianness)?);
}
else if tag == 0x101 {
reader.seek(SeekFrom::Current(6))?;
height = Some(read_u32(reader, &endianness)?);
} else {
let kind = read_u16(reader, &endianness)?;
let count = read_u32(reader, &endianness)? as i64;
let skip_count = match kind {
1 | 2 => count,
3 => count * 2,
4 => count * 4,
5 => count * 8,
_ => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid IDF type").into()),
};
reader.seek(SeekFrom::Current(skip_count))?;
}
if let (Some(width), Some(height)) = (width, height) {
return Ok(ImageSize {
width: width as usize,
height: height as usize,
});
}
}
Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "No dimensions in IFD tags").into())
}
fn webp_size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
let mut buffer = [0; 4];
reader.read_exact(&mut buffer)?;
if buffer[3] == b' ' {
webp_vp8_size(reader)
} else if buffer[3] == b'L' {
webp_vp8l_size(reader)
} else if buffer[3] == b'X' {
webp_vp8x_size(reader)
} else {
Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid VP8 Tag").into())
}
}
fn webp_vp8x_size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
reader.seek(SeekFrom::Start(0x18))?;
Ok(ImageSize {
width: read_u24(reader, &Endian::Little)? as usize + 1,
height: read_u24(reader, &Endian::Little)? as usize + 1,
})
}
fn webp_vp8l_size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
reader.seek(SeekFrom::Start(0x15))?;
let dims = read_u32(reader, &Endian::Little)?;
Ok(ImageSize {
width: (dims & 0x3FFF) as usize + 1,
height: ((dims >> 14) & 0x3FFF) as usize + 1,
})
}
fn webp_vp8_size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
reader.seek(SeekFrom::Start(0x1A))?;
Ok(ImageSize {
width: read_u16(reader, &Endian::Little)? as usize,
height: read_u16(reader, &Endian::Little)? as usize,
})
}
fn read_u32<R: BufRead + Seek>(reader: &mut R, endianness: &Endian) -> ImageResult<u32> {
let mut buf = [0; 4];
reader.read_exact(&mut buf)?;
match endianness {
Endian::Little => Ok(((buf[3] as u32) << 24) | ((buf[2] as u32) << 16) | ((buf[1] as u32) << 8) | (buf[0] as u32)),
Endian::Big => Ok(((buf[0] as u32) << 24) | ((buf[1] as u32) << 16) | ((buf[2] as u32) << 8) | (buf[3] as u32)),
}
}
fn read_u24<R: BufRead + Seek>(reader: &mut R, endianness: &Endian) -> ImageResult<u32> {
let mut buf = [0; 3];
reader.read_exact(&mut buf)?;
match endianness {
Endian::Little => Ok(((buf[2] as u32) << 16) | ((buf[1] as u32) << 8) | (buf[0] as u32)),
Endian::Big => Ok(((buf[0] as u32) << 16) | ((buf[1] as u32) << 8) | (buf[2] as u32)),
}
}
fn read_u16<R: BufRead + Seek>(reader: &mut R, endianness: &Endian) -> ImageResult<u16> {
let mut buf = [0; 2];
reader.read_exact(&mut buf)?;
match endianness {
Endian::Little => Ok(((buf[1] as u16) << 8) | (buf[0] as u16)),
Endian::Big => Ok(((buf[0] as u16) << 8) | (buf[1] as u16)),
}
}
fn read_u8<R: BufRead + Seek>(reader: &mut R) -> ImageResult<u8> {
let mut buf = [0; 1];
reader.read_exact(&mut buf)?;
Ok(buf[0])
}