├── .github └── workflows │ └── tests.yml ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── icns2png.rs ├── png2icns.rs └── readicns.rs ├── rustfmt.toml ├── src ├── element.rs ├── family.rs ├── icontype.rs ├── image.rs ├── lib.rs └── pngio.rs └── tests ├── golden.rs ├── icns ├── ic07.icns ├── ic11.icns ├── icp4.icns ├── icp5.icns ├── il32.icns ├── is32.icns └── it32.icns └── png ├── 128x128.png ├── 16x16.png └── 32x32.png /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'LICENSE-*' 7 | - '**.md' 8 | pull_request: 9 | paths-ignore: 10 | - 'LICENSE-*' 11 | - '**.md' 12 | 13 | jobs: 14 | tests: 15 | strategy: 16 | matrix: 17 | os: [ ubuntu-latest, windows-latest, macos-latest] 18 | rust: [ stable ] 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Install rust toolchain 23 | uses: dtolnay/rust-toolchain@master 24 | with: 25 | toolchain: ${{ matrix.rust }} 26 | - name: Test 27 | run: cargo test --verbose 28 | 29 | linters: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v3 33 | with: 34 | submodules: true 35 | - name: Install rust toolchain 36 | uses: dtolnay/rust-toolchain@master 37 | with: 38 | toolchain: stable 39 | components: rustfmt, clippy 40 | - name: Cargo clippy 41 | run: cargo clippy --all-features -- -D warnings 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.rs.bk 3 | /Cargo.lock 4 | /target/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | matrix: 7 | allow_failures: 8 | - rust: nightly 9 | fast_finish: true 10 | notifications: 11 | email: false 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "icns" 3 | version = "0.3.1" 4 | authors = ["Matthew D. Steele "] 5 | description = "A library for encoding/decoding Apple Icon Image (.icns) files." 6 | repository = "https://github.com/mdsteele/rust-icns" 7 | keywords = ["icns", "icon", "image"] 8 | license = "MIT" 9 | readme = "README.md" 10 | 11 | [dependencies] 12 | byteorder = "1" 13 | png = { version = "0.16", optional = true } 14 | 15 | [features] 16 | default = ["pngio"] 17 | pngio = ["png"] 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Matthew D. Steele 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-icns 2 | 3 | [![Build Status](https://github.com/mdsteele/rust-icns/actions/workflows/tests.yml/badge.svg)](https://github.com/mdsteele/rust-icns/actions/workflows/tests.yml) 4 | [![Crates.io](https://img.shields.io/crates/v/icns.svg)](https://crates.io/crates/icns) 5 | [![Documentation](https://docs.rs/icns/badge.svg)](https://docs.rs/icns) 6 | 7 | A Rust library for encoding/decoding Apple Icon Image (.icns) files. 8 | 9 | ## Overview 10 | 11 | The `icns` crate implements reading and writing of ICNS files, encoding and 12 | decoding images into and out of an ICNS icon family, converting those images to 13 | other pixel formats (in case you need to transfer the image data to another 14 | library that expects the data in a particular format), and saving/loading those 15 | images to/from PNG files. 16 | 17 | The [crate documentation](https://docs.rs/icns) has more information about how 18 | to use the library. 19 | 20 | ## Example usage 21 | 22 | ```rust 23 | extern crate icns; 24 | use icns::{IconFamily, IconType, Image}; 25 | use std::fs::File; 26 | use std::io::{BufReader, BufWriter}; 27 | 28 | fn main() { 29 | // Load an icon family from an ICNS file. 30 | let file = BufReader::new(File::open("16.icns").unwrap()); 31 | let mut icon_family = IconFamily::read(file).unwrap(); 32 | 33 | // Extract an icon from the family and save it as a PNG. 34 | let image = icon_family.get_icon_with_type(IconType::RGB24_16x16).unwrap(); 35 | let file = BufWriter::new(File::create("16.png").unwrap()); 36 | image.write_png(file).unwrap(); 37 | 38 | // Read in another icon from a PNG file, and add it to the icon family. 39 | let file = BufReader::new(File::open("32.png").unwrap()); 40 | let image = Image::read_png(file).unwrap(); 41 | icon_family.add_icon(&image).unwrap(); 42 | 43 | // Save the updated icon family to a new ICNS file. 44 | let file = BufWriter::new(File::create("16-and-32.icns").unwrap()); 45 | icon_family.write(file).unwrap(); 46 | } 47 | ``` 48 | 49 | ## Supported icon types 50 | 51 | ICNS files can contain a number of different icon types. This library supports 52 | the most commonly-used types, but some of the older ones are not yet supported. 53 | The table below indicates which types are currently supported; see 54 | https://en.wikipedia.org/wiki/Apple_Icon_Image_format#Icon_types for more 55 | information about each type. 56 | 57 | The biggest limitation at this time is that a number of the newer icon types 58 | can be encoded with either PNG or JPEG 2000 data, but this library does not yet 59 | support JPEG 2000; attempting to decode such an icon will result an an error 60 | value being returned (although you can still decode other icons from the same 61 | ICNS file). The reason for this is that I don't currently know of any JPEG 62 | 2000 libraries for Rust; if one exists, please feel free to file a bug or send 63 | a pull request. 64 | 65 | | OSType | Description | Supported? | 66 | |--------|-----------------------------------------|------------| 67 | | `ICON` | 32×32 1-bit icon | No | 68 | | `ICN#` | 32×32 1-bit icon with 1-bit mask | No | 69 | | `icm#` | 16×12 1-bit icon with 1-bit mask | No | 70 | | `icm4` | 16×12 4-bit icon | No | 71 | | `icm8` | 16×12 8-bit icon | No | 72 | | `ics#` | 16×16 1-bit mask | No | 73 | | `ics4` | 16×16 4-bit icon | No | 74 | | `ics8` | 16x16 8-bit icon | No | 75 | | `is32` | 16×16 24-bit icon | Yes | 76 | | `s8mk` | 16x16 8-bit mask | Yes | 77 | | `icl4` | 32×32 4-bit icon | No | 78 | | `icl8` | 32×32 8-bit icon | No | 79 | | `il32` | 32x32 24-bit icon | Yes | 80 | | `l8mk` | 32×32 8-bit mask | Yes | 81 | | `ich#` | 48×48 1-bit mask | No | 82 | | `ich4` | 48×48 4-bit icon | No | 83 | | `ich8` | 48×48 8-bit icon | No | 84 | | `ih32` | 48×48 24-bit icon | Yes | 85 | | `h8mk` | 48×48 8-bit mask | Yes | 86 | | `it32` | 128×128 24-bit icon | Yes | 87 | | `t8mk` | 128×128 8-bit mask | Yes | 88 | | `icp4` | 16x16 32-bit PNG/JP2 icon | PNG only | 89 | | `icp5` | 32x32 32-bit PNG/JP2 icon | PNG only | 90 | | `icp6` | 64x64 32-bit PNG/JP2 icon | PNG only | 91 | | `ic07` | 128x128 32-bit PNG/JP2 icon | PNG only | 92 | | `ic08` | 256×256 32-bit PNG/JP2 icon | PNG only | 93 | | `ic09` | 512×512 32-bit PNG/JP2 icon | PNG only | 94 | | `ic10` | 512x512@2x "retina" 32-bit PNG/JP2 icon | PNG only | 95 | | `ic11` | 16x16@2x "retina" 32-bit PNG/JP2 icon | PNG only | 96 | | `ic12` | 32x32@2x "retina" 32-bit PNG/JP2 icon | PNG only | 97 | | `ic13` | 128x128@2x "retina" 32-bit PNG/JP2 icon | PNG only | 98 | | `ic14` | 256x256@2x "retina" 32-bit PNG/JP2 icon | PNG only | 99 | 100 | ## License 101 | 102 | rust-icns is made available under the 103 | [MIT License](http://spdx.org/licenses/MIT.html). 104 | -------------------------------------------------------------------------------- /examples/icns2png.rs: -------------------------------------------------------------------------------- 1 | //! Extracts a single icon from an ICNS file and saves it as a PNG. 2 | //! 3 | //! To extract the highest-resolution icon in the ICNS file, run: 4 | //! 5 | //! ```shell 6 | //! cargo run --example icns2png 7 | //! # image will be saved to path/to/file.png 8 | //! ``` 9 | //! 10 | //! To extract a specific icon from the file, run: 11 | //! 12 | //! ```shell 13 | //! cargo run --example icns2png 14 | //! # image will be saved to path/to/file..png 15 | //! ``` 16 | //! 17 | //! Where is the OSType for the icon you want to extract (e.g. il32 18 | //! for the 32x32 RLE-encoded icon, or ic08 for the 256x256 PNG-encoded icon). 19 | //! See https://en.wikipedia.org/wiki/Apple_Icon_Image_format#Icon_types for a 20 | //! list of possible OSTypes. 21 | 22 | extern crate icns; 23 | 24 | use icns::{IconFamily, IconType, OSType}; 25 | use std::env; 26 | use std::fs::File; 27 | use std::io::{BufReader, BufWriter}; 28 | use std::path::Path; 29 | use std::str::FromStr; 30 | 31 | fn main() { 32 | let num_args = env::args().count(); 33 | if num_args < 2 || num_args > 3 { 34 | println!("Usage: icns2png []"); 35 | return; 36 | } 37 | let icns_path = env::args().nth(1).unwrap(); 38 | let icns_path = Path::new(&icns_path); 39 | let icns_file = BufReader::new(File::open(icns_path) 40 | .expect("failed to open ICNS file")); 41 | let family = IconFamily::read(icns_file) 42 | .expect("failed to read ICNS file"); 43 | let (icon_type, png_path) = if num_args == 3 { 44 | let ostype = OSType::from_str(&env::args().nth(2).unwrap()).unwrap(); 45 | let icon_type = IconType::from_ostype(ostype) 46 | .expect("unsupported ostype"); 47 | let png_path = icns_path.with_extension(format!("{}.png", ostype)); 48 | (icon_type, png_path) 49 | } else { 50 | // If no OSType is specified, extract the highest-resolution icon. 51 | let &icon_type = family.available_icons() 52 | .iter() 53 | .max_by_key(|icon_type| { 54 | icon_type.pixel_width() * icon_type.pixel_height() 55 | }) 56 | .expect("ICNS file contains no icons"); 57 | let png_path = icns_path.with_extension("png"); 58 | (icon_type, png_path) 59 | }; 60 | let image = family.get_icon_with_type(icon_type) 61 | .expect("ICNS file does not contain that icon type"); 62 | let png_file = BufWriter::new(File::create(png_path) 63 | .expect("failed to create PNG file")); 64 | image.write_png(png_file).expect("failed to write PNG file"); 65 | } 66 | -------------------------------------------------------------------------------- /examples/png2icns.rs: -------------------------------------------------------------------------------- 1 | //! Creates an ICNS file containing a single image, read from a PNG file. 2 | //! 3 | //! To create an ICNS file from a PNG, run: 4 | //! 5 | //! ```shell 6 | //! cargo run --example png2icns 7 | //! # ICNS will be saved to path/to/file.icns 8 | //! ``` 9 | //! 10 | //! Note that the dimensions of the input image must be exactly those of one of 11 | //! the supported icon types (for example, 32x32 or 128x128). 12 | //! 13 | //! To create an ICNS file from a PNG using a specific icon type within the 14 | //! ICNS file, run: 15 | //! 16 | //! ```shell 17 | //! cargo run --example png2icns 18 | //! # ICNS will be saved to path/to/file..icns 19 | //! ``` 20 | //! 21 | //! Where is the OSType for the icon type you want to encode in. In 22 | //! this case, the dimensions of the input image must match the particular 23 | //! chosen icon type. 24 | 25 | extern crate icns; 26 | 27 | use icns::{IconFamily, IconType, Image, OSType}; 28 | use std::env; 29 | use std::fs::File; 30 | use std::io::{BufReader, BufWriter}; 31 | use std::path::Path; 32 | use std::str::FromStr; 33 | 34 | fn main() { 35 | let num_args = env::args().count(); 36 | if num_args < 2 || num_args > 3 { 37 | println!("Usage: png2icns []"); 38 | return; 39 | } 40 | let png_path = env::args().nth(1).unwrap(); 41 | let png_path = Path::new(&png_path); 42 | let png_file = BufReader::new(File::open(png_path) 43 | .expect("failed to open PNG file")); 44 | let image = Image::read_png(png_file).expect("failed to read PNG file"); 45 | let mut family = IconFamily::new(); 46 | let icns_path = if num_args == 3 { 47 | let ostype = OSType::from_str(&env::args().nth(2).unwrap()).unwrap(); 48 | let icon_type = IconType::from_ostype(ostype) 49 | .expect("unsupported ostype"); 50 | family.add_icon_with_type(&image, icon_type) 51 | .expect("failed to encode image"); 52 | png_path.with_extension(format!("{}.icns", ostype)) 53 | } else { 54 | family.add_icon(&image).expect("failed to encode image"); 55 | png_path.with_extension("icns") 56 | }; 57 | let icns_file = BufWriter::new(File::create(icns_path) 58 | .expect("failed to create ICNS file")); 59 | family.write(icns_file).expect("failed to write ICNS file"); 60 | } 61 | -------------------------------------------------------------------------------- /examples/readicns.rs: -------------------------------------------------------------------------------- 1 | extern crate icns; 2 | 3 | use icns::IconFamily; 4 | use std::env; 5 | use std::fs::File; 6 | use std::io::BufReader; 7 | 8 | fn main() { 9 | if env::args().count() != 2 { 10 | println!("Usage: readicns "); 11 | return; 12 | } 13 | let path = env::args().nth(1).unwrap(); 14 | let file = File::open(path).expect("failed to open file"); 15 | let buffered = BufReader::new(file); 16 | let family = IconFamily::read(buffered).expect("failed to read ICNS file"); 17 | println!("ICNS file contains {} element(s).", family.elements.len()); 18 | for (index, element) in family.elements.iter().enumerate() { 19 | println!("Element {}: {} ({} byte payload)", 20 | index, 21 | element.ostype, 22 | element.data.len()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 79 2 | -------------------------------------------------------------------------------- /src/element.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use std::cmp; 3 | use std::io::{self, Error, ErrorKind, Read, Write}; 4 | 5 | use super::icontype::{Encoding, IconType, OSType}; 6 | use super::image::{Image, PixelFormat}; 7 | 8 | /// The length of an icon element header, in bytes: 9 | const ICON_ELEMENT_HEADER_LENGTH: u32 = 8; 10 | 11 | /// The first twelve bytes of a JPEG 2000 file are always this: 12 | #[cfg(feature = "pngio")] 13 | const JPEG_2000_FILE_MAGIC_NUMBER: [u8; 12] = 14 | [0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A]; 15 | 16 | /// One data block in an ICNS file. Depending on the resource type, this may 17 | /// represent an icon, or part of an icon (such as an alpha mask, or color 18 | /// data without the mask). 19 | pub struct IconElement { 20 | /// The OSType for this element (e.g. `it32` or `t8mk`). 21 | pub ostype: OSType, 22 | /// The raw data payload for this element. 23 | pub data: Vec, 24 | } 25 | 26 | impl IconElement { 27 | /// Creates an icon element with the given OSType and data payload. 28 | pub fn new(ostype: OSType, data: Vec) -> IconElement { 29 | IconElement { ostype, data } 30 | } 31 | 32 | /// Creates an icon element that encodes the given image as the given icon 33 | /// type. Image color channels that aren't relevant to the specified icon 34 | /// type will be ignored (e.g. if the icon type is a mask, then only the 35 | /// alpha channel of the image will be used). Returns an error if the 36 | /// image dimensions don't match the icon type. 37 | /// 38 | /// Note that if `icon_type` has an associated mask type, this method will 39 | /// _not_ encode the mask, and will in fact ignore any alpha channel in the 40 | /// image; you'll need to encode a second `IconElement` with the mask type. 41 | /// For a higher-level interface that will encode both elements at once, 42 | /// see the [`IconFamily.add_icon_with_type`]( 43 | /// struct.IconFamily.html#method.add_icon_with_type) method. 44 | pub fn encode_image_with_type(image: &Image, 45 | icon_type: IconType) 46 | -> io::Result { 47 | let width = icon_type.pixel_width(); 48 | let height = icon_type.pixel_height(); 49 | if image.width() != width || image.height() != height { 50 | let msg = format!("image has wrong dimensions for {:?} ({}x{} \ 51 | instead of {}x{}))", 52 | icon_type, 53 | image.width(), 54 | image.height(), 55 | width, 56 | height); 57 | return Err(Error::new(ErrorKind::InvalidInput, msg)); 58 | } 59 | let mut data: Vec; 60 | match icon_type.encoding() { 61 | #[cfg(feature = "pngio")] 62 | Encoding::JP2PNG => { 63 | data = Vec::new(); 64 | image.write_png(&mut data)?; 65 | } 66 | #[cfg(not(feature = "pngio"))] 67 | Encoding::JP2PNG => unimplemented!(), 68 | Encoding::RLE24 => { 69 | let num_pixels = (width * height) as usize; 70 | match image.pixel_format() { 71 | PixelFormat::RGBA => { 72 | data = encode_rle(image.data(), 4, num_pixels); 73 | } 74 | PixelFormat::RGB => { 75 | data = encode_rle(image.data(), 3, num_pixels); 76 | } 77 | // Convert to RGB if the image isn't already RGB or RGBA. 78 | _ => { 79 | let image = image.convert_to(PixelFormat::RGB); 80 | data = encode_rle(image.data(), 3, num_pixels); 81 | } 82 | } 83 | } 84 | Encoding::Mask8 => { 85 | // Convert to Alpha format unconditionally -- if the image is 86 | // already Alpha format, this will simply clone its data array, 87 | // which we'd need to do anyway. 88 | let image = image.convert_to(PixelFormat::Alpha); 89 | data = image.into_data().into_vec(); 90 | } 91 | } 92 | Ok(IconElement::new(icon_type.ostype(), data)) 93 | } 94 | 95 | /// Decodes the icon element into an image. Returns an error if this 96 | /// element does not represent an icon type supported by this library, or 97 | /// if the data is malformed. 98 | /// 99 | /// Note that if the element's icon type has an associated mask type, this 100 | /// method will simply produce an image with no alpha channel (since the 101 | /// mask lives in a separate `IconElement`). To decode image and mask 102 | /// together into a single image, you can either use the 103 | /// [`decode_image_with_mask`](#method.decode_image_with_mask) method, 104 | /// or the higher-level [`IconFamily.get_icon_with_type`]( 105 | /// struct.IconFamily.html#method.get_icon_with_type) method. 106 | pub fn decode_image(&self) -> io::Result { 107 | let icon_type = self.icon_type().ok_or_else(|| { 108 | Error::new(ErrorKind::InvalidInput, 109 | format!("unsupported OSType: {}", self.ostype)) 110 | })?; 111 | let width = icon_type.pixel_width(); 112 | let height = icon_type.pixel_width(); 113 | match icon_type.encoding() { 114 | #[cfg(feature = "pngio")] 115 | Encoding::JP2PNG => { 116 | if self.data.starts_with(&JPEG_2000_FILE_MAGIC_NUMBER) { 117 | let msg = "element to be decoded contains JPEG 2000 \ 118 | data, which is not yet supported"; 119 | return Err(Error::new(ErrorKind::InvalidInput, msg)); 120 | } 121 | let image = Image::read_png(io::Cursor::new(&self.data))?; 122 | if image.width() != width || image.height() != height { 123 | let msg = format!("decoded PNG has wrong dimensions \ 124 | ({}x{} instead of {}x{})", 125 | image.width(), 126 | image.height(), 127 | width, 128 | height); 129 | return Err(Error::new(ErrorKind::InvalidData, msg)); 130 | } 131 | Ok(image) 132 | } 133 | #[cfg(not(feature = "pngio"))] 134 | Encoding::JP2PNG => unimplemented!(), 135 | Encoding::RLE24 => { 136 | let mut image = Image::new(PixelFormat::RGB, width, height); 137 | decode_rle(&self.data, 3, image.data_mut())?; 138 | Ok(image) 139 | } 140 | Encoding::Mask8 => { 141 | let num_pixels = width * height; 142 | if self.data.len() as u32 != num_pixels { 143 | let msg = format!("wrong data payload length ({} \ 144 | instead of {})", 145 | self.data.len(), 146 | num_pixels); 147 | return Err(Error::new(ErrorKind::InvalidData, msg)); 148 | } 149 | let mut image = Image::new(PixelFormat::Alpha, width, height); 150 | image.data_mut().clone_from_slice(&self.data); 151 | Ok(image) 152 | } 153 | } 154 | } 155 | 156 | /// Decodes this element, together with a separate mask element, into a 157 | /// single image with alpha channel. Returns an error if this element does 158 | /// not represent an icon type supported by this library, or if the given 159 | /// mask element does not represent the correct mask type for this element, 160 | /// or if any of the data is malformed. 161 | /// 162 | /// For a more convenient alternative to this method, consider using the 163 | /// higher-level [`IconFamily.get_icon_with_type`]( 164 | /// struct.IconFamily.html#method.get_icon_with_type) method instead. 165 | pub fn decode_image_with_mask(&self, 166 | mask: &IconElement) 167 | -> io::Result { 168 | let icon_type = self.icon_type().ok_or_else(|| { 169 | Error::new(ErrorKind::InvalidInput, 170 | format!("unsupported OSType: {}", self.ostype)) 171 | })?; 172 | let mask_type = icon_type.mask_type().ok_or_else(|| { 173 | let msg = format!("icon type {:?} does not use a mask", icon_type); 174 | Error::new(ErrorKind::InvalidInput, msg) 175 | })?; 176 | assert_eq!(icon_type.encoding(), Encoding::RLE24); 177 | if mask.ostype != mask_type.ostype() { 178 | let msg = format!("wrong OSType for mask ('{}' instead of '{}')", 179 | mask.ostype, 180 | mask_type.ostype()); 181 | return Err(Error::new(ErrorKind::InvalidInput, msg)); 182 | } 183 | let width = icon_type.pixel_width(); 184 | let height = icon_type.pixel_height(); 185 | let num_pixels = (width * height) as usize; 186 | if mask.data.len() != num_pixels { 187 | let msg = format!("wrong mask data payload length ({} instead \ 188 | of {})", 189 | mask.data.len(), 190 | num_pixels); 191 | return Err(Error::new(ErrorKind::InvalidInput, msg)); 192 | } 193 | let mut image = Image::new(PixelFormat::RGBA, width, height); 194 | decode_rle(&self.data, 4, image.data_mut())?; 195 | for (i, &alpha) in mask.data.iter().enumerate() { 196 | image.data_mut()[4 * i + 3] = alpha; 197 | } 198 | Ok(image) 199 | } 200 | 201 | /// Returns the type of icon encoded by this element, or `None` if this 202 | /// element does not encode a supported icon type. 203 | pub fn icon_type(&self) -> Option { 204 | IconType::from_ostype(self.ostype) 205 | } 206 | 207 | /// Reads an icon element from within an ICNS file. 208 | pub fn read(mut reader: R) -> io::Result { 209 | let mut raw_ostype = [0u8; 4]; 210 | reader.read_exact(&mut raw_ostype)?; 211 | let element_length = reader.read_u32::()?; 212 | if element_length < ICON_ELEMENT_HEADER_LENGTH { 213 | return Err(Error::new(ErrorKind::InvalidData, 214 | "invalid element length")); 215 | } 216 | let data_length = element_length - ICON_ELEMENT_HEADER_LENGTH; 217 | let mut data = vec![0u8; data_length as usize]; 218 | reader.read_exact(&mut data)?; 219 | Ok(IconElement::new(OSType(raw_ostype), data)) 220 | } 221 | 222 | /// Writes the icon element to within an ICNS file. 223 | pub fn write(&self, mut writer: W) -> io::Result<()> { 224 | let OSType(ref raw_ostype) = self.ostype; 225 | writer.write_all(raw_ostype)?; 226 | writer.write_u32::(self.total_length())?; 227 | writer.write_all(&self.data)?; 228 | Ok(()) 229 | } 230 | 231 | /// Returns the encoded length of the element, in bytes, including the 232 | /// length of the header. 233 | pub fn total_length(&self) -> u32 { 234 | ICON_ELEMENT_HEADER_LENGTH + (self.data.len() as u32) 235 | } 236 | } 237 | 238 | fn encode_rle(input: &[u8], 239 | num_input_channels: usize, 240 | num_pixels: usize) 241 | -> Vec { 242 | assert!(num_input_channels == 3 || num_input_channels == 4); 243 | let mut output = Vec::new(); 244 | if num_pixels == 128 * 128 { 245 | // The 128x128 RLE icon (it32) starts with four extra zeros. 246 | output.extend_from_slice(&[0, 0, 0, 0]); 247 | } 248 | for channel in 0..3 { 249 | let mut pixel: usize = 0; 250 | let mut literal_start: usize = 0; 251 | while pixel < num_pixels { 252 | let value = input[num_input_channels * pixel + channel]; 253 | let mut run_length = 1; 254 | while pixel + run_length < num_pixels && 255 | input[num_input_channels * (pixel + run_length) + 256 | channel] == value && run_length < 130 { 257 | run_length += 1; 258 | } 259 | if run_length >= 3 { 260 | while literal_start < pixel { 261 | let literal_length = cmp::min(128, pixel - literal_start); 262 | output.push((literal_length - 1) as u8); 263 | for i in 0..literal_length { 264 | output.push(input[num_input_channels * 265 | (literal_start + i) + 266 | channel]); 267 | } 268 | literal_start += literal_length; 269 | } 270 | output.push((run_length + 125) as u8); 271 | output.push(value); 272 | pixel += run_length; 273 | literal_start = pixel; 274 | } else { 275 | pixel += run_length; 276 | } 277 | } 278 | while literal_start < pixel { 279 | let literal_length = cmp::min(128, pixel - literal_start); 280 | output.push((literal_length - 1) as u8); 281 | for i in 0..literal_length { 282 | output.push(input[num_input_channels * (literal_start + i) + 283 | channel]); 284 | } 285 | literal_start += literal_length; 286 | } 287 | } 288 | output 289 | } 290 | 291 | fn decode_rle(input: &[u8], 292 | num_output_channels: usize, 293 | output: &mut [u8]) 294 | -> io::Result<()> { 295 | assert!(num_output_channels == 3 || num_output_channels == 4); 296 | assert_eq!(output.len() % num_output_channels, 0); 297 | let num_pixels = output.len() / num_output_channels; 298 | // Sometimes, RLE-encoded data starts with four extra zeros that must be 299 | // skipped. 300 | let skip: usize = if input.starts_with(&[0, 0, 0, 0]) { 301 | 4 302 | } else { 303 | 0 304 | }; 305 | let input = &input[skip..input.len()]; 306 | let mut iter = input.iter(); 307 | let mut remaining: usize = 0; 308 | let mut within_run = false; 309 | let mut run_value: u8 = 0; 310 | for channel in 0..3 { 311 | for pixel in 0..num_pixels { 312 | if remaining == 0 { 313 | let next: u8 = *iter.next().ok_or_else(rle_error)?; 314 | if next < 128 { 315 | remaining = (next as usize) + 1; 316 | within_run = false; 317 | } else { 318 | remaining = (next as usize) - 125; 319 | within_run = true; 320 | run_value = *iter.next().ok_or_else(rle_error)?; 321 | } 322 | } 323 | output[num_output_channels * pixel + channel] = if within_run { 324 | run_value 325 | } else { 326 | *iter.next().ok_or_else(rle_error)? 327 | }; 328 | remaining -= 1; 329 | } 330 | if remaining != 0 { 331 | return Err(rle_error()); 332 | } 333 | } 334 | if iter.next().is_some() { 335 | Err(rle_error()) 336 | } else { 337 | Ok(()) 338 | } 339 | } 340 | 341 | fn rle_error() -> Error { 342 | Error::new(ErrorKind::InvalidData, "invalid RLE-compressed data") 343 | } 344 | 345 | #[cfg(test)] 346 | mod tests { 347 | use super::*; 348 | use super::super::icontype::{IconType, OSType}; 349 | use super::super::image::{Image, PixelFormat}; 350 | 351 | #[test] 352 | fn encode_rle() { 353 | let mut image = Image::new(PixelFormat::Gray, 16, 16); 354 | image.data_mut()[0] = 44; 355 | image.data_mut()[1] = 55; 356 | image.data_mut()[2] = 66; 357 | image.data_mut()[3] = 66; 358 | image.data_mut()[4] = 66; 359 | let element = 360 | IconElement::encode_image_with_type(&image, IconType::RGB24_16x16) 361 | .expect("failed to encode image"); 362 | assert_eq!(element.ostype, OSType(*b"is32")); 363 | assert_eq!(element.data[0..5], [1, 44, 55, 128, 66]); 364 | } 365 | 366 | #[test] 367 | fn decode_rle() { 368 | let data: Vec = vec![0, 12, 255, 0, 250, 0, 128, 34, 255, 0, 248, 369 | 0, 1, 56, 99, 255, 0, 249, 0]; 370 | let element = IconElement::new(OSType(*b"is32"), data); 371 | let image = element.decode_image().expect("failed to decode image"); 372 | assert_eq!(image.pixel_format(), PixelFormat::RGB); 373 | assert_eq!(image.width(), 16); 374 | assert_eq!(image.height(), 16); 375 | assert_eq!(image.data()[0], 12); 376 | assert_eq!(image.data()[1], 34); 377 | assert_eq!(image.data()[2], 56); 378 | } 379 | 380 | #[test] 381 | fn decode_rle_skip_extra_zeros() { 382 | let data: Vec = vec![0, 0, 0, 0, 0, 12, 255, 0, 250, 0, 128, 34, 383 | 255, 0, 248, 0, 1, 56, 99, 255, 0, 249, 0]; 384 | let element = IconElement::new(OSType(*b"is32"), data); 385 | let image = element.decode_image().expect("failed to decode image"); 386 | assert_eq!(image.data()[0], 12); 387 | assert_eq!(image.data()[1], 34); 388 | assert_eq!(image.data()[2], 56); 389 | } 390 | 391 | #[test] 392 | fn encode_mask() { 393 | let mut image = Image::new(PixelFormat::Alpha, 16, 16); 394 | image.data_mut()[2] = 127; 395 | let element = 396 | IconElement::encode_image_with_type(&image, IconType::Mask8_16x16) 397 | .expect("failed to encode image"); 398 | assert_eq!(element.ostype, OSType(*b"s8mk")); 399 | assert_eq!(element.data[2], 127); 400 | } 401 | 402 | #[test] 403 | fn decode_mask() { 404 | let mut data = vec![0u8; 256]; 405 | data[2] = 127; 406 | let element = IconElement::new(OSType(*b"s8mk"), data); 407 | let image = element.decode_image().expect("failed to decode image"); 408 | assert_eq!(image.pixel_format(), PixelFormat::Alpha); 409 | assert_eq!(image.width(), 16); 410 | assert_eq!(image.height(), 16); 411 | assert_eq!(image.data()[2], 127); 412 | } 413 | 414 | #[test] 415 | fn decode_rle_with_mask() { 416 | let color_data: Vec = vec![0, 12, 255, 0, 250, 0, 128, 34, 255, 417 | 0, 248, 0, 1, 56, 99, 255, 0, 249, 0]; 418 | let color_element = IconElement::new(OSType(*b"is32"), color_data); 419 | let mask_data = vec![78u8; 256]; 420 | let mask_element = IconElement::new(OSType(*b"s8mk"), mask_data); 421 | let image = color_element.decode_image_with_mask(&mask_element) 422 | .expect("failed to decode image"); 423 | assert_eq!(image.pixel_format(), PixelFormat::RGBA); 424 | assert_eq!(image.width(), 16); 425 | assert_eq!(image.height(), 16); 426 | assert_eq!(image.data()[0], 12); 427 | assert_eq!(image.data()[1], 34); 428 | assert_eq!(image.data()[2], 56); 429 | assert_eq!(image.data()[3], 78); 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /src/family.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use std::io::{self, Error, ErrorKind, Read, Write}; 3 | 4 | use super::element::IconElement; 5 | use super::icontype::IconType; 6 | use super::image::Image; 7 | 8 | /// The first four bytes of an ICNS file: 9 | const ICNS_MAGIC_LITERAL: &[u8; 4] = b"icns"; 10 | 11 | /// The length of an icon family header, in bytes: 12 | const ICON_FAMILY_HEADER_LENGTH: u32 = 8; 13 | 14 | /// A set of icons stored in a single ICNS file. 15 | #[derive(Default)] 16 | pub struct IconFamily { 17 | /// The icon elements stored in the ICNS file. 18 | pub elements: Vec, 19 | } 20 | 21 | impl IconFamily { 22 | /// Creates a new, empty icon family. 23 | pub fn new() -> IconFamily { 24 | IconFamily { elements: Vec::new() } 25 | } 26 | 27 | /// Returns true if the icon family contains no icons nor any other 28 | /// elements. 29 | pub fn is_empty(&self) -> bool { 30 | self.elements.is_empty() 31 | } 32 | 33 | /// Encodes the image into the family, automatically choosing an 34 | /// appropriate icon type based on the dimensions of the image. Returns 35 | /// an error if there is no supported icon type matching the image 36 | /// dimensions. 37 | pub fn add_icon(&mut self, image: &Image) -> io::Result<()> { 38 | if let Some(icon_type) = IconType::from_pixel_size(image.width(), 39 | image.height()) { 40 | self.add_icon_with_type(image, icon_type) 41 | } else { 42 | let msg = format!("no supported icon type has dimensions {}x{}", 43 | image.width(), 44 | image.height()); 45 | Err(Error::new(ErrorKind::InvalidInput, msg)) 46 | } 47 | } 48 | 49 | /// Encodes the image into the family using the given icon type. If the 50 | /// selected type has an associated mask type, the image mask will also be 51 | /// added to the family. Returns an error if the image has the wrong 52 | /// dimensions for the selected type. 53 | pub fn add_icon_with_type(&mut self, 54 | image: &Image, 55 | icon_type: IconType) 56 | -> io::Result<()> { 57 | self.elements 58 | .push(IconElement::encode_image_with_type(image, icon_type)?); 59 | if let Some(mask_type) = icon_type.mask_type() { 60 | self.elements 61 | .push(IconElement::encode_image_with_type(image, 62 | mask_type)?); 63 | } 64 | Ok(()) 65 | } 66 | 67 | /// Returns a list of all (non-mask) icon types for which the icon family 68 | /// contains the necessary element(s) for a complete icon image (including 69 | /// alpha channel). These icon types can be passed to the 70 | /// [`get_icon_with_type`](#method.get_icon_with_type) method to decode the 71 | /// icons. 72 | pub fn available_icons(&self) -> Vec { 73 | let mut result = Vec::new(); 74 | for element in &self.elements { 75 | if let Some(icon_type) = element.icon_type() { 76 | if !icon_type.is_mask() { 77 | if let Some(mask_type) = icon_type.mask_type() { 78 | if self.find_element(mask_type).is_ok() { 79 | result.push(icon_type); 80 | } 81 | } else { 82 | result.push(icon_type); 83 | } 84 | } 85 | } 86 | } 87 | result 88 | } 89 | 90 | /// Determines whether the icon family contains a complete icon with the 91 | /// given type (including the mask, if the given icon type has an 92 | /// associated mask type). 93 | pub fn has_icon_with_type(&self, icon_type: IconType) -> bool { 94 | if self.find_element(icon_type).is_err() { 95 | return false; 96 | } else if let Some(mask_type) = icon_type.mask_type() { 97 | return self.find_element(mask_type).is_ok(); 98 | } 99 | true 100 | } 101 | 102 | /// Decodes an image from the family with the given icon type. If the 103 | /// selected type has an associated mask type, the two elements will 104 | /// decoded together into a single image. Returns an error if the 105 | /// element(s) for the selected type are not present in the icon family, or 106 | /// the if the encoded data is malformed. 107 | pub fn get_icon_with_type(&self, 108 | icon_type: IconType) 109 | -> io::Result { 110 | let element = self.find_element(icon_type)?; 111 | if let Some(mask_type) = icon_type.mask_type() { 112 | let mask = self.find_element(mask_type)?; 113 | element.decode_image_with_mask(mask) 114 | } else { 115 | element.decode_image() 116 | } 117 | } 118 | 119 | /// Private helper method. 120 | fn find_element(&self, icon_type: IconType) -> io::Result<&IconElement> { 121 | let ostype = icon_type.ostype(); 122 | self.elements.iter().find(|el| el.ostype == ostype).ok_or_else(|| { 123 | let msg = format!("the icon family does not contain a '{}' \ 124 | element", 125 | ostype); 126 | Error::new(ErrorKind::NotFound, msg) 127 | }) 128 | } 129 | 130 | /// Reads an icon family from an ICNS file. 131 | pub fn read(mut reader: R) -> io::Result { 132 | let mut magic = [0u8; 4]; 133 | reader.read_exact(&mut magic)?; 134 | if magic != *ICNS_MAGIC_LITERAL { 135 | let msg = "not an icns file (wrong magic literal)"; 136 | return Err(Error::new(ErrorKind::InvalidData, msg)); 137 | } 138 | let file_length = reader.read_u32::()?; 139 | let mut file_position: u32 = ICON_FAMILY_HEADER_LENGTH; 140 | let mut family = IconFamily::new(); 141 | while file_position < file_length { 142 | let element = IconElement::read(reader.by_ref())?; 143 | file_position += element.total_length(); 144 | family.elements.push(element); 145 | } 146 | Ok(family) 147 | } 148 | 149 | /// Writes the icon family to an ICNS file. 150 | pub fn write(&self, mut writer: W) -> io::Result<()> { 151 | writer.write_all(ICNS_MAGIC_LITERAL)?; 152 | writer.write_u32::(self.total_length())?; 153 | for element in &self.elements { 154 | element.write(writer.by_ref())?; 155 | } 156 | Ok(()) 157 | } 158 | 159 | /// Returns the encoded length of the file, in bytes, including the 160 | /// length of the header. 161 | pub fn total_length(&self) -> u32 { 162 | let mut length = ICON_FAMILY_HEADER_LENGTH; 163 | for element in &self.elements { 164 | length += element.total_length(); 165 | } 166 | length 167 | } 168 | } 169 | 170 | #[cfg(test)] 171 | mod tests { 172 | use super::*; 173 | use super::super::element::IconElement; 174 | use super::super::icontype::{IconType, OSType}; 175 | use super::super::image::{Image, PixelFormat}; 176 | use std::io::Cursor; 177 | 178 | #[test] 179 | fn icon_with_type() { 180 | let mut family = IconFamily::new(); 181 | assert!(!family.has_icon_with_type(IconType::RGB24_16x16)); 182 | let image = Image::new(PixelFormat::Gray, 16, 16); 183 | family.add_icon_with_type(&image, IconType::RGB24_16x16).unwrap(); 184 | assert!(family.has_icon_with_type(IconType::RGB24_16x16)); 185 | assert!(family.get_icon_with_type(IconType::RGB24_16x16).is_ok()); 186 | } 187 | 188 | #[test] 189 | fn write_empty_icon_family() { 190 | let family = IconFamily::new(); 191 | assert!(family.is_empty()); 192 | assert_eq!(0, family.elements.len()); 193 | let mut output: Vec = vec![]; 194 | family.write(&mut output).expect("write failed"); 195 | assert_eq!(b"icns\0\0\0\x08", &output as &[u8]); 196 | } 197 | 198 | #[test] 199 | fn read_icon_family_with_fake_elements() { 200 | let input: Cursor<&[u8]> = 201 | Cursor::new(b"icns\0\0\0\x1fquux\0\0\0\x0efoobarbaz!\0\0\0\x09#"); 202 | let family = IconFamily::read(input).expect("read failed"); 203 | assert_eq!(2, family.elements.len()); 204 | assert_eq!(OSType(*b"quux"), family.elements[0].ostype); 205 | assert_eq!(6, family.elements[0].data.len()); 206 | assert_eq!(OSType(*b"baz!"), family.elements[1].ostype); 207 | assert_eq!(1, family.elements[1].data.len()); 208 | } 209 | 210 | #[test] 211 | fn write_icon_family_with_fake_elements() { 212 | let mut family = IconFamily::new(); 213 | family.elements 214 | .push(IconElement::new(OSType(*b"quux"), b"foobar".to_vec())); 215 | family.elements 216 | .push(IconElement::new(OSType(*b"baz!"), b"#".to_vec())); 217 | let mut output: Vec = vec![]; 218 | family.write(&mut output).expect("write failed"); 219 | assert_eq!(b"icns\0\0\0\x1fquux\0\0\0\x0efoobarbaz!\0\0\0\x09#", 220 | &output as &[u8]); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/icontype.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// Types of icon elements that can be decoded as images or masks. 4 | /// 5 | /// This type enumerates the kinds of [`IconElement`](struct.IconElement.html) 6 | /// that can be decoded by this library; each `IconType` corresponds to a 7 | /// particular [`OSType`](struct.OSType.html). The non-mask `IconType` values 8 | /// can also be used with the higher-level 9 | /// [`IconFamily`](struct.IconFamily.html) methods to 10 | /// [encode](struct.IconFamily.html#method.add_icon_with_type) and 11 | /// [decode](struct.IconFamily.html#method.get_icon_with_type) complete icons 12 | /// that consist of multiple `IconElements`. 13 | #[allow(non_camel_case_types)] 14 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 15 | pub enum IconType { 16 | /// 16x16 24-bit icon (without alpha). 17 | RGB24_16x16, 18 | /// 16x16 8-bit alpha mask. 19 | Mask8_16x16, 20 | /// 32x32 24-bit icon (without alpha). 21 | RGB24_32x32, 22 | /// 32x32 8-bit alpha mask. 23 | Mask8_32x32, 24 | /// 48x48 24-bit icon (without alpha). 25 | RGB24_48x48, 26 | /// 48x48 8-bit alpha mask. 27 | Mask8_48x48, 28 | /// 128x128 24-bit icon (without alpha). 29 | RGB24_128x128, 30 | /// 128x128 8-bit alpha mask. 31 | Mask8_128x128, 32 | /// 16x16 32-bit icon. 33 | RGBA32_16x16, 34 | /// 16x16 32-bit icon at 2x "retina" density (so, 32 by 32 pixels). 35 | RGBA32_16x16_2x, 36 | /// 32x32 32-bit icon. 37 | RGBA32_32x32, 38 | /// 32x32 32-bit icon at 2x "retina" density (so, 64 by 64 pixels). 39 | RGBA32_32x32_2x, 40 | /// 64x64 32-bit icon. (For whatever reason, the ICNS format has no 41 | /// corresponding type for a 64x64 icon at 2x "retina" density.) 42 | RGBA32_64x64, 43 | /// 128x128 32-bit icon. 44 | RGBA32_128x128, 45 | /// 128x128 32-bit icon at 2x "retina" density (so, 256 by 256 pixels). 46 | RGBA32_128x128_2x, 47 | /// 256x256 32-bit icon. 48 | RGBA32_256x256, 49 | /// 256x256 32-bit icon at 2x "retina" density (so, 512 by 512 pixels). 50 | RGBA32_256x256_2x, 51 | /// 512x512 32-bit icon. 52 | RGBA32_512x512, 53 | /// 512x512 32-bit icon at 2x "retina" density (so, 1024 by 1024 pixels). 54 | RGBA32_512x512_2x, 55 | } 56 | 57 | impl IconType { 58 | /// Get the icon type associated with the given OSType, if any. 59 | pub fn from_ostype(ostype: OSType) -> Option { 60 | let OSType(raw_ostype) = ostype; 61 | match &raw_ostype { 62 | b"is32" => Some(IconType::RGB24_16x16), 63 | b"s8mk" => Some(IconType::Mask8_16x16), 64 | b"il32" => Some(IconType::RGB24_32x32), 65 | b"l8mk" => Some(IconType::Mask8_32x32), 66 | b"ih32" => Some(IconType::RGB24_48x48), 67 | b"h8mk" => Some(IconType::Mask8_48x48), 68 | b"it32" => Some(IconType::RGB24_128x128), 69 | b"t8mk" => Some(IconType::Mask8_128x128), 70 | b"icp4" => Some(IconType::RGBA32_16x16), 71 | b"ic11" => Some(IconType::RGBA32_16x16_2x), 72 | b"icp5" => Some(IconType::RGBA32_32x32), 73 | b"ic12" => Some(IconType::RGBA32_32x32_2x), 74 | b"icp6" => Some(IconType::RGBA32_64x64), 75 | b"ic07" => Some(IconType::RGBA32_128x128), 76 | b"ic13" => Some(IconType::RGBA32_128x128_2x), 77 | b"ic08" => Some(IconType::RGBA32_256x256), 78 | b"ic14" => Some(IconType::RGBA32_256x256_2x), 79 | b"ic09" => Some(IconType::RGBA32_512x512), 80 | b"ic10" => Some(IconType::RGBA32_512x512_2x), 81 | _ => None, 82 | } 83 | } 84 | 85 | /// Return a (non-mask) icon type that has the given pixel width/height, if 86 | /// any. 87 | /// 88 | /// # Examples 89 | /// ``` 90 | /// use icns::IconType; 91 | /// assert_eq!(IconType::from_pixel_size(48, 48), 92 | /// Some(IconType::RGB24_48x48)); 93 | /// assert_eq!(IconType::from_pixel_size(256, 256), 94 | /// Some(IconType::RGBA32_256x256)); 95 | /// assert_eq!(IconType::from_pixel_size(1024, 1024), 96 | /// Some(IconType::RGBA32_512x512_2x)); 97 | /// ``` 98 | pub fn from_pixel_size(width: u32, height: u32) -> Option { 99 | match (width, height) { 100 | (16, 16) => Some(IconType::RGB24_16x16), 101 | (32, 32) => Some(IconType::RGB24_32x32), 102 | (48, 48) => Some(IconType::RGB24_48x48), 103 | (64, 64) => Some(IconType::RGBA32_64x64), 104 | (128, 128) => Some(IconType::RGB24_128x128), 105 | (256, 256) => Some(IconType::RGBA32_256x256), 106 | (512, 512) => Some(IconType::RGBA32_512x512), 107 | (1024, 1024) => Some(IconType::RGBA32_512x512_2x), 108 | _ => None, 109 | } 110 | } 111 | 112 | /// Return a (non-mask) icon type that has the given pixel width/height and 113 | /// pixel density, if any. 114 | /// 115 | /// # Examples 116 | /// ``` 117 | /// use icns::IconType; 118 | /// assert_eq!(IconType::from_pixel_size_and_density(48, 48, 1), 119 | /// Some(IconType::RGB24_48x48)); 120 | /// assert_eq!(IconType::from_pixel_size_and_density(256, 256, 1), 121 | /// Some(IconType::RGBA32_256x256)); 122 | /// assert_eq!(IconType::from_pixel_size_and_density(256, 256, 2), 123 | /// Some(IconType::RGBA32_128x128_2x)); 124 | /// ``` 125 | pub fn from_pixel_size_and_density(width: u32, 126 | height: u32, 127 | density: u32) 128 | -> Option { 129 | match (width, height, density) { 130 | (16, 16, 1) => Some(IconType::RGB24_16x16), 131 | (32, 32, 1) => Some(IconType::RGB24_32x32), 132 | (32, 32, 2) => Some(IconType::RGBA32_16x16_2x), 133 | (48, 48, 1) => Some(IconType::RGB24_48x48), 134 | (64, 64, 1) => Some(IconType::RGBA32_64x64), 135 | (64, 64, 2) => Some(IconType::RGBA32_32x32_2x), 136 | (128, 128, 1) => Some(IconType::RGB24_128x128), 137 | (256, 256, 1) => Some(IconType::RGBA32_256x256), 138 | (256, 256, 2) => Some(IconType::RGBA32_128x128_2x), 139 | (512, 512, 1) => Some(IconType::RGBA32_512x512), 140 | (512, 512, 2) => Some(IconType::RGBA32_256x256_2x), 141 | (1024, 1024, 2) => Some(IconType::RGBA32_512x512_2x), 142 | _ => None, 143 | } 144 | } 145 | 146 | /// Get the OSType that represents this icon type. 147 | pub fn ostype(self) -> OSType { 148 | match self { 149 | IconType::RGB24_16x16 => OSType(*b"is32"), 150 | IconType::Mask8_16x16 => OSType(*b"s8mk"), 151 | IconType::RGB24_32x32 => OSType(*b"il32"), 152 | IconType::Mask8_32x32 => OSType(*b"l8mk"), 153 | IconType::RGB24_48x48 => OSType(*b"ih32"), 154 | IconType::Mask8_48x48 => OSType(*b"h8mk"), 155 | IconType::RGB24_128x128 => OSType(*b"it32"), 156 | IconType::Mask8_128x128 => OSType(*b"t8mk"), 157 | IconType::RGBA32_16x16 => OSType(*b"icp4"), 158 | IconType::RGBA32_16x16_2x => OSType(*b"ic11"), 159 | IconType::RGBA32_32x32 => OSType(*b"icp5"), 160 | IconType::RGBA32_32x32_2x => OSType(*b"ic12"), 161 | IconType::RGBA32_64x64 => OSType(*b"icp6"), 162 | IconType::RGBA32_128x128 => OSType(*b"ic07"), 163 | IconType::RGBA32_128x128_2x => OSType(*b"ic13"), 164 | IconType::RGBA32_256x256 => OSType(*b"ic08"), 165 | IconType::RGBA32_256x256_2x => OSType(*b"ic14"), 166 | IconType::RGBA32_512x512 => OSType(*b"ic09"), 167 | IconType::RGBA32_512x512_2x => OSType(*b"ic10"), 168 | } 169 | } 170 | 171 | /// Returns true if this is icon type is a mask for some other icon type. 172 | /// 173 | /// # Examples 174 | /// ``` 175 | /// use icns::IconType; 176 | /// assert!(!IconType::RGB24_16x16.is_mask()); 177 | /// assert!(IconType::Mask8_16x16.is_mask()); 178 | /// assert!(!IconType::RGBA32_16x16.is_mask()); 179 | /// ``` 180 | pub fn is_mask(self) -> bool { 181 | matches!(self, 182 | IconType::Mask8_16x16 | 183 | IconType::Mask8_32x32 | 184 | IconType::Mask8_48x48 | 185 | IconType::Mask8_128x128) 186 | } 187 | 188 | /// If this icon type has an associated mask type, returns that mask type; 189 | /// if this is a mask icon type, or a non-mask icon type that has no 190 | /// associated mask type, returns `None`. 191 | /// 192 | /// # Examples 193 | /// ``` 194 | /// use icns::IconType; 195 | /// assert_eq!(IconType::RGB24_16x16.mask_type(), 196 | /// Some(IconType::Mask8_16x16)); 197 | /// assert_eq!(IconType::Mask8_16x16.mask_type(), None); 198 | /// assert_eq!(IconType::RGBA32_16x16.mask_type(), None); 199 | /// ``` 200 | pub fn mask_type(self) -> Option { 201 | match self { 202 | IconType::RGB24_16x16 => Some(IconType::Mask8_16x16), 203 | IconType::RGB24_32x32 => Some(IconType::Mask8_32x32), 204 | IconType::RGB24_48x48 => Some(IconType::Mask8_48x48), 205 | IconType::RGB24_128x128 => Some(IconType::Mask8_128x128), 206 | _ => None, 207 | } 208 | } 209 | 210 | /// Returns the pixel data width of this icon type. Normally this is the 211 | /// same as the screen width, but for 2x "retina" density icons, this will 212 | /// be twice that value. 213 | /// 214 | /// # Examples 215 | /// ``` 216 | /// use icns::IconType; 217 | /// assert_eq!(IconType::Mask8_128x128.pixel_width(), 128); 218 | /// assert_eq!(IconType::RGBA32_256x256.pixel_width(), 256); 219 | /// assert_eq!(IconType::RGBA32_256x256_2x.pixel_width(), 512); 220 | /// ``` 221 | pub fn pixel_width(self) -> u32 { 222 | self.screen_width() * self.pixel_density() 223 | } 224 | 225 | /// Returns the pixel data height of this icon type. Normally this is the 226 | /// same as the screen height, but for 2x "retina" density icons, this will 227 | /// be twice that value. 228 | /// 229 | /// # Examples 230 | /// ``` 231 | /// use icns::IconType; 232 | /// assert_eq!(IconType::Mask8_128x128.pixel_height(), 128); 233 | /// assert_eq!(IconType::RGBA32_256x256.pixel_height(), 256); 234 | /// assert_eq!(IconType::RGBA32_256x256_2x.pixel_height(), 512); 235 | /// ``` 236 | pub fn pixel_height(self) -> u32 { 237 | self.screen_height() * self.pixel_density() 238 | } 239 | 240 | /// Returns the pixel density for this icon type -- that is, 2 for 2x 241 | /// "retina" density icons, or 1 for other icon types. 242 | /// 243 | /// # Examples 244 | /// ``` 245 | /// use icns::IconType; 246 | /// assert_eq!(IconType::Mask8_128x128.pixel_density(), 1); 247 | /// assert_eq!(IconType::RGBA32_256x256.pixel_density(), 1); 248 | /// assert_eq!(IconType::RGBA32_256x256_2x.pixel_density(), 2); 249 | /// ``` 250 | pub fn pixel_density(self) -> u32 { 251 | match self { 252 | IconType::RGBA32_16x16_2x | 253 | IconType::RGBA32_32x32_2x | 254 | IconType::RGBA32_128x128_2x | 255 | IconType::RGBA32_256x256_2x | 256 | IconType::RGBA32_512x512_2x => 2, 257 | _ => 1, 258 | } 259 | } 260 | 261 | /// Returns the screen width of this icon type. Normally this is the same 262 | /// as the pixel width, but for 2x "retina" density icons, this will be 263 | /// half that value. 264 | /// 265 | /// # Examples 266 | /// ``` 267 | /// use icns::IconType; 268 | /// assert_eq!(IconType::Mask8_128x128.screen_width(), 128); 269 | /// assert_eq!(IconType::RGBA32_256x256.screen_width(), 256); 270 | /// assert_eq!(IconType::RGBA32_256x256_2x.screen_width(), 256); 271 | /// ``` 272 | pub fn screen_width(self) -> u32 { 273 | match self { 274 | IconType::RGB24_16x16 => 16, 275 | IconType::Mask8_16x16 => 16, 276 | IconType::RGB24_32x32 => 32, 277 | IconType::Mask8_32x32 => 32, 278 | IconType::RGB24_48x48 => 48, 279 | IconType::Mask8_48x48 => 48, 280 | IconType::RGB24_128x128 => 128, 281 | IconType::Mask8_128x128 => 128, 282 | IconType::RGBA32_16x16 => 16, 283 | IconType::RGBA32_16x16_2x => 16, 284 | IconType::RGBA32_32x32 => 32, 285 | IconType::RGBA32_32x32_2x => 32, 286 | IconType::RGBA32_64x64 => 64, 287 | IconType::RGBA32_128x128 => 128, 288 | IconType::RGBA32_128x128_2x => 128, 289 | IconType::RGBA32_256x256 => 256, 290 | IconType::RGBA32_256x256_2x => 256, 291 | IconType::RGBA32_512x512 => 512, 292 | IconType::RGBA32_512x512_2x => 512, 293 | } 294 | } 295 | 296 | /// Returns the screen height of this icon type. Normally this is the same 297 | /// as the pixel height, but for 2x "retina" density icons, this will be 298 | /// half that value. 299 | /// 300 | /// # Examples 301 | /// ``` 302 | /// use icns::IconType; 303 | /// assert_eq!(IconType::Mask8_128x128.screen_height(), 128); 304 | /// assert_eq!(IconType::RGBA32_256x256.screen_height(), 256); 305 | /// assert_eq!(IconType::RGBA32_256x256_2x.screen_height(), 256); 306 | /// ``` 307 | pub fn screen_height(self) -> u32 { 308 | match self { 309 | IconType::RGB24_16x16 => 16, 310 | IconType::Mask8_16x16 => 16, 311 | IconType::RGB24_32x32 => 32, 312 | IconType::Mask8_32x32 => 32, 313 | IconType::RGB24_48x48 => 48, 314 | IconType::Mask8_48x48 => 48, 315 | IconType::RGB24_128x128 => 128, 316 | IconType::Mask8_128x128 => 128, 317 | IconType::RGBA32_16x16 => 16, 318 | IconType::RGBA32_16x16_2x => 16, 319 | IconType::RGBA32_32x32 => 32, 320 | IconType::RGBA32_32x32_2x => 32, 321 | IconType::RGBA32_64x64 => 64, 322 | IconType::RGBA32_128x128 => 128, 323 | IconType::RGBA32_128x128_2x => 128, 324 | IconType::RGBA32_256x256 => 256, 325 | IconType::RGBA32_256x256_2x => 256, 326 | IconType::RGBA32_512x512 => 512, 327 | IconType::RGBA32_512x512_2x => 512, 328 | } 329 | } 330 | 331 | /// Returns the encoding used within an ICNS file for this icon type. 332 | pub fn encoding(self) -> Encoding { 333 | match self { 334 | IconType::RGB24_16x16 | 335 | IconType::RGB24_32x32 | 336 | IconType::RGB24_48x48 | 337 | IconType::RGB24_128x128 => Encoding::RLE24, 338 | IconType::Mask8_16x16 | 339 | IconType::Mask8_32x32 | 340 | IconType::Mask8_48x48 | 341 | IconType::Mask8_128x128 => Encoding::Mask8, 342 | IconType::RGBA32_16x16 | 343 | IconType::RGBA32_16x16_2x | 344 | IconType::RGBA32_32x32 | 345 | IconType::RGBA32_32x32_2x | 346 | IconType::RGBA32_64x64 | 347 | IconType::RGBA32_128x128 | 348 | IconType::RGBA32_128x128_2x | 349 | IconType::RGBA32_256x256 | 350 | IconType::RGBA32_256x256_2x | 351 | IconType::RGBA32_512x512 | 352 | IconType::RGBA32_512x512_2x => Encoding::JP2PNG, 353 | } 354 | } 355 | } 356 | 357 | /// A Macintosh OSType (also known as a ResType), used in ICNS files to 358 | /// identify the type of each icon element. 359 | /// 360 | /// An OSType is a four-byte identifier used throughout Mac OS. In an ICNS 361 | /// file, it indicates the type of data stored in an 362 | /// [`IconElement`](struct.IconElement.html) data block. For example, OSType 363 | /// `is32` represents 24-bit color data for a 16x16 icon, while OSType `s8mk` 364 | /// represents the 8-bit alpha mask for that same icon. 365 | /// 366 | /// See the [`IconType`](enum.IconType.html) enum for an easier-to-use 367 | /// representation of icon data types. 368 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 369 | pub struct OSType(pub [u8; 4]); 370 | 371 | impl fmt::Display for OSType { 372 | fn fmt(&self, out: &mut fmt::Formatter) -> Result<(), fmt::Error> { 373 | let &OSType(raw) = self; 374 | for &byte in &raw { 375 | let character = std::char::from_u32(u32::from(byte)).unwrap(); 376 | write!(out, "{}", character)?; 377 | } 378 | Ok(()) 379 | } 380 | } 381 | 382 | impl std::str::FromStr for OSType { 383 | type Err = String; 384 | 385 | fn from_str(input: &str) -> Result { 386 | let chars: Vec = input.chars().collect(); 387 | if chars.len() != 4 { 388 | return Err(format!("OSType string must be 4 chars (was {})", 389 | chars.len())); 390 | } 391 | let mut bytes = [0u8; 4]; 392 | for (i, &ch) in chars.iter().enumerate() { 393 | let value = ch as u32; 394 | if value > u8::MAX as u32 { 395 | return Err(format!("OSType chars must have value of at \ 396 | most 0x{:X} (found 0x{:X})", 397 | u8::MAX, 398 | value)); 399 | } 400 | bytes[i] = value as u8; 401 | } 402 | Ok(OSType(bytes)) 403 | } 404 | } 405 | 406 | /// Methods of encoding an image within an icon element. 407 | /// 408 | /// Each [`IconType`](enum.IconType.html) uses a particular encoding within 409 | /// an ICNS file; this type enumerates those encodings. 410 | /// 411 | /// (This type is used internally by the library, but is irrelvant to most 412 | /// library users; if you're not sure whether you need to use it, you probably 413 | /// don't.) 414 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 415 | pub enum Encoding { 416 | /// Icon element data payload is an uncompressed 8-bit alpha mask. 417 | Mask8, 418 | /// Icon element data payload is an RLE-compressed 24-bit RGB image. 419 | RLE24, 420 | /// Icon element data payload is a JPEG 2000 or PNG file. 421 | JP2PNG, 422 | } 423 | 424 | #[cfg(test)] 425 | mod tests { 426 | use super::*; 427 | use std::str::FromStr; 428 | 429 | const ALL_ICON_TYPES: [IconType; 19] = [IconType::RGB24_16x16, 430 | IconType::Mask8_16x16, 431 | IconType::RGB24_32x32, 432 | IconType::Mask8_32x32, 433 | IconType::RGB24_48x48, 434 | IconType::Mask8_48x48, 435 | IconType::RGB24_128x128, 436 | IconType::Mask8_128x128, 437 | IconType::RGBA32_16x16, 438 | IconType::RGBA32_16x16_2x, 439 | IconType::RGBA32_32x32, 440 | IconType::RGBA32_32x32_2x, 441 | IconType::RGBA32_64x64, 442 | IconType::RGBA32_128x128, 443 | IconType::RGBA32_128x128_2x, 444 | IconType::RGBA32_256x256, 445 | IconType::RGBA32_256x256_2x, 446 | IconType::RGBA32_512x512, 447 | IconType::RGBA32_512x512_2x]; 448 | 449 | #[test] 450 | fn icon_type_ostype_round_trip() { 451 | for icon_type in &ALL_ICON_TYPES { 452 | let ostype = icon_type.ostype(); 453 | let from = IconType::from_ostype(ostype); 454 | assert_eq!(Some(*icon_type), from); 455 | } 456 | } 457 | 458 | #[test] 459 | fn icon_type_size_round_trip() { 460 | for icon_type in &ALL_ICON_TYPES { 461 | let width = icon_type.pixel_width(); 462 | let height = icon_type.pixel_height(); 463 | let from = IconType::from_pixel_size(width, height).unwrap(); 464 | assert_eq!(from.pixel_width(), width); 465 | assert_eq!(from.pixel_height(), height); 466 | } 467 | } 468 | 469 | #[test] 470 | fn icon_type_size_and_density_round_trip() { 471 | for icon_type in &ALL_ICON_TYPES { 472 | let width = icon_type.pixel_width(); 473 | let height = icon_type.pixel_height(); 474 | let density = icon_type.pixel_density(); 475 | let from = 476 | IconType::from_pixel_size_and_density(width, height, density) 477 | .unwrap(); 478 | assert_eq!(from.pixel_width(), width); 479 | assert_eq!(from.pixel_height(), height); 480 | assert_eq!(from.pixel_density(), density); 481 | } 482 | } 483 | 484 | #[test] 485 | fn icon_type_mask_type() { 486 | for icon_type in &ALL_ICON_TYPES { 487 | match icon_type.encoding() { 488 | Encoding::Mask8 => { 489 | assert!(icon_type.is_mask()); 490 | assert_eq!(icon_type.mask_type(), None); 491 | } 492 | Encoding::RLE24 => { 493 | assert!(!icon_type.is_mask()); 494 | if let Some(mask_type) = icon_type.mask_type() { 495 | assert_eq!(mask_type.encoding(), Encoding::Mask8); 496 | assert_eq!(icon_type.pixel_width(), 497 | mask_type.pixel_width()); 498 | assert_eq!(icon_type.pixel_height(), 499 | mask_type.pixel_height()); 500 | } else { 501 | panic!("{:?} is missing a mask type", icon_type); 502 | } 503 | } 504 | Encoding::JP2PNG => { 505 | assert!(!icon_type.is_mask()); 506 | assert_eq!(icon_type.mask_type(), None); 507 | } 508 | } 509 | } 510 | } 511 | 512 | #[test] 513 | fn ostype_to_and_from_str() { 514 | let ostype = OSType::from_str("abcd").expect("failed to parse OSType"); 515 | assert_eq!(ostype.to_string(), "abcd".to_string()); 516 | } 517 | 518 | #[test] 519 | fn ostype_to_and_from_str_non_ascii() { 520 | let ostype = OSType(*b"sp\xf6b"); 521 | let string = ostype.to_string(); 522 | assert_eq!(string, "sp\u{f6}b".to_string()); 523 | assert_eq!(OSType::from_str(&string), Ok(ostype)); 524 | } 525 | 526 | #[test] 527 | fn ostype_from_str_failure() { 528 | assert_eq!(OSType::from_str("abc"), 529 | Err("OSType string must be 4 chars (was 3)".to_string())); 530 | assert_eq!(OSType::from_str("abcde"), 531 | Err("OSType string must be 4 chars (was 5)".to_string())); 532 | assert_eq!(OSType::from_str("ab\u{2603}d"), 533 | Err("OSType chars must have value of at most 0xFF \ 534 | (found 0x2603)" 535 | .to_string())); 536 | } 537 | } 538 | -------------------------------------------------------------------------------- /src/image.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | /// A decoded icon image. 4 | /// 5 | /// An `Image` struct consists of a width, a height, a 6 | /// [`PixelFormat`](enum.PixelFormat.html), and a data array encoding the image 7 | /// pixels in that format. 8 | /// 9 | /// Regardless of format, pixel data for an image is always stored one complete 10 | /// pixel at a time, in row-major order (that is, the top-left pixel comes 11 | /// first, followed by the rest of the top row from left to right; then comes 12 | /// the second row down, again from left to right, and so on until finally the 13 | /// bottom-right pixel comes last). 14 | #[derive(Clone)] 15 | pub struct Image { 16 | pub(crate) format: PixelFormat, 17 | pub(crate) width: u32, 18 | pub(crate) height: u32, 19 | pub(crate) data: Box<[u8]>, 20 | } 21 | 22 | impl Image { 23 | /// Creates a new image with all pixel data set to zero. 24 | pub fn new(format: PixelFormat, width: u32, height: u32) -> Image { 25 | let data_bits = format.bits_per_pixel() * width * height; 26 | let data_bytes = ((data_bits + 7) / 8) as usize; 27 | Image { 28 | format, 29 | width, 30 | height, 31 | data: vec![0u8; data_bytes].into_boxed_slice(), 32 | } 33 | } 34 | 35 | /// Creates a new image using the given pixel data. Returns an error if 36 | /// the data array is not the correct length. 37 | pub fn from_data(format: PixelFormat, 38 | width: u32, 39 | height: u32, 40 | data: Vec) 41 | -> io::Result { 42 | let data_bits = format.bits_per_pixel() * width * height; 43 | let data_bytes = ((data_bits + 7) / 8) as usize; 44 | if data.len() == data_bytes { 45 | Ok(Image { 46 | format, 47 | width, 48 | height, 49 | data: data.into_boxed_slice(), 50 | }) 51 | } else { 52 | let msg = format!("incorrect pixel data array length for \ 53 | speicifed format and dimensions ({} instead \ 54 | of {})", 55 | data.len(), 56 | data_bytes); 57 | Err(io::Error::new(io::ErrorKind::InvalidInput, msg)) 58 | } 59 | } 60 | 61 | /// Returns the format in which this image's pixel data is stored. 62 | pub fn pixel_format(&self) -> PixelFormat { 63 | self.format 64 | } 65 | 66 | /// Returns the width of the image, in pixels. 67 | pub fn width(&self) -> u32 { 68 | self.width 69 | } 70 | 71 | /// Returns the height of the image, in pixels. 72 | pub fn height(&self) -> u32 { 73 | self.height 74 | } 75 | 76 | /// Returns a reference to the image's pixel data. 77 | pub fn data(&self) -> &[u8] { 78 | &self.data 79 | } 80 | 81 | /// Returns a mutable reference to the image's pixel data. 82 | pub fn data_mut(&mut self) -> &mut [u8] { 83 | &mut self.data 84 | } 85 | 86 | /// Consumes the image, returning the pixel data without cloning it. 87 | pub fn into_data(self) -> Box<[u8]> { 88 | self.data 89 | } 90 | 91 | /// Creates a copy of this image by converting to the specified pixel 92 | /// format. This operation always succeeds, but may lose information (e.g. 93 | /// converting from RGBA to RGB will silently drop the alpha channel). If 94 | /// the source image is already in the requested format, this is equivalant 95 | /// to simply calling `clone()`. 96 | pub fn convert_to(&self, format: PixelFormat) -> Image { 97 | let new_data = match self.format { 98 | PixelFormat::RGBA => { 99 | match format { 100 | PixelFormat::RGBA => self.data.clone(), 101 | PixelFormat::RGB => rgba_to_rgb(&self.data), 102 | PixelFormat::GrayAlpha => rgba_to_grayalpha(&self.data), 103 | PixelFormat::Gray => rgba_to_gray(&self.data), 104 | PixelFormat::Alpha => rgba_to_alpha(&self.data), 105 | } 106 | } 107 | PixelFormat::RGB => { 108 | match format { 109 | PixelFormat::RGBA => rgb_to_rgba(&self.data), 110 | PixelFormat::RGB => self.data.clone(), 111 | PixelFormat::GrayAlpha => rgb_to_grayalpha(&self.data), 112 | PixelFormat::Gray => rgb_to_gray(&self.data), 113 | PixelFormat::Alpha => rgb_to_alpha(&self.data), 114 | } 115 | } 116 | PixelFormat::GrayAlpha => { 117 | match format { 118 | PixelFormat::RGBA => grayalpha_to_rgba(&self.data), 119 | PixelFormat::RGB => grayalpha_to_rgb(&self.data), 120 | PixelFormat::GrayAlpha => self.data.clone(), 121 | PixelFormat::Gray => grayalpha_to_gray(&self.data), 122 | PixelFormat::Alpha => grayalpha_to_alpha(&self.data), 123 | } 124 | } 125 | PixelFormat::Gray => { 126 | match format { 127 | PixelFormat::RGBA => gray_to_rgba(&self.data), 128 | PixelFormat::RGB => gray_to_rgb(&self.data), 129 | PixelFormat::GrayAlpha => gray_to_grayalpha(&self.data), 130 | PixelFormat::Gray => self.data.clone(), 131 | PixelFormat::Alpha => gray_to_alpha(&self.data), 132 | } 133 | } 134 | PixelFormat::Alpha => { 135 | match format { 136 | PixelFormat::RGBA => alpha_to_rgba(&self.data), 137 | PixelFormat::RGB => alpha_to_rgb(&self.data), 138 | PixelFormat::GrayAlpha => alpha_to_grayalpha(&self.data), 139 | PixelFormat::Gray => alpha_to_gray(&self.data), 140 | PixelFormat::Alpha => self.data.clone(), 141 | } 142 | } 143 | }; 144 | Image { 145 | format, 146 | width: self.width, 147 | height: self.height, 148 | data: new_data, 149 | } 150 | } 151 | } 152 | 153 | /// Formats for storing pixel data in an image. 154 | /// 155 | /// This type determines how the raw data array of an 156 | /// [`Image`](struct.Image.html) is to be interpreted. 157 | /// 158 | /// Regardless of format, pixel data for an image is always stored one complete 159 | /// pixel at a time, in row-major order (that is, the top-left pixel comes 160 | /// first, followed by the rest of the top row from left to right; then comes 161 | /// the second row down, again from left to right, and so on until finally the 162 | /// bottom-right pixel comes last). 163 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 164 | pub enum PixelFormat { 165 | /// 32-bit color with alpha channel. Each pixel is four bytes, with red 166 | /// first and alpha last. 167 | RGBA, 168 | /// 24-bit color with no alpha. Each pixel is three bytes, with red 169 | /// first and blue last. 170 | RGB, 171 | /// 16-bit grayscale-with-alpha. Each pixel is two bytes, with the 172 | /// grayscale value first and alpha second. 173 | GrayAlpha, 174 | /// 8-bit grayscale with no alpha. Each pixel is one byte (0=black, 175 | /// 255=white). 176 | Gray, 177 | /// 8-bit alpha mask with no color. Each pixel is one byte (0=transparent, 178 | /// 255=opaque). 179 | Alpha, 180 | } 181 | 182 | impl PixelFormat { 183 | /// Returns the number of bits needed to store a single pixel in this 184 | /// format. 185 | pub fn bits_per_pixel(self) -> u32 { 186 | match self { 187 | PixelFormat::RGBA => 32, 188 | PixelFormat::RGB => 24, 189 | PixelFormat::GrayAlpha => 16, 190 | PixelFormat::Gray => 8, 191 | PixelFormat::Alpha => 8, 192 | } 193 | } 194 | } 195 | 196 | /// Converts RGBA image data into RGB. 197 | fn rgba_to_rgb(rgba: &[u8]) -> Box<[u8]> { 198 | assert_eq!(rgba.len() % 4, 0); 199 | let num_pixels = rgba.len() / 4; 200 | let mut rgb = Vec::with_capacity(num_pixels * 3); 201 | for i in 0..num_pixels { 202 | rgb.extend_from_slice(&rgba[(4 * i)..(4 * i + 3)]); 203 | } 204 | rgb.into_boxed_slice() 205 | } 206 | 207 | /// Converts RGB image data into RGBA. 208 | fn rgb_to_rgba(rgb: &[u8]) -> Box<[u8]> { 209 | assert_eq!(rgb.len() % 3, 0); 210 | let num_pixels = rgb.len() / 3; 211 | let mut rgba = Vec::with_capacity(num_pixels * 4); 212 | for i in 0..num_pixels { 213 | rgba.extend_from_slice(&rgb[(3 * i)..(3 * i + 3)]); 214 | rgba.push(u8::MAX); 215 | } 216 | rgba.into_boxed_slice() 217 | } 218 | 219 | /// Converts RGBA image data into grayscale. 220 | fn rgba_to_gray(rgba: &[u8]) -> Box<[u8]> { 221 | assert_eq!(rgba.len() % 4, 0); 222 | let num_pixels = rgba.len() / 4; 223 | let mut gray = Vec::with_capacity(num_pixels); 224 | for i in 0..num_pixels { 225 | let red = u32::from(rgba[4 * i]); 226 | let green = u32::from(rgba[4 * i + 1]); 227 | let blue = u32::from(rgba[4 * i + 2]); 228 | gray.push(((red + green + blue) / 3) as u8); 229 | } 230 | gray.into_boxed_slice() 231 | } 232 | 233 | /// Converts RGB image data into grayscale. 234 | fn rgb_to_gray(rgb: &[u8]) -> Box<[u8]> { 235 | assert_eq!(rgb.len() % 3, 0); 236 | let num_pixels = rgb.len() / 3; 237 | let mut gray = Vec::with_capacity(num_pixels); 238 | for i in 0..num_pixels { 239 | let red = u32::from(rgb[3 * i]); 240 | let green = u32::from(rgb[3 * i + 1]); 241 | let blue = u32::from(rgb[3 * i + 2]); 242 | gray.push(((red + green + blue) / 3) as u8); 243 | } 244 | gray.into_boxed_slice() 245 | } 246 | 247 | /// Converts RGBA image data into grayscale-with-alpha. 248 | fn rgba_to_grayalpha(rgba: &[u8]) -> Box<[u8]> { 249 | assert_eq!(rgba.len() % 4, 0); 250 | let num_pixels = rgba.len() / 4; 251 | let mut grayalpha = Vec::with_capacity(num_pixels * 2); 252 | for i in 0..num_pixels { 253 | let red = u32::from(rgba[4 * i]); 254 | let green = u32::from(rgba[4 * i + 1]); 255 | let blue = u32::from(rgba[4 * i + 2]); 256 | let alpha = rgba[4 * i + 3]; 257 | grayalpha.push(((red + green + blue) / 3) as u8); 258 | grayalpha.push(alpha); 259 | } 260 | grayalpha.into_boxed_slice() 261 | } 262 | 263 | /// Converts RGB image data into grayscale-with-alpha. 264 | fn rgb_to_grayalpha(rgb: &[u8]) -> Box<[u8]> { 265 | assert_eq!(rgb.len() % 3, 0); 266 | let num_pixels = rgb.len() / 3; 267 | let mut gray = Vec::with_capacity(num_pixels); 268 | for i in 0..num_pixels { 269 | let red = u32::from(rgb[3 * i]); 270 | let green = u32::from(rgb[3 * i + 1]); 271 | let blue = u32::from(rgb[3 * i + 2]); 272 | gray.push(((red + green + blue) / 3) as u8); 273 | gray.push(u8::MAX); 274 | } 275 | gray.into_boxed_slice() 276 | } 277 | 278 | /// Converts RGBA image data into an alpha mask. 279 | fn rgba_to_alpha(rgba: &[u8]) -> Box<[u8]> { 280 | assert_eq!(rgba.len() % 4, 0); 281 | let num_pixels = rgba.len() / 4; 282 | let mut alpha = Vec::with_capacity(num_pixels); 283 | for i in 0..num_pixels { 284 | alpha.push(rgba[4 * i + 3]); 285 | } 286 | alpha.into_boxed_slice() 287 | } 288 | 289 | /// Converts RGB image data into an alpha mask. 290 | fn rgb_to_alpha(rgb: &[u8]) -> Box<[u8]> { 291 | assert_eq!(rgb.len() % 3, 0); 292 | let num_pixels = rgb.len() / 3; 293 | vec![u8::MAX; num_pixels].into_boxed_slice() 294 | } 295 | 296 | /// Converts grayscale-with-alpha image data into RGBA. 297 | fn grayalpha_to_rgba(grayalpha: &[u8]) -> Box<[u8]> { 298 | assert_eq!(grayalpha.len() % 2, 0); 299 | let num_pixels = grayalpha.len() / 2; 300 | let mut rgba = Vec::with_capacity(num_pixels * 4); 301 | for i in 0..num_pixels { 302 | let gray = grayalpha[2 * i]; 303 | let alpha = grayalpha[2 * i + 1]; 304 | rgba.push(gray); 305 | rgba.push(gray); 306 | rgba.push(gray); 307 | rgba.push(alpha); 308 | } 309 | rgba.into_boxed_slice() 310 | } 311 | 312 | /// Converts grayscale-with-alpha image data into RGB. 313 | fn grayalpha_to_rgb(grayalpha: &[u8]) -> Box<[u8]> { 314 | assert_eq!(grayalpha.len() % 2, 0); 315 | let num_pixels = grayalpha.len() / 2; 316 | let mut rgb = Vec::with_capacity(num_pixels * 3); 317 | for i in 0..num_pixels { 318 | let gray = grayalpha[2 * i]; 319 | rgb.push(gray); 320 | rgb.push(gray); 321 | rgb.push(gray); 322 | } 323 | rgb.into_boxed_slice() 324 | } 325 | 326 | /// Converts grayscale-with-alpha image data into grayscale-without-alpha. 327 | fn grayalpha_to_gray(grayalpha: &[u8]) -> Box<[u8]> { 328 | assert_eq!(grayalpha.len() % 2, 0); 329 | let num_pixels = grayalpha.len() / 2; 330 | let mut gray = Vec::with_capacity(num_pixels); 331 | for i in 0..num_pixels { 332 | let value = grayalpha[2 * i]; 333 | gray.push(value); 334 | } 335 | gray.into_boxed_slice() 336 | } 337 | 338 | /// Converts grayscale-with-alpha image data into an alpha mask. 339 | fn grayalpha_to_alpha(grayalpha: &[u8]) -> Box<[u8]> { 340 | assert_eq!(grayalpha.len() % 2, 0); 341 | let num_pixels = grayalpha.len() / 2; 342 | let mut alpha = Vec::with_capacity(num_pixels); 343 | for i in 0..num_pixels { 344 | let value = grayalpha[2 * i + 1]; 345 | alpha.push(value); 346 | } 347 | alpha.into_boxed_slice() 348 | } 349 | 350 | /// Converts grayscale image data into RGBA. 351 | fn gray_to_rgba(gray: &[u8]) -> Box<[u8]> { 352 | let num_pixels = gray.len(); 353 | let mut rgba = Vec::with_capacity(num_pixels * 4); 354 | for &value in gray { 355 | rgba.push(value); 356 | rgba.push(value); 357 | rgba.push(value); 358 | rgba.push(u8::MAX); 359 | } 360 | rgba.into_boxed_slice() 361 | } 362 | 363 | /// Converts grayscale image data into RGB. 364 | fn gray_to_rgb(gray: &[u8]) -> Box<[u8]> { 365 | let num_pixels = gray.len(); 366 | let mut rgb = Vec::with_capacity(num_pixels * 3); 367 | for &value in gray { 368 | rgb.push(value); 369 | rgb.push(value); 370 | rgb.push(value); 371 | } 372 | rgb.into_boxed_slice() 373 | } 374 | 375 | /// Converts grayscale image data into grayscale-with-alpha. 376 | fn gray_to_grayalpha(gray: &[u8]) -> Box<[u8]> { 377 | let num_pixels = gray.len(); 378 | let mut grayalpha = Vec::with_capacity(num_pixels * 2); 379 | for &value in gray { 380 | grayalpha.push(value); 381 | grayalpha.push(u8::MAX); 382 | } 383 | grayalpha.into_boxed_slice() 384 | } 385 | 386 | /// Converts grayscale image data into an alpha mask. 387 | fn gray_to_alpha(gray: &[u8]) -> Box<[u8]> { 388 | vec![u8::MAX; gray.len()].into_boxed_slice() 389 | } 390 | 391 | /// Converts alpha mask image data into RGBA. 392 | fn alpha_to_rgba(alpha: &[u8]) -> Box<[u8]> { 393 | let num_pixels = alpha.len(); 394 | let mut rgba = Vec::with_capacity(num_pixels * 4); 395 | for &value in alpha { 396 | rgba.push(0); 397 | rgba.push(0); 398 | rgba.push(0); 399 | rgba.push(value); 400 | } 401 | rgba.into_boxed_slice() 402 | } 403 | 404 | /// Converts alpha mask image data into RGB. 405 | fn alpha_to_rgb(alpha: &[u8]) -> Box<[u8]> { 406 | vec![0u8; alpha.len() * 3].into_boxed_slice() 407 | } 408 | 409 | /// Converts alpha mask image data into grayscale-with-alpha. 410 | fn alpha_to_grayalpha(alpha: &[u8]) -> Box<[u8]> { 411 | let num_pixels = alpha.len(); 412 | let mut grayalpha = Vec::with_capacity(num_pixels * 2); 413 | for &value in alpha { 414 | grayalpha.push(0); 415 | grayalpha.push(value); 416 | } 417 | grayalpha.into_boxed_slice() 418 | } 419 | 420 | /// Converts alpha mask image data into grayscale. 421 | fn alpha_to_gray(alpha: &[u8]) -> Box<[u8]> { 422 | vec![0u8; alpha.len()].into_boxed_slice() 423 | } 424 | 425 | #[cfg(test)] 426 | mod tests { 427 | use super::*; 428 | use std::io::Cursor; 429 | 430 | #[test] 431 | fn image_from_data() { 432 | let data: Vec = vec![255, 0, 0, 0, 255, 0, 0, 0, 255, 95, 95, 95]; 433 | let image = Image::from_data(PixelFormat::RGB, 2, 2, data.clone()) 434 | .unwrap(); 435 | assert_eq!(image.data(), &data as &[u8]); 436 | } 437 | 438 | #[test] 439 | fn image_from_data_wrong_size() { 440 | let data: Vec = vec![1, 2, 3]; 441 | let result = Image::from_data(PixelFormat::Alpha, 2, 2, data); 442 | assert!(result.is_err()); 443 | } 444 | 445 | #[test] 446 | fn alpha_to_gray() { 447 | let alpha_data: Vec = vec![63, 127, 191, 255]; 448 | let mut alpha_image = Image::new(PixelFormat::Alpha, 2, 2); 449 | alpha_image.data_mut().clone_from_slice(&alpha_data); 450 | let gray_image = alpha_image.convert_to(PixelFormat::Gray); 451 | let gray_data: Vec = vec![0, 0, 0, 0]; 452 | assert_eq!(gray_image.data(), &gray_data as &[u8]); 453 | } 454 | 455 | #[test] 456 | fn alpha_to_grayalpha() { 457 | let alpha_data: Vec = vec![63, 127, 191, 255]; 458 | let mut alpha_image = Image::new(PixelFormat::Alpha, 2, 2); 459 | alpha_image.data_mut().clone_from_slice(&alpha_data); 460 | let grayalpha_image = alpha_image.convert_to(PixelFormat::GrayAlpha); 461 | let grayalpha_data: Vec = vec![0, 63, 0, 127, 0, 191, 0, 255]; 462 | assert_eq!(grayalpha_image.data(), &grayalpha_data as &[u8]); 463 | } 464 | 465 | #[test] 466 | fn alpha_to_rgb() { 467 | let alpha_data: Vec = vec![63, 127, 191, 255]; 468 | let mut alpha_image = Image::new(PixelFormat::Alpha, 2, 2); 469 | alpha_image.data_mut().clone_from_slice(&alpha_data); 470 | let rgb_image = alpha_image.convert_to(PixelFormat::RGB); 471 | let rgb_data: Vec = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 472 | assert_eq!(rgb_image.data(), &rgb_data as &[u8]); 473 | } 474 | 475 | #[test] 476 | fn alpha_to_rgba() { 477 | let alpha_data: Vec = vec![63, 127, 191, 255]; 478 | let mut alpha_image = Image::new(PixelFormat::Alpha, 2, 2); 479 | alpha_image.data_mut().clone_from_slice(&alpha_data); 480 | let rgba_image = alpha_image.convert_to(PixelFormat::RGBA); 481 | let rgba_data: Vec = vec![0, 0, 0, 63, 0, 0, 0, 127, 0, 0, 0, 482 | 191, 0, 0, 0, 255]; 483 | assert_eq!(rgba_image.data(), &rgba_data as &[u8]); 484 | } 485 | 486 | #[test] 487 | fn gray_to_alpha() { 488 | let gray_data: Vec = vec![63, 127, 191, 255]; 489 | let mut gray_image = Image::new(PixelFormat::Gray, 2, 2); 490 | gray_image.data_mut().clone_from_slice(&gray_data); 491 | let alpha_image = gray_image.convert_to(PixelFormat::Alpha); 492 | let alpha_data: Vec = vec![255, 255, 255, 255]; 493 | assert_eq!(alpha_image.data(), &alpha_data as &[u8]); 494 | } 495 | 496 | #[test] 497 | fn gray_to_grayalpha() { 498 | let gray_data: Vec = vec![63, 127, 191, 255]; 499 | let mut gray_image = Image::new(PixelFormat::Gray, 2, 2); 500 | gray_image.data_mut().clone_from_slice(&gray_data); 501 | let grayalpha_image = gray_image.convert_to(PixelFormat::GrayAlpha); 502 | let grayalpha_data: Vec = vec![63, 255, 127, 255, 191, 255, 255, 503 | 255]; 504 | assert_eq!(grayalpha_image.data(), &grayalpha_data as &[u8]); 505 | } 506 | 507 | #[test] 508 | fn gray_to_rgb() { 509 | let gray_data: Vec = vec![63, 127, 191, 255]; 510 | let mut gray_image = Image::new(PixelFormat::Gray, 2, 2); 511 | gray_image.data_mut().clone_from_slice(&gray_data); 512 | let rgb_image = gray_image.convert_to(PixelFormat::RGB); 513 | let rgb_data: Vec = vec![63, 63, 63, 127, 127, 127, 191, 191, 514 | 191, 255, 255, 255]; 515 | assert_eq!(rgb_image.data(), &rgb_data as &[u8]); 516 | } 517 | 518 | #[test] 519 | fn gray_to_rgba() { 520 | let gray_data: Vec = vec![63, 127, 191, 255]; 521 | let mut gray_image = Image::new(PixelFormat::Gray, 2, 2); 522 | gray_image.data_mut().clone_from_slice(&gray_data); 523 | let rgba_image = gray_image.convert_to(PixelFormat::RGBA); 524 | assert_eq!(rgba_image.pixel_format(), PixelFormat::RGBA); 525 | assert_eq!(rgba_image.width(), 2); 526 | assert_eq!(rgba_image.height(), 2); 527 | let rgba_data: Vec = vec![63, 63, 63, 255, 127, 127, 127, 255, 528 | 191, 191, 191, 255, 255, 255, 255, 255]; 529 | assert_eq!(rgba_image.data(), &rgba_data as &[u8]); 530 | } 531 | 532 | #[test] 533 | fn grayalpha_to_alpha() { 534 | let grayalpha_data: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8]; 535 | let mut grayalpha_image = Image::new(PixelFormat::GrayAlpha, 2, 2); 536 | grayalpha_image.data_mut().clone_from_slice(&grayalpha_data); 537 | let alpha_image = grayalpha_image.convert_to(PixelFormat::Alpha); 538 | let alpha_data: Vec = vec![2, 4, 6, 8]; 539 | assert_eq!(alpha_image.data(), &alpha_data as &[u8]); 540 | } 541 | 542 | #[test] 543 | fn grayalpha_to_gray() { 544 | let grayalpha_data: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8]; 545 | let mut grayalpha_image = Image::new(PixelFormat::GrayAlpha, 2, 2); 546 | grayalpha_image.data_mut().clone_from_slice(&grayalpha_data); 547 | let gray_image = grayalpha_image.convert_to(PixelFormat::Gray); 548 | let gray_data: Vec = vec![1, 3, 5, 7]; 549 | assert_eq!(gray_image.data(), &gray_data as &[u8]); 550 | } 551 | 552 | #[test] 553 | fn grayalpha_to_rgb() { 554 | let grayalpha_data: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8]; 555 | let mut grayalpha_image = Image::new(PixelFormat::GrayAlpha, 2, 2); 556 | grayalpha_image.data_mut().clone_from_slice(&grayalpha_data); 557 | let rgb_image = grayalpha_image.convert_to(PixelFormat::RGB); 558 | let rgb_data: Vec = vec![1, 1, 1, 3, 3, 3, 5, 5, 5, 7, 7, 7]; 559 | assert_eq!(rgb_image.data(), &rgb_data as &[u8]); 560 | } 561 | 562 | #[test] 563 | fn grayalpha_to_rgba() { 564 | let grayalpha_data: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8]; 565 | let mut grayalpha_image = Image::new(PixelFormat::GrayAlpha, 2, 2); 566 | grayalpha_image.data_mut().clone_from_slice(&grayalpha_data); 567 | let rgba_image = grayalpha_image.convert_to(PixelFormat::RGBA); 568 | let rgba_data: Vec = vec![1, 1, 1, 2, 3, 3, 3, 4, 5, 5, 5, 6, 7, 569 | 7, 7, 8]; 570 | assert_eq!(rgba_image.data(), &rgba_data as &[u8]); 571 | } 572 | 573 | #[test] 574 | fn rgb_to_alpha() { 575 | let rgb_data: Vec = vec![30, 0, 0, 0, 60, 0, 0, 0, 90, 40, 40, 40]; 576 | let mut rgb_image = Image::new(PixelFormat::RGB, 2, 2); 577 | rgb_image.data_mut().clone_from_slice(&rgb_data); 578 | let alpha_image = rgb_image.convert_to(PixelFormat::Alpha); 579 | let alpha_data: Vec = vec![255, 255, 255, 255]; 580 | assert_eq!(alpha_image.data(), &alpha_data as &[u8]); 581 | } 582 | 583 | #[test] 584 | fn rgb_to_gray() { 585 | let rgb_data: Vec = vec![30, 0, 0, 0, 60, 0, 0, 0, 90, 40, 40, 40]; 586 | let mut rgb_image = Image::new(PixelFormat::RGB, 2, 2); 587 | rgb_image.data_mut().clone_from_slice(&rgb_data); 588 | let gray_image = rgb_image.convert_to(PixelFormat::Gray); 589 | let gray_data: Vec = vec![10, 20, 30, 40]; 590 | assert_eq!(gray_image.data(), &gray_data as &[u8]); 591 | } 592 | 593 | #[test] 594 | fn rgb_to_grayalpha() { 595 | let rgb_data: Vec = vec![30, 0, 0, 0, 60, 0, 0, 0, 90, 40, 40, 40]; 596 | let mut rgb_image = Image::new(PixelFormat::RGB, 2, 2); 597 | rgb_image.data_mut().clone_from_slice(&rgb_data); 598 | let grayalpha_image = rgb_image.convert_to(PixelFormat::GrayAlpha); 599 | let grayalpha_data: Vec = vec![10, 255, 20, 255, 30, 255, 40, 255]; 600 | assert_eq!(grayalpha_image.data(), &grayalpha_data as &[u8]); 601 | } 602 | 603 | #[test] 604 | fn rgb_to_rgba() { 605 | let rgb_data: Vec = vec![255, 0, 0, 0, 255, 0, 0, 0, 255, 127, 606 | 127, 127]; 607 | let mut rgb_image = Image::new(PixelFormat::RGB, 2, 2); 608 | rgb_image.data_mut().clone_from_slice(&rgb_data); 609 | let rgba_image = rgb_image.convert_to(PixelFormat::RGBA); 610 | assert_eq!(rgba_image.pixel_format(), PixelFormat::RGBA); 611 | assert_eq!(rgba_image.width(), 2); 612 | assert_eq!(rgba_image.height(), 2); 613 | let rgba_data: Vec = vec![255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 614 | 255, 255, 127, 127, 127, 255]; 615 | assert_eq!(rgba_image.data(), &rgba_data as &[u8]); 616 | } 617 | 618 | #[test] 619 | fn rgba_to_alpha() { 620 | let rgba_data: Vec = vec![30, 0, 0, 200, 0, 60, 0, 150, 0, 0, 90, 621 | 100, 40, 40, 40, 50]; 622 | let mut rgba_image = Image::new(PixelFormat::RGBA, 2, 2); 623 | rgba_image.data_mut().clone_from_slice(&rgba_data); 624 | let alpha_image = rgba_image.convert_to(PixelFormat::Alpha); 625 | let alpha_data: Vec = vec![200, 150, 100, 50]; 626 | assert_eq!(alpha_image.data(), &alpha_data as &[u8]); 627 | } 628 | 629 | #[test] 630 | fn rgba_to_gray() { 631 | let rgba_data: Vec = vec![30, 0, 0, 200, 0, 60, 0, 150, 0, 0, 90, 632 | 100, 40, 40, 40, 50]; 633 | let mut rgba_image = Image::new(PixelFormat::RGBA, 2, 2); 634 | rgba_image.data_mut().clone_from_slice(&rgba_data); 635 | let gray_image = rgba_image.convert_to(PixelFormat::Gray); 636 | let gray_data: Vec = vec![10, 20, 30, 40]; 637 | assert_eq!(gray_image.data(), &gray_data as &[u8]); 638 | } 639 | 640 | #[test] 641 | fn rgba_to_grayalpha() { 642 | let rgba_data: Vec = vec![30, 0, 0, 200, 0, 60, 0, 150, 0, 0, 90, 643 | 100, 40, 40, 40, 50]; 644 | let mut rgba_image = Image::new(PixelFormat::RGBA, 2, 2); 645 | rgba_image.data_mut().clone_from_slice(&rgba_data); 646 | let grayalpha_image = rgba_image.convert_to(PixelFormat::GrayAlpha); 647 | let grayalpha_data: Vec = vec![10, 200, 20, 150, 30, 100, 40, 50]; 648 | assert_eq!(grayalpha_image.data(), &grayalpha_data as &[u8]); 649 | } 650 | 651 | #[test] 652 | fn rgba_to_rgb() { 653 | let rgba_data: Vec = vec![30, 0, 0, 200, 0, 60, 0, 150, 0, 0, 90, 654 | 100, 40, 40, 40, 50]; 655 | let mut rgba_image = Image::new(PixelFormat::RGBA, 2, 2); 656 | rgba_image.data_mut().clone_from_slice(&rgba_data); 657 | let rgb_image = rgba_image.convert_to(PixelFormat::RGB); 658 | let rgb_data: Vec = vec![30, 0, 0, 0, 60, 0, 0, 0, 90, 40, 40, 40]; 659 | assert_eq!(rgb_image.data(), &rgb_data as &[u8]); 660 | } 661 | 662 | #[test] 663 | #[cfg(feature = "pngio")] 664 | fn write_grayscale_png() { 665 | let gray_data: Vec = vec![63, 127, 191, 255]; 666 | let mut image = Image::new(PixelFormat::Gray, 2, 2); 667 | image.data_mut().clone_from_slice(&gray_data); 668 | let mut output: Vec = Vec::new(); 669 | image.write_png(&mut output).expect("failed to write PNG"); 670 | let expected: Vec = 671 | vec![137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 672 | 82, 0, 0, 0, 2, 0, 0, 0, 2, 8, 0, 0, 0, 0, 87, 221, 82, 248, 673 | 0, 0, 0, 14, 73, 68, 65, 84, 120, 156, 99, 180, 119, 96, 674 | 220, 239, 0, 0, 4, 8, 1, 129, 134, 46, 201, 141, 0, 0, 0, 0, 675 | 73, 69, 78, 68, 174, 66, 96, 130]; 676 | assert_eq!(output, expected); 677 | } 678 | 679 | #[test] 680 | #[cfg(feature = "pngio")] 681 | fn write_rgb_png() { 682 | let rgb_data: Vec = vec![255, 0, 0, 0, 255, 0, 0, 0, 255, 127, 683 | 127, 127]; 684 | let mut image = Image::new(PixelFormat::RGB, 2, 2); 685 | image.data_mut().clone_from_slice(&rgb_data); 686 | let mut output: Vec = Vec::new(); 687 | image.write_png(&mut output).expect("failed to write PNG"); 688 | let expected: Vec = 689 | vec![137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 690 | 82, 0, 0, 0, 2, 0, 0, 0, 2, 8, 2, 0, 0, 0, 253, 212, 154, 691 | 115, 0, 0, 0, 20, 73, 68, 65, 84, 120, 156, 99, 252, 207, 692 | 192, 0, 196, 140, 12, 12, 255, 235, 235, 27, 0, 29, 14, 4, 693 | 127, 253, 15, 140, 153, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 694 | 96, 130]; 695 | assert_eq!(output, expected); 696 | } 697 | 698 | #[test] 699 | #[cfg(feature = "pngio")] 700 | fn write_rgba_png() { 701 | let rgba_data: Vec = vec![255, 0, 0, 63, 0, 255, 0, 127, 0, 0, 702 | 255, 191, 127, 127, 127, 255]; 703 | let mut image = Image::new(PixelFormat::RGBA, 2, 2); 704 | image.data_mut().clone_from_slice(&rgba_data); 705 | let mut output: Vec = Vec::new(); 706 | image.write_png(&mut output).expect("failed to write PNG"); 707 | let expected: Vec = 708 | vec![137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 709 | 82, 0, 0, 0, 2, 0, 0, 0, 2, 8, 6, 0, 0, 0, 114, 182, 13, 36, 710 | 0, 0, 0, 25, 73, 68, 65, 84, 120, 156, 99, 252, 207, 192, 711 | 96, 15, 36, 28, 24, 25, 24, 254, 239, 175, 175, 111, 112, 0, 712 | 0, 49, 125, 5, 253, 88, 193, 178, 240, 0, 0, 0, 0, 73, 69, 713 | 78, 68, 174, 66, 96, 130]; 714 | assert_eq!(output, expected); 715 | } 716 | 717 | #[test] 718 | #[cfg(feature = "pngio")] 719 | fn read_rgba_png() { 720 | let png: Vec = 721 | vec![137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 722 | 82, 0, 0, 0, 2, 0, 0, 0, 2, 8, 6, 0, 0, 0, 114, 182, 13, 36, 723 | 0, 0, 0, 29, 73, 68, 65, 84, 120, 1, 1, 18, 0, 237, 255, 1, 724 | 255, 0, 0, 63, 1, 255, 0, 64, 1, 0, 0, 255, 191, 127, 127, 725 | 128, 64, 49, 125, 5, 253, 198, 70, 247, 56, 0, 0, 0, 0, 73, 726 | 69, 78, 68, 174, 66, 96, 130]; 727 | let image = Image::read_png(Cursor::new(&png)) 728 | .expect("failed to read PNG"); 729 | assert_eq!(image.pixel_format(), PixelFormat::RGBA); 730 | assert_eq!(image.width(), 2); 731 | assert_eq!(image.height(), 2); 732 | let rgba_data: Vec = vec![255, 0, 0, 63, 0, 255, 0, 127, 0, 0, 733 | 255, 191, 127, 127, 127, 255]; 734 | assert_eq!(image.data(), &rgba_data as &[u8]); 735 | } 736 | 737 | #[test] 738 | #[cfg(feature = "pngio")] 739 | fn png_round_trip() { 740 | let rgba_data: Vec = vec![127, 0, 0, 63, 0, 191, 0, 127, 0, 0, 741 | 255, 191, 127, 127, 127, 255]; 742 | let mut rgba_image = Image::new(PixelFormat::RGBA, 2, 2); 743 | rgba_image.data_mut().clone_from_slice(&rgba_data); 744 | let pixel_formats = [PixelFormat::RGBA, 745 | PixelFormat::RGB, 746 | PixelFormat::GrayAlpha, 747 | PixelFormat::Gray, 748 | PixelFormat::Alpha]; 749 | for &format in pixel_formats.iter() { 750 | // For each pixel format, try writing a PNG from an image in that 751 | // format. 752 | let image_1 = rgba_image.convert_to(format); 753 | let mut png_data = Vec::::new(); 754 | image_1.write_png(&mut png_data).expect("failed to write PNG"); 755 | // We should be able to read the PNG back in successfully. 756 | let mut image_2 = Image::read_png(Cursor::new(&png_data)) 757 | .expect("failed to read PNG"); 758 | // We may get the image back in a different pixel format. However, 759 | // in such cases we should be able to convert back to the original 760 | // pixel format and still get back exactly the same data. 761 | if image_2.pixel_format() != image_1.pixel_format() { 762 | image_2 = image_2.convert_to(image_1.pixel_format()); 763 | } 764 | assert_eq!(image_1.data(), image_2.data()); 765 | } 766 | } 767 | } 768 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A library for encoding/decoding Apple Icon Image (.icns) files. 2 | //! 3 | //! # ICNS concepts 4 | //! 5 | //! To understand this library, it helps to be familiar with the structure of 6 | //! an ICNS file; this section will give a high-level overview, or see 7 | //! [Wikipedia](https://en.wikipedia.org/wiki/Apple_Icon_Image_format) for more 8 | //! details about the file format. If you prefer to learn by example, you can 9 | //! just skip down to the [Example usage](#example-usage) section below. 10 | //! 11 | //! An ICNS file encodes a collection of images (typically different versions 12 | //! of the same icon at different resolutions) called an _icon family_. The 13 | //! file consists of a short header followed by a sequence of data blocks 14 | //! called _icon elements_. Each icon element consists of a header with an 15 | //! _OSType_ -- which is essentially a four-byte identifier indicating the type 16 | //! of data in the element -- and a blob of binary data. 17 | //! 18 | //! Each image in the ICNS file is encoded either as a single icon element, or 19 | //! as two elements -- one for the color data and one for the alpha mask. For 20 | //! example, 48x48 pixel icons are stored as two elements: an `ih32` element 21 | //! containing compressed 24-bit RGB data, and an `h8mk` element containing the 22 | //! 8-bit alpha mask. By contrast, 64x64 pixel icons are stored as a single 23 | //! `icp6` element, which contains either PNG or JPEG 2000 data for the whole 24 | //! 32-bit image. 25 | //! 26 | //! Some icon sizes have multiple possible encodings. For example, a 128x128 27 | //! icon can be stored either as an `it32` and an `t8mk` element together 28 | //! (containing compressed RGB and alpha, respectively), or as a single `ic07` 29 | //! element (containing PNG or JPEG 2000 data). And for some icon sizes, there 30 | //! are separate OSTypes for single and double-pixel-density versions of the 31 | //! icon (for "retina" displays). For example, an `ic08` element encodes a 32 | //! single-density 256x256 image, while an `ic14` element encodes a 33 | //! double-density 256x256 image -- that is, the image data is actually 512x512 34 | //! pixels, and is considered different from the single-density 512x512 pixel 35 | //! image encoded by an `ic09` element. 36 | //! 37 | //! Finally, there are some additional, optional element types that don't 38 | //! encode images at all. For example, the `TOC` element summarizes the 39 | //! contents of the ICNS file, and the `icnV` element stores version 40 | //! information. 41 | //! 42 | //! # API overview 43 | //! 44 | //! The API for this library is modelled loosely after that of 45 | //! [libicns](http://icns.sourceforge.net/apidocs.html). 46 | //! 47 | //! The icon family stored in an ICNS file is represeted by the 48 | //! [`IconFamily`](struct.IconFamily.html) struct, which provides methods for 49 | //! [reading](struct.IconFamily.html#method.read) and 50 | //! [writing](struct.IconFamily.html#method.write) ICNS files, as well as for 51 | //! high-level operations on the icon set, such as 52 | //! [adding](struct.IconFamily.html#method.add_icon_with_type), 53 | //! [extracting](struct.IconFamily.html#method.get_icon_with_type), and 54 | //! [listing](struct.IconFamily.html#method.available_icons) the encoded 55 | //! images. 56 | //! 57 | //! An `IconFamily` contains a vector of 58 | //! [`IconElement`](struct.IconElement.html) structs, which represent 59 | //! individual data blocks in the ICNS file. Each `IconElement` has an 60 | //! [`OSType`](struct.OSType.html) indicating the type of data in the element, 61 | //! as well as a `Vec` containing the data itself. Usually, you won't 62 | //! need to work with `IconElement`s directly, and can instead use the 63 | //! higher-level operations provided by `IconFamily`. 64 | //! 65 | //! Since raw OSTypes like `t8mk` and `icp4` can be hard to remember, the 66 | //! [`IconType`](enum.IconType.html) type enumerates all the icon element types 67 | //! that are supported by this library, with more mnemonic names (for example, 68 | //! `IconType::RGB24_48x48` indicates 24-bit RGB data for a 48x48 pixel icon, 69 | //! and is a bit more understandable than the corresponding OSType, `ih32`). 70 | //! The `IconType` enum also provides methods for getting the properties of 71 | //! each icon type, such as the size of the encoded image, or the associated 72 | //! mask type (for icons that are stored as two elements instead of one). 73 | //! 74 | //! Regardless of whether you use the higher-level `IconFamily` methods or the 75 | //! lower-level `IconElement` methods, icons from the ICNS file can be decoded 76 | //! into [`Image`](struct.Image.html) structs, which can be 77 | //! [converted](struct.Image.html#method.convert_to) to and from any of several 78 | //! [`PixelFormats`](enum.PixelFormat.html) to allow the raw pixel data to be 79 | //! easily transferred to another image library for further processing. Since 80 | //! this library already depends on the PNG codec anyway (since some ICNS icons 81 | //! are PNG-encoded), as a convenience, the [`Image`](struct.Image.html) struct 82 | //! also provides methods for [reading](struct.Image.html#method.read_png) and 83 | //! [writing](struct.Image.html#method.write_png) PNG files. 84 | //! 85 | //! # Limitations 86 | //! 87 | //! The ICNS format allows some icon types to be encoded either as PNG data or 88 | //! as JPEG 2000 data; however, when encoding icons, this library always uses 89 | //! PNG format, and when decoding icons, it cannot decode JPEG 2000 icons at 90 | //! all (it will detect the JPEG 2000 header and return an error). The reason 91 | //! for this is the apparent lack of JPEG 2000 libraries for Rust; if this ever 92 | //! changes, please feel free to file a bug or a send a pull request. 93 | //! 94 | //! Additionally, this library does not yet support many of the older icon 95 | //! types used by earlier versions of Mac OS (such as `ICN#`, a 32x32 black and 96 | //! white icon). Again, pull requests (with suitable tests) are welcome. 97 | //! 98 | //! # Example usage 99 | //! 100 | //! ```no_run 101 | //! use icns::{IconFamily, IconType, Image}; 102 | //! use std::fs::File; 103 | //! use std::io::{BufReader, BufWriter}; 104 | //! 105 | //! // Load an icon family from an ICNS file. 106 | //! let file = BufReader::new(File::open("16.icns").unwrap()); 107 | //! let mut icon_family = IconFamily::read(file).unwrap(); 108 | //! 109 | //! // Extract an icon from the family and save it as a PNG. 110 | //! let image = icon_family.get_icon_with_type(IconType::RGB24_16x16).unwrap(); 111 | //! let file = BufWriter::new(File::create("16.png").unwrap()); 112 | //! image.write_png(file).unwrap(); 113 | //! 114 | //! // Read in another icon from a PNG file, and add it to the icon family. 115 | //! let file = BufReader::new(File::open("32.png").unwrap()); 116 | //! let image = Image::read_png(file).unwrap(); 117 | //! icon_family.add_icon(&image).unwrap(); 118 | //! 119 | //! // Save the updated icon family to a new ICNS file. 120 | //! let file = BufWriter::new(File::create("16-and-32.icns").unwrap()); 121 | //! icon_family.write(file).unwrap(); 122 | //! ``` 123 | 124 | #![warn(missing_docs)] 125 | 126 | extern crate byteorder; 127 | 128 | #[cfg(feature = "pngio")] 129 | extern crate png; 130 | 131 | #[cfg(feature = "pngio")] 132 | mod pngio; 133 | 134 | mod element; 135 | pub use self::element::IconElement; 136 | 137 | mod family; 138 | pub use self::family::IconFamily; 139 | 140 | mod icontype; 141 | pub use self::icontype::{Encoding, IconType, OSType}; 142 | 143 | mod image; 144 | pub use self::image::{Image, PixelFormat}; 145 | -------------------------------------------------------------------------------- /src/pngio.rs: -------------------------------------------------------------------------------- 1 | use png; 2 | use std::io::{self, Read, Write}; 3 | use image::{Image, PixelFormat}; 4 | 5 | impl Image { 6 | 7 | /// Reads an image from a PNG file. 8 | pub fn read_png(input: R) -> io::Result { 9 | let decoder = png::Decoder::new(input); 10 | let (info, mut reader) = decoder.read_info()?; 11 | let pixel_format = match info.color_type { 12 | png::ColorType::RGBA => PixelFormat::RGBA, 13 | png::ColorType::RGB => PixelFormat::RGB, 14 | png::ColorType::GrayscaleAlpha => PixelFormat::GrayAlpha, 15 | png::ColorType::Grayscale => PixelFormat::Gray, 16 | _ => { 17 | // TODO: Support other color types. 18 | return Err(io::Error::new(io::ErrorKind::InvalidData, 19 | format!("unsupported PNG color \ 20 | type: {:?}", 21 | info.color_type))); 22 | } 23 | }; 24 | if info.bit_depth != png::BitDepth::Eight { 25 | // TODO: Support other bit depths. 26 | return Err(io::Error::new(io::ErrorKind::InvalidData, 27 | format!("unsupported PNG bit depth: \ 28 | {:?}", 29 | info.bit_depth))); 30 | 31 | } 32 | let mut image = Image::new(pixel_format, info.width, info.height); 33 | assert_eq!(image.data().len(), info.buffer_size()); 34 | reader.next_frame(image.data_mut())?; 35 | Ok(image) 36 | } 37 | 38 | /// Writes the image to a PNG file. 39 | pub fn write_png(&self, output: W) -> io::Result<()> { 40 | let color_type = match self.format { 41 | PixelFormat::RGBA => png::ColorType::RGBA, 42 | PixelFormat::RGB => png::ColorType::RGB, 43 | PixelFormat::GrayAlpha => png::ColorType::GrayscaleAlpha, 44 | PixelFormat::Gray => png::ColorType::Grayscale, 45 | PixelFormat::Alpha => { 46 | return self.convert_to(PixelFormat::GrayAlpha) 47 | .write_png(output); 48 | } 49 | }; 50 | let mut encoder = png::Encoder::new(output, self.width, self.height); 51 | encoder.set_color(color_type); 52 | encoder.set_depth(png::BitDepth::Eight); 53 | let mut writer = encoder.write_header()?; 54 | writer.write_image_data(&self.data).map_err(|err| match err { 55 | png::EncodingError::IoError(err) => err, 56 | png::EncodingError::Format(msg) => { 57 | io::Error::new(io::ErrorKind::InvalidData, msg.into_owned()) 58 | } 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/golden.rs: -------------------------------------------------------------------------------- 1 | extern crate icns; 2 | 3 | use icns::{IconFamily, IconType, Image}; 4 | use std::fs::File; 5 | use std::io::{self, BufReader}; 6 | 7 | #[test] 8 | fn decode_is32() { 9 | decoder_test("is32.icns", IconType::RGB24_16x16, "16x16.png"); 10 | } 11 | 12 | #[test] 13 | fn encode_is32() { 14 | encoder_test("16x16.png", IconType::RGB24_16x16, "is32.icns"); 15 | } 16 | 17 | #[test] 18 | fn decode_il32() { 19 | decoder_test("il32.icns", IconType::RGB24_32x32, "32x32.png"); 20 | } 21 | 22 | #[test] 23 | fn encode_il32() { 24 | encoder_test("32x32.png", IconType::RGB24_32x32, "il32.icns"); 25 | } 26 | 27 | #[test] 28 | fn decode_it32() { 29 | decoder_test("it32.icns", IconType::RGB24_128x128, "128x128.png"); 30 | } 31 | 32 | #[test] 33 | fn encode_it32() { 34 | encoder_test("128x128.png", IconType::RGB24_128x128, "it32.icns"); 35 | } 36 | 37 | #[test] 38 | fn decode_icp4() { 39 | decoder_test("icp4.icns", IconType::RGBA32_16x16, "16x16.png"); 40 | } 41 | 42 | #[test] 43 | fn encode_icp4() { 44 | encoder_test("16x16.png", IconType::RGBA32_16x16, "icp4.icns"); 45 | } 46 | 47 | #[test] 48 | fn decode_icp5() { 49 | decoder_test("icp5.icns", IconType::RGBA32_32x32, "32x32.png"); 50 | } 51 | 52 | #[test] 53 | fn encode_icp5() { 54 | encoder_test("32x32.png", IconType::RGBA32_32x32, "icp5.icns"); 55 | } 56 | 57 | #[test] 58 | fn decode_ic07() { 59 | decoder_test("ic07.icns", IconType::RGBA32_128x128, "128x128.png"); 60 | } 61 | 62 | #[test] 63 | fn encode_ic07() { 64 | encoder_test("128x128.png", IconType::RGBA32_128x128, "ic07.icns"); 65 | } 66 | 67 | #[test] 68 | fn decode_ic11() { 69 | decoder_test("ic11.icns", IconType::RGBA32_16x16_2x, "32x32.png"); 70 | } 71 | 72 | #[test] 73 | fn encode_ic11() { 74 | encoder_test("32x32.png", IconType::RGBA32_16x16_2x, "ic11.icns"); 75 | } 76 | 77 | fn decoder_test(icns_name: &str, icon_type: IconType, png_name: &str) { 78 | let family = load_icns_file(icns_name).unwrap(); 79 | let image = family.get_icon_with_type(icon_type).unwrap(); 80 | let reference = load_png_file(png_name).unwrap(); 81 | assert_images_match(&image, &reference); 82 | } 83 | 84 | fn encoder_test(png_name: &str, icon_type: IconType, icns_name: &str) { 85 | let image = load_png_file(png_name).unwrap(); 86 | let mut family = IconFamily::new(); 87 | family.add_icon_with_type(&image, icon_type).unwrap(); 88 | let reference = load_icns_file(icns_name).unwrap(); 89 | assert_families_match(&family, &reference); 90 | } 91 | 92 | fn load_icns_file(name: &str) -> io::Result { 93 | let path = format!("tests/icns/{}", name); 94 | let file = BufReader::new(File::open(path)?); 95 | IconFamily::read(file) 96 | } 97 | 98 | fn load_png_file(name: &str) -> io::Result { 99 | let path = format!("tests/png/{}", name); 100 | let file = BufReader::new(File::open(path)?); 101 | Image::read_png(file) 102 | } 103 | 104 | fn assert_images_match(image: &Image, reference: &Image) { 105 | assert_eq!(image.width(), reference.width()); 106 | assert_eq!(image.height(), reference.height()); 107 | assert_eq!(image.pixel_format(), reference.pixel_format()); 108 | assert!(image.data() == reference.data()); 109 | } 110 | 111 | fn assert_families_match(family: &IconFamily, reference: &IconFamily) { 112 | let mut family_data = Vec::::new(); 113 | family.write(&mut family_data).unwrap(); 114 | let mut reference_data = Vec::::new(); 115 | reference.write(&mut reference_data).unwrap(); 116 | assert!(family_data == reference_data); 117 | } 118 | -------------------------------------------------------------------------------- /tests/icns/ic07.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdsteele/rust-icns/2e95a2eaf2f4475b6a6e68f75725421117707734/tests/icns/ic07.icns -------------------------------------------------------------------------------- /tests/icns/ic11.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdsteele/rust-icns/2e95a2eaf2f4475b6a6e68f75725421117707734/tests/icns/ic11.icns -------------------------------------------------------------------------------- /tests/icns/icp4.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdsteele/rust-icns/2e95a2eaf2f4475b6a6e68f75725421117707734/tests/icns/icp4.icns -------------------------------------------------------------------------------- /tests/icns/icp5.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdsteele/rust-icns/2e95a2eaf2f4475b6a6e68f75725421117707734/tests/icns/icp5.icns -------------------------------------------------------------------------------- /tests/icns/il32.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdsteele/rust-icns/2e95a2eaf2f4475b6a6e68f75725421117707734/tests/icns/il32.icns -------------------------------------------------------------------------------- /tests/icns/is32.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdsteele/rust-icns/2e95a2eaf2f4475b6a6e68f75725421117707734/tests/icns/is32.icns -------------------------------------------------------------------------------- /tests/icns/it32.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdsteele/rust-icns/2e95a2eaf2f4475b6a6e68f75725421117707734/tests/icns/it32.icns -------------------------------------------------------------------------------- /tests/png/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdsteele/rust-icns/2e95a2eaf2f4475b6a6e68f75725421117707734/tests/png/128x128.png -------------------------------------------------------------------------------- /tests/png/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdsteele/rust-icns/2e95a2eaf2f4475b6a6e68f75725421117707734/tests/png/16x16.png -------------------------------------------------------------------------------- /tests/png/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdsteele/rust-icns/2e95a2eaf2f4475b6a6e68f75725421117707734/tests/png/32x32.png --------------------------------------------------------------------------------