use byteorder::{LittleEndian, ReadBytesExt};
use std::convert::TryFrom;
use std::io::{self, Cursor, Read, Seek, SeekFrom};
use std::marker::PhantomData;
use std::mem;
use crate::color::ColorType;
use crate::error::{ImageError, ImageResult};
use crate::image::{self, ImageDecoder};
use self::InnerDecoder::*;
use crate::bmp::BmpDecoder;
use crate::png::PngDecoder;
const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10];
pub struct IcoDecoder<R: Read> {
selected_entry: DirEntry,
inner_decoder: InnerDecoder<R>,
}
enum InnerDecoder<R: Read> {
BMP(BmpDecoder<R>),
PNG(PngDecoder<R>),
}
#[derive(Clone, Copy, Default)]
struct DirEntry {
width: u8,
height: u8,
color_count: u8,
reserved: u8,
num_color_planes: u16,
bits_per_pixel: u16,
image_length: u32,
image_offset: u32,
}
impl<R: Read + Seek> IcoDecoder<R> {
pub fn new(mut r: R) -> ImageResult<IcoDecoder<R>> {
let entries = read_entries(&mut r)?;
let entry = best_entry(entries)?;
let decoder = entry.decoder(r)?;
Ok(IcoDecoder {
selected_entry: entry,
inner_decoder: decoder,
})
}
}
fn read_entries<R: Read>(r: &mut R) -> ImageResult<Vec<DirEntry>> {
let _reserved = r.read_u16::<LittleEndian>()?;
let _type = r.read_u16::<LittleEndian>()?;
let count = r.read_u16::<LittleEndian>()?;
(0..count).map(|_| read_entry(r)).collect()
}
fn read_entry<R: Read>(r: &mut R) -> ImageResult<DirEntry> {
let mut entry = DirEntry::default();
entry.width = r.read_u8()?;
entry.height = r.read_u8()?;
entry.color_count = r.read_u8()?;
entry.reserved = r.read_u8()?;
entry.num_color_planes = r.read_u16::<LittleEndian>()?;
if entry.num_color_planes > 256 {
return Err(ImageError::FormatError(
"ICO image entry has a too large color planes/hotspot value".to_string(),
));
}
entry.bits_per_pixel = r.read_u16::<LittleEndian>()?;
if entry.bits_per_pixel > 256 {
return Err(ImageError::FormatError(
"ICO image entry has a too large bits per pixel/hotspot value".to_string(),
));
}
entry.image_length = r.read_u32::<LittleEndian>()?;
entry.image_offset = r.read_u32::<LittleEndian>()?;
Ok(entry)
}
fn best_entry(mut entries: Vec<DirEntry>) -> ImageResult<DirEntry> {
let mut best = entries.pop().ok_or_else(|| ImageError::FormatError(
"ICO directory contains no image".to_string(),
))?;
let mut best_score = (
best.bits_per_pixel,
u32::from(best.real_width()) * u32::from(best.real_height()),
);
for entry in entries {
let score = (
entry.bits_per_pixel,
u32::from(entry.real_width()) * u32::from(entry.real_height()),
);
if score > best_score {
best = entry;
best_score = score;
}
}
Ok(best)
}
impl DirEntry {
fn real_width(&self) -> u16 {
match self.width {
0 => 256,
w => u16::from(w),
}
}
fn real_height(&self) -> u16 {
match self.height {
0 => 256,
h => u16::from(h),
}
}
fn matches_dimensions(&self, width: u32, height: u32) -> bool {
u32::from(self.real_width()) == width && u32::from(self.real_height()) == height
}
fn seek_to_start<R: Read + Seek>(&self, r: &mut R) -> ImageResult<()> {
r.seek(SeekFrom::Start(u64::from(self.image_offset)))?;
Ok(())
}
fn is_png<R: Read + Seek>(&self, r: &mut R) -> ImageResult<bool> {
self.seek_to_start(r)?;
let mut signature = [0u8; 8];
r.read_exact(&mut signature)?;
Ok(signature == PNG_SIGNATURE)
}
fn decoder<R: Read + Seek>(&self, mut r: R) -> ImageResult<InnerDecoder<R>> {
let is_png = self.is_png(&mut r)?;
self.seek_to_start(&mut r)?;
if is_png {
Ok(PNG(PngDecoder::new(r)?))
} else {
Ok(BMP(BmpDecoder::new_with_ico_format(r)?))
}
}
}
pub struct IcoReader<R>(Cursor<Vec<u8>>, PhantomData<R>);
impl<R> Read for IcoReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
if self.0.position() == 0 && buf.is_empty() {
mem::swap(buf, self.0.get_mut());
Ok(buf.len())
} else {
self.0.read_to_end(buf)
}
}
}
impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for IcoDecoder<R> {
type Reader = IcoReader<R>;
fn dimensions(&self) -> (u32, u32) {
match self.inner_decoder {
BMP(ref decoder) => decoder.dimensions(),
PNG(ref decoder) => decoder.dimensions(),
}
}
fn color_type(&self) -> ColorType {
match self.inner_decoder {
BMP(ref decoder) => decoder.color_type(),
PNG(ref decoder) => decoder.color_type(),
}
}
fn into_reader(self) -> ImageResult<Self::Reader> {
Ok(IcoReader(Cursor::new(image::decoder_to_vec(self)?), PhantomData))
}
fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
match self.inner_decoder {
PNG(decoder) => {
if self.selected_entry.image_length < PNG_SIGNATURE.len() as u32 {
return Err(ImageError::FormatError(
"Entry specified a length that is shorter than PNG header!".to_string(),
));
}
let (width, height) = decoder.dimensions();
if !self.selected_entry.matches_dimensions(width, height) {
return Err(ImageError::FormatError(
"Entry and PNG dimensions do not match!".to_string(),
));
}
let color_type = decoder.color_type();
if let ColorType::Rgba8 = color_type {
} else {
return Err(ImageError::FormatError(
"The PNG is not in RGBA format!".to_string(),
));
}
decoder.read_image(buf)
}
BMP(mut decoder) => {
let (width, height) = decoder.dimensions();
if !self.selected_entry.matches_dimensions(width, height) {
return Err(ImageError::FormatError(
"Entry({:?}) and BMP({:?}) dimensions do not match!".to_string(),
));
}
if decoder.color_type() != ColorType::Rgba8 {
return Err(ImageError::UnsupportedColor(decoder.color_type().into()));
}
decoder.read_image_data(buf)?;
let r = decoder.reader();
let mask_start = r.seek(SeekFrom::Current(0))?;
let mask_end =
u64::from(self.selected_entry.image_offset + self.selected_entry.image_length);
let mask_length = mask_end - mask_start;
if mask_length > 0 {
let mask_row_bytes = ((width + 31) / 32) * 4;
let expected_length = u64::from(mask_row_bytes) * u64::from(height);
if mask_length < expected_length {
return Err(ImageError::FormatError(
"ICO mask too short for the image".to_string(),
));
}
for y in 0..height {
let mut x = 0;
for _ in 0..mask_row_bytes {
let mask_byte = r.read_u8()?;
for bit in (0..8).rev() {
if x >= width {
break;
}
if mask_byte & (1 << bit) != 0 {
buf[((height - y - 1) * width + x) as usize * 4 + 3] = 0;
}
x += 1;
}
}
}
}
Ok(())
}
}
}
}