📄 mkicon.py
字号:
#!/usr/bin/env python
import math
# Python code which draws the PuTTY icon components at a range of
# sizes.
# TODO
# ----
#
# - use of alpha blending
# + try for variable-transparency borders
#
# - can we integrate the Mac icons into all this? Do we want to?
def pixel(x, y, colour, canvas):
canvas[(int(x),int(y))] = colour
def overlay(src, x, y, dst):
x = int(x)
y = int(y)
for (sx, sy), colour in src.items():
dst[sx+x, sy+y] = blend(colour, dst.get((sx+x, sy+y), cT))
def finalise(canvas):
for k in canvas.keys():
canvas[k] = finalisepix(canvas[k])
def bbox(canvas):
minx, miny, maxx, maxy = None, None, None, None
for (x, y) in canvas.keys():
if minx == None:
minx, miny, maxx, maxy = x, y, x+1, y+1
else:
minx = min(minx, x)
miny = min(miny, y)
maxx = max(maxx, x+1)
maxy = max(maxy, y+1)
return (minx, miny, maxx, maxy)
def topy(canvas):
miny = {}
for (x, y) in canvas.keys():
miny[x] = min(miny.get(x, y), y)
return miny
def render(canvas, minx, miny, maxx, maxy):
w = maxx - minx
h = maxy - miny
ret = []
for y in range(h):
ret.append([outpix(cT)] * w)
for (x, y), colour in canvas.items():
if x >= minx and x < maxx and y >= miny and y < maxy:
ret[y-miny][x-minx] = outpix(colour)
return ret
# Code to actually draw pieces of icon. These don't generally worry
# about positioning within a canvas; they just draw at a standard
# location, return some useful coordinates, and leave composition
# to other pieces of code.
sqrthash = {}
def memoisedsqrt(x):
if not sqrthash.has_key(x):
sqrthash[x] = math.sqrt(x)
return sqrthash[x]
BR, TR, BL, TL = range(4) # enumeration of quadrants for border()
def border(canvas, thickness, squarecorners, out={}):
# I haven't yet worked out exactly how to do borders in a
# properly alpha-blended fashion.
#
# When you have two shades of dark available (half-dark H and
# full-dark F), the right sequence of circular border sections
# around a pixel x starts off with these two layouts:
#
# H F
# HxH FxF
# H F
#
# Where it goes after that I'm not entirely sure, but I'm
# absolutely sure those are the right places to start. However,
# every automated algorithm I've tried has always started off
# with the two layouts
#
# H HHH
# HxH HxH
# H HHH
#
# which looks much worse. This is true whether you do
# pixel-centre sampling (define an inner circle and an outer
# circle with radii differing by 1, set any pixel whose centre
# is inside the inner circle to F, any pixel whose centre is
# outside the outer one to nothing, interpolate between the two
# and round sensibly), _or_ whether you plot a notional circle
# of a given radius and measure the actual _proportion_ of each
# pixel square taken up by it.
#
# It's not clear what I should be doing to prevent this. One
# option is to attempt error-diffusion: Ian Jackson proved on
# paper that if you round each pixel's ideal value to the
# nearest of the available output values, then measure the
# error at each pixel, propagate that error outwards into the
# original values of the surrounding pixels, and re-round
# everything, you do get the correct second stage. However, I
# haven't tried it at a proper range of radii.
#
# Another option is that the automated mechanisms described
# above would be entirely adequate if it weren't for the fact
# that the human visual centres are adapted to detect
# horizontal and vertical lines in particular, so the only
# place you have to behave a bit differently is at the ends of
# the top and bottom row of pixels in the circle, and the top
# and bottom of the extreme columns.
#
# For the moment, what I have below is a very simple mechanism
# which always uses only one alpha level for any given border
# thickness, and which seems to work well enough for Windows
# 16-colour icons. Everything else will have to wait.
thickness = memoisedsqrt(thickness)
if thickness < 0.9:
darkness = 0.5
else:
darkness = 1
if thickness < 1: thickness = 1
thickness = round(thickness - 0.5) + 0.3
out["borderthickness"] = thickness
dmax = int(round(thickness))
if dmax < thickness: dmax = dmax + 1
cquadrant = [[0] * (dmax+1) for x in range(dmax+1)]
squadrant = [[0] * (dmax+1) for x in range(dmax+1)]
for x in range(dmax+1):
for y in range(dmax+1):
if max(x, y) < thickness:
squadrant[x][y] = darkness
if memoisedsqrt(x*x+y*y) < thickness:
cquadrant[x][y] = darkness
bvalues = {}
for (x, y), colour in canvas.items():
for dx in range(-dmax, dmax+1):
for dy in range(-dmax, dmax+1):
quadrant = 2 * (dx < 0) + (dy < 0)
if (x, y, quadrant) in squarecorners:
bval = squadrant[abs(dx)][abs(dy)]
else:
bval = cquadrant[abs(dx)][abs(dy)]
if bvalues.get((x+dx,y+dy),0) < bval:
bvalues[(x+dx,y+dy)] = bval
for (x, y), value in bvalues.items():
if not canvas.has_key((x,y)):
canvas[(x,y)] = dark(value)
def sysbox(size, out={}):
canvas = {}
# The system box of the computer.
height = int(round(3.6*size))
width = int(round(16.51*size))
depth = int(round(2*size))
highlight = int(round(1*size))
bothighlight = int(round(1*size))
out["sysboxheight"] = height
floppystart = int(round(19*size)) # measured in half-pixels
floppyend = int(round(29*size)) # measured in half-pixels
floppybottom = height - bothighlight
floppyrheight = 0.7 * size
floppyheight = int(round(floppyrheight))
if floppyheight < 1:
floppyheight = 1
floppytop = floppybottom - floppyheight
# The front panel is rectangular.
for x in range(width):
for y in range(height):
grey = 3
if x < highlight or y < highlight:
grey = grey + 1
if x >= width-highlight or y >= height-bothighlight:
grey = grey - 1
if y < highlight and x >= width-highlight:
v = (highlight-1-y) - (x-(width-highlight))
if v < 0:
grey = grey - 1
elif v > 0:
grey = grey + 1
if y >= floppytop and y < floppybottom and \
2*x+2 > floppystart and 2*x < floppyend:
if 2*x >= floppystart and 2*x+2 <= floppyend and \
floppyrheight >= 0.7:
grey = 0
else:
grey = 2
pixel(x, y, greypix(grey/4.0), canvas)
# The side panel is a parallelogram.
for x in range(depth):
for y in range(height):
pixel(x+width, y-(x+1), greypix(0.5), canvas)
# The top panel is another parallelogram.
for x in range(width-1):
for y in range(depth):
grey = 3
if x >= width-1 - highlight:
grey = grey + 1
pixel(x+(y+1), -(y+1), greypix(grey/4.0), canvas)
# And draw a border.
border(canvas, size, [], out)
return canvas
def monitor(size):
canvas = {}
# The computer's monitor.
height = int(round(9.55*size))
width = int(round(11.49*size))
surround = int(round(1*size))
botsurround = int(round(2*size))
sheight = height - surround - botsurround
swidth = width - 2*surround
depth = int(round(2*size))
highlight = int(round(math.sqrt(size)))
shadow = int(round(0.55*size))
# The front panel is rectangular.
for x in range(width):
for y in range(height):
if x >= surround and y >= surround and \
x < surround+swidth and y < surround+sheight:
# Screen.
sx = (float(x-surround) - swidth/3) / swidth
sy = (float(y-surround) - sheight/3) / sheight
shighlight = 1.0 - (sx*sx+sy*sy)*0.27
pix = bluepix(shighlight)
if x < surround+shadow or y < surround+shadow:
pix = blend(cD, pix) # sharp-edged shadow on top and left
else:
# Complicated double bevel on the screen surround.
# First, the outer bevel. We compute the distance
# from this pixel to each edge of the front
# rectangle.
list = [
(x, +1),
(y, +1),
(width-1-x, -1),
(height-1-y, -1)
]
# Now sort the list to find the distance to the
# _nearest_ edge, or the two joint nearest.
list.sort()
# If there's one nearest edge, that determines our
# bevel colour. If there are two joint nearest, our
# bevel colour is their shared one if they agree,
# and neutral otherwise.
outerbevel = 0
if list[0][0] < list[1][0] or list[0][1] == list[1][1]:
if list[0][0] < highlight:
outerbevel = list[0][1]
# Now, the inner bevel. We compute the distance
# from this pixel to each edge of the screen
# itself.
list = [
(surround-1-x, -1),
(surround-1-y, -1),
(x-(surround+swidth), +1),
(y-(surround+sheight), +1)
]
# Now we sort to find the _maximum_ distance, which
# conveniently ignores any less than zero.
list.sort()
# And now the strategy is pretty much the same as
# above, only we're working from the opposite end
# of the list.
innerbevel = 0
if list[-1][0] > list[-2][0] or list[-1][1] == list[-2][1]:
if list[-1][0] >= 0 and list[-1][0] < highlight:
innerbevel = list[-1][1]
# Now we know the adjustment we want to make to the
# pixel's overall grey shade due to the outer
# bevel, and due to the inner one. We break a tie
# in favour of a light outer bevel, but otherwise
# add.
grey = 3
if outerbevel > 0 or outerbevel == innerbevel:
innerbevel = 0
grey = grey + outerbevel + innerbevel
pix = greypix(grey / 4.0)
pixel(x, y, pix, canvas)
# The side panel is a parallelogram.
for x in range(depth):
for y in range(height):
pixel(x+width, y-x, greypix(0.5), canvas)
# The top panel is another parallelogram.
for x in range(width):
for y in range(depth-1):
pixel(x+(y+1), -(y+1), greypix(0.75), canvas)
# And draw a border.
border(canvas, size, [(0,int(height-1),BL)])
return canvas
def computer(size):
# Monitor plus sysbox.
out = {}
m = monitor(size)
s = sysbox(size, out)
x = int(round((2+size/(size+1))*size))
y = int(out["sysboxheight"] + out["borderthickness"])
mb = bbox(m)
sb = bbox(s)
xoff = sb[0] - mb[0] + x
yoff = sb[3] - mb[3] - y
overlay(m, xoff, yoff, s)
return s
def lightning(size):
canvas = {}
# The lightning bolt motif.
# We always want this to be an even number of pixels in height,
# and an odd number in width.
width = round(7*size) * 2 - 1
height = round(8*size) * 2
# The outer edge of each side of the bolt goes to this point.
outery = round(8.4*size)
outerx = round(11*size)
# And the inner edge goes to this point.
innery = height - 1 - outery
innerx = round(7*size)
for y in range(int(height)):
list = []
if y <= outery:
list.append(width-1-int(outerx * float(y) / outery + 0.3))
if y <= innery:
list.append(width-1-int(innerx * float(y) / innery + 0.3))
y0 = height-1-y
if y0 <= outery:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -