1 use byteorder::{LittleEndian, ReadBytesExt};
2 use std::convert::TryFrom;
3 use std::io::{self, Cursor, Read, Seek, SeekFrom};
4 use std::marker::PhantomData;
5 use std::{error, fmt, mem};
6
7 use crate::color::ColorType;
8 use crate::error::{DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind};
9 use crate::image::{self, ImageDecoder, ImageFormat};
10
11 use self::InnerDecoder::*;
12 use crate::bmp::BmpDecoder;
13 use crate::png::PngDecoder;
14
15 // http://www.w3.org/TR/PNG-Structure.html
16 // The first eight bytes of a PNG file always contain the following (decimal) values:
17 const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10];
18
19 /// Errors that can occur during decoding and parsing an ICO image or one of its enclosed images.
20 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
21 enum DecoderError {
22 /// The ICO directory is empty
23 NoEntries,
24 /// The number of color planes (0 or 1), or the horizontal coordinate of the hotspot for CUR files too big.
25 IcoEntryTooManyPlanesOrHotspot,
26 /// The bit depth (may be 0 meaning unspecified), or the vertical coordinate of the hotspot for CUR files too big.
27 IcoEntryTooManyBitsPerPixelOrHotspot,
28
29 /// The entry is in PNG format and specified a length that is shorter than PNG header.
30 PngShorterThanHeader,
31 /// The enclosed PNG is not in RGBA, which is invalid: https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473/.
32 PngNotRgba,
33
34 /// The entry is in BMP format and specified a data size that is not correct for the image and optional mask data.
35 InvalidDataSize,
36
37 /// The dimensions specified by the entry does not match the dimensions in the header of the enclosed image.
38 ImageEntryDimensionMismatch {
39 /// The mismatched subimage's type
40 format: IcoEntryImageFormat,
41 /// The dimensions specified by the entry
42 entry: (u16, u16),
43 /// The dimensions of the image itself
44 image: (u32, u32)
45 },
46 }
47
48 impl fmt::Display for DecoderError {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 match self {
51 DecoderError::NoEntries =>
52 f.write_str("ICO directory contains no image"),
53 DecoderError::IcoEntryTooManyPlanesOrHotspot =>
54 f.write_str("ICO image entry has too many color planes or too large hotspot value"),
55 DecoderError::IcoEntryTooManyBitsPerPixelOrHotspot =>
56 f.write_str("ICO image entry has too many bits per pixel or too large hotspot value"),
57 DecoderError::PngShorterThanHeader =>
58 f.write_str("Entry specified a length that is shorter than PNG header!"),
59 DecoderError::PngNotRgba =>
60 f.write_str("The PNG is not in RGBA format!"),
61 DecoderError::InvalidDataSize =>
62 f.write_str("ICO image data size did not match expected size"),
63 DecoderError::ImageEntryDimensionMismatch { format, entry, image } =>
64 f.write_fmt(format_args!("Entry{:?} and {}{:?} dimensions do not match!", entry, format, image)),
65 }
66 }
67 }
68
69 impl From<DecoderError> for ImageError {
from(e: DecoderError) -> ImageError70 fn from(e: DecoderError) -> ImageError {
71 ImageError::Decoding(DecodingError::new(ImageFormat::Ico.into(), e))
72 }
73 }
74
75 impl error::Error for DecoderError {}
76
77 /// The image formats an ICO may contain
78 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
79 enum IcoEntryImageFormat {
80 /// PNG in ARGB
81 Png,
82 /// BMP with optional alpha mask
83 Bmp,
84 }
85
86 impl fmt::Display for IcoEntryImageFormat {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 f.write_str(match self {
89 IcoEntryImageFormat::Png => "PNG",
90 IcoEntryImageFormat::Bmp => "BMP",
91 })
92 }
93 }
94
95 impl Into<ImageFormat> for IcoEntryImageFormat {
into(self) -> ImageFormat96 fn into(self) -> ImageFormat {
97 match self {
98 IcoEntryImageFormat::Png => ImageFormat::Png,
99 IcoEntryImageFormat::Bmp => ImageFormat::Bmp,
100 }
101 }
102 }
103
104 /// An ico decoder
105 pub struct IcoDecoder<R: Read> {
106 selected_entry: DirEntry,
107 inner_decoder: InnerDecoder<R>,
108 }
109
110 enum InnerDecoder<R: Read> {
111 BMP(BmpDecoder<R>),
112 PNG(PngDecoder<R>),
113 }
114
115 #[derive(Clone, Copy, Default)]
116 struct DirEntry {
117 width: u8,
118 height: u8,
119 // We ignore some header fields as they will be replicated in the PNG, BMP and they are not
120 // necessary for determining the best_entry.
121 #[allow(unused)]
122 color_count: u8,
123 // Wikipedia has this to say:
124 // Although Microsoft's technical documentation states that this value must be zero, the icon
125 // encoder built into .NET (System.Drawing.Icon.Save) sets this value to 255. It appears that
126 // the operating system ignores this value altogether.
127 #[allow(unused)]
128 reserved: u8,
129
130 // We ignore some header fields as they will be replicated in the PNG, BMP and they are not
131 // necessary for determining the best_entry.
132 #[allow(unused)]
133 num_color_planes: u16,
134 bits_per_pixel: u16,
135
136 image_length: u32,
137 image_offset: u32,
138 }
139
140 impl<R: Read + Seek> IcoDecoder<R> {
141 /// Create a new decoder that decodes from the stream ```r```
new(mut r: R) -> ImageResult<IcoDecoder<R>>142 pub fn new(mut r: R) -> ImageResult<IcoDecoder<R>> {
143 let entries = read_entries(&mut r)?;
144 let entry = best_entry(entries)?;
145 let decoder = entry.decoder(r)?;
146
147 Ok(IcoDecoder {
148 selected_entry: entry,
149 inner_decoder: decoder,
150 })
151 }
152 }
153
read_entries<R: Read>(r: &mut R) -> ImageResult<Vec<DirEntry>>154 fn read_entries<R: Read>(r: &mut R) -> ImageResult<Vec<DirEntry>> {
155 let _reserved = r.read_u16::<LittleEndian>()?;
156 let _type = r.read_u16::<LittleEndian>()?;
157 let count = r.read_u16::<LittleEndian>()?;
158 (0..count).map(|_| read_entry(r)).collect()
159 }
160
read_entry<R: Read>(r: &mut R) -> ImageResult<DirEntry>161 fn read_entry<R: Read>(r: &mut R) -> ImageResult<DirEntry> {
162 Ok(DirEntry {
163 width: r.read_u8()?,
164 height: r.read_u8()?,
165 color_count: r.read_u8()?,
166 reserved: r.read_u8()?,
167 num_color_planes: {
168 // This may be either the number of color planes (0 or 1), or the horizontal coordinate
169 // of the hotspot for CUR files.
170 let num = r.read_u16::<LittleEndian>()?;
171 if num > 256 {
172 return Err(DecoderError::IcoEntryTooManyPlanesOrHotspot.into());
173 }
174 num
175 },
176 bits_per_pixel: {
177 // This may be either the bit depth (may be 0 meaning unspecified),
178 // or the vertical coordinate of the hotspot for CUR files.
179 let num = r.read_u16::<LittleEndian>()?;
180 if num > 256 {
181 return Err(DecoderError::IcoEntryTooManyBitsPerPixelOrHotspot.into());
182 }
183 num
184 },
185 image_length: r.read_u32::<LittleEndian>()?,
186 image_offset: r.read_u32::<LittleEndian>()?,
187 })
188 }
189
190 /// Find the entry with the highest (color depth, size).
best_entry(mut entries: Vec<DirEntry>) -> ImageResult<DirEntry>191 fn best_entry(mut entries: Vec<DirEntry>) -> ImageResult<DirEntry> {
192 let mut best = entries.pop().ok_or(DecoderError::NoEntries)?;
193
194 let mut best_score = (
195 best.bits_per_pixel,
196 u32::from(best.real_width()) * u32::from(best.real_height()),
197 );
198
199 for entry in entries {
200 let score = (
201 entry.bits_per_pixel,
202 u32::from(entry.real_width()) * u32::from(entry.real_height()),
203 );
204 if score > best_score {
205 best = entry;
206 best_score = score;
207 }
208 }
209 Ok(best)
210 }
211
212 impl DirEntry {
real_width(&self) -> u16213 fn real_width(&self) -> u16 {
214 match self.width {
215 0 => 256,
216 w => u16::from(w),
217 }
218 }
219
real_height(&self) -> u16220 fn real_height(&self) -> u16 {
221 match self.height {
222 0 => 256,
223 h => u16::from(h),
224 }
225 }
226
matches_dimensions(&self, width: u32, height: u32) -> bool227 fn matches_dimensions(&self, width: u32, height: u32) -> bool {
228 u32::from(self.real_width()) == width && u32::from(self.real_height()) == height
229 }
230
seek_to_start<R: Read + Seek>(&self, r: &mut R) -> ImageResult<()>231 fn seek_to_start<R: Read + Seek>(&self, r: &mut R) -> ImageResult<()> {
232 r.seek(SeekFrom::Start(u64::from(self.image_offset)))?;
233 Ok(())
234 }
235
is_png<R: Read + Seek>(&self, r: &mut R) -> ImageResult<bool>236 fn is_png<R: Read + Seek>(&self, r: &mut R) -> ImageResult<bool> {
237 self.seek_to_start(r)?;
238
239 // Read the first 8 bytes to sniff the image.
240 let mut signature = [0u8; 8];
241 r.read_exact(&mut signature)?;
242
243 Ok(signature == PNG_SIGNATURE)
244 }
245
decoder<R: Read + Seek>(&self, mut r: R) -> ImageResult<InnerDecoder<R>>246 fn decoder<R: Read + Seek>(&self, mut r: R) -> ImageResult<InnerDecoder<R>> {
247 let is_png = self.is_png(&mut r)?;
248 self.seek_to_start(&mut r)?;
249
250 if is_png {
251 Ok(PNG(PngDecoder::new(r)?))
252 } else {
253 Ok(BMP(BmpDecoder::new_with_ico_format(r)?))
254 }
255 }
256 }
257
258 /// Wrapper struct around a `Cursor<Vec<u8>>`
259 pub struct IcoReader<R>(Cursor<Vec<u8>>, PhantomData<R>);
260 impl<R> Read for IcoReader<R> {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>261 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
262 self.0.read(buf)
263 }
read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize>264 fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
265 if self.0.position() == 0 && buf.is_empty() {
266 mem::swap(buf, self.0.get_mut());
267 Ok(buf.len())
268 } else {
269 self.0.read_to_end(buf)
270 }
271 }
272 }
273
274 impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for IcoDecoder<R> {
275 type Reader = IcoReader<R>;
276
dimensions(&self) -> (u32, u32)277 fn dimensions(&self) -> (u32, u32) {
278 match self.inner_decoder {
279 BMP(ref decoder) => decoder.dimensions(),
280 PNG(ref decoder) => decoder.dimensions(),
281 }
282 }
283
color_type(&self) -> ColorType284 fn color_type(&self) -> ColorType {
285 match self.inner_decoder {
286 BMP(ref decoder) => decoder.color_type(),
287 PNG(ref decoder) => decoder.color_type(),
288 }
289 }
290
into_reader(self) -> ImageResult<Self::Reader>291 fn into_reader(self) -> ImageResult<Self::Reader> {
292 Ok(IcoReader(Cursor::new(image::decoder_to_vec(self)?), PhantomData))
293 }
294
read_image(self, buf: &mut [u8]) -> ImageResult<()>295 fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
296 assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
297 match self.inner_decoder {
298 PNG(decoder) => {
299 if self.selected_entry.image_length < PNG_SIGNATURE.len() as u32 {
300 return Err(DecoderError::PngShorterThanHeader.into());
301 }
302
303 // Check if the image dimensions match the ones in the image data.
304 let (width, height) = decoder.dimensions();
305 if !self.selected_entry.matches_dimensions(width, height) {
306 return Err(DecoderError::ImageEntryDimensionMismatch {
307 format: IcoEntryImageFormat::Png,
308 entry: (self.selected_entry.real_width(), self.selected_entry.real_height()),
309 image: (width, height)
310 }.into());
311 }
312
313 // Embedded PNG images can only be of the 32BPP RGBA format.
314 // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473/
315 if decoder.color_type() != ColorType::Rgba8 {
316 return Err(DecoderError::PngNotRgba.into());
317 }
318
319 decoder.read_image(buf)
320 }
321 BMP(mut decoder) => {
322 let (width, height) = decoder.dimensions();
323 if !self.selected_entry.matches_dimensions(width, height) {
324 return Err(DecoderError::ImageEntryDimensionMismatch {
325 format: IcoEntryImageFormat::Bmp,
326 entry: (self.selected_entry.real_width(), self.selected_entry.real_height()),
327 image: (width, height)
328 }.into());
329 }
330
331 // The ICO decoder needs an alpha channel to apply the AND mask.
332 if decoder.color_type() != ColorType::Rgba8 {
333 return Err(ImageError::Unsupported(UnsupportedError::from_format_and_kind(
334 ImageFormat::Bmp.into(),
335 UnsupportedErrorKind::Color(decoder.color_type().into()),
336 )));
337 }
338
339 decoder.read_image_data(buf)?;
340
341 let r = decoder.reader();
342 let image_end = r.seek(SeekFrom::Current(0))?;
343 let data_end =
344 u64::from(self.selected_entry.image_offset + self.selected_entry.image_length);
345
346 let mask_row_bytes = ((width + 31) / 32) * 4;
347 let mask_length = u64::from(mask_row_bytes) * u64::from(height);
348
349 // data_end should be image_end + the mask length (mask_row_bytes * height).
350 // According to
351 // https://devblogs.microsoft.com/oldnewthing/20101021-00/?p=12483
352 // the mask is required, but according to Wikipedia
353 // https://en.wikipedia.org/wiki/ICO_(file_format)
354 // the mask is not required. Unfortunately, Wikipedia does not have a citation
355 // for that claim, so we can't be sure which is correct.
356 if data_end >= image_end + mask_length {
357 // If there's an AND mask following the image, read and apply it.
358 for y in 0..height {
359 let mut x = 0;
360 for _ in 0..mask_row_bytes {
361 // Apply the bits of each byte until we reach the end of the row.
362 let mask_byte = r.read_u8()?;
363 for bit in (0..8).rev() {
364 if x >= width {
365 break;
366 }
367 if mask_byte & (1 << bit) != 0 {
368 // Set alpha channel to transparent.
369 buf[((height - y - 1) * width + x) as usize * 4 + 3] = 0;
370 }
371 x += 1;
372 }
373 }
374 }
375
376 Ok(())
377 } else if data_end == image_end {
378 // accept images with no mask data
379 Ok(())
380 } else {
381 Err(DecoderError::InvalidDataSize.into())
382 }
383 }
384 }
385 }
386 }
387