1 use byteorder::{LittleEndian, WriteBytesExt};
2 use std::io::{self, Write};
3 
4 use crate::color::ColorType;
5 use crate::error::ImageResult;
6 use crate::image::ImageEncoder;
7 
8 use crate::png::PngEncoder;
9 
10 // Enum value indicating an ICO image (as opposed to a CUR image):
11 const ICO_IMAGE_TYPE: u16 = 1;
12 // The length of an ICO file ICONDIR structure, in bytes:
13 const ICO_ICONDIR_SIZE: u32 = 6;
14 // The length of an ICO file DIRENTRY structure, in bytes:
15 const ICO_DIRENTRY_SIZE: u32 = 16;
16 
17 /// ICO encoder
18 pub struct IcoEncoder<W: Write> {
19     w: W,
20 }
21 
22 /// ICO encoder
23 ///
24 /// An alias of [`IcoEncoder`].
25 ///
26 /// TODO: remove
27 ///
28 /// [`IcoEncoder`]: struct.IcoEncoder.html
29 #[allow(dead_code)]
30 #[deprecated(note = "Use `IcoEncoder` instead")]
31 pub type ICOEncoder<W> = IcoEncoder<W>;
32 
33 impl<W: Write> IcoEncoder<W> {
34     /// Create a new encoder that writes its output to ```w```.
new(w: W) -> IcoEncoder<W>35     pub fn new(w: W) -> IcoEncoder<W> {
36         IcoEncoder { w }
37     }
38 
39     /// Encodes the image ```image``` that has dimensions ```width``` and
40     /// ```height``` and ```ColorType``` ```c```.  The dimensions of the image
41     /// must be between 1 and 256 (inclusive) or an error will be returned.
encode( mut self, data: &[u8], width: u32, height: u32, color: ColorType, ) -> ImageResult<()>42     pub fn encode(
43         mut self,
44         data: &[u8],
45         width: u32,
46         height: u32,
47         color: ColorType,
48     ) -> ImageResult<()> {
49         let mut image_data: Vec<u8> = Vec::new();
50         PngEncoder::new(&mut image_data).encode(data, width, height, color)?;
51 
52         write_icondir(&mut self.w, 1)?;
53         write_direntry(
54             &mut self.w,
55             width,
56             height,
57             color,
58             ICO_ICONDIR_SIZE + ICO_DIRENTRY_SIZE,
59             image_data.len() as u32,
60         )?;
61         self.w.write_all(&image_data)?;
62         Ok(())
63     }
64 }
65 
66 impl<W: Write> ImageEncoder for IcoEncoder<W> {
write_image( self, buf: &[u8], width: u32, height: u32, color_type: ColorType, ) -> ImageResult<()>67     fn write_image(
68         self,
69         buf: &[u8],
70         width: u32,
71         height: u32,
72         color_type: ColorType,
73     ) -> ImageResult<()> {
74         self.encode(buf, width, height, color_type)
75     }
76 }
77 
write_icondir<W: Write>(w: &mut W, num_images: u16) -> io::Result<()>78 fn write_icondir<W: Write>(w: &mut W, num_images: u16) -> io::Result<()> {
79     // Reserved field (must be zero):
80     w.write_u16::<LittleEndian>(0)?;
81     // Image type (ICO or CUR):
82     w.write_u16::<LittleEndian>(ICO_IMAGE_TYPE)?;
83     // Number of images in the file:
84     w.write_u16::<LittleEndian>(num_images)?;
85     Ok(())
86 }
87 
write_direntry<W: Write>( w: &mut W, width: u32, height: u32, color: ColorType, data_start: u32, data_size: u32, ) -> io::Result<()>88 fn write_direntry<W: Write>(
89     w: &mut W,
90     width: u32,
91     height: u32,
92     color: ColorType,
93     data_start: u32,
94     data_size: u32,
95 ) -> io::Result<()> {
96     // Image dimensions:
97     write_width_or_height(w, width)?;
98     write_width_or_height(w, height)?;
99     // Number of colors in palette (or zero for no palette):
100     w.write_u8(0)?;
101     // Reserved field (must be zero):
102     w.write_u8(0)?;
103     // Color planes:
104     w.write_u16::<LittleEndian>(0)?;
105     // Bits per pixel:
106     w.write_u16::<LittleEndian>(color.bits_per_pixel())?;
107     // Image data size, in bytes:
108     w.write_u32::<LittleEndian>(data_size)?;
109     // Image data offset, in bytes:
110     w.write_u32::<LittleEndian>(data_start)?;
111     Ok(())
112 }
113 
114 /// Encode a width/height value as a single byte, where 0 means 256.
write_width_or_height<W: Write>(w: &mut W, value: u32) -> io::Result<()>115 fn write_width_or_height<W: Write>(w: &mut W, value: u32) -> io::Result<()> {
116     if value < 1 || value > 256 {
117         // TODO: this is not very idiomatic yet. Should return an EncodingError and be checked
118         // prior to encoding.
119         return Err(io::Error::new(
120             io::ErrorKind::InvalidData,
121             "Invalid ICO dimensions (width and \
122              height must be between 1 and 256)",
123         ));
124     }
125     w.write_u8(if value < 256 { value as u8 } else { 0 })
126 }
127