icns-rs

Easily create .icns files (Mac Icons) with this Rust library or the included CLI app.
Log | Files | Refs | LICENSE

commit dc186d347eea7a5fc8d52a169382613709544cb0
parent 5747b8516f66191d2edf98084f76949ee63af76e
Author: Jack Mordaunt <jackmordaunt@gmail.com>
Date:   Mon, 31 Dec 2018 12:00:11 +1300

[+] Encoding sketch.

Diffstat:
Asrc/encode.rs | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/os_type.rs | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 167 insertions(+), 0 deletions(-)

diff --git a/src/encode.rs b/src/encode.rs @@ -0,0 +1,99 @@ +use std::cmp::max; +use std::io::{self, Write}; +use byteorder::{BigEndian, WriteBytesExt}; +use image::{self, DynamicImage, RgbaImage, GenericImageView, png::PNGEncoder}; +use image::imageops::{resize, Lanczos3}; + +use crate::os_type::OSType; + +/// Encoder encodes icns image into the provided writer. +pub struct Encoder<W: Write> { + w: W +} + +impl<W: Write> Encoder<W> { + /// Create a new encoder that writes to ```w```. + pub fn new(w: W) -> Self { + Encoder{w} + } + // Encode the icns from a source png encoded buffer. + pub fn encode(&mut self, img: &DynamicImage) -> io::Result<()> { + IconSet::from(img).write_to(self.w.by_ref()) + } +} + +/// IconSet encodes a vector of icons. +struct IconSet { + icons: Vec<Icon>, +} + +/// Magic bytes that denote an icns file. These bytes appear at index 0. +const ICONSET_MAGIC: [char; 4] = ['i', 'c', 'n', 's']; + +impl IconSet { + /// Create an IconSet from the provided image. + /// If width != height, the image will be resized using the largest side + /// without preserving the aspect ratio. + /// TODO: - Reject images smaller than 16x16 pixels. + /// - Parallelise the resizing via rayon. + fn from(img: &DynamicImage) -> Self { + let mut icons: Vec<Icon> = vec![]; + let kind = OSType::nearest(max(img.width(), img.height())); + for variant in kind.smaller_variants() { + let buffer = resize(img, variant.size(), variant.size(), Lanczos3); + let icon = Icon{ + kind: variant, + image: buffer, + }; + icons.push(icon); + } + IconSet { icons } + } + /// Write the encoded iconset to writer ```w```. + fn write_to(self, mut wr: impl Write) -> io::Result<()> { + // Pre-buffer the encoded icons so we can calculate the final size. + let mut buffer: Vec<u8> = vec![]; + for icon in self.icons { + icon.write_to(&mut buffer)?; + } + // Write the 4-byte magic bytes to identify this as an icns image. + wr.write_all(&ICONSET_MAGIC + .into_iter() + .map(|c| *c as u8) + .collect::<Vec<u8>>())?; + // Write the 4-byte container size in bytes. + wr.write_u32::<BigEndian>((buffer.len() + 8) as u32)?; + // Write the encoded icons. + wr.write_all(&buffer)?; + Ok(()) + } +} + +/// Icon encodes a single icon. +struct Icon { + kind: OSType, + image: RgbaImage, +} + +impl Icon { + /// Write the encoded icon to writer ```w```. + fn write_to(self, mut wr: impl Write) -> io::Result<()> { + let w = self.image.width(); + let h = self.image.height(); + let mut png_data: Vec<u8> = vec![]; + // Pre-buffer the png image so we can calculate size total. + PNGEncoder::new(&mut png_data) + .encode(self.image.into_raw().as_ref(), w, h, image::RGBA(8))?; + // Write the 4-byte OSType identifier. + // Coerce array of characters into slice of bytes through a vec deref. + wr.write_all(&self.kind.header() + .into_iter() + .map(|c| *c as u8) + .collect::<Vec<u8>>())?; + // Write the 4-byte icon size in bytes (data.len + header.len). + wr.write_u32::<BigEndian>((png_data.len() + 8) as u32)?; + // Write the image data. + wr.write_all(&png_data)?; + Ok(()) + } +} diff --git a/src/os_type.rs b/src/os_type.rs @@ -0,0 +1,67 @@ +/// OSType is an enum of various icon types that can exist inside an icns +/// container. This enum only contains the high resolution variants that we care +/// about. Find the full list here: https://en.wikipedia.org/wiki/Apple_Icon_Image_format +#[derive(Clone)] +pub enum OSType { + IC10, + IC14, + IC13, + IC07, + IC12, + IC11, +} + +impl OSType { + /// Returns the largest OSType for the given dimension. + pub fn nearest(d: u32) -> Self { + for variant in OSType::variants() { + if d >= variant.size(){ + return variant; + } + } + OSType::IC11 + } + /// Get a list of all OSType variants. + pub fn variants() -> Vec<OSType> { + vec![ + OSType::IC10, + OSType::IC14, + OSType::IC13, + OSType::IC07, + OSType::IC12, + OSType::IC11, + ] + } + /// Size in pixels. + pub fn size(&self) -> u32 { + match self { + OSType::IC10 => 1024, + OSType::IC14 => 512, + OSType::IC13 => 256, + OSType::IC07 => 128, + OSType::IC12 => 64, + OSType::IC11 => 32, + } + } + /// 4 byte header corresponding to the OSType. + pub fn header(&self) -> [char; 4] { + match self { + OSType::IC10 => ['i', 'c', '1', '0'], + OSType::IC14 => ['i', 'c', '1', '4'], + OSType::IC13 => ['i', 'c', '1', '3'], + OSType::IC07 => ['i', 'c', '0', '7'], + OSType::IC12 => ['i', 'c', '1', '2'], + OSType::IC11 => ['i', 'c', '1', '1'], + } + } + /// Get a list of all variants equal to or smaller than the current one. + pub fn smaller_variants(&self) -> Vec<OSType> { + let variants = OSType::variants(); + for (ii, v) in variants.iter().enumerate() { + if v.size() <= self.size() { + return variants[ii..].to_vec(); + } + } + variants + } +} +\ No newline at end of file