Reduce a favicon’s footprint with ICO's PNG support

ICO remains a popular format for favicons. Historically images within an ICO were bitmaps, but the format has long supported PNG too. Since it makes for smaller filesizes, I figured ImageMagick would make a PNG-based ICO if fed only PNG sources:

convert 16x16.png 32x32.png 48x48.png favicon.ico

Output size: 15 KB 🙀 – almost 8× my sources. Icon Slate yields similar: 18 KB. Both tools are bundling bitmaps into the ICO rather than the source PNGs.

I thrashed around looking for an open source lib or tool that reliably packages an ICO of PNG images but my luck was bad enough I began to wonder if I’d made up the whole thing about ICO serving as a PNG container.

I hadn’t, but I reflexively turned to Wikipedia where the ICO format is so well documented (and the format itself so straightforward) that I decided to write a small Node library dedicated to the task of packaging PNG images into an ICO. Here’s how I use it:

const fs = require('fs');
const pack = require('ico-packer');

fs.writeFileSync('favicon.ico', pack([
  fs.readFileSync('16x16.png'),
  fs.readFileSync('32x32.png'),
  fs.readFileSync('48x48.png'),
]));

Output size: 2 KB 😽

This isn’t a new trick – it’s baked into the format and several “favicon generator” websites have been doing it for years – but if anyone else has the same trouble finding a working tool then hopefully this strikes the right keywords to help you out. ❤️

ico-packer is available on npm.

Questions/corrections? Reach out!