tauri_icns/
element.rs

1use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
2use std::cmp;
3use std::io::{self, Error, ErrorKind, Read, Write};
4
5use super::icontype::{Encoding, IconType, OSType};
6use super::image::{Image, PixelFormat};
7
8/// The length of an icon element header, in bytes:
9const ICON_ELEMENT_HEADER_LENGTH: u32 = 8;
10
11/// The first twelve bytes of a JPEG 2000 file are always this:
12#[cfg(feature = "pngio")]
13const JPEG_2000_FILE_MAGIC_NUMBER: [u8; 12] = [
14    0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A,
15];
16
17/// One data block in an ICNS file.  Depending on the resource type, this may
18/// represent an icon, or part of an icon (such as an alpha mask, or color
19/// data without the mask).
20pub struct IconElement {
21    /// The OSType for this element (e.g. `it32` or `t8mk`).
22    pub ostype: OSType,
23    /// The raw data payload for this element.
24    pub data: Vec<u8>,
25}
26
27impl IconElement {
28    /// Creates an icon element with the given OSType and data payload.
29    pub fn new(ostype: OSType, data: Vec<u8>) -> IconElement {
30        IconElement { ostype, data }
31    }
32
33    /// Creates an icon element that encodes the given image as the given icon
34    /// type.  Image color channels that aren't relevant to the specified icon
35    /// type will be ignored (e.g. if the icon type is a mask, then only the
36    /// alpha channel of the image will be used).  Returns an error if the
37    /// image dimensions don't match the icon type.
38    ///
39    /// Note that if `icon_type` has an associated mask type, this method will
40    /// _not_ encode the mask, and will in fact ignore any alpha channel in the
41    /// image; you'll need to encode a second `IconElement` with the mask type.
42    /// For a higher-level interface that will encode both elements at once,
43    /// see the [`IconFamily.add_icon_with_type`](
44    /// struct.IconFamily.html#method.add_icon_with_type) method.
45    pub fn encode_image_with_type(image: &Image, icon_type: IconType) -> io::Result<IconElement> {
46        let width = icon_type.pixel_width();
47        let height = icon_type.pixel_height();
48        if image.width() != width || image.height() != height {
49            let msg = format!(
50                "image has wrong dimensions for {:?} ({}x{} \
51                               instead of {}x{}))",
52                icon_type,
53                image.width(),
54                image.height(),
55                width,
56                height
57            );
58            return Err(Error::new(ErrorKind::InvalidInput, msg));
59        }
60        let mut data: Vec<u8>;
61        match icon_type.encoding() {
62            #[cfg(feature = "pngio")]
63            Encoding::JP2PNG => {
64                data = Vec::new();
65                image.write_png(&mut data)?;
66            }
67            #[cfg(not(feature = "pngio"))]
68            Encoding::JP2PNG => unimplemented!(),
69            Encoding::RLE24 => {
70                let num_pixels = (width * height) as usize;
71                match image.pixel_format() {
72                    PixelFormat::RGBA => {
73                        data = encode_rle(image.data(), 4, num_pixels);
74                    }
75                    PixelFormat::RGB => {
76                        data = encode_rle(image.data(), 3, num_pixels);
77                    }
78                    // Convert to RGB if the image isn't already RGB or RGBA.
79                    _ => {
80                        let image = image.convert_to(PixelFormat::RGB);
81                        data = encode_rle(image.data(), 3, num_pixels);
82                    }
83                }
84            }
85            Encoding::Mask8 => {
86                // Convert to Alpha format unconditionally -- if the image is
87                // already Alpha format, this will simply clone its data array,
88                // which we'd need to do anyway.
89                let image = image.convert_to(PixelFormat::Alpha);
90                data = image.into_data().into_vec();
91            }
92        }
93        Ok(IconElement::new(icon_type.ostype(), data))
94    }
95
96    /// Decodes the icon element into an image.  Returns an error if this
97    /// element does not represent an icon type supported by this library, or
98    /// if the data is malformed.
99    ///
100    /// Note that if the element's icon type has an associated mask type, this
101    /// method will simply produce an image with no alpha channel (since the
102    /// mask lives in a separate `IconElement`).  To decode image and mask
103    /// together into a single image, you can either use the
104    /// [`decode_image_with_mask`](#method.decode_image_with_mask) method,
105    /// or the higher-level [`IconFamily.get_icon_with_type`](
106    /// struct.IconFamily.html#method.get_icon_with_type) method.
107    pub fn decode_image(&self) -> io::Result<Image> {
108        let icon_type = self.icon_type().ok_or_else(|| {
109            Error::new(
110                ErrorKind::InvalidInput,
111                format!("unsupported OSType: {}", self.ostype),
112            )
113        })?;
114        let width = icon_type.pixel_width();
115        let height = icon_type.pixel_width();
116        match icon_type.encoding() {
117            #[cfg(feature = "pngio")]
118            Encoding::JP2PNG => {
119                if self.data.starts_with(&JPEG_2000_FILE_MAGIC_NUMBER) {
120                    let msg = "element to be decoded contains JPEG 2000 \
121                               data, which is not yet supported";
122                    return Err(Error::new(ErrorKind::InvalidInput, msg));
123                }
124                let image = Image::read_png(io::Cursor::new(&self.data))?;
125                if image.width() != width || image.height() != height {
126                    let msg = format!(
127                        "decoded PNG has wrong dimensions \
128                                       ({}x{} instead of {}x{})",
129                        image.width(),
130                        image.height(),
131                        width,
132                        height
133                    );
134                    return Err(Error::new(ErrorKind::InvalidData, msg));
135                }
136                Ok(image)
137            }
138            #[cfg(not(feature = "pngio"))]
139            Encoding::JP2PNG => unimplemented!(),
140            Encoding::RLE24 => {
141                let mut image = Image::new(PixelFormat::RGB, width, height);
142                decode_rle(&self.data, 3, image.data_mut())?;
143                Ok(image)
144            }
145            Encoding::Mask8 => {
146                let num_pixels = width * height;
147                if self.data.len() as u32 != num_pixels {
148                    let msg = format!(
149                        "wrong data payload length ({} \
150                                       instead of {})",
151                        self.data.len(),
152                        num_pixels
153                    );
154                    return Err(Error::new(ErrorKind::InvalidData, msg));
155                }
156                let mut image = Image::new(PixelFormat::Alpha, width, height);
157                image.data_mut().clone_from_slice(&self.data);
158                Ok(image)
159            }
160        }
161    }
162
163    /// Decodes this element, together with a separate mask element, into a
164    /// single image with alpha channel.  Returns an error if this element does
165    /// not represent an icon type supported by this library, or if the given
166    /// mask element does not represent the correct mask type for this element,
167    /// or if any of the data is malformed.
168    ///
169    /// For a more convenient alternative to this method, consider using the
170    /// higher-level [`IconFamily.get_icon_with_type`](
171    /// struct.IconFamily.html#method.get_icon_with_type) method instead.
172    pub fn decode_image_with_mask(&self, mask: &IconElement) -> io::Result<Image> {
173        let icon_type = self.icon_type().ok_or_else(|| {
174            Error::new(
175                ErrorKind::InvalidInput,
176                format!("unsupported OSType: {}", self.ostype),
177            )
178        })?;
179        let mask_type = icon_type.mask_type().ok_or_else(|| {
180            let msg = format!("icon type {:?} does not use a mask", icon_type);
181            Error::new(ErrorKind::InvalidInput, msg)
182        })?;
183        assert_eq!(icon_type.encoding(), Encoding::RLE24);
184        if mask.ostype != mask_type.ostype() {
185            let msg = format!(
186                "wrong OSType for mask ('{}' instead of '{}')",
187                mask.ostype,
188                mask_type.ostype()
189            );
190            return Err(Error::new(ErrorKind::InvalidInput, msg));
191        }
192        let width = icon_type.pixel_width();
193        let height = icon_type.pixel_height();
194        let num_pixels = (width * height) as usize;
195        if mask.data.len() != num_pixels {
196            let msg = format!(
197                "wrong mask data payload length ({} instead \
198                               of {})",
199                mask.data.len(),
200                num_pixels
201            );
202            return Err(Error::new(ErrorKind::InvalidInput, msg));
203        }
204        let mut image = Image::new(PixelFormat::RGBA, width, height);
205        decode_rle(&self.data, 4, image.data_mut())?;
206        for (i, &alpha) in mask.data.iter().enumerate() {
207            image.data_mut()[4 * i + 3] = alpha;
208        }
209        Ok(image)
210    }
211
212    /// Returns the type of icon encoded by this element, or `None` if this
213    /// element does not encode a supported icon type.
214    pub fn icon_type(&self) -> Option<IconType> {
215        IconType::from_ostype(self.ostype)
216    }
217
218    /// Reads an icon element from within an ICNS file.
219    pub fn read<R: Read>(mut reader: R) -> io::Result<IconElement> {
220        let mut raw_ostype = [0u8; 4];
221        reader.read_exact(&mut raw_ostype)?;
222        let element_length = reader.read_u32::<BigEndian>()?;
223        if element_length < ICON_ELEMENT_HEADER_LENGTH {
224            return Err(Error::new(ErrorKind::InvalidData, "invalid element length"));
225        }
226        let data_length = element_length - ICON_ELEMENT_HEADER_LENGTH;
227        let mut data = vec![0u8; data_length as usize];
228        reader.read_exact(&mut data)?;
229        Ok(IconElement::new(OSType(raw_ostype), data))
230    }
231
232    /// Writes the icon element to within an ICNS file.
233    pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
234        let OSType(ref raw_ostype) = self.ostype;
235        writer.write_all(raw_ostype)?;
236        writer.write_u32::<BigEndian>(self.total_length())?;
237        writer.write_all(&self.data)?;
238        Ok(())
239    }
240
241    /// Returns the encoded length of the element, in bytes, including the
242    /// length of the header.
243    pub fn total_length(&self) -> u32 {
244        ICON_ELEMENT_HEADER_LENGTH + (self.data.len() as u32)
245    }
246}
247
248fn encode_rle(input: &[u8], num_input_channels: usize, num_pixels: usize) -> Vec<u8> {
249    assert!(num_input_channels == 3 || num_input_channels == 4);
250    let mut output = Vec::new();
251    if num_pixels == 128 * 128 {
252        // The 128x128 RLE icon (it32) starts with four extra zeros.
253        output.extend_from_slice(&[0, 0, 0, 0]);
254    }
255    for channel in 0..3 {
256        let mut pixel: usize = 0;
257        let mut literal_start: usize = 0;
258        while pixel < num_pixels {
259            let value = input[num_input_channels * pixel + channel];
260            let mut run_length = 1;
261            while pixel + run_length < num_pixels
262                && input[num_input_channels * (pixel + run_length) + channel] == value
263                && run_length < 130
264            {
265                run_length += 1;
266            }
267            if run_length >= 3 {
268                while literal_start < pixel {
269                    let literal_length = cmp::min(128, pixel - literal_start);
270                    output.push((literal_length - 1) as u8);
271                    for i in 0..literal_length {
272                        output.push(input[num_input_channels * (literal_start + i) + channel]);
273                    }
274                    literal_start += literal_length;
275                }
276                output.push((run_length + 125) as u8);
277                output.push(value);
278                pixel += run_length;
279                literal_start = pixel;
280            } else {
281                pixel += run_length;
282            }
283        }
284        while literal_start < pixel {
285            let literal_length = cmp::min(128, pixel - literal_start);
286            output.push((literal_length - 1) as u8);
287            for i in 0..literal_length {
288                output.push(input[num_input_channels * (literal_start + i) + channel]);
289            }
290            literal_start += literal_length;
291        }
292    }
293    output
294}
295
296fn decode_rle(input: &[u8], num_output_channels: usize, output: &mut [u8]) -> io::Result<()> {
297    assert!(num_output_channels == 3 || num_output_channels == 4);
298    assert_eq!(output.len() % num_output_channels, 0);
299    let num_pixels = output.len() / num_output_channels;
300    // Sometimes, RLE-encoded data starts with four extra zeros that must be
301    // skipped.
302    let skip: usize = if input.starts_with(&[0, 0, 0, 0]) {
303        4
304    } else {
305        0
306    };
307    let input = &input[skip..input.len()];
308    let mut iter = input.iter();
309    let mut remaining: usize = 0;
310    let mut within_run = false;
311    let mut run_value: u8 = 0;
312    for channel in 0..3 {
313        for pixel in 0..num_pixels {
314            if remaining == 0 {
315                let next: u8 = *iter.next().ok_or_else(rle_error)?;
316                if next < 128 {
317                    remaining = (next as usize) + 1;
318                    within_run = false;
319                } else {
320                    remaining = (next as usize) - 125;
321                    within_run = true;
322                    run_value = *iter.next().ok_or_else(rle_error)?;
323                }
324            }
325            output[num_output_channels * pixel + channel] = if within_run {
326                run_value
327            } else {
328                *iter.next().ok_or_else(rle_error)?
329            };
330            remaining -= 1;
331        }
332        if remaining != 0 {
333            return Err(rle_error());
334        }
335    }
336    if iter.next().is_some() {
337        Err(rle_error())
338    } else {
339        Ok(())
340    }
341}
342
343fn rle_error() -> Error {
344    Error::new(ErrorKind::InvalidData, "invalid RLE-compressed data")
345}
346
347#[cfg(test)]
348mod tests {
349    use super::super::icontype::{IconType, OSType};
350    use super::super::image::{Image, PixelFormat};
351    use super::*;
352
353    #[test]
354    fn encode_rle() {
355        let mut image = Image::new(PixelFormat::Gray, 16, 16);
356        image.data_mut()[0] = 44;
357        image.data_mut()[1] = 55;
358        image.data_mut()[2] = 66;
359        image.data_mut()[3] = 66;
360        image.data_mut()[4] = 66;
361        let element = IconElement::encode_image_with_type(&image, IconType::RGB24_16x16)
362            .expect("failed to encode image");
363        assert_eq!(element.ostype, OSType(*b"is32"));
364        assert_eq!(element.data[0..5], [1, 44, 55, 128, 66]);
365    }
366
367    #[test]
368    fn decode_rle() {
369        let data: Vec<u8> = vec![
370            0, 12, 255, 0, 250, 0, 128, 34, 255, 0, 248, 0, 1, 56, 99, 255, 0, 249, 0,
371        ];
372        let element = IconElement::new(OSType(*b"is32"), data);
373        let image = element.decode_image().expect("failed to decode image");
374        assert_eq!(image.pixel_format(), PixelFormat::RGB);
375        assert_eq!(image.width(), 16);
376        assert_eq!(image.height(), 16);
377        assert_eq!(image.data()[0], 12);
378        assert_eq!(image.data()[1], 34);
379        assert_eq!(image.data()[2], 56);
380    }
381
382    #[test]
383    fn decode_rle_skip_extra_zeros() {
384        let data: Vec<u8> = vec![
385            0, 0, 0, 0, 0, 12, 255, 0, 250, 0, 128, 34, 255, 0, 248, 0, 1, 56, 99, 255, 0, 249, 0,
386        ];
387        let element = IconElement::new(OSType(*b"is32"), data);
388        let image = element.decode_image().expect("failed to decode image");
389        assert_eq!(image.data()[0], 12);
390        assert_eq!(image.data()[1], 34);
391        assert_eq!(image.data()[2], 56);
392    }
393
394    #[test]
395    fn encode_mask() {
396        let mut image = Image::new(PixelFormat::Alpha, 16, 16);
397        image.data_mut()[2] = 127;
398        let element = IconElement::encode_image_with_type(&image, IconType::Mask8_16x16)
399            .expect("failed to encode image");
400        assert_eq!(element.ostype, OSType(*b"s8mk"));
401        assert_eq!(element.data[2], 127);
402    }
403
404    #[test]
405    fn decode_mask() {
406        let mut data = vec![0u8; 256];
407        data[2] = 127;
408        let element = IconElement::new(OSType(*b"s8mk"), data);
409        let image = element.decode_image().expect("failed to decode image");
410        assert_eq!(image.pixel_format(), PixelFormat::Alpha);
411        assert_eq!(image.width(), 16);
412        assert_eq!(image.height(), 16);
413        assert_eq!(image.data()[2], 127);
414    }
415
416    #[test]
417    fn decode_rle_with_mask() {
418        let color_data: Vec<u8> = vec![
419            0, 12, 255, 0, 250, 0, 128, 34, 255, 0, 248, 0, 1, 56, 99, 255, 0, 249, 0,
420        ];
421        let color_element = IconElement::new(OSType(*b"is32"), color_data);
422        let mask_data = vec![78u8; 256];
423        let mask_element = IconElement::new(OSType(*b"s8mk"), mask_data);
424        let image = color_element
425            .decode_image_with_mask(&mask_element)
426            .expect("failed to decode image");
427        assert_eq!(image.pixel_format(), PixelFormat::RGBA);
428        assert_eq!(image.width(), 16);
429        assert_eq!(image.height(), 16);
430        assert_eq!(image.data()[0], 12);
431        assert_eq!(image.data()[1], 34);
432        assert_eq!(image.data()[2], 56);
433        assert_eq!(image.data()[3], 78);
434    }
435}