mod packbits;
use anyhow::{Context, Result};
use packbits::rle_encode_channel;
use resvg::{tiny_skia, usvg};
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::io::Write;
use std::path::Path;
use crate::{optimize, render};
const MAGIC: &[u8; 4] = b"icns";
const ARGB_MAGIC: &[u8; 4] = b"ARGB";
struct IconSpec {
ostype: &'static [u8; 4],
px: u32,
argb: bool,
}
const SPECS: &[IconSpec] = &[
IconSpec {
ostype: b"ic04",
px: 16,
argb: true,
}, IconSpec {
ostype: b"ic05",
px: 32,
argb: true,
}, IconSpec {
ostype: b"ic11",
px: 32,
argb: false,
}, IconSpec {
ostype: b"icp6",
px: 48,
argb: false,
}, IconSpec {
ostype: b"ic12",
px: 64,
argb: false,
}, IconSpec {
ostype: b"ic07",
px: 128,
argb: false,
}, IconSpec {
ostype: b"ic13",
px: 256,
argb: false,
}, IconSpec {
ostype: b"ic08",
px: 256,
argb: false,
}, IconSpec {
ostype: b"ic14",
px: 512,
argb: false,
}, IconSpec {
ostype: b"ic09",
px: 512,
argb: false,
}, IconSpec {
ostype: b"ic10",
px: 1024,
argb: false,
}, ];
pub fn write_icns(tree: &usvg::Tree, opt: optimize::Opts, out: &Path) -> Result<()> {
let mut pixmap_cache: HashMap<u32, tiny_skia::Pixmap> = HashMap::new();
let mut png_cache: HashMap<u32, Vec<u8>> = HashMap::new();
let mut chunks: Vec<u8> = Vec::new();
for spec in SPECS {
let pixmap = match pixmap_cache.entry(spec.px) {
Entry::Occupied(e) => e.into_mut(),
Entry::Vacant(e) => e.insert(render::render_size(tree, spec.px, spec.px)?),
};
if spec.argb {
let data = encode_argb_chunk(pixmap);
write_chunk(&mut chunks, *spec.ostype, &data);
} else {
let png = if let Some(cached) = png_cache.get(&spec.px) {
cached.clone()
} else {
let raw = render::encode_png(pixmap)?;
let optimized = optimize::maybe_optimize(raw, opt)?;
png_cache.insert(spec.px, optimized.clone());
optimized
};
write_chunk(&mut chunks, *spec.ostype, &png);
}
}
let total = u32::try_from(8 + chunks.len()).context("icns file exceeds 4 GiB")?;
let mut file =
std::fs::File::create(out).with_context(|| format!("creating {}", out.display()))?;
file.write_all(MAGIC)?;
file.write_all(&total.to_be_bytes())?;
file.write_all(&chunks)?;
Ok(())
}
fn write_chunk(buf: &mut Vec<u8>, ostype: [u8; 4], data: &[u8]) {
let size = u32::try_from(8 + data.len()).expect("icns chunk exceeds 4 GiB");
buf.extend_from_slice(&ostype);
buf.extend_from_slice(&size.to_be_bytes());
buf.extend_from_slice(data);
}
fn encode_argb_chunk(pixmap: &tiny_skia::Pixmap) -> Vec<u8> {
let count = (pixmap.width() * pixmap.height()) as usize;
let (mut a, mut r, mut g, mut b) = (
Vec::with_capacity(count),
Vec::with_capacity(count),
Vec::with_capacity(count),
Vec::with_capacity(count),
);
for px in pixmap.pixels() {
let c = px.demultiply();
a.push(c.alpha());
r.push(c.red());
g.push(c.green());
b.push(c.blue());
}
let mut data = Vec::new();
data.extend_from_slice(ARGB_MAGIC);
data.extend_from_slice(&rle_encode_channel(&a));
data.extend_from_slice(&rle_encode_channel(&r));
data.extend_from_slice(&rle_encode_channel(&g));
data.extend_from_slice(&rle_encode_channel(&b));
data
}