From 450a995f05a7dfbf75d7e0913accf1af2ee9d165 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 23 Mar 2016 06:41:27 +0000 Subject: [PATCH] Use NetPBM .pam as an intermediate format in the icon makefile. mkicon.py now outputs .pam by hand, rather than using ImageMagick to go straight to .png. For most purposes the main makefile then uses ImageMagick anyway, to convert those .pams straight to the .pngs that the rest of the scripts were expecting. But one script that doesn't do that is macicon.py, which builds the MacOS .icns file by directly reading those .pam files back in. This allows the 'make icns' target in the icons directory to build from a clean checkout on vanilla MacOS, without requiring a user to install ImageMagick or any other non-core Python image handling module. (I could probably take this change at least a little bit further. I don't see any reason why icon.pl - generating the Windows .ico files - couldn't read the .pam files directly, about as easily as macicon.py did, if anyone had a use case for building the Windows icons in the presence of Python and Perl but in the absence of ImageMagick. But the .png files are directly useful outputs for Unix, so _some_ PNG-writing will have to remain here.) --- .gitignore | 1 + icons/Makefile | 37 ++++++++++++++++++++--------------- icons/macicon.py | 35 ++++++++++++++++++++++----------- icons/mkicon.py | 50 +++++++++++++++++++++++++----------------------- 4 files changed, 73 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 200dc4c3..05f52c67 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,7 @@ /doc/*.hhk /doc/licence.but /doc/copy.but +/icons/*.pam /icons/*.png /icons/*.ico /icons/*.icns diff --git a/icons/Makefile b/icons/Makefile index 5e845b27..c5d1fde9 100644 --- a/icons/Makefile +++ b/icons/Makefile @@ -5,9 +5,13 @@ SIZES = 16 32 48 128 MODE = # override to -it on command line for opaque testing -PNGS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S).png)) -MONOPNGS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S)-mono.png)) -TRUEPNGS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S)-true.png)) +PAMS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S).pam)) +MONOPAMS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S)-mono.pam)) +TRUEPAMS = $(foreach I,$(ICONS),$(foreach S,$(SIZES),$(I)-$(S)-true.pam)) + +PNGS = $(patsubst %.pam,%.png,$(PAMS)) +MONOPNGS = $(patsubst %.pam,%.png,$(MONOPAMS)) +TRUEPNGS = $(patsubst %.pam,%.png,$(TRUEPAMS)) ICOS = putty.ico puttygen.ico pscp.ico pageant.ico pageants.ico puttycfg.ico \ puttyins.ico @@ -30,13 +34,16 @@ install: icos cicons cp $(ICOS) ../windows cp $(CICONS) ../unix -$(PNGS): %.png: mkicon.py +$(PAMS): %.pam: mkicon.py ./mkicon.py $(MODE) $(join $(subst -, ,$(basename $@)),_icon) $@ -$(MONOPNGS): %.png: mkicon.py +$(PNGS) $(MONOPNGS) $(TRUEPNGS): %.png: %.pam + convert $< $@ + +$(MONOPAMS): %.pam: mkicon.py ./mkicon.py -2 $(MODE) $(join $(subst -, ,$(subst -mono,,$(basename $@))),_icon) $@ -$(TRUEPNGS): %.png: mkicon.py +$(TRUEPAMS): %.pam: mkicon.py ./mkicon.py -T $(MODE) $(join $(subst -, ,$(subst -true,,$(basename $@))),_icon) $@ putty.ico: putty-16.png putty-32.png putty-48.png \ @@ -90,15 +97,15 @@ xpmpterm.c: pterm-16.png pterm-32.png pterm-48.png xpmptcfg.c: ptermcfg-16.png ptermcfg-32.png ptermcfg-48.png ./cicon.pl cfg_icon $^ > $@ -PuTTY.icns: putty-16-mono.png putty-16.png \ - putty-32-mono.png putty-32.png \ - putty-48-mono.png putty-48.png \ - putty-128.png - ./macicon.py mono:putty-16-mono.png colour:putty-16.png \ - mono:putty-32-mono.png colour:putty-32.png \ - mono:putty-48-mono.png colour:putty-48.png \ - colour:putty-128.png \ +PuTTY.icns: putty-16-mono.pam putty-16.pam \ + putty-32-mono.pam putty-32.pam \ + putty-48-mono.pam putty-48.pam \ + putty-128.pam + ./macicon.py mono:putty-16-mono.pam colour:putty-16.pam \ + mono:putty-32-mono.pam colour:putty-32.pam \ + mono:putty-48-mono.pam colour:putty-48.pam \ + colour:putty-128.pam \ output:$@ clean: - rm -f *.png *.ico *.icns *.c + rm -f *.pam *.png *.ico *.icns *.c diff --git a/icons/macicon.py b/icons/macicon.py index 9dfc87ff..b7fed6b2 100755 --- a/icons/macicon.py +++ b/icons/macicon.py @@ -112,18 +112,31 @@ def make_colour_icon(size, rgba): # 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. +# To avoid adding any build dependency on ImageMagick or Python +# imaging libraries, none of which comes as standard on OS X, I insist +# here that the file is in RGBA .pam format (as mkicon.py will have +# generated it). 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 + with open(filename) as f: + assert f.readline() == "P7\n" + for line in iter(f.readline, ''): + words = line.rstrip("\n").split() + if words[0] == "WIDTH": + width = int(words[1]) + elif words[0] == "HEIGHT": + height = int(words[1]) + elif words[0] == "DEPTH": + assert int(words[1]) == 4 + elif words[0] == "TUPLTYPE": + assert words[1] == "RGB_ALPHA" + elif words[0] == "ENDHDR": + break + + assert width == height + data = f.read() + 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 = "" diff --git a/icons/mkicon.py b/icons/mkicon.py index 3e6d81c9..30f41b6a 100755 --- a/icons/mkicon.py +++ b/icons/mkicon.py @@ -893,17 +893,18 @@ def testrun(func, fname): for canvas in canvases: minx, miny, maxx, maxy = bbox(canvas) block.extend(render(canvas, minx-2, miny-2, minx-2+wid, maxy+2)) - p = os.popen("convert -depth 8 -size %dx%d rgb:- %s" % (wid,ht,fname), "w") - assert len(block) == ht - for line in block: - assert len(line) == wid - for r, g, b, a in line: - # Composite on to orange. - r = int(round((r * a + 255 * (255-a)) / 255.0)) - g = int(round((g * a + 128 * (255-a)) / 255.0)) - b = int(round((b * a + 0 * (255-a)) / 255.0)) - p.write("%c%c%c" % (r,g,b)) - p.close() + with open(fname, "w") as f: + f.write(("P7\nWIDTH %d\nHEIGHT %d\nDEPTH 3\nMAXVAL 255\n" + + "TUPLTYPE RGB\nENDHDR\n") % (wid, ht)) + assert len(block) == ht + for line in block: + assert len(line) == wid + for r, g, b, a in line: + # Composite on to orange. + r = int(round((r * a + 255 * (255-a)) / 255.0)) + g = int(round((g * a + 128 * (255-a)) / 255.0)) + b = int(round((b * a + 0 * (255-a)) / 255.0)) + f.write("%c%c%c" % (r,g,b)) def drawicon(func, width, fname, orangebackground = 0): canvas = func(width / 32.0) @@ -912,19 +913,20 @@ def drawicon(func, width, fname, orangebackground = 0): assert minx >= 0 and miny >= 0 and maxx <= width and maxy <= width block = render(canvas, 0, 0, width, width) - p = os.popen("convert -depth 8 -size %dx%d rgba:- %s" % (width,width,fname), "w") - assert len(block) == width - for line in block: - assert len(line) == width - for r, g, b, a in line: - if orangebackground: - # Composite on to orange. - r = int(round((r * a + 255 * (255-a)) / 255.0)) - g = int(round((g * a + 128 * (255-a)) / 255.0)) - b = int(round((b * a + 0 * (255-a)) / 255.0)) - a = 255 - p.write("%c%c%c%c" % (r,g,b,a)) - p.close() + with open(fname, "w") as f: + f.write(("P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\n" + + "TUPLTYPE RGB_ALPHA\nENDHDR\n") % (width, width)) + assert len(block) == width + for line in block: + assert len(line) == width + for r, g, b, a in line: + if orangebackground: + # Composite on to orange. + r = int(round((r * a + 255 * (255-a)) / 255.0)) + g = int(round((g * a + 128 * (255-a)) / 255.0)) + b = int(round((b * a + 0 * (255-a)) / 255.0)) + a = 255 + f.write("%c%c%c%c" % (r,g,b,a)) args = sys.argv[1:] -- 2.45.2