decode.rs (5298B)
1 use crate::os_type::OSType; 2 use byteorder::{BigEndian, ReadBytesExt}; 3 use image::{self, DynamicImage}; 4 use std::error::Error; 5 use std::io::{self, Read}; 6 7 /// Decoder decodes the largest image from an icns image. 8 pub struct Decoder<R: Read> { 9 r: R, 10 } 11 12 impl<R: Read> Decoder<R> { 13 /// Create a new Decoder that reads from `r`. 14 pub fn new(r: R) -> Self { 15 Decoder { r } 16 } 17 /// Decode icns from reader into image buffer. 18 /// Picks the largest image in the icns container. 19 /// 20 // This algorithm simply reads off the byte stream `r`, expecting 21 // data to come in a certain order. 22 // 23 // If the data is not in the expected order, we return an error. 24 // ICNS is a container format that generally contains differently sized 25 // png images, and some other bits and pieces like a table of contents. 26 // 27 // Each image is preceeded by an 8 byte header containing 2 data points: 28 // 1. 4 byte ascii ID indicating the OS_Type. 29 // 2. 4 byte unsigned integer (u32) that contains the size of the png 30 // content including the header. 31 // 32 // Once we've parsed out all the icon data, we return the largest available 33 // image. 34 pub fn decode(&mut self) -> Result<DynamicImage, Box<dyn Error>> { 35 // Note(jfm): 4 bytes is the width of the headers and the size integers, 36 // hence this buffer is 4 bytes long. 37 let mut buffer: [u8; 4] = [0; 4]; 38 self.r.read_exact(&mut buffer)?; 39 40 // Check the header. 41 let header = std::str::from_utf8(&buffer) 42 .map_err(|e| format!("parsing header as utf8 string: {:?}", e))?; 43 if header != "icns" { 44 return Err(format!("invalid header for icns file: got {}", header).into()); 45 } 46 47 let _size = self.r.read_u32::<BigEndian>()?; 48 let mut icons: Vec<IconReader> = Vec::new(); 49 50 loop { 51 if let Err(err) = self.r.read_exact(&mut buffer) { 52 if err.kind() == io::ErrorKind::UnexpectedEof { 53 break; 54 } else { 55 return Err(err.into()); 56 } 57 }; 58 match std::str::from_utf8(&buffer) 59 .map_err(|e| format!("parsing chunk as utf8 string: {:?}", e)) 60 { 61 Ok("TOC ") => { 62 // Note(jfm): Advance the reader to skip over the TOC. 63 // TODO(jfm): Could we use TOC to jump to the PNG we care about? 64 let toc_size = self.r.read_u32::<BigEndian>()?; 65 self.r 66 .read_exact(&mut Vec::with_capacity(toc_size as usize))?; 67 continue; 68 } 69 Ok("icnV") => continue, 70 Ok(next) => { 71 if let Ok(os_type) = next.parse::<OSType>() { 72 let data_size = self.r.read_u32::<BigEndian>()?; 73 if data_size == 0 { 74 continue; 75 } 76 let mut data: Vec<u8> = vec![0; (data_size as usize) - 8]; 77 self.r 78 .read_exact(&mut data) 79 .map_err(|e| format!("reading into data buffer: {}", e))?; 80 assert!( 81 data.len() > 0, 82 "data buffer should not be empty after reading" 83 ); 84 icons.push(IconReader { os_type, data }); 85 } 86 } 87 Err(_) => continue, 88 }; 89 } 90 91 if icons.is_empty() { 92 return Err("no icons found".into()); 93 } 94 95 let largest = icons 96 .into_iter() 97 .fold(None, |mut largest: Option<IconReader>, next| { 98 if let Some(l) = largest.as_ref() { 99 if next.os_type.size() > l.os_type.size() { 100 largest.replace(next); 101 } 102 } else { 103 largest = Some(next); 104 } 105 largest 106 }); 107 if let Some(icon) = largest { 108 Ok(image::load_from_memory(&icon.data.as_slice()) 109 .map_err(|e| format!("loading image from memory: {}", e))?) 110 } else { 111 Err("no icons found".into()) 112 } 113 } 114 } 115 116 // IconReader assosciates os_type with icon data. 117 struct IconReader { 118 os_type: OSType, 119 data: Vec<u8>, 120 } 121 122 #[cfg(test)] 123 mod tests { 124 use super::*; 125 use crate::Encoder; 126 use std::error::Error; 127 128 #[test] 129 fn codec_symmetry() -> Result<(), Box<dyn Error>> { 130 let input_img = image::RgbaImage::new(64, 64); 131 let mut icns_buffer: Vec<u8> = vec![]; 132 Encoder::new(&mut icns_buffer) 133 .encode(&input_img) 134 .map_err(|e| format!("encoding icns: {}", e))?; 135 let got_img = Decoder::new(icns_buffer.as_slice()) 136 .decode() 137 .map_err(|e| format!("decoding icns: {}", e))? 138 .into_rgba(); 139 let (l, r) = (input_img.dimensions(), got_img.dimensions()); 140 assert_eq!(l, r, "dimension mismatch: {:?} != {:?}", l, r); 141 assert_eq!(input_img.into_vec(), got_img.into_vec(), "buffer mismatch"); 142 Ok(()) 143 } 144 }