1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-04-01 11:12:50 -05:00

SVG icons: support black-and-white mode.

If I'm going to use this as a means of generating bitmap icons at
large sizes, I want it to support all the same modes as the existing
bitmap script. So this adds a mode to the SVG generator that produces
the same black and white colour scheme as the existing monochrome
bitmap icons.

(Plus, who knows, the black and white SVGs might come in useful for
other purposes. Printing as a logo on black-and-white printers springs
to mind.)

The existing monochrome icons aren't greyscale: all colours are
literally either black or white, except for the cardboard box in the
installer icon, which is halftoned. Here I've rendered that box as
mid-grey. When I convert the rendered SVG output to an actual
1-bit (plus alpha) image, I'll have to redo that halftoning.
This commit is contained in:
Simon Tatham 2025-03-08 11:19:54 +00:00
parent a3cd2a5724
commit 0a77b18481
3 changed files with 67 additions and 25 deletions

View File

@ -14,6 +14,7 @@ MONOPNGS = $(patsubst %.pam,%.png,$(MONOPAMS))
TRUEPNGS = $(patsubst %.pam,%.png,$(TRUEPAMS))
SVGS = $(patsubst %,%.svg,$(ICONS))
MONOSVGS = $(patsubst %,%-mono.svg,$(ICONS))
ICOS = putty.ico puttygen.ico pscp.ico pageant.ico pageants.ico puttycfg.ico \
puttyins.ico pterm.ico ptermcfg.ico
@ -22,12 +23,13 @@ CICONS = xpmputty.c xpmpucfg.c xpmpterm.c xpmptcfg.c
base: icos cicons
all: pngs monopngs base icns svgs truepngs
all: pngs monopngs base icns svgs monosvgs truepngs
pngs: $(PNGS)
monopngs: $(MONOPNGS)
truepngs: $(TRUEPNGS)
svgs: $(SVGS)
monosvgs: $(MONOSVGS)
icos: $(ICOS)
icns: $(ICNS)
@ -52,6 +54,9 @@ $(TRUEPAMS): %.pam: mkicon.py
$(SVGS): %.svg: mksvg.py
./mksvg.py $(patsubst %.svg,%_icon,$@) -o $@
$(MONOSVGS): %.svg: mksvg.py
./mksvg.py --mode=bw $(patsubst %-mono.svg,%_icon,$@) -o $@
putty.ico: putty-16.png putty-32.png putty-48.png \
putty-16-mono.png putty-32-mono.png putty-48-mono.png
./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@

View File

