1 | use byteorder_lite::{LittleEndian, WriteBytesExt}; |
2 | use std::borrow::Cow; |
3 | use std::io::{self, Write}; |
4 | |
5 | use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; |
6 | use crate::image::ImageEncoder; |
7 | |
8 | use crate::codecs::png::PngEncoder; |
9 | use crate::ExtendedColorType; |
10 | |
11 | // Enum value indicating an ICO image (as opposed to a CUR image): |
12 | const ICO_IMAGE_TYPE: u16 = 1; |
13 | // The length of an ICO file ICONDIR structure, in bytes: |
14 | const ICO_ICONDIR_SIZE: u32 = 6; |
15 | // The length of an ICO file DIRENTRY structure, in bytes: |
16 | const ICO_DIRENTRY_SIZE: u32 = 16; |
17 | |
18 | /// ICO encoder |
19 | pub struct IcoEncoder<W: Write> { |
20 | w: W, |
21 | } |
22 | |
23 | /// An ICO image entry |
24 | pub 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 | |
34 | impl<'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 | |
87 | impl<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 | |
129 | impl<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 | |
157 | fn 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 | |
167 | fn 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 | |