const PNG_SIGNATURE: &[u8; 8] = b"\x89PNG\r\n\x1a\n";
pub fn png_size(png: &[u8]) -> Option<(u32, u32)> {
if png.len() < 24 || &png[..8] != PNG_SIGNATURE || &png[12..16] != b"IHDR" {
return None;
}
let width = u32::from_be_bytes(png[16..20].try_into().ok()?);
let height = u32::from_be_bytes(png[20..24].try_into().ok()?);
Some((width, height))
}
pub fn png_to_ico(png: &[u8]) -> Result<Vec<u8>, String> {
let (width, height) = png_size(png).ok_or("the icon is not a PNG image")?;
if width > 256 || height > 256 {
return Err(format!(
"a PNG icon may be at most 256 × 256 pixels for Windows, this one is {} × {}",
width, height
));
}
let size = u32::try_from(png.len()).map_err(|_| "the icon is too large")?;
let mut ico = Vec::with_capacity(22 + png.len());
ico.extend_from_slice(&[0, 0, 1, 0, 1, 0]); ico.push(width as u8);
ico.push(height as u8);
ico.extend_from_slice(&[0, 0]); ico.extend_from_slice(&1u16.to_le_bytes()); ico.extend_from_slice(&32u16.to_le_bytes()); ico.extend_from_slice(&size.to_le_bytes());
ico.extend_from_slice(&22u32.to_le_bytes()); ico.extend_from_slice(png);
Ok(ico)
}
pub fn png_from_ico(ico: &[u8]) -> Option<Vec<u8>> {
if ico.len() < 6 || ico[..4] != [0, 0, 1, 0] {
return None;
}
let count = u16::from_le_bytes([ico[4], ico[5]]) as usize;
let mut best: Option<(u32, &[u8])> = None;
for index in 0..count {
let entry = ico.get(6 + index * 16..6 + index * 16 + 16)?;
let size = u32::from_le_bytes(entry[8..12].try_into().ok()?) as usize;
let offset = u32::from_le_bytes(entry[12..16].try_into().ok()?) as usize;
let image = ico.get(offset..offset.checked_add(size)?)?;
if let Some((width, _)) = png_size(image)
&& best.is_none_or(|(w, _)| width > w)
{
best = Some((width, image));
}
}
best.map(|(_, image)| image.to_vec())
}
pub fn png_to_icns(png: &[u8]) -> Result<Vec<u8>, String> {
let (width, height) = png_size(png).ok_or("the icon is not a PNG image")?;
let kind: &[u8; 4] = match (width, height) {
(16, 16) => b"icp4",
(32, 32) => b"icp5",
(64, 64) => b"icp6",
(128, 128) => b"ic07",
(256, 256) => b"ic08",
(512, 512) => b"ic09",
(1024, 1024) => b"ic10",
_ => {
return Err(format!(
"a macOS icon must be a square of 16, 32, 64, 128, 256, 512 or 1024 pixels, this one is {} × {}",
width, height
));
}
};
let entry_len = u32::try_from(png.len() + 8).map_err(|_| "the icon is too large")?;
let total = entry_len.checked_add(8).ok_or("the icon is too large")?;
let mut icns = Vec::with_capacity(total as usize);
icns.extend_from_slice(b"icns");
icns.extend_from_slice(&total.to_be_bytes());
icns.extend_from_slice(kind);
icns.extend_from_slice(&entry_len.to_be_bytes());
icns.extend_from_slice(png);
Ok(icns)
}