use std::collections::HashMap;
use std::fmt::{self, Display};
use std::io::{Cursor, Read, Seek, SeekFrom};
use image::error::{
DecodingError, ImageFormatHint, LimitError, LimitErrorKind, UnsupportedError,
UnsupportedErrorKind,
};
use image::{ColorType, ImageDecoder, ImageError, ImageReader, ImageResult, LimitSupport, Limits};
use icns::{Encoding, IconElement, IconType, OSType, PixelFormat};
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
enum DecoderError {
ImageEndAfterEndOfStream,
NotICNS,
DuplicateEntry(IconType),
IncompleteEntry,
NoImageFound,
MissingMask(IconType),
BadEntryLength,
NotPNGorJP2(IconType),
BadPngSize(u32, u32, u32),
BadJp2Size(u32, u32, u32),
}
impl From<DecoderError> for ImageError {
fn from(e: DecoderError) -> ImageError {
ImageError::Decoding(DecodingError::new(icns_format_hint(), e))
}
}
impl Display for DecoderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DecoderError::ImageEndAfterEndOfStream => {
f.write_str("The end of the image would have a stream position >u64::MAX")
}
DecoderError::NotICNS => f.write_str("Image file does not start with 'icns'"),
DecoderError::DuplicateEntry(t) => f.write_fmt(format_args!(
"Image file contains multiple entries of type {t:?}"
)),
DecoderError::MissingMask(t) => f.write_fmt(format_args!(
"Image file did not contain a mask entry for the entry of type {t:?}"
)),
DecoderError::IncompleteEntry => f.write_str(
"The last entry in the file would extend past the end of file indicated by the header"
),
DecoderError::NoImageFound => f.write_str(
"No image entry that the decoder might support was found"
),
DecoderError::BadEntryLength => f.write_str(
"Image file contained an entry with invalid length (less than 8)"
),
DecoderError::NotPNGorJP2(t) => f.write_fmt(format_args!(
"Image file entry of type {t:?} contained neither PNG nor Jpeg 2000 data"
)),
DecoderError::BadPngSize(w,h,s) => f.write_fmt(format_args!(
"Image file entry with PNG data had size {w}x{h} instead of expected {s}x{s}"
)),
DecoderError::BadJp2Size(w,h,s) => f.write_fmt(format_args!(
"Image file entry with Jpeg 2000 data had size {w}x{h} instead of expected {s}x{s}"
)),
}
}
}
impl std::error::Error for DecoderError {}
fn icns_format_hint() -> ImageFormatHint {
ImageFormatHint::Name("ICNS".into())
}
fn png_to_image_error(err: png::DecodingError) -> ImageError {
use png::DecodingError::*;
match err {
IoError(err) => ImageError::IoError(err),
err @ Format(_) => ImageError::Decoding(DecodingError::new(icns_format_hint(), err)),
Parameter(_) => unreachable!(),
LimitsExceeded => {
ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory))
}
}
}
fn jp2_to_image_error(err: ImageError) -> ImageError {
match err {
ImageError::Decoding(e) => ImageError::Decoding(DecodingError::new(icns_format_hint(), e)),
ImageError::Encoding(_) => {
unreachable!();
}
ImageError::Parameter(e) => ImageError::Parameter(e),
ImageError::Limits(e) => ImageError::Limits(e),
ImageError::Unsupported(e) => {
ImageError::Decoding(DecodingError::new(icns_format_hint(), e))
}
ImageError::IoError(e) => ImageError::IoError(e),
}
}
pub type SubformatDecodeFn = Box<dyn Fn(&[u8], u32, &mut [u8], u64) -> ImageResult<()>>;
fn decode_png(data: &[u8], size: u32, buf: &mut [u8], allocation_limit: u64) -> ImageResult<()> {
let mut decoder = png::Decoder::new_with_limits(
Cursor::new(data),
png::Limits {
bytes: allocation_limit.try_into().unwrap_or(usize::MAX),
},
);
decoder.set_transformations(png::Transformations::STRIP_16 | png::Transformations::ALPHA);
let info = decoder.read_header_info().map_err(png_to_image_error)?;
if info.width != size || info.height != size {
return Err(DecoderError::BadPngSize(info.width, info.height, size).into());
}
let mut reader = decoder.read_info().map_err(png_to_image_error)?;
let (color_type, bits) = reader.output_color_type();
assert!(bits == png::BitDepth::Eight);
match color_type {
png::ColorType::GrayscaleAlpha => {
let mut tmp = vec![0u8; (size as usize) * (size as usize) * 2];
reader.next_frame(&mut tmp).map_err(png_to_image_error)?;
for (ga, rgba) in tmp.chunks_exact(2).zip(buf.chunks_exact_mut(4)) {
rgba.copy_from_slice(&[ga[0], ga[0], ga[0], ga[1]]);
}
}
png::ColorType::Rgba => {
reader.next_frame(buf).map_err(png_to_image_error)?;
}
_ => unreachable!(),
}
reader.finish().map_err(png_to_image_error)?;
Ok(())
}
pub fn decode_jpeg2000_using_hook(
data: &[u8],
size: u32,
buf: &mut [u8],
allocation_limit: u64,
) -> ImageResult<()> {
let mut reader = ImageReader::new(Cursor::new(data));
reader = reader.with_guessed_format()?;
let mut limits = Limits::no_limits();
limits.max_alloc = Some(allocation_limit);
reader.limits(limits);
let image = reader.decode().map_err(jp2_to_image_error)?;
if image.width() != size || image.height() != size {
return Err(DecoderError::BadJp2Size(image.width(), image.height(), size).into());
}
buf.copy_from_slice(image.to_rgba8().as_flat_samples().samples);
Ok(())
}
pub fn unsupported_jpeg2000(
_data: &[u8],
_size: u32,
_buf: &mut [u8],
_allocation_limit: u64,
) -> ImageResult<()> {
Err(ImageError::Unsupported(
UnsupportedError::from_format_and_kind(
icns_format_hint(),
UnsupportedErrorKind::GenericFeature("Jpeg 2000 subimage".to_string()),
),
))
}
#[derive(Clone, Copy)]
struct IcnsEntry {
code: IconType,
stream_pos: u64,
length: u32,
}
impl IcnsEntry {
fn score(&self) -> (u32, u8, u64) {
let bit_depth = match self.code.encoding() {
Encoding::Mask8 => {
panic!("Entry with color data required, not Mask8");
}
Encoding::Mono => 1,
Encoding::MonoA => 2,
Encoding::Palette4 => 5,
Encoding::Palette8 => 9,
Encoding::RLE24 => 32,
Encoding::JP2PNG => 32,
};
(
self.code.pixel_width(),
bit_depth,
u64::MAX - self.stream_pos,
)
}
}
pub struct IcnsDecoder<R> {
reader: R,
main: IcnsEntry,
mask: Option<IcnsEntry>,
limits: Limits,
jp2: SubformatDecodeFn,
}
fn read_vec_at<R>(reader: &mut R, start: u64, len: u32) -> Result<Vec<u8>, std::io::Error>
where
R: Read + Seek,
{
assert!(start.checked_add(len as u64).is_some());
reader.seek(SeekFrom::Start(start))?;
let mut data = vec![0; len.try_into().unwrap()];
reader.read_exact(&mut data)?;
Ok(data)
}
impl<R> IcnsDecoder<R>
where
R: Read + Seek,
{
pub fn new(reader: R) -> Result<IcnsDecoder<R>, ImageError> {
Self::new_with_decode_func(reader, Box::new(unsupported_jpeg2000))
}
pub fn new_with_decode_func(
mut reader: R,
jp2: SubformatDecodeFn,
) -> Result<IcnsDecoder<R>, ImageError> {
let mut header = [0u8; 8];
reader.read_exact(&mut header)?;
let (magic, file_len_field) = header.split_at(4);
if magic != b"icns" {
return Err(DecoderError::NotICNS.into());
}
let file_length = u32::from_be_bytes(file_len_field.try_into().unwrap());
let Some(remaining_len) = file_length.checked_sub(8) else {
return Err(DecoderError::BadEntryLength.into());
};
let mut first_entries: HashMap<IconType, IcnsEntry> = HashMap::new();
let base_pos = reader.stream_position()?;
let Some(end) = base_pos.checked_add(u64::from(remaining_len)) else {
return Err(DecoderError::ImageEndAfterEndOfStream.into());
};
let mut cur_pos = base_pos;
while cur_pos < end {
let image_start_pos = cur_pos;
if cur_pos > end.saturating_sub(8) {
return Err(DecoderError::IncompleteEntry.into());
}
let mut entry = [0u8; 8];
reader.read_exact(&mut entry)?;
let (ostype_field, entry_len_field) = entry.split_at(4);
let ostype = OSType(ostype_field.try_into().unwrap());
let entry_len = u32::from_be_bytes(entry_len_field.try_into().unwrap());
let Some(data_len) = entry_len.checked_sub(8) else {
return Err(DecoderError::BadEntryLength.into());
};
if cur_pos > end.saturating_sub(entry_len as u64) {
return Err(DecoderError::IncompleteEntry.into());
}
if cur_pos < end {
reader.seek_relative(data_len as i64)?;
}
cur_pos += entry_len as u64;
let Some(code) = IconType::from_ostype(ostype) else {
continue;
};
let entry = IcnsEntry {
code,
stream_pos: image_start_pos + 8,
length: data_len,
};
if first_entries.insert(code, entry).is_some() {
return Err(DecoderError::DuplicateEntry(code).into());
}
}
let main_entry = first_entries
.values()
.filter(|entry| entry.code.encoding() != Encoding::Mask8)
.max_by_key(|entry| entry.score());
let Some(main) = main_entry.copied() else {
return Err(DecoderError::NoImageFound.into());
};
let mut mask = None;
if let Some(mtype) = main.code.mask_type() {
mask = first_entries.get(&mtype).copied();
if mask.is_none() {
return Err(DecoderError::MissingMask(main.code).into());
}
}
Ok(IcnsDecoder {
reader,
main,
mask,
limits: Limits::no_limits(),
jp2,
})
}
}
impl<R: Read + Seek> ImageDecoder for IcnsDecoder<R> {
fn dimensions(&self) -> (u32, u32) {
(self.main.code.pixel_width(), self.main.code.pixel_height())
}
fn color_type(&self) -> ColorType {
match self.main.code.encoding() {
Encoding::Mask8 => unreachable!(),
Encoding::Mono => ColorType::L8,
Encoding::MonoA => ColorType::La8,
_ => ColorType::Rgba8,
}
}
fn set_limits(&mut self, mut limits: Limits) -> ImageResult<()> {
limits.check_support(&LimitSupport::default())?;
let (width, height) = self.dimensions();
limits.check_dimensions(width, height)?;
let icon_size = self.main.code.pixel_width();
let main_data = self.main.length;
let mask_data = self.mask.map(|x| x.length).unwrap_or_default();
assert!(icon_size <= 1024);
let icon_decode_space = icon_size * icon_size * 8;
let space_req = u64::from(icon_decode_space) + u64::from(main_data) + u64::from(mask_data);
limits.reserve(space_req)?;
self.limits = limits;
Ok(())
}
fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
assert!(self.total_bytes() == buf.len().try_into().unwrap());
let main_data = read_vec_at(&mut self.reader, self.main.stream_pos, self.main.length)?;
const PNG_SIGNATURE: &[u8] = &[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
const JP2_SIGNATURE: &[u8] = &[
0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A,
];
if self.main.code.encoding() == Encoding::JP2PNG {
if main_data.starts_with(PNG_SIGNATURE) {
decode_png(
&main_data,
self.main.code.pixel_width(),
buf,
self.limits.max_alloc.unwrap_or(u64::MAX),
)?;
} else if main_data.starts_with(JP2_SIGNATURE) {
(self.jp2)(
&main_data,
self.main.code.pixel_width(),
buf,
self.limits.max_alloc.unwrap_or(u64::MAX),
)?;
} else {
return Err(DecoderError::NotPNGorJP2(self.main.code).into());
}
} else {
let main = IconElement::new(self.main.code.ostype(), main_data);
let img = if let Some(mask_entry) = &self.mask {
let mask_data =
read_vec_at(&mut self.reader, mask_entry.stream_pos, mask_entry.length)?;
let mask = IconElement::new(mask_entry.code.ostype(), mask_data);
main.decode_image_with_mask(&mask)?
} else {
assert!(self.main.code.mask_type().is_none());
main.decode_image()?
};
assert!((img.width(), img.height()) == self.dimensions());
assert!(img.pixel_format() != PixelFormat::Alpha);
match (img.pixel_format(), self.color_type()) {
(PixelFormat::Gray, ColorType::L8) => {
buf.copy_from_slice(img.data());
}
(PixelFormat::GrayAlpha, ColorType::La8) => {
buf.copy_from_slice(img.data());
}
(PixelFormat::RGBA, ColorType::Rgba8)
| (PixelFormat::RGB, ColorType::Rgba8)
| (PixelFormat::Gray, ColorType::Rgba8)
| (PixelFormat::GrayAlpha, ColorType::Rgba8) => {
let converted = img.convert_to(PixelFormat::RGBA);
buf.copy_from_slice(converted.data());
}
_ => unreachable!(
"icns crate produced {:?}, not compatible with {:?}",
img.pixel_format(),
self.color_type()
),
};
}
Ok(())
}
fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
(*self).read_image(buf)
}
}