Created
December 19, 2019 02:52
-
-
Save heyimalex/92a4f056a5de5106e5c026bf6714a546 to your computer and use it in GitHub Desktop.
Combine a bunch of pngs into an ico file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::{env, fs, process}; | |
use std::error::Error; | |
use std::convert::{TryFrom,TryInto}; | |
use std::io::{Write,BufWriter}; | |
static HELP_TEXT: &str = "ico-join [<PNG>...] <DEST> | |
<PNG> Path to png files you want to combine. | |
<DEST> Path to the output file. | |
"; | |
fn main() -> Result<(), Box<dyn Error>> { | |
let mut args: Vec<String> = env::args().collect(); | |
if args.len() < 3 || args.iter().find(|arg| arg.starts_with('-')).is_some() { | |
println!("{}", HELP_TEXT); | |
process::exit(1); | |
} | |
let dest = args.pop().unwrap(); | |
let srcs = &args[1..]; | |
let mut pngs: Vec<(Png, Vec<u8>)> = Vec::with_capacity(srcs.len()); | |
for path in srcs { | |
let data = fs::read(path).expect("failed to read input png"); | |
let header = parse_png(&data).expect("failed to parse input png"); | |
if header.width > 256 || header.width > 256 { | |
println!("input image width/height must be below 256px, was {}x{}", header.width, header.height); | |
process::exit(1); | |
} | |
pngs.push((header, data)); | |
} | |
let dest = fs::File::create(dest)?; | |
let mut w = BufWriter::new(dest); | |
w.write_all(&0u16.to_le_bytes())?; | |
w.write_all(&1u16.to_le_bytes())?; | |
w.write_all(&u16::try_from(srcs.len())?.to_le_bytes())?; | |
let mut icondir = vec![0u8; 16]; | |
let mut offset: u32 = (6 + pngs.len()*16).try_into()?; | |
for png in &pngs { | |
let header = &png.0; | |
let data_size = u32::try_from(png.1.len())?; | |
let width: u8 = if header.width == 256 { | |
0 | |
} else { | |
header.width.try_into()? | |
}; | |
let height: u8 = if header.height == 256 { | |
0 | |
} else { | |
header.height.try_into()? | |
}; | |
icondir[0] = width; // width | |
icondir[1] = height; // height | |
icondir[2] = 0; // colors in palette (optional for pngs??) | |
icondir[3] = 0; // reserved | |
icondir[4] = 0; // color planes | |
icondir[5] = 0; // color planes pt 2 | |
icondir[6] = 0; // bits per pixel (optional for pngs??) | |
icondir[7] = 0; // bits per pixel pt 2 | |
icondir[8..12].copy_from_slice(&data_size.to_le_bytes()); | |
icondir[12..16].copy_from_slice(&offset.to_le_bytes()); | |
offset += data_size; | |
w.write_all(&icondir)?; | |
} | |
for png in &pngs { | |
w.write_all(&png.1)?; | |
} | |
w.flush()?; | |
println!("Done!"); | |
Ok(()) | |
} | |
#[derive(Debug, Copy, Clone)] | |
pub struct Png { | |
pub width: u32, | |
pub height: u32, | |
pub color_type: ColorType, | |
pub bit_depth: u8, | |
} | |
#[derive(Debug, Copy, Clone)] | |
pub enum ColorType { | |
Gray, | |
RGB, | |
PLTE, | |
GrayAlpha, | |
RGBA, | |
} | |
impl TryFrom<u8> for ColorType { | |
type Error = String; | |
fn try_from(value: u8) -> Result<Self, Self::Error> { | |
match value { | |
0 => Ok(ColorType::Gray), | |
2 => Ok(ColorType::RGB), | |
3 => Ok(ColorType::PLTE), | |
4 => Ok(ColorType::GrayAlpha), | |
6 => Ok(ColorType::RGBA), | |
_ => Err(format!("Color type {} is not valid", value)), | |
} | |
} | |
} | |
// PNG signature, plus the initial IHDR chunk header. | |
const PNG_SIGNATURE: [u8; 16] = [137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, b'I', b'H', b'D', b'R']; | |
fn parse_png(data: &[u8]) -> Result<Png, Box<dyn Error>> { | |
if data.len() < 29 { | |
return Err("not long enough to be a valid png".into()); | |
} | |
// Check for the signature, plus the beginning of the IHDR chunk, which | |
// should be the first chunk in any valid png. | |
if data[0..16] != PNG_SIGNATURE { | |
return Err("png had invalid signature".into()); | |
} | |
let width = u32::from_be_bytes(data[16..20].try_into()?); | |
let height = u32::from_be_bytes(data[20..24].try_into()?); | |
let bit_depth = data[24]; | |
let color_type = ColorType::try_from(data[25])?; | |
let png = Png{ | |
width, | |
height, | |
bit_depth, | |
color_type | |
}; | |
Ok(png) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment