commit dc186d347eea7a5fc8d52a169382613709544cb0
parent 5747b8516f66191d2edf98084f76949ee63af76e
Author: Jack Mordaunt <jackmordaunt@gmail.com>
Date: Mon, 31 Dec 2018 12:00:11 +1300
[+] Encoding sketch.
Diffstat:
| A | src/encode.rs | | | 99 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/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