image/codecs/ico/
encoder.rs1use byteorder_lite::{LittleEndian, WriteBytesExt};
2use std::borrow::Cow;
3use std::io::{self, Write};
4
5use crate::codecs::png::PngEncoder;
6use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind};
7use crate::{ExtendedColorType, ImageEncoder};
8
9const ICO_IMAGE_TYPE: u16 = 1;
11const ICO_ICONDIR_SIZE: u32 = 6;
13const ICO_DIRENTRY_SIZE: u32 = 16;
15
16pub struct IcoEncoder<W: Write> {
18 w: W,
19}
20
21pub struct IcoFrame<'a> {
23 encoded_image: Cow<'a, [u8]>,
25 width: u8,
27 height: u8,
29 color_type: ExtendedColorType,
30}
31
32impl<'a> IcoFrame<'a> {
33 pub fn with_encoded(
37 encoded_image: impl Into<Cow<'a, [u8]>>,
38 width: u32,
39 height: u32,
40 color_type: ExtendedColorType,
41 ) -> ImageResult<Self> {
42 let encoded_image = encoded_image.into();
43
44 if !(1..=256).contains(&width) {
45 return Err(ImageError::Parameter(ParameterError::from_kind(
46 ParameterErrorKind::Generic(format!(
47 "the image width must be `1..=256`, instead width {width} was provided",
48 )),
49 )));
50 }
51
52 if !(1..=256).contains(&height) {
53 return Err(ImageError::Parameter(ParameterError::from_kind(
54 ParameterErrorKind::Generic(format!(
55 "the image height must be `1..=256`, instead height {height} was provided",
56 )),
57 )));
58 }
59
60 Ok(Self {
61 encoded_image,
62 width: width as u8,
63 height: height as u8,
64 color_type,
65 })
66 }
67
68 pub fn as_png(
72 buf: &[u8],
73 width: u32,
74 height: u32,
75 color_type: ExtendedColorType,
76 ) -> ImageResult<Self> {
77 let mut image_data: Vec<u8> = Vec::new();
78 PngEncoder::new(&mut image_data).write_image(buf, width, height, color_type)?;
79
80 let frame = Self::with_encoded(image_data, width, height, color_type)?;
81 Ok(frame)
82 }
83}
84
85impl<W: Write> IcoEncoder<W> {
86 pub fn new(w: W) -> IcoEncoder<W> {
88 IcoEncoder { w }
89 }
90
91 pub fn encode_images(mut self, images: &[IcoFrame<'_>]) -> ImageResult<()> {
96 if !(1..=usize::from(u16::MAX)).contains(&images.len()) {
97 return Err(ImageError::Parameter(ParameterError::from_kind(
98 ParameterErrorKind::Generic(format!(
99 "the number of images must be `1..=u16::MAX`, instead {} images were provided",
100 images.len(),
101 )),
102 )));
103 }
104 let num_images = images.len() as u16;
105
106 let mut offset = ICO_ICONDIR_SIZE + (ICO_DIRENTRY_SIZE * (images.len() as u32));
107 write_icondir(&mut self.w, num_images)?;
108 for image in images {
109 write_direntry(
110 &mut self.w,
111 image.width,
112 image.height,
113 image.color_type,
114 offset,
115 image.encoded_image.len() as u32,
116 )?;
117
118 offset += image.encoded_image.len() as u32;
119 }
120 for image in images {
121 self.w.write_all(&image.encoded_image)?;
122 }
123 Ok(())
124 }
125}
126
127impl<W: Write> ImageEncoder for IcoEncoder<W> {
128 #[track_caller]
135 fn write_image(
136 self,
137 buf: &[u8],
138 width: u32,
139 height: u32,
140 color_type: ExtendedColorType,
141 ) -> ImageResult<()> {
142 let expected_buffer_len = color_type.buffer_size(width, height);
143 assert_eq!(
144 expected_buffer_len,
145 buf.len() as u64,
146 "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
147 buf.len(),
148 );
149
150 let image = IcoFrame::as_png(buf, width, height, color_type)?;
151 self.encode_images(&[image])
152 }
153}
154
155fn write_icondir<W: Write>(w: &mut W, num_images: u16) -> io::Result<()> {
156 w.write_u16::<LittleEndian>(0)?;
158 w.write_u16::<LittleEndian>(ICO_IMAGE_TYPE)?;
160 w.write_u16::<LittleEndian>(num_images)?;
162 Ok(())
163}
164
165fn write_direntry<W: Write>(
166 w: &mut W,
167 width: u8,
168 height: u8,
169 color: ExtendedColorType,
170 data_start: u32,
171 data_size: u32,
172) -> io::Result<()> {
173 w.write_u8(width)?;
175 w.write_u8(height)?;
176 w.write_u8(0)?;
178 w.write_u8(0)?;
180 w.write_u16::<LittleEndian>(0)?;
182 w.write_u16::<LittleEndian>(color.bits_per_pixel())?;
184 w.write_u32::<LittleEndian>(data_size)?;
186 w.write_u32::<LittleEndian>(data_start)?;
188 Ok(())
189}