1 use byteorder::{LittleEndian, ReadBytesExt};
2 use std::io::{Cursor, Read, Seek, SeekFrom};
3
4 use color::ColorType;
5 use image::{ImageDecoder, ImageError, ImageResult};
6
7 use self::InnerDecoder::*;
8 use bmp::BMPDecoder;
9 use png::PNGDecoder;
10
11 // http://www.w3.org/TR/PNG-Structure.html
12 // The first eight bytes of a PNG file always contain the following (decimal) values:
13 const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10];
14
15 /// An ico decoder
16 pub struct ICODecoder<R: Read> {
17 selected_entry: DirEntry,
18 inner_decoder: InnerDecoder<R>,
19 }
20
21 enum InnerDecoder<R: Read> {
22 BMP(BMPDecoder<R>),
23 PNG(PNGDecoder<R>),
24 }
25
26 #[derive(Clone, Copy, Default)]
27 struct DirEntry {
28 width: u8,
29 height: u8,
30 color_count: u8,
31 reserved: u8,
32
33 num_color_planes: u16,
34 bits_per_pixel: u16,
35
36 image_length: u32,
37 image_offset: u32,
38 }
39
40 impl<R: Read + Seek> ICODecoder<R> {
41 /// Create a new decoder that decodes from the stream ```r```
new(mut r: R) -> ImageResult<ICODecoder<R>>42 pub fn new(mut r: R) -> ImageResult<ICODecoder<R>> {
43 let entries = try!(read_entries(&mut r));
44 let entry = try!(best_entry(entries));
45 let decoder = try!(entry.decoder(r));
46
47 Ok(ICODecoder {
48 selected_entry: entry,
49 inner_decoder: decoder,
50 })
51 }
52 }
53
read_entries<R: Read>(r: &mut R) -> ImageResult<Vec<DirEntry>>54 fn read_entries<R: Read>(r: &mut R) -> ImageResult<Vec<DirEntry>> {
55 let _reserved = try!(r.read_u16::<LittleEndian>());
56 let _type = try!(r.read_u16::<LittleEndian>());
57 let count = try!(r.read_u16::<LittleEndian>());
58 (0..count).map(|_| read_entry(r)).collect()
59 }
60
read_entry<R: Read>(r: &mut R) -> ImageResult<DirEntry>61 fn read_entry<R: Read>(r: &mut R) -> ImageResult<DirEntry> {
62 let mut entry = DirEntry::default();
63
64 entry.width = try!(r.read_u8());
65 entry.height = try!(r.read_u8());
66 entry.color_count = try!(r.read_u8());
67 // Reserved value (not used)
68 entry.reserved = try!(r.read_u8());
69
70 // This may be either the number of color planes (0 or 1), or the horizontal coordinate
71 // of the hotspot for CUR files.
72 entry.num_color_planes = try!(r.read_u16::<LittleEndian>());
73 if entry.num_color_planes > 256 {
74 return Err(ImageError::FormatError(
75 "ICO image entry has a too large color planes/hotspot value".to_string(),
76 ));
77 }
78
79 // This may be either the bit depth (may be 0 meaning unspecified),
80 // or the vertical coordinate of the hotspot for CUR files.
81 entry.bits_per_pixel = try!(r.read_u16::<LittleEndian>());
82 if entry.bits_per_pixel > 256 {
83 return Err(ImageError::FormatError(
84 "ICO image entry has a too large bits per pixel/hotspot value".to_string(),
85 ));
86 }
87
88 entry.image_length = try!(r.read_u32::<LittleEndian>());
89 entry.image_offset = try!(r.read_u32::<LittleEndian>());
90
91 Ok(entry)
92 }
93
94 /// Find the entry with the highest (color depth, size).
best_entry(mut entries: Vec<DirEntry>) -> ImageResult<DirEntry>95 fn best_entry(mut entries: Vec<DirEntry>) -> ImageResult<DirEntry> {
96 let mut best = try!(entries.pop().ok_or(ImageError::ImageEnd));
97 let mut best_score = (
98 best.bits_per_pixel,
99 u32::from(best.real_width()) * u32::from(best.real_height()),
100 );
101
102 for entry in entries {
103 let score = (
104 entry.bits_per_pixel,
105 u32::from(entry.real_width()) * u32::from(entry.real_height()),
106 );
107 if score > best_score {
108 best = entry;
109 best_score = score;
110 }
111 }
112 Ok(best)
113 }
114
115 impl DirEntry {
real_width(&self) -> u16116 fn real_width(&self) -> u16 {
117 match self.width {
118 0 => 256,
119 w => u16::from(w),
120 }
121 }
122
real_height(&self) -> u16123 fn real_height(&self) -> u16 {
124 match self.height {
125 0 => 256,
126 h => u16::from(h),
127 }
128 }
129
matches_dimensions(&self, width: u64, height: u64) -> bool130 fn matches_dimensions(&self, width: u64, height: u64) -> bool {
131 u64::from(self.real_width()) == width && u64::from(self.real_height()) == height
132 }
133
seek_to_start<R: Read + Seek>(&self, r: &mut R) -> ImageResult<()>134 fn seek_to_start<R: Read + Seek>(&self, r: &mut R) -> ImageResult<()> {
135 try!(r.seek(SeekFrom::Start(u64::from(self.image_offset))));
136 Ok(())
137 }
138
is_png<R: Read + Seek>(&self, r: &mut R) -> ImageResult<bool>139 fn is_png<R: Read + Seek>(&self, r: &mut R) -> ImageResult<bool> {
140 try!(self.seek_to_start(r));
141
142 // Read the first 8 bytes to sniff the image.
143 let mut signature = [0u8; 8];
144 try!(r.read_exact(&mut signature));
145
146 Ok(signature == PNG_SIGNATURE)
147 }
148
decoder<R: Read + Seek>(&self, mut r: R) -> ImageResult<InnerDecoder<R>>149 fn decoder<R: Read + Seek>(&self, mut r: R) -> ImageResult<InnerDecoder<R>> {
150 let is_png = try!(self.is_png(&mut r));
151 try!(self.seek_to_start(&mut r));
152
153 if is_png {
154 Ok(PNG(PNGDecoder::new(r)?))
155 } else {
156 Ok(BMP(BMPDecoder::new_with_ico_format(r)?))
157 }
158 }
159 }
160
161 impl<R: Read + Seek> ImageDecoder for ICODecoder<R> {
162 type Reader = Cursor<Vec<u8>>;
163
dimensions(&self) -> (u64, u64)164 fn dimensions(&self) -> (u64, u64) {
165 match self.inner_decoder {
166 BMP(ref decoder) => decoder.dimensions(),
167 PNG(ref decoder) => decoder.dimensions(),
168 }
169 }
170
colortype(&self) -> ColorType171 fn colortype(&self) -> ColorType {
172 match self.inner_decoder {
173 BMP(ref decoder) => decoder.colortype(),
174 PNG(ref decoder) => decoder.colortype(),
175 }
176 }
177
into_reader(self) -> ImageResult<Self::Reader>178 fn into_reader(self) -> ImageResult<Self::Reader> {
179 Ok(Cursor::new(self.read_image()?))
180 }
181
read_image(self) -> ImageResult<Vec<u8>>182 fn read_image(self) -> ImageResult<Vec<u8>> {
183 match self.inner_decoder {
184 PNG(decoder) => {
185 if self.selected_entry.image_length < PNG_SIGNATURE.len() as u32 {
186 return Err(ImageError::FormatError(
187 "Entry specified a length that is shorter than PNG header!".to_string(),
188 ));
189 }
190
191 // Check if the image dimensions match the ones in the image data.
192 let (width, height) = decoder.dimensions();
193 if !self.selected_entry.matches_dimensions(width, height) {
194 return Err(ImageError::FormatError(
195 "Entry and PNG dimensions do not match!".to_string(),
196 ));
197 }
198
199 // Embedded PNG images can only be of the 32BPP RGBA format.
200 // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473/
201 let color_type = decoder.colortype();
202 if let ColorType::RGBA(8) = color_type {
203 } else {
204 return Err(ImageError::FormatError(
205 "The PNG is not in RGBA format!".to_string(),
206 ));
207 }
208
209 decoder.read_image()
210 }
211 BMP(mut decoder) => {
212 let (width, height) = decoder.dimensions();
213 if !self.selected_entry.matches_dimensions(width, height) {
214 return Err(ImageError::FormatError(
215 "Entry({:?}) and BMP({:?}) dimensions do not match!".to_string(),
216 ));
217 }
218
219 // The ICO decoder needs an alpha channel to apply the AND mask.
220 if decoder.colortype() != ColorType::RGBA(8) {
221 return Err(ImageError::UnsupportedError(
222 "Unsupported color type".to_string(),
223 ));
224 }
225
226 let mut pixel_data = decoder.read_image_data()?;
227
228 // If there's an AND mask following the image, read and apply it.
229 let r = decoder.reader();
230 let mask_start = try!(r.seek(SeekFrom::Current(0)));
231 let mask_end =
232 u64::from(self.selected_entry.image_offset + self.selected_entry.image_length);
233 let mask_length = mask_end - mask_start;
234
235 if mask_length > 0 {
236 // A mask row contains 1 bit per pixel, padded to 4 bytes.
237 let mask_row_bytes = ((width + 31) / 32) * 4;
238 let expected_length = u64::from(mask_row_bytes * height);
239 if mask_length < expected_length {
240 return Err(ImageError::ImageEnd);
241 }
242
243 for y in 0..height {
244 let mut x = 0;
245 for _ in 0..mask_row_bytes {
246 // Apply the bits of each byte until we reach the end of the row.
247 let mask_byte = try!(r.read_u8());
248 for bit in (0..8).rev() {
249 if x >= width {
250 break;
251 }
252 if mask_byte & (1 << bit) != 0 {
253 // Set alpha channel to transparent.
254 pixel_data[((height - y - 1) * width + x) as usize * 4 + 3] = 0;
255 }
256 x += 1;
257 }
258 }
259 }
260 }
261 Ok(pixel_data)
262 }
263 }
264 }
265 }
266