Skip to content

Instantly share code, notes, and snippets.

@vslavik
Created April 10, 2024 18:10
Show Gist options
  • Save vslavik/6ca955b60d09a92bd39a28b2187fb0f9 to your computer and use it in GitHub Desktop.
Save vslavik/6ca955b60d09a92bd39a28b2187fb0f9 to your computer and use it in GitHub Desktop.
Script to create ICO files from PNGs while preserving compresison
#!/usr/bin/env python3
#
# Create an .ico file from a bunch of PNGs.
# Unlike ImageMagick's convert, this does not convert PNGs into bitmaps or
# otherwise modify the source PNGs; in particular, their compression is
# preserved.
#
# ICO format is actually very simple:
# https://en.wikipedia.org/wiki/ICO_(file_format)
#
# Embedded PNGs as the only content are supported since Windows Vista.
#
import argparse
import struct
import io
import PIL.Image
ap = argparse.ArgumentParser()
ap.add_argument('inputs', nargs='+')
ap.add_argument('-o', '--output', required=True)
args = ap.parse_args()
png_data = []
for path in args.inputs:
with open(path, 'rb') as fp:
png_data.append(fp.read())
icons = []
for data in png_data:
with PIL.Image.open(io.BytesIO(data)) as im:
w, h = im.size
icons.append((w, h, data))
icons.sort()
with open(args.output, 'wb') as f:
f.truncate()
f.write(struct.pack('<HHH', 0, 1, len(icons)))
offset = 6 + 16 * len(png_data)
for w, h, data in icons:
if w > 255:
w = 0
if h > 255:
h = 0
f.write(struct.pack('<BBBBHHII', w, h, 0, 0, 1, 0, len(data), offset))
offset += len(data)
for _, _, data in icons:
f.write(data)
@vslavik
Copy link
Author

vslavik commented Apr 11, 2024

Inspecting what the ICO file contains:

$ magick identify Poedit.ico
Poedit.ico[0] PNG 16x16 16x16+0+0 8-bit sRGB 372B 0.000u 0:00.000
Poedit.ico[1] PNG 24x24 24x24+0+0 8-bit sRGB 624B 0.000u 0:00.000
Poedit.ico[2] PNG 32x32 32x32+0+0 8-bit sRGB 932B 0.000u 0:00.000
Poedit.ico[3] PNG 48x48 48x48+0+0 8-bit sRGB 1534B 0.000u 0:00.000
Poedit.ico[4] PNG 64x64 64x64+0+0 8-bit sRGB 3148B 0.000u 0:00.000
Poedit.ico[5] PNG 256x256 256x256+0+0 8-bit sRGB 25619B 0.000u 0:00.000

$ magick identify FilePO.ico 
FilePO.ico[0] ICO 16x16 16x16+0+0 8-bit sRGB 0.000u 0:00.022
FilePO.ico[1] ICO 32x32 32x32+0+0 8-bit sRGB 0.000u 0:00.022
FilePO.ico[2] ICO 48x48 48x48+0+0 8-bit sRGB 0.000u 0:00.022
FilePO.ico[1] PNG 256x256 256x256+0+0 8-bit sRGB 31246B 0.000u 0:00.001

Extracting all sub-images from ICO file:

$ magick convert -set filename:f "%t-%wpx" "ICO:FilePO.ico" "%[filename:f].png"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment