forked from Yasushi/putty
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New script to generate OS X icon files.
The Xcode icon composer doesn't seem to exist any more in modern versions of Xcode, or at least if it does then it's well hidden and certainly doesn't live at the top-level path at /Developer where web pages still claim it can be found. There is a free software 'libicns' and associated command-line tools, but they're large, complicated, picky about the exact format of PNGs they get as input, and in any case a needless extra build dependency when it turns out the important parts of the file format can be done in a few dozen lines of Python. So here's a new macicon.py, and icons/Makefile additions to build a demo icon for OS X PuTTY, as and when I finally get it working. Also I've deleted the static icon file in the neglected 'macosx' source directory, because this one is better anyway - the old one was appalling quality, and must have been autogenerated from a single image in some way.
- Loading branch information
Showing
5 changed files
with
167 additions
and
4 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,6 +80,7 @@ | |
/doc/*.hhk | ||
/icons/*.png | ||
/icons/*.ico | ||
/icons/*.icns | ||
/icons/*.xpm | ||
/icons/*.c | ||
/macosx/Makefile | ||
|
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
#!/usr/bin/env python | ||
|
||
# Generate Mac OS X .icns files, or at least the simple subformats | ||
# that don't involve JPEG encoding and the like. | ||
# | ||
# Sources: https://en.wikipedia.org/wiki/Apple_Icon_Image_format and | ||
# some details implicitly documented by the source code of 'libicns'. | ||
|
||
import sys | ||
import struct | ||
import subprocess | ||
|
||
# The file format has a typical IFF-style (type, length, data) chunk | ||
# structure, with one outer chunk containing subchunks for various | ||
# different icon sizes and formats. | ||
def make_chunk(chunkid, data): | ||
assert len(chunkid) == 4 | ||
return chunkid + struct.pack(">I", len(data) + 8) + data | ||
|
||
# Monochrome icons: a single chunk containing a 1 bpp image followed | ||
# by a 1 bpp transparency mask. Both uncompressed, unless you count | ||
# packing the bits into bytes. | ||
def make_mono_icon(size, rgba): | ||
assert len(rgba) == size * size | ||
|
||
# We assume our input image was monochrome, so that the R,G,B | ||
# channels are all the same; we want the image and then the mask, | ||
# so we take the R channel followed by the alpha channel. However, | ||
# we have to flip the former, because in the output format the | ||
# image has 0=white and 1=black, while the mask has 0=transparent | ||
# and 1=opaque. | ||
pixels = [rgba[index][chan] ^ flip for (chan, flip) in [(0,0xFF),(3,0)] | ||
for index in range(len(rgba))] | ||
|
||
# Encode in 1-bit big-endian format. | ||
data = "" | ||
for i in range(0, len(pixels), 8): | ||
byte = 0 | ||
for j in range(8): | ||
if pixels[i+j] >= 0x80: | ||
byte |= 0x80 >> j | ||
data += chr(byte) | ||
|
||
# This size-32 chunk id is an anomaly in what would otherwise be a | ||
# consistent system of using {s,l,h,t} for {16,32,48,128}-pixel | ||
# icon sizes. | ||
chunkid = { 16: "ics#", 32: "ICN#", 48: "ich#" }[size] | ||
return make_chunk(chunkid, data) | ||
|
||
# Mask for full-colour icons: a chunk containing an 8 bpp alpha | ||
# bitmap, uncompressed. The RGB data appears in a separate chunk. | ||
def make_colour_mask(size, rgba): | ||
assert len(rgba) == size * size | ||
|
||
data = "".join(map(lambda pix: chr(pix[3]), rgba)) | ||
|
||
chunkid = { 16: "s8mk", 32: "l8mk", 48: "h8mk", 128: "t8mk" }[size] | ||
return make_chunk(chunkid, data) | ||
|
||
# Helper routine for deciding when to start and stop run-length | ||
# encoding. | ||
def runof3(string, position): | ||
return (position < len(string) and | ||
string[position:position+3] == string[position] * 3) | ||
|
||
# RGB data for full-colour icons: a chunk containing 8 bpp red, green | ||
# and blue images, each run-length encoded (see comment inside the | ||
# function), and then concatenated. | ||
def make_colour_icon(size, rgba): | ||
assert len(rgba) == size * size | ||
|
||
data = "" | ||
|
||
# Mysterious extra zero header word appearing only in the size-128 | ||
# icon chunk. libicns doesn't know what it's for, and neither do | ||
# I. | ||
if size == 128: | ||
data += "\0\0\0\0" | ||
|
||
# Handle R,G,B channels in sequence. (Ignore the alpha channel; it | ||
# goes into the separate mask chunk constructed above.) | ||
for chan in range(3): | ||
pixels = "".join([chr(rgba[index][chan]) | ||
for index in range(len(rgba))]) | ||
|
||
# Run-length encode each channel using the following format: | ||
# * byte 0x80-0xFF followed by one literal byte means repeat | ||
# that byte 3-130 times | ||
# * byte 0x00-0x7F followed by n+1 literal bytes means emit | ||
# those bytes once each. | ||
pos = 0 | ||
while pos < len(pixels): | ||
start = pos | ||
if runof3(pixels, start): | ||
pos += 3 | ||
pixval = pixels[start] | ||
while (pos - start < 130 and | ||
pos < len(pixels) and | ||
pixels[pos] == pixval): | ||
pos += 1 | ||
data += chr(0x80 + pos-start - 3) + pixval | ||
else: | ||
while (pos - start < 128 and | ||
pos < len(pixels) and | ||
not runof3(pixels, pos)): | ||
pos += 1 | ||
data += chr(0x00 + pos-start - 1) + pixels[start:pos] | ||
|
||
chunkid = { 16: "is32", 32: "il32", 48: "ih32", 128: "it32" }[size] | ||
return make_chunk(chunkid, data) | ||
|
||
# Load an image file from disk and turn it into a simple list of | ||
# 4-tuples giving 8-bit R,G,B,A values for each pixel. | ||
# | ||
# My icon-building makefile already depends on ImageMagick, so I use | ||
# identify and convert here in place of more sensible Python libraries | ||
# so as to add no build dependency that wasn't already needed. | ||
def load_rgba(filename): | ||
size = subprocess.check_output(["identify", "-format", "%wx%h", filename]) | ||
width, height = map(int, size.split("x")) | ||
assert width == height | ||
data = subprocess.check_output(["convert", "-depth", "8", | ||
filename, "rgba:-"]) | ||
assert len(data) == width*height*4 | ||
rgba = [map(ord, data[i:i+4]) for i in range(0, len(data), 4)] | ||
return width, rgba | ||
|
||
data = "" | ||
|
||
# Trivial argument format: each argument is a filename prefixed with | ||
# "mono:", "colour:" or "output:". The first two indicate image files | ||
# to use as part of the icon, and the last gives the output file name. | ||
# Icon subformat chunks are written out in the order of the arguments. | ||
for arg in sys.argv[1:]: | ||
kind, filename = arg.split(":", 2) | ||
if kind == "output": | ||
outfile = filename | ||
else: | ||
size, rgba = load_rgba(filename) | ||
if kind == "mono": | ||
data += make_mono_icon(size, rgba) | ||
elif kind == "colour": | ||
data += make_colour_icon(size, rgba) + make_colour_mask(size, rgba) | ||
else: | ||
assert False, "bad argument '%s'" % arg | ||
|
||
data = make_chunk("icns", data) | ||
|
||
with open(outfile, "w") as f: | ||
f.write(data) |
Binary file not shown.
This file contains 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