image/codecs/ico/
encoder.rs

1use byteorder::{LittleEndian, WriteBytesExt};
2use std::borrow::Cow;
3use std::io::{self, Write};
4
5use crate::color::ColorType;
6use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind};
7use crate::image::ImageEncoder;
8
9use crate::codecs::png::PngEncoder;
10
11// Enum value indicating an ICO image (as opposed to a CUR image):
12const ICO_IMAGE_TYPE: u16 = 1;
13// The length of an ICO file ICONDIR structure, in bytes:
14const ICO_ICONDIR_SIZE: u32 = 6;
15// The length of an ICO file DIRENTRY structure, in bytes:
16const ICO_DIRENTRY_SIZE: u32 = 16;
17
18/// ICO encoder
19pub struct IcoEncoder<W: Write> {
20    w: W,
21}
22
23/// An ICO image entry
24pub struct IcoFrame<'a> {
25    // Pre-encoded PNG or BMP
26    encoded_image: Cow<'a, [u8]>,
27    // Stored as `0 => 256, n => n`
28    width: u8,
29    // Stored as `0 => 256, n => n`
30    height: u8,
31    color_type: ColorType,
32}
33
34impl<'a> IcoFrame<'a> {
35    /// Construct a new `IcoFrame` using a pre-encoded PNG or BMP
36    ///
37    /// The `width` and `height` must be between 1 and 256 (inclusive).
38    pub fn with_encoded(
39        encoded_image: impl Into<Cow<'a, [u8]>>,
40        width: u32,
41        height: u32,
42        color_type: ColorType,
43    ) -> ImageResult<Self> {
44        let encoded_image = encoded_image.into();
45
46        if !(1..=256).contains(&width) {
47            return Err(ImageError::Parameter(ParameterError::from_kind(
48                ParameterErrorKind::Generic(format!(
49                    "the image width must be `1..=256`, instead width {} was provided",
50                    width,
51                )),
52            )));
53        }
54
55        if !(1..=256).contains(&height) {
56            return Err(ImageError::Parameter(ParameterError::from_kind(
57                ParameterErrorKind::Generic(format!(
58                    "the image height must be `1..=256`, instead height {} was provided",
59                    height,
60                )),
61            )));
62        }
63
64        Ok(Self {
65            encoded_image,
66            width: width as u8,
67            height: height as u8,
68            color_type,
69        })
70    }
71
72    /// Construct a new `IcoFrame` by encoding `buf` as a PNG
73    ///
74    /// The `width` and `height` must be between 1 and 256 (inclusive)
75    pub fn as_png(buf: &[u8], width: u32, height: u32, color_type: ColorType) -> ImageResult<Self> {
76        let mut image_data: Vec<u8> = Vec::new();
77        PngEncoder::new(&mut image_data).write_image(buf, width, height, color_type)?;
78
79        let frame = Self::with_encoded(image_data, width, height, color_type)?;
80        Ok(frame)
81    }
82}
83
84impl<W: Write> IcoEncoder<W> {
85    /// Create a new encoder that writes its output to ```w```.
86    pub fn new(w: W) -> IcoEncoder<W> {
87        IcoEncoder { w }
88    }
89
90    /// Encodes the image ```image``` that has dimensions ```width``` and
91    /// ```height``` and ```ColorType``` ```c```.  The dimensions of the image
92    /// must be between 1 and 256 (inclusive) or an error will be returned.
93    ///
94    /// Expects data to be big endian.
95    #[deprecated = "Use `IcoEncoder::write_image` instead. Beware that `write_image` has a different endianness convention"]
96    pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> {
97        let mut image_data: Vec<u8> = Vec::new();
98        #[allow(deprecated)]
99        PngEncoder::new(&mut image_data).encode(data, width, height, color)?;
100
101        let image = IcoFrame::with_encoded(&image_data, width, height, color)?;
102        self.encode_images(&[image])
103    }
104
105    /// Takes some [`IcoFrame`]s and encodes them into an ICO.
106    ///
107    /// `images` is a list of images, usually ordered by dimension, which
108    /// must be between 1 and 65535 (inclusive) in length.
109    pub fn encode_images(mut self, images: &[IcoFrame<'_>]) -> ImageResult<()> {
110        if !(1..=usize::from(u16::MAX)).contains(&images.len()) {
111            return Err(ImageError::Parameter(ParameterError::from_kind(
112                ParameterErrorKind::Generic(format!(
113                    "the number of images must be `1..=u16::MAX`, instead {} images were provided",
114                    images.len(),
115                )),
116            )));
117        }
118        let num_images = images.len() as u16;
119
120        let mut offset = ICO_ICONDIR_SIZE + (ICO_DIRENTRY_SIZE * (images.len() as u32));
121        write_icondir(&mut self.w, num_images)?;
122        for image in images {
123            write_direntry(
124                &mut self.w,
125                image.width,
126                image.height,
127                image.color_type,
128                offset,
129                image.encoded_image.len() as u32,
130            )?;
131
132            offset += image.encoded_image.len() as u32;
133        }
134        for image in images {
135            self.w.write_all(&image.encoded_image)?;
136        }
137        Ok(())
138    }
139}
140
141impl<W: Write> ImageEncoder for IcoEncoder<W> {
142    /// Write an ICO image with the specified width, height, and color type.
143    ///
144    /// For color types with 16-bit per channel or larger, the contents of `buf` should be in
145    /// native endian.
146    ///
147    /// WARNING: In image 0.23.14 and earlier this method erroneously expected buf to be in big endian.
148    #[track_caller]
149    fn write_image(
150        self,
151        buf: &[u8],
152        width: u32,
153        height: u32,
154        color_type: ColorType,
155    ) -> ImageResult<()> {
156        let expected_buffer_len =
157            (width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64);
158        assert_eq!(
159            expected_buffer_len,
160            buf.len() as u64,
161            "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
162            buf.len(),
163        );
164
165        let image = IcoFrame::as_png(buf, width, height, color_type)?;
166        self.encode_images(&[image])
167    }
168}
169
170fn write_icondir<W: Write>(w: &mut W, num_images: u16) -> io::Result<()> {
171    // Reserved field (must be zero):
172    w.write_u16::<LittleEndian>(0)?;
173    // Image type (ICO or CUR):
174    w.write_u16::<LittleEndian>(ICO_IMAGE_TYPE)?;
175    // Number of images in the file:
176    w.write_u16::<LittleEndian>(num_images)?;
177    Ok(())
178}
179
180fn write_direntry<W: Write>(
181    w: &mut W,
182    width: u8,
183    height: u8,
184    color: ColorType,
185    data_start: u32,
186    data_size: u32,
187) -> io::Result<()> {
188    // Image dimensions:
189    w.write_u8(width)?;
190    w.write_u8(height)?;
191    // Number of colors in palette (or zero for no palette):
192    w.write_u8(0)?;
193    // Reserved field (must be zero):
194    w.write_u8(0)?;
195    // Color planes:
196    w.write_u16::<LittleEndian>(0)?;
197    // Bits per pixel:
198    w.write_u16::<LittleEndian>(color.bits_per_pixel())?;
199    // Image data size, in bytes:
200    w.write_u32::<LittleEndian>(data_size)?;
201    // Image data offset, in bytes:
202    w.write_u32::<LittleEndian>(data_start)?;
203    Ok(())
204}