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 }