in image/src/codecs/ico/decoder.rs [295:385]
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(DecoderError::PngShorterThanHeader.into());
}
// Check if the image dimensions match the ones in the image data.
let (width, height) = decoder.dimensions();
if !self.selected_entry.matches_dimensions(width, height) {
return Err(DecoderError::ImageEntryDimensionMismatch {
format: IcoEntryImageFormat::Png,
entry: (self.selected_entry.real_width(), self.selected_entry.real_height()),
image: (width, height)
}.into());
}
// Embedded PNG images can only be of the 32BPP RGBA format.
// https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473/
if decoder.color_type() != ColorType::Rgba8 {
return Err(DecoderError::PngNotRgba.into());
}
decoder.read_image(buf)
}
BMP(mut decoder) => {
let (width, height) = decoder.dimensions();
if !self.selected_entry.matches_dimensions(width, height) {
return Err(DecoderError::ImageEntryDimensionMismatch {
format: IcoEntryImageFormat::Bmp,
entry: (self.selected_entry.real_width(), self.selected_entry.real_height()),
image: (width, height)
}.into());
}
// The ICO decoder needs an alpha channel to apply the AND mask.
if decoder.color_type() != ColorType::Rgba8 {
return Err(ImageError::Unsupported(UnsupportedError::from_format_and_kind(
ImageFormat::Bmp.into(),
UnsupportedErrorKind::Color(decoder.color_type().into()),
)));
}
decoder.read_image_data(buf)?;
let r = decoder.reader();
let image_end = r.seek(SeekFrom::Current(0))?;
let data_end =
u64::from(self.selected_entry.image_offset + self.selected_entry.image_length);
let mask_row_bytes = ((width + 31) / 32) * 4;
let mask_length = u64::from(mask_row_bytes) * u64::from(height);
// data_end should be image_end + the mask length (mask_row_bytes * height).
// According to
// https://devblogs.microsoft.com/oldnewthing/20101021-00/?p=12483
// the mask is required, but according to Wikipedia
// https://en.wikipedia.org/wiki/ICO_(file_format)
// the mask is not required. Unfortunately, Wikipedia does not have a citation
// for that claim, so we can't be sure which is correct.
if data_end >= image_end + mask_length {
// If there's an AND mask following the image, read and apply it.
for y in 0..height {
let mut x = 0;
for _ in 0..mask_row_bytes {
// Apply the bits of each byte until we reach the end of the row.
let mask_byte = r.read_u8()?;
for bit in (0..8).rev() {
if x >= width {
break;
}
if mask_byte & (1 << bit) != 0 {
// Set alpha channel to transparent.
buf[((height - y - 1) * width + x) as usize * 4 + 3] = 0;
}
x += 1;
}
}
}
Ok(())
} else if data_end == image_end {
// accept images with no mask data
Ok(())
} else {
Err(DecoderError::InvalidDataSize.into())
}
}
}
}