@ -675,9 +675,12 @@ def box(size, wantback):
# Three shades of basically acceptable brown, all achieved by
# halftoning between two of the Windows-16 colours. I'm quite
# pleased that was feasible at all!
dark = halftone(cr, cK)
med = halftone(cr, cy)
light = halftone(cr, cY)
if not is_bw:
dark = halftone(cr, cK)
med = halftone(cr, cy)
light = halftone(cr, cY)
else:
dark = med = light = halftone(cK, cY)
# We define our halftoning parity in such a way that the black
# pixels along the RHS of the visible part of the box back
# match up with the one-pixel black outline around the
@ -864,33 +867,53 @@ def pageant_icon(size):
# Test and output functions.
cK = (0x00, 0x00, 0x00, 0xFF)
cr = (0x80, 0x00, 0x00, 0xFF)
cg = (0x00, 0x80, 0x00, 0xFF)
cy = (0x80, 0x80, 0x00, 0xFF)
cb = (0x00, 0x00, 0x80, 0xFF)
cm = (0x80, 0x00, 0x80, 0xFF)
cc = (0x00, 0x80, 0x80, 0xFF)
cP = (0xC0, 0xC0, 0xC0, 0xFF)
cw = (0x80, 0x80, 0x80, 0xFF)
cR = (0xFF, 0x00, 0x00, 0xFF)
cG = (0x00, 0xFF, 0x00, 0xFF)
cY = (0xFF, 0xFF, 0x00, 0xFF)
cB = (0x00, 0x00, 0xFF, 0xFF)
cM = (0xFF, 0x00, 0xFF, 0xFF)
cC = (0x00, 0xFF, 0xFF, 0xFF)
cW = (0xFF, 0xFF, 0xFF, 0xFF)
cD = (0x00, 0x00, 0x00, 0x80)
cT = (0x00, 0x00, 0x00, 0x00)
def setup_colours(mode):
global cK,cr,cg,cy,cb,cm,cc,cP,cw,cR,cG,cY,cB,cM,cC,cW,cD,cT,is_bw
is_bw = mode == 'bw'
cK = (0x00, 0x00, 0x00, 0xFF)
cW = (0xFF, 0xFF, 0xFF, 0xFF)
cT = (0x00, 0x00, 0x00, 0x00)
if mode == 'colour':
cr = (0x80, 0x00, 0x00, 0xFF)
cg = (0x00, 0x80, 0x00, 0xFF)
cy = (0x80, 0x80, 0x00, 0xFF)
cb = (0x00, 0x00, 0x80, 0xFF)
cm = (0x80, 0x00, 0x80, 0xFF)
cc = (0x00, 0x80, 0x80, 0xFF)
cP = (0xC0, 0xC0, 0xC0, 0xFF)
cw = (0x80, 0x80, 0x80, 0xFF)
cR = (0xFF, 0x00, 0x00, 0xFF)
cG = (0x00, 0xFF, 0x00, 0xFF)
cY = (0xFF, 0xFF, 0x00, 0xFF)
cB = (0x00, 0x00, 0xFF, 0xFF)
cM = (0xFF, 0x00, 0xFF, 0xFF)
cC = (0x00, 0xFF, 0xFF, 0xFF)
cD = (0x00, 0x00, 0x00, 0x80)
elif mode == 'bw':
cr=cg=cb=cm=cc=cP=cw=cR=cG=cB=cM=cC=cD = cK
cY=cy = cW
else:
assert False, f"unexpected mode {mode!r}"
def greypix(value):
value = max(min(value, 1), 0)
if is_bw:
value = 1 if value > 0.3 else 0
return (int(round(0xFF*value)),) * 3 + (0xFF,)
def yellowpix(value):
value = max(min(value, 1), 0)
return (int(round(0xFF*value)),) * 2 + (0, 0xFF)
if is_bw:
return (int(round(0xFF*value)),) * 3 + (0xFF)
else:
return (int(round(0xFF*value)),) * 2 + (0, 0xFF)
def bluepix(value):
value = max(min(value, 1), 0)
return (0, 0, int(round(0xFF*value)), 0xFF)
if is_bw:
return (0, 0, 0, 0xFF)
else:
return (0, 0, int(round(0xFF*value)), 0xFF)
def dark(value):
value = max(min(value, 1), 0)
return (0, 0, 0, int(round(0xFF*value)))
@ -928,12 +951,15 @@ def drawicon(func, width, fname):
def main():
parser = argparse.ArgumentParser(description='Generate PuTTY SVG icons.')
parser.add_argument("icon", help="Which icon to generate.")
parser.add_argument("--mode", choices=('colour', 'bw'), default='colour',
help="Colour mode to generate the icon in.")
parser.add_argument("-s", "--size", type=int, default=48,
help="Notional pixel size to base the SVG on.")
parser.add_argument("-o", "--output", required=True,
help="Output file name.")
args = parser.parse_args()
setup_colours(args.mode)
drawicon(eval(args.icon), args.size, args.output)
if __name__ == '__main__':

View File

@ -96,7 +96,18 @@
<td><img src="puttyins-48-true.png" /><img src="puttyins-48.png" /></td>
</tr>
<tr>
<th>SVG</th>
<th>SVG mono</th>
<td><img src="putty-mono.svg" width="128" height="128" /></td>
<td><img src="puttycfg-mono.svg" width="128" height="128" /></td>
<td><img src="pterm-mono.svg" width="128" height="128" /></td>
<td><img src="ptermcfg-mono.svg" width="128" height="128" /></td>
<td><img src="pscp-mono.svg" width="128" height="128" /></td>
<td><img src="pageant-mono.svg" width="128" height="128" /></td>
<td><img src="puttygen-mono.svg" width="128" height="128" /></td>
<td><img src="puttyins-mono.svg" width="128" height="128" /></td>
</tr>
<tr>
<th>SVG colour</th>
<td><img src="putty.svg" width="128" height="128" /></td>
<td><img src="puttycfg.svg" width="128" height="128" /></td>
<td><img src="pterm.svg" width="128" height="128" /></td>