icns-rs

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

encode.rs (4505B)


      1 use byteorder::{BigEndian, WriteBytesExt};
      2 use image::imageops::{resize, Lanczos3};
      3 use image::{self, DynamicImage, GenericImageView, RgbaImage};
      4 use rayon::{self, prelude::*};
      5 use std::cmp::max;
      6 use std::io::Write;
      7 
      8 use crate::os_type::OSType;
      9 
     10 /// Encoder encodes icns image into the provided writer.
     11 pub struct Encoder<W: Write> {
     12     w: W,
     13 }
     14 
     15 impl<W: Write> Encoder<W> {
     16     /// Create a new encoder that writes to `w`.
     17     pub fn new(w: W) -> Self {
     18         Encoder { w }
     19     }
     20     /// Encode encode an icns into the writer, using the source image.
     21     pub fn encode<Img>(&mut self, img: Img) -> Result<(), Box<dyn std::error::Error>>
     22     where
     23         Img: Into<IconSet>,
     24     {
     25         // Note(jfm): CPU intensive work is being done in `From` trait.
     26         // This is probably not good practice since it hides the actual work.
     27         img.into().write_to(self.w.by_ref())
     28     }
     29 }
     30 
     31 /// IconSet encodes a vector of icons.
     32 pub struct IconSet {
     33     icons: Vec<Icon>,
     34 }
     35 
     36 /// Magic bytes that denote an icns file. These bytes appear at index 0.
     37 const ICONSET_MAGIC: &'static str = "icns";
     38 
     39 impl IconSet {
     40     /// Write the encoded iconset to writer `w`.
     41     pub fn write_to(self, mut wr: impl Write) -> Result<(), Box<dyn std::error::Error>> {
     42         // Pre-buffer the encoded icons so we can calculate the final size.
     43         let mut buffer: Vec<u8> = vec![];
     44         for icon in self.icons {
     45             icon.write_to(&mut buffer)?;
     46         }
     47         // Write the 4-byte magic bytes to identify this as an icns image.
     48         wr.write_all(ICONSET_MAGIC.as_bytes())?;
     49         // Write the 4-byte container size in bytes.
     50         wr.write_u32::<BigEndian>((buffer.len() + 8) as u32)?;
     51         // Write the encoded icons.
     52         wr.write_all(&buffer)?;
     53         Ok(())
     54     }
     55 }
     56 
     57 /// Icon encodes a single icon.
     58 struct Icon {
     59     kind: OSType,
     60     image: RgbaImage,
     61 }
     62 
     63 impl Icon {
     64     /// Write the encoded icon to writer `w`.
     65     fn write_to(self, mut wr: impl Write) -> Result<(), Box<dyn std::error::Error>> {
     66         // Pre-buffer the png image so we can calculate size total.
     67         let (width, height) = (self.image.width(), self.image.height());
     68         let mut buffer: Vec<u8> = vec![];
     69         PNGEncoder::new(&mut buffer).encode(
     70             self.image.into_raw().as_ref(),
     71             width,
     72             height,
     73             image::ColorType::Rgba8,
     74         )?;
     75         // Write the 4-byte OSType identifier.
     76         wr.write_all(&self.kind.header().as_bytes())?;
     77         // Write the 4-byte icon size in bytes (data.len + header.len).
     78         wr.write_u32::<BigEndian>((buffer.len() + 8) as u32)?;
     79         // Write the image data.
     80         wr.write_all(&buffer)?;
     81         Ok(())
     82     }
     83 }
     84 
     85 /// PNGEncoder is a convenience wrapper around `png::Encoder`.
     86 struct PNGEncoder<W: Write> {
     87     w: W,
     88 }
     89 
     90 impl<W: Write> PNGEncoder<W> {
     91     fn new(w: W) -> Self {
     92         PNGEncoder { w }
     93     }
     94     fn encode(
     95         self,
     96         data: &[u8],
     97         width: u32,
     98         height: u32,
     99         ct: image::ColorType,
    100     ) -> Result<(), Box<dyn std::error::Error>> {
    101         image::png::PNGEncoder::new(self.w).encode(data, width, height, ct)?;
    102         Ok(())
    103     }
    104 }
    105 
    106 /// Create an IconSet from the provided image.
    107 /// If width != height, the image will be resized using the largest side
    108 /// without preserving the aspect ratio.
    109 impl From<&DynamicImage> for IconSet {
    110     fn from(img: &DynamicImage) -> Self {
    111         let kind = OSType::nearest(max(img.width(), img.height()));
    112         let icons: Vec<Icon> = kind
    113             .smaller_variants()
    114             .into_par_iter()
    115             .map(|v| {
    116                 let size = v.size();
    117                 Icon {
    118                     kind: v,
    119                     image: resize(img, size, size, Lanczos3),
    120                 }
    121             })
    122             .collect();
    123         IconSet { icons }
    124     }
    125 }
    126 
    127 /// Create an IconSet from the provided image.
    128 /// If width != height, the image will be resized using the largest side
    129 /// without preserving the aspect ratio.
    130 impl From<&RgbaImage> for IconSet {
    131     fn from(img: &RgbaImage) -> Self {
    132         let kind = OSType::nearest(max(img.width(), img.height()));
    133         let icons: Vec<Icon> = kind
    134             .smaller_variants()
    135             .into_par_iter()
    136             .map(|v| {
    137                 let size = v.size();
    138                 Icon {
    139                     kind: v,
    140                     image: resize(img, size, size, Lanczos3),
    141                 }
    142             })
    143             .collect();
    144         IconSet { icons }
    145     }
    146 }