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: 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>, width: u32, height: u32, color_type: ColorType, ) -> ImageResult { 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 { let mut image_data: Vec = 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 IcoEncoder { /// Create a new encoder that writes its output to ```w```. pub fn new(w: W) -> IcoEncoder { 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 = 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 ImageEncoder for IcoEncoder { /// 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: &mut W, num_images: u16) -> io::Result<()> { // Reserved field (must be zero): w.write_u16::(0)?; // Image type (ICO or CUR): w.write_u16::(ICO_IMAGE_TYPE)?; // Number of images in the file: w.write_u16::(num_images)?; Ok(()) } fn write_direntry( 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::(0)?; // Bits per pixel: w.write_u16::(color.bits_per_pixel())?; // Image data size, in bytes: w.write_u32::(data_size)?; // Image data offset, in bytes: w.write_u32::(data_start)?; Ok(()) }