image/codecs/ico/
encoder.rs1use 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
11const ICO_IMAGE_TYPE: u16 = 1;
13const ICO_ICONDIR_SIZE: u32 = 6;
15const ICO_DIRENTRY_SIZE: u32 = 16;
17
18pub struct IcoEncoder<W: Write> {
20 w: W,
21}
22
23pub struct IcoFrame<'a> {
25 encoded_image: Cow<'a, [u8]>,
27 width: u8,
29 height: u8,
31 color_type: ColorType,
32}
33
34impl<'a> IcoFrame<'a> {
35 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 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 pub fn new(w: W) -> IcoEncoder<W> {
87 IcoEncoder { w }
88 }
89
90 #[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 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 #[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 w.write_u16::<LittleEndian>(0)?;
173 w.write_u16::<LittleEndian>(ICO_IMAGE_TYPE)?;
175 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 w.write_u8(width)?;
190 w.write_u8(height)?;
191 w.write_u8(0)?;
193 w.write_u8(0)?;
195 w.write_u16::<LittleEndian>(0)?;
197 w.write_u16::<LittleEndian>(color.bits_per_pixel())?;
199 w.write_u32::<LittleEndian>(data_size)?;
201 w.write_u32::<LittleEndian>(data_start)?;
203 Ok(())
204}