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