diff options
Diffstat (limited to 'vendor/image/src/codecs/ico/encoder.rs')
-rw-r--r-- | vendor/image/src/codecs/ico/encoder.rs | 194 |
1 files changed, 194 insertions, 0 deletions
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(()) +} |