1use byteorder_lite::{LittleEndian, WriteBytesExt};
2use std::borrow::Cow;
3use std::io::{self, Write};
4
5use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind};
6use crate::image::ImageEncoder;
7
8use crate::codecs::png::PngEncoder;
9use crate::ExtendedColorType;
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: ExtendedColorType,
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: ExtendedColorType,
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 {width} was provided",
50 )),
51 )));
52 }
53
54 if !(1..=256).contains(&height) {
55 return Err(ImageError::Parameter(ParameterError::from_kind(
56 ParameterErrorKind::Generic(format!(
57 "the image height must be `1..=256`, instead height {height} was provided",
58 )),
59 )));
60 }
61
62 Ok(Self {
63 encoded_image,
64 width: width as u8,
65 height: height as u8,
66 color_type,
67 })
68 }
69
70 /// Construct a new `IcoFrame` by encoding `buf` as a PNG
71 ///
72 /// The `width` and `height` must be between 1 and 256 (inclusive)
73 pub fn as_png(
74 buf: &[u8],
75 width: u32,
76 height: u32,
77 color_type: ExtendedColorType,
78 ) -> ImageResult<Self> {
79 let mut image_data: Vec<u8> = Vec::new();
80 PngEncoder::new(&mut image_data).write_image(buf, width, height, color_type)?;
81
82 let frame = Self::with_encoded(image_data, width, height, color_type)?;
83 Ok(frame)
84 }
85}
86
87impl<W: Write> IcoEncoder<W> {
88 /// Create a new encoder that writes its output to ```w```.
89 pub fn new(w: W) -> IcoEncoder<W> {
90 IcoEncoder { w }
91 }
92
93 /// Takes some [`IcoFrame`]s and encodes them into an ICO.
94 ///
95 /// `images` is a list of images, usually ordered by dimension, which
96 /// must be between 1 and 65535 (inclusive) in length.
97 pub fn encode_images(mut self, images: &[IcoFrame<'_>]) -> ImageResult<()> {
98 if !(1..=usize::from(u16::MAX)).contains(&images.len()) {
99 return Err(ImageError::Parameter(ParameterError::from_kind(
100 ParameterErrorKind::Generic(format!(
101 "the number of images must be `1..=u16::MAX`, instead {} images were provided",
102 images.len(),
103 )),
104 )));
105 }
106 let num_images = images.len() as u16;
107
108 let mut offset = ICO_ICONDIR_SIZE + (ICO_DIRENTRY_SIZE * (images.len() as u32));
109 write_icondir(&mut self.w, num_images)?;
110 for image in images {
111 write_direntry(
112 &mut self.w,
113 image.width,
114 image.height,
115 image.color_type,
116 offset,
117 image.encoded_image.len() as u32,
118 )?;
119
120 offset += image.encoded_image.len() as u32;
121 }
122 for image in images {
123 self.w.write_all(&image.encoded_image)?;
124 }
125 Ok(())
126 }
127}
128
129impl<W: Write> ImageEncoder for IcoEncoder<W> {
130 /// Write an ICO image with the specified width, height, and color type.
131 ///
132 /// For color types with 16-bit per channel or larger, the contents of `buf` should be in
133 /// native endian.
134 ///
135 /// WARNING: In image 0.23.14 and earlier this method erroneously expected buf to be in big endian.
136 #[track_caller]
137 fn write_image(
138 self,
139 buf: &[u8],
140 width: u32,
141 height: u32,
142 color_type: ExtendedColorType,
143 ) -> ImageResult<()> {
144 let expected_buffer_len = color_type.buffer_size(width, height);
145 assert_eq!(
146 expected_buffer_len,
147 buf.len() as u64,
148 "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
149 buf.len(),
150 );
151
152 let image = IcoFrame::as_png(buf, width, height, color_type)?;
153 self.encode_images(&[image])
154 }
155}
156
157fn write_icondir<W: Write>(w: &mut W, num_images: u16) -> io::Result<()> {
158 // Reserved field (must be zero):
159 w.write_u16::<LittleEndian>(0)?;
160 // Image type (ICO or CUR):
161 w.write_u16::<LittleEndian>(ICO_IMAGE_TYPE)?;
162 // Number of images in the file:
163 w.write_u16::<LittleEndian>(num_images)?;
164 Ok(())
165}
166
167fn write_direntry<W: Write>(
168 w: &mut W,
169 width: u8,
170 height: u8,
171 color: ExtendedColorType,
172 data_start: u32,
173 data_size: u32,
174) -> io::Result<()> {
175 // Image dimensions:
176 w.write_u8(width)?;
177 w.write_u8(height)?;
178 // Number of colors in palette (or zero for no palette):
179 w.write_u8(0)?;
180 // Reserved field (must be zero):
181 w.write_u8(0)?;
182 // Color planes:
183 w.write_u16::<LittleEndian>(0)?;
184 // Bits per pixel:
185 w.write_u16::<LittleEndian>(color.bits_per_pixel())?;
186 // Image data size, in bytes:
187 w.write_u32::<LittleEndian>(data_size)?;
188 // Image data offset, in bytes:
189 w.write_u32::<LittleEndian>(data_start)?;
190 Ok(())
191}
192