diff options
Diffstat (limited to 'third_party/rust/image/src/ico')
-rw-r--r-- | third_party/rust/image/src/ico/decoder.rs | 284 | ||||
-rw-r--r-- | third_party/rust/image/src/ico/encoder.rs | 113 | ||||
-rw-r--r-- | third_party/rust/image/src/ico/mod.rs | 13 |
3 files changed, 410 insertions, 0 deletions
diff --git a/third_party/rust/image/src/ico/decoder.rs b/third_party/rust/image/src/ico/decoder.rs new file mode 100644 index 0000000000..0dfc44f112 --- /dev/null +++ b/third_party/rust/image/src/ico/decoder.rs @@ -0,0 +1,284 @@ +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; + +// http://www.w3.org/TR/PNG-Structure.html +// The first eight bytes of a PNG file always contain the following (decimal) values: +const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10]; + +/// An ico decoder +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> { + /// 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> { + let mut entry = DirEntry::default(); + + entry.width = r.read_u8()?; + entry.height = r.read_u8()?; + entry.color_count = r.read_u8()?; + // Reserved value (not used) + entry.reserved = r.read_u8()?; + + // This may be either the number of color planes (0 or 1), or the horizontal coordinate + // of the hotspot for CUR files. + 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(), + )); + } + + // This may be either the bit depth (may be 0 meaning unspecified), + // or the vertical coordinate of the hotspot for CUR files. + 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) +} + +/// 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(ImageError::ImageEnd)?; + 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)?; + + // 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(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(ImageError::FormatError( + "Entry specified a length that is shorter than PNG header!".to_string(), + )); + } + + // 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(ImageError::FormatError( + "Entry and PNG dimensions do not match!".to_string(), + )); + } + + // Embedded PNG images can only be of the 32BPP RGBA format. + // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473/ + 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(), + )); + } + + // The ICO decoder needs an alpha channel to apply the AND mask. + if decoder.color_type() != ColorType::Rgba8 { + return Err(ImageError::UnsupportedColor(decoder.color_type().into())); + } + + decoder.read_image_data(buf)?; + + // If there's an AND mask following the image, read and apply it. + 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 { + // A mask row contains 1 bit per pixel, padded to 4 bytes. + 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::ImageEnd); + } + + 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(()) + } + } + } +} diff --git a/third_party/rust/image/src/ico/encoder.rs b/third_party/rust/image/src/ico/encoder.rs new file mode 100644 index 0000000000..57a14a1ce8 --- /dev/null +++ b/third_party/rust/image/src/ico/encoder.rs @@ -0,0 +1,113 @@ +use byteorder::{LittleEndian, WriteBytesExt}; +use std::io::{self, Write}; + +use crate::color::ColorType; +use crate::error::ImageResult; +use crate::image::ImageEncoder; + +use crate::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, +} + +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. + pub fn encode( + mut self, + data: &[u8], + width: u32, + height: u32, + color: ColorType, + ) -> ImageResult<()> { + let mut image_data: Vec<u8> = Vec::new(); + PNGEncoder::new(&mut image_data).encode(data, width, height, color)?; + + write_icondir(&mut self.w, 1)?; + write_direntry( + &mut self.w, + width, + height, + color, + ICO_ICONDIR_SIZE + ICO_DIRENTRY_SIZE, + image_data.len() as u32, + )?; + self.w.write_all(&image_data)?; + Ok(()) + } +} + +impl<W: Write> ImageEncoder for ICOEncoder<W> { + fn write_image( + self, + buf: &[u8], + width: u32, + height: u32, + color_type: ColorType, + ) -> ImageResult<()> { + self.encode(buf, width, height, color_type) + } +} + +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: u32, + height: u32, + color: ColorType, + data_start: u32, + data_size: u32, +) -> io::Result<()> { + // Image dimensions: + write_width_or_height(w, width)?; + write_width_or_height(w, 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(()) +} + +/// Encode a width/height value as a single byte, where 0 means 256. +fn write_width_or_height<W: Write>(w: &mut W, value: u32) -> io::Result<()> { + if value < 1 || value > 256 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Invalid ICO dimensions (width and \ + height must be between 1 and 256)", + )); + } + w.write_u8(if value < 256 { value as u8 } else { 0 }) +} diff --git a/third_party/rust/image/src/ico/mod.rs b/third_party/rust/image/src/ico/mod.rs new file mode 100644 index 0000000000..fd65df1f6e --- /dev/null +++ b/third_party/rust/image/src/ico/mod.rs @@ -0,0 +1,13 @@ +//! 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; +pub use self::encoder::ICOEncoder; + +mod decoder; +mod encoder; |