diff options
author | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
---|---|---|
committer | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
commit | 1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch) | |
tree | 7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/image/src/codecs/ico | |
parent | 5ecd8cf2cba827454317368b68571df0d13d7842 (diff) | |
download | fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip |
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/image/src/codecs/ico')
-rw-r--r-- | vendor/image/src/codecs/ico/decoder.rs | 470 | ||||
-rw-r--r-- | vendor/image/src/codecs/ico/encoder.rs | 194 | ||||
-rw-r--r-- | vendor/image/src/codecs/ico/mod.rs | 14 |
3 files changed, 678 insertions, 0 deletions
diff --git a/vendor/image/src/codecs/ico/decoder.rs b/vendor/image/src/codecs/ico/decoder.rs new file mode 100644 index 0000000..4f02787 --- /dev/null +++ b/vendor/image/src/codecs/ico/decoder.rs @@ -0,0 +1,470 @@ +use byteorder::{LittleEndian, ReadBytesExt}; +use std::convert::TryFrom; +use std::io::{self, Cursor, Read, Seek, SeekFrom}; +use std::marker::PhantomData; +use std::{error, fmt, mem}; + +use crate::color::ColorType; +use crate::error::{ + DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, +}; +use crate::image::{self, ImageDecoder, ImageFormat}; + +use self::InnerDecoder::*; +use crate::codecs::bmp::BmpDecoder; +use crate::codecs::png::{PngDecoder, PNG_SIGNATURE}; + +/// Errors that can occur during decoding and parsing an ICO image or one of its enclosed images. +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +enum DecoderError { + /// The ICO directory is empty + NoEntries, + /// The number of color planes (0 or 1), or the horizontal coordinate of the hotspot for CUR files too big. + IcoEntryTooManyPlanesOrHotspot, + /// The bit depth (may be 0 meaning unspecified), or the vertical coordinate of the hotspot for CUR files too big. + IcoEntryTooManyBitsPerPixelOrHotspot, + + /// The entry is in PNG format and specified a length that is shorter than PNG header. + PngShorterThanHeader, + /// The enclosed PNG is not in RGBA, which is invalid: https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473/. + PngNotRgba, + + /// The entry is in BMP format and specified a data size that is not correct for the image and optional mask data. + InvalidDataSize, + + /// The dimensions specified by the entry does not match the dimensions in the header of the enclosed image. + ImageEntryDimensionMismatch { + /// The mismatched subimage's type + format: IcoEntryImageFormat, + /// The dimensions specified by the entry + entry: (u16, u16), + /// The dimensions of the image itself + image: (u32, u32), + }, +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DecoderError::NoEntries => f.write_str("ICO directory contains no image"), + DecoderError::IcoEntryTooManyPlanesOrHotspot => { + f.write_str("ICO image entry has too many color planes or too large hotspot value") + } + DecoderError::IcoEntryTooManyBitsPerPixelOrHotspot => f.write_str( + "ICO image entry has too many bits per pixel or too large hotspot value", + ), + DecoderError::PngShorterThanHeader => { + f.write_str("Entry specified a length that is shorter than PNG header!") + } + DecoderError::PngNotRgba => f.write_str("The PNG is not in RGBA format!"), + DecoderError::InvalidDataSize => { + f.write_str("ICO image data size did not match expected size") + } + DecoderError::ImageEntryDimensionMismatch { + format, + entry, + image, + } => f.write_fmt(format_args!( + "Entry{:?} and {}{:?} dimensions do not match!", + entry, format, image + )), + } + } +} + +impl From<DecoderError> for ImageError { + fn from(e: DecoderError) -> ImageError { + ImageError::Decoding(DecodingError::new(ImageFormat::Ico.into(), e)) + } +} + +impl error::Error for DecoderError {} + +/// The image formats an ICO may contain +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +enum IcoEntryImageFormat { + /// PNG in ARGB + Png, + /// BMP with optional alpha mask + Bmp, +} + +impl fmt::Display for IcoEntryImageFormat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + IcoEntryImageFormat::Png => "PNG", + IcoEntryImageFormat::Bmp => "BMP", + }) + } +} + +impl From<IcoEntryImageFormat> for ImageFormat { + fn from(val: IcoEntryImageFormat) -> Self { + match val { + IcoEntryImageFormat::Png => ImageFormat::Png, + IcoEntryImageFormat::Bmp => ImageFormat::Bmp, + } + } +} + +/// An ico decoder +pub struct IcoDecoder<R: Read> { + selected_entry: DirEntry, + inner_decoder: InnerDecoder<R>, +} + +enum InnerDecoder<R: Read> { + Bmp(BmpDecoder<R>), + Png(Box<PngDecoder<R>>), +} + +#[derive(Clone, Copy, Default)] +struct DirEntry { + width: u8, + height: u8, + // We ignore some header fields as they will be replicated in the PNG, BMP and they are not + // necessary for determining the best_entry. + #[allow(unused)] + color_count: u8, + // Wikipedia has this to say: + // Although Microsoft's technical documentation states that this value must be zero, the icon + // encoder built into .NET (System.Drawing.Icon.Save) sets this value to 255. It appears that + // the operating system ignores this value altogether. + #[allow(unused)] + reserved: u8, + + // We ignore some header fields as they will be replicated in the PNG, BMP and they are not + // necessary for determining the best_entry. + #[allow(unused)] + num_color_planes: u16, + bits_per_pixel: u16, + + image_length: u32, + image_offset: u32, +} + +impl<R: Read + Seek> IcoDecoder<R> { + /// Create a new decoder that decodes from the stream ```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> { + Ok(DirEntry { + width: r.read_u8()?, + height: r.read_u8()?, + color_count: r.read_u8()?, + reserved: r.read_u8()?, + num_color_planes: { + // This may be either the number of color planes (0 or 1), or the horizontal coordinate + // of the hotspot for CUR files. + let num = r.read_u16::<LittleEndian>()?; + if num > 256 { + return Err(DecoderError::IcoEntryTooManyPlanesOrHotspot.into()); + } + num + }, + bits_per_pixel: { + // This may be either the bit depth (may be 0 meaning unspecified), + // or the vertical coordinate of the hotspot for CUR files. + let num = r.read_u16::<LittleEndian>()?; + if num > 256 { + return Err(DecoderError::IcoEntryTooManyBitsPerPixelOrHotspot.into()); + } + num + }, + image_length: r.read_u32::<LittleEndian>()?, + image_offset: r.read_u32::<LittleEndian>()?, + }) +} + +/// Find the entry with the highest (color depth, size). +fn best_entry(mut entries: Vec<DirEntry>) -> ImageResult<DirEntry> { + let mut best = entries.pop().ok_or(DecoderError::NoEntries)?; + + 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.min(256) + && u32::from(self.real_height()) == height.min(256) + } + + 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)?; + + // Read the first 8 bytes to sniff the image. + 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(Box::new(PngDecoder::new(r)?))) + } else { + Ok(Bmp(BmpDecoder::new_with_ico_format(r)?)) + } + } +} + +/// Wrapper struct around a `Cursor<Vec<u8>>` +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(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.stream_position()?; + let data_end = u64::from(self.selected_entry.image_offset) + + u64::from(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()) + } + } + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + // Test if BMP images without alpha channel inside ICOs don't panic. + // Because the test data is invalid decoding should produce an error. + #[test] + fn bmp_16_with_missing_alpha_channel() { + let data = vec![ + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0e, 0x04, 0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xf8, 0xff, 0xff, 0xff, 0x01, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x8f, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x66, 0x74, 0x83, 0x70, 0x61, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xeb, 0x00, 0x9b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x47, 0x0d, + 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x62, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0c, + 0x00, 0x00, 0x00, 0xc3, 0x3f, 0x94, 0x61, 0xaa, 0x17, 0x4d, 0x8d, 0x79, 0x1d, 0x8b, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x2e, 0x28, 0x40, 0xe5, 0x9f, + 0x4b, 0x4d, 0xe9, 0x87, 0xd3, 0xda, 0xd6, 0x89, 0x81, 0xc5, 0xa4, 0xa1, 0x60, 0x98, + 0x31, 0xc7, 0x1d, 0xb6, 0x8f, 0x20, 0xc8, 0x3e, 0xee, 0xd8, 0xe4, 0x8f, 0xee, 0x7b, + 0x48, 0x9b, 0x88, 0x25, 0x13, 0xda, 0xa4, 0x13, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x16, 0x01, 0xff, 0xff, 0xff, 0xff, 0xe9, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xa3, 0x66, 0x64, 0x41, 0x54, 0xa3, 0xa3, 0x00, 0x00, 0x00, 0xb8, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0x66, 0x64, 0x41, 0x54, 0xa3, 0xa3, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0xf6, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x83, 0x70, 0x61, 0x76, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x62, 0x49, + 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x94, 0xc8, 0x00, 0x02, 0x0c, 0x00, 0xff, 0xff, 0xc6, + 0x84, 0x00, 0x2a, 0x75, 0x03, 0xa3, 0x05, 0xfb, 0xe1, 0x6e, 0xe8, 0x27, 0xd6, 0xd3, + 0x96, 0xc1, 0xe4, 0x30, 0x0c, 0x05, 0xb9, 0xa3, 0x8b, 0x29, 0xda, 0xa4, 0xf1, 0x4d, + 0xf3, 0xb2, 0x98, 0x2b, 0xe6, 0x93, 0x07, 0xf9, 0xca, 0x2b, 0xc2, 0x39, 0x20, 0xba, + 0x7c, 0xa0, 0xb1, 0x43, 0xe6, 0xf9, 0xdc, 0xd1, 0xc2, 0x52, 0xdc, 0x41, 0xc1, 0x2f, + 0x29, 0xf7, 0x46, 0x32, 0xda, 0x1b, 0x72, 0x8c, 0xe6, 0x2b, 0x01, 0xe5, 0x49, 0x21, + 0x89, 0x89, 0xe4, 0x3d, 0xa1, 0xdb, 0x3b, 0x4a, 0x0b, 0x52, 0x86, 0x52, 0x33, 0x9d, + 0xb2, 0xcf, 0x4a, 0x86, 0x53, 0xd7, 0xa9, 0x4b, 0xaf, 0x62, 0x06, 0x49, 0x53, 0x00, + 0xc3, 0x3f, 0x94, 0x61, 0xaa, 0x17, 0x4d, 0x8d, 0x79, 0x1d, 0x8b, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x2e, 0x28, 0x40, 0xe5, 0x9f, 0x4b, 0x4d, 0xe9, + 0x87, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe7, 0xc5, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x0b, 0x00, 0x50, 0x31, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x76, 0x76, 0x01, 0x00, 0x00, 0x00, 0x76, 0x00, + 0x00, 0x23, 0x3f, 0x52, 0x41, 0x44, 0x49, 0x41, 0x4e, 0x43, 0x45, 0x61, 0x50, 0x35, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x4d, 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x05, + 0x50, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x37, 0x61, + ]; + + let decoder = IcoDecoder::new(Cursor::new(&data)).unwrap(); + let mut buf = vec![0; usize::try_from(decoder.total_bytes()).unwrap()]; + assert!(decoder.read_image(&mut buf).is_err()); + } +} diff --git a/vendor/image/src/codecs/ico/encoder.rs b/vendor/image/src/codecs/ico/encoder.rs new file mode 100644 index 0000000..dd5961b --- /dev/null +++ b/vendor/image/src/codecs/ico/encoder.rs @@ -0,0 +1,194 @@ +use byteorder::{LittleEndian, WriteBytesExt}; +use std::borrow::Cow; +use std::io::{self, Write}; + +use crate::color::ColorType; +use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; +use crate::image::ImageEncoder; + +use crate::codecs::png::PngEncoder; + +// Enum value indicating an ICO image (as opposed to a CUR image): +const ICO_IMAGE_TYPE: u16 = 1; +// The length of an ICO file ICONDIR structure, in bytes: +const ICO_ICONDIR_SIZE: u32 = 6; +// The length of an ICO file DIRENTRY structure, in bytes: +const ICO_DIRENTRY_SIZE: u32 = 16; + +/// ICO encoder +pub struct IcoEncoder<W: Write> { + w: W, +} + +/// An ICO image entry +pub struct IcoFrame<'a> { + // Pre-encoded PNG or BMP + encoded_image: Cow<'a, [u8]>, + // Stored as `0 => 256, n => n` + width: u8, + // Stored as `0 => 256, n => n` + height: u8, + color_type: ColorType, +} + +impl<'a> IcoFrame<'a> { + /// Construct a new `IcoFrame` using a pre-encoded PNG or BMP + /// + /// The `width` and `height` must be between 1 and 256 (inclusive). + pub fn with_encoded( + encoded_image: impl Into<Cow<'a, [u8]>>, + width: u32, + height: u32, + color_type: ColorType, + ) -> ImageResult<Self> { + let encoded_image = encoded_image.into(); + + if !(1..=256).contains(&width) { + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::Generic(format!( + "the image width must be `1..=256`, instead width {} was provided", + width, + )), + ))); + } + + if !(1..=256).contains(&height) { + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::Generic(format!( + "the image height must be `1..=256`, instead height {} was provided", + height, + )), + ))); + } + + Ok(Self { + encoded_image, + width: width as u8, + height: height as u8, + color_type, + }) + } + + /// Construct a new `IcoFrame` by encoding `buf` as a PNG + /// + /// The `width` and `height` must be between 1 and 256 (inclusive) + pub fn as_png(buf: &[u8], width: u32, height: u32, color_type: ColorType) -> ImageResult<Self> { + let mut image_data: Vec<u8> = Vec::new(); + PngEncoder::new(&mut image_data).write_image(buf, width, height, color_type)?; + + let frame = Self::with_encoded(image_data, width, height, color_type)?; + Ok(frame) + } +} + +impl<W: Write> IcoEncoder<W> { + /// Create a new encoder that writes its output to ```w```. + pub fn new(w: W) -> IcoEncoder<W> { + IcoEncoder { w } + } + + /// Encodes the image ```image``` that has dimensions ```width``` and + /// ```height``` and ```ColorType``` ```c```. The dimensions of the image + /// must be between 1 and 256 (inclusive) or an error will be returned. + /// + /// Expects data to be big endian. + #[deprecated = "Use `IcoEncoder::write_image` instead. Beware that `write_image` has a different endianness convention"] + pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> { + let mut image_data: Vec<u8> = Vec::new(); + #[allow(deprecated)] + PngEncoder::new(&mut image_data).encode(data, width, height, color)?; + + let image = IcoFrame::with_encoded(&image_data, width, height, color)?; + self.encode_images(&[image]) + } + + /// Takes some [`IcoFrame`]s and encodes them into an ICO. + /// + /// `images` is a list of images, usually ordered by dimension, which + /// must be between 1 and 65535 (inclusive) in length. + pub fn encode_images(mut self, images: &[IcoFrame<'_>]) -> ImageResult<()> { + if !(1..=usize::from(u16::MAX)).contains(&images.len()) { + return Err(ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::Generic(format!( + "the number of images must be `1..=u16::MAX`, instead {} images were provided", + images.len(), + )), + ))); + } + let num_images = images.len() as u16; + + let mut offset = ICO_ICONDIR_SIZE + (ICO_DIRENTRY_SIZE * (images.len() as u32)); + write_icondir(&mut self.w, num_images)?; + for image in images { + write_direntry( + &mut self.w, + image.width, + image.height, + image.color_type, + offset, + image.encoded_image.len() as u32, + )?; + + offset += image.encoded_image.len() as u32; + } + for image in images { + self.w.write_all(&image.encoded_image)?; + } + Ok(()) + } +} + +impl<W: Write> ImageEncoder for IcoEncoder<W> { + /// Write an ICO image with the specified width, height, and color type. + /// + /// For color types with 16-bit per channel or larger, the contents of `buf` should be in + /// native endian. + /// + /// WARNING: In image 0.23.14 and earlier this method erroneously expected buf to be in big endian. + fn write_image( + self, + buf: &[u8], + width: u32, + height: u32, + color_type: ColorType, + ) -> ImageResult<()> { + let image = IcoFrame::as_png(buf, width, height, color_type)?; + self.encode_images(&[image]) + } +} + +fn write_icondir<W: Write>(w: &mut W, num_images: u16) -> io::Result<()> { + // Reserved field (must be zero): + w.write_u16::<LittleEndian>(0)?; + // Image type (ICO or CUR): + w.write_u16::<LittleEndian>(ICO_IMAGE_TYPE)?; + // Number of images in the file: + w.write_u16::<LittleEndian>(num_images)?; + Ok(()) +} + +fn write_direntry<W: Write>( + w: &mut W, + width: u8, + height: u8, + color: ColorType, + data_start: u32, + data_size: u32, +) -> io::Result<()> { + // Image dimensions: + w.write_u8(width)?; + w.write_u8(height)?; + // Number of colors in palette (or zero for no palette): + w.write_u8(0)?; + // Reserved field (must be zero): + w.write_u8(0)?; + // Color planes: + w.write_u16::<LittleEndian>(0)?; + // Bits per pixel: + w.write_u16::<LittleEndian>(color.bits_per_pixel())?; + // Image data size, in bytes: + w.write_u32::<LittleEndian>(data_size)?; + // Image data offset, in bytes: + w.write_u32::<LittleEndian>(data_start)?; + Ok(()) +} diff --git a/vendor/image/src/codecs/ico/mod.rs b/vendor/image/src/codecs/ico/mod.rs new file mode 100644 index 0000000..11493ac --- /dev/null +++ b/vendor/image/src/codecs/ico/mod.rs @@ -0,0 +1,14 @@ +//! Decoding and Encoding of ICO files +//! +//! A decoder and encoder for ICO (Windows Icon) image container files. +//! +//! # Related Links +//! * <https://msdn.microsoft.com/en-us/library/ms997538.aspx> +//! * <https://en.wikipedia.org/wiki/ICO_%28file_format%29> + +pub use self::decoder::IcoDecoder; +#[allow(deprecated)] +pub use self::encoder::{IcoEncoder, IcoFrame}; + +mod decoder; +mod encoder; |