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))
+ 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])
+ 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)
+ 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)
+ miny[x] = min(miny.get(x, y), y)
return miny
def render(canvas, minx, miny, maxx, maxy):
h = maxy - miny
ret = []
for y in range(h):
- ret.append([outpix(cT)] * w)
+ 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)
+ 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
sqrthash = {}
def memoisedsqrt(x):
if not sqrthash.has_key(x):
- sqrthash[x] = math.sqrt(x)
+ sqrthash[x] = math.sqrt(x)
return sqrthash[x]
BR, TR, BL, TL = range(4) # enumeration of quadrants for border()
thickness = memoisedsqrt(thickness)
if thickness < 0.9:
- darkness = 0.5
+ darkness = 0.5
else:
- darkness = 1
+ darkness = 1
if thickness < 1: thickness = 1
thickness = round(thickness - 0.5) + 0.3
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
+ 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 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)
+ if not canvas.has_key((x,y)):
+ canvas[(x,y)] = dark(value)
def sysbox(size, out={}):
canvas = {}
floppyrheight = 0.7 * size
floppyheight = int(round(floppyrheight))
if floppyheight < 1:
- 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)
+ 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)
+ 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)
+ 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)
# 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)
+ 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)
+ 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)
+ 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)])
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:
- list.append(int(outerx * float(y0) / outery + 0.3))
- if y0 <= innery:
- list.append(int(innerx * float(y0) / innery + 0.3))
- list.sort()
- for x in range(int(list[0]), int(list[-1]+1)):
- pixel(x, y, cY, canvas)
+ 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:
+ list.append(int(outerx * float(y0) / outery + 0.3))
+ if y0 <= innery:
+ list.append(int(innerx * float(y0) / innery + 0.3))
+ list.sort()
+ for x in range(int(list[0]), int(list[-1]+1)):
+ pixel(x, y, cY, canvas)
# And draw a border.
border(canvas, size, [(int(width-1),0,TR), (0,int(height-1),BL)])
# Start by drawing a big white rectangle.
for y in range(int(height)):
- for x in range(int(width)):
- pixel(x, y, cW, canvas)
+ for x in range(int(width)):
+ pixel(x, y, cW, canvas)
# Now draw lines of text.
for line in range(nlines):
- # Decide where this line of text begins.
- if line == 0:
- start = round(4*size)
- elif line < 5*nlines/7:
- start = round((line - (nlines/7)) * size)
- else:
- start = round(1*size)
- if start < round(1*size):
- start = round(1*size)
- # Decide where it ends.
- endpoints = [10, 8, 11, 6, 5, 7, 5]
- ey = line * 6.0 / (nlines-1)
- eyf = math.floor(ey)
- eyc = math.ceil(ey)
- exf = endpoints[int(eyf)]
- exc = endpoints[int(eyc)]
- if eyf == eyc:
- end = exf
- else:
- end = exf * (eyc-ey) + exc * (ey-eyf)
- end = round(end * size)
-
- liney = height - (lineht+linespc) * (line+1)
- for x in range(int(start), int(end)):
- for y in range(int(lineht)):
- pixel(x, y+liney, cK, canvas)
+ # Decide where this line of text begins.
+ if line == 0:
+ start = round(4*size)
+ elif line < 5*nlines/7:
+ start = round((line - (nlines/7)) * size)
+ else:
+ start = round(1*size)
+ if start < round(1*size):
+ start = round(1*size)
+ # Decide where it ends.
+ endpoints = [10, 8, 11, 6, 5, 7, 5]
+ ey = line * 6.0 / (nlines-1)
+ eyf = math.floor(ey)
+ eyc = math.ceil(ey)
+ exf = endpoints[int(eyf)]
+ exc = endpoints[int(eyc)]
+ if eyf == eyc:
+ end = exf
+ else:
+ end = exf * (eyc-ey) + exc * (ey-eyf)
+ end = round(end * size)
+
+ liney = height - (lineht+linespc) * (line+1)
+ for x in range(int(start), int(end)):
+ for y in range(int(lineht)):
+ pixel(x, y+liney, cK, canvas)
# And draw a border.
border(canvas, size, \
brimbotc = round(10*size/3)
for x in range(int(width)):
- xs = float(x) * (len(topa)-1) / (width-1)
- xf = math.floor(xs)
- xc = math.ceil(xs)
- topf = topa[int(xf)]
- topc = topa[int(xc)]
- if xf == xc:
- top = topf
- else:
- top = topf * (xc-xs) + topc * (xs-xf)
- top = math.floor(top)
- bot = round(botl + (botr-botl) * x/(width-1))
-
- for y in range(int(top), int(bot)):
- pixel(x, y, cK, canvas)
+ xs = float(x) * (len(topa)-1) / (width-1)
+ xf = math.floor(xs)
+ xc = math.ceil(xs)
+ topf = topa[int(xf)]
+ topc = topa[int(xc)]
+ if xf == xc:
+ top = topf
+ else:
+ top = topf * (xc-xs) + topc * (xs-xf)
+ top = math.floor(top)
+ bot = round(botl + (botr-botl) * x/(width-1))
+
+ for y in range(int(top), int(bot)):
+ pixel(x, y, cK, canvas)
# Now draw the brim.
for x in range(int(width)):
- brimtop = brimtopc + brimm * x
- brimbot = brimbotc + brimm * x
- for y in range(int(math.floor(brimtop)), int(math.ceil(brimbot))):
- tophere = max(min(brimtop - y, 1), 0)
- bothere = max(min(brimbot - y, 1), 0)
- grey = bothere - tophere
- # Only draw brim pixels over pixels which are (a) part
- # of the main hat, and (b) not right on its edge.
- if canvas.has_key((x,y)) and \
- canvas.has_key((x,y-1)) and \
- canvas.has_key((x,y+1)) and \
- canvas.has_key((x-1,y)) and \
- canvas.has_key((x+1,y)):
- pixel(x, y, greypix(grey), canvas)
+ brimtop = brimtopc + brimm * x
+ brimbot = brimbotc + brimm * x
+ for y in range(int(math.floor(brimtop)), int(math.ceil(brimbot))):
+ tophere = max(min(brimtop - y, 1), 0)
+ bothere = max(min(brimbot - y, 1), 0)
+ grey = bothere - tophere
+ # Only draw brim pixels over pixels which are (a) part
+ # of the main hat, and (b) not right on its edge.
+ if canvas.has_key((x,y)) and \
+ canvas.has_key((x,y-1)) and \
+ canvas.has_key((x,y+1)) and \
+ canvas.has_key((x-1,y)) and \
+ canvas.has_key((x+1,y)):
+ pixel(x, y, greypix(grey), canvas)
return canvas
# Ellipse for the key head, minus an off-centre circular hole.
for y in range(int(keyheadh)):
- dy = (y-(keyheadh-1)/2.0) / (keyheadh/2.0)
- dyh = (y-(keyheadh-1)/2.0) / (keyholed/2.0)
- for x in range(int(keyheadw)):
- dx = (x-(keyheadw-1)/2.0) / (keyheadw/2.0)
- dxh = (x-(keyheadw-1)/2.0-keyholeoff) / (keyholed/2.0)
- if dy*dy+dx*dx <= 1 and dyh*dyh+dxh*dxh > 1:
- pixel(x + keyshaftw, y, cy, canvas)
+ dy = (y-(keyheadh-1)/2.0) / (keyheadh/2.0)
+ dyh = (y-(keyheadh-1)/2.0) / (keyholed/2.0)
+ for x in range(int(keyheadw)):
+ dx = (x-(keyheadw-1)/2.0) / (keyheadw/2.0)
+ dxh = (x-(keyheadw-1)/2.0-keyholeoff) / (keyholed/2.0)
+ if dy*dy+dx*dx <= 1 and dyh*dyh+dxh*dxh > 1:
+ pixel(x + keyshaftw, y, cy, canvas)
# Rectangle for the key shaft, extended at the bottom for the
# key head detail.
for x in range(int(keyshaftw)):
- top = round((keyheadh - keyshafth) / 2)
- bot = round((keyheadh + keyshafth) / 2)
- xs = float(x) * (len(keyhead)-1) / round((len(keyhead)-1)*size)
- xf = math.floor(xs)
- xc = math.ceil(xs)
- in_head = 0
- if xc < len(keyhead):
- in_head = 1
- yf = keyhead[int(xf)]
- yc = keyhead[int(xc)]
- if xf == xc:
- bot = yf
- else:
- bot = yf * (xc-xs) + yc * (xs-xf)
- for y in range(int(top),int(bot)):
- pixel(x, y, cy, canvas)
- if in_head:
- last = (x, y)
- if x == 0:
- squarepix.append((x, int(top), TL))
- if x == 0:
- squarepix.append(last + (BL,))
- if last != None and not in_head:
- squarepix.append(last + (BR,))
- last = None
+ top = round((keyheadh - keyshafth) / 2)
+ bot = round((keyheadh + keyshafth) / 2)
+ xs = float(x) * (len(keyhead)-1) / round((len(keyhead)-1)*size)
+ xf = math.floor(xs)
+ xc = math.ceil(xs)
+ in_head = 0
+ if xc < len(keyhead):
+ in_head = 1
+ yf = keyhead[int(xf)]
+ yc = keyhead[int(xc)]
+ if xf == xc:
+ bot = yf
+ else:
+ bot = yf * (xc-xs) + yc * (xs-xf)
+ for y in range(int(top),int(bot)):
+ pixel(x, y, cy, canvas)
+ if in_head:
+ last = (x, y)
+ if x == 0:
+ squarepix.append((x, int(top), TL))
+ if x == 0:
+ squarepix.append(last + (BL,))
+ if last != None and not in_head:
+ squarepix.append(last + (BR,))
+ last = None
# And draw a border.
border(canvas, size, squarepix)
# Special case: if x1,y1 and x2,y2 are the same point, we
# don't attempt to extrapolate it into a line at all.
if x1 != x2 or y1 != y2:
- # First, find the nearest point to x,y on the infinite
- # projection of the line segment. So we construct a vector
- # n perpendicular to that segment...
- nx = y2-y1
- ny = x1-x2
- # ... compute the dot product of (x1,y1)-(x,y) with that
- # vector...
- nd = (x1-x)*nx + (y1-y)*ny
- # ... multiply by the vector we first thought of...
- ndx = nd * nx
- ndy = nd * ny
- # ... and divide twice by the length of n.
- ndx = ndx / (nx*nx+ny*ny)
- ndy = ndy / (nx*nx+ny*ny)
- # That gives us a displacement vector from x,y to the
- # nearest point. See if it's within the range of the line
- # segment.
- cx = x + ndx
- cy = y + ndy
- if cx >= min(x1,x2) and cx <= max(x1,x2) and \
- cy >= min(y1,y2) and cy <= max(y1,y2):
- vectors.append((ndx,ndy))
+ # First, find the nearest point to x,y on the infinite
+ # projection of the line segment. So we construct a vector
+ # n perpendicular to that segment...
+ nx = y2-y1
+ ny = x1-x2
+ # ... compute the dot product of (x1,y1)-(x,y) with that
+ # vector...
+ nd = (x1-x)*nx + (y1-y)*ny
+ # ... multiply by the vector we first thought of...
+ ndx = nd * nx
+ ndy = nd * ny
+ # ... and divide twice by the length of n.
+ ndx = ndx / (nx*nx+ny*ny)
+ ndy = ndy / (nx*nx+ny*ny)
+ # That gives us a displacement vector from x,y to the
+ # nearest point. See if it's within the range of the line
+ # segment.
+ cx = x + ndx
+ cy = y + ndy
+ if cx >= min(x1,x2) and cx <= max(x1,x2) and \
+ cy >= min(y1,y2) and cy <= max(y1,y2):
+ vectors.append((ndx,ndy))
# Now we have up to three candidate result vectors: (ndx,ndy)
# as computed just above, and the two vectors to the ends of
vectors = vectors + [(x1-x,y1-y), (x2-x,y2-y)]
bestlen, best = None, None
for v in vectors:
- vlen = v[0]*v[0]+v[1]*v[1]
- if bestlen == None or bestlen > vlen:
- bestlen = vlen
- best = v
+ vlen = v[0]*v[0]+v[1]*v[1]
+ if bestlen == None or bestlen > vlen:
+ bestlen = vlen
+ best = v
return best
def spanner(size):
]
for y in range(int(cmax)):
- for x in range(int(cmax)):
- vectors = [linedist(a,b,c,d,x,y) for ((a,b),(c,d)) in segments]
- dists = [memoisedsqrt(vx*vx+vy*vy) for (vx,vy) in vectors]
-
- # If the distance to the hole line is less than
- # holeradius, we're not part of the spanner.
- if dists[0] < holeradius:
- continue
- # If the distance to the head `line' is less than
- # headradius, we are part of the spanner; likewise if
- # the distance to the shaft line is less than
- # shaftwidth _and_ the resulting shaft point isn't
- # beyond the shaft end.
- if dists[1] > headradius and \
- (dists[2] > shaftwidth or x+vectors[2][0] >= shaftend):
- continue
-
- # We're part of the spanner. Now compute the highlight
- # on this pixel. We do this by computing a `slope
- # vector', which points from this pixel in the
- # direction of its nearest edge. We store an array of
- # slope vectors, in polar coordinates.
- angles = [math.atan2(vy,vx) for (vx,vy) in vectors]
- slopes = []
- if dists[0] < holeradius + holehighlight:
- slopes.append(((dists[0]-holeradius)/holehighlight,angles[0]))
- if dists[1]/headradius < dists[2]/shaftwidth:
- if dists[1] > headradius - headhighlight and dists[1] < headradius:
- slopes.append(((headradius-dists[1])/headhighlight,math.pi+angles[1]))
- else:
- if dists[2] > shaftwidth - shafthighlight and dists[2] < shaftwidth:
- slopes.append(((shaftwidth-dists[2])/shafthighlight,math.pi+angles[2]))
- # Now we find the smallest distance in that array, if
- # any, and that gives us a notional position on a
- # sphere which we can use to compute the final
- # highlight level.
- bestdist = None
- bestangle = 0
- for dist, angle in slopes:
- if bestdist == None or bestdist > dist:
- bestdist = dist
- bestangle = angle
- if bestdist == None:
- bestdist = 1.0
- sx = (1.0-bestdist) * math.cos(bestangle)
- sy = (1.0-bestdist) * math.sin(bestangle)
- sz = math.sqrt(1.0 - sx*sx - sy*sy)
- shade = sx-sy+sz / math.sqrt(3) # can range from -1 to +1
- shade = 1.0 - (1-shade)/3
-
- pixel(x, y, yellowpix(shade), canvas)
+ for x in range(int(cmax)):
+ vectors = [linedist(a,b,c,d,x,y) for ((a,b),(c,d)) in segments]
+ dists = [memoisedsqrt(vx*vx+vy*vy) for (vx,vy) in vectors]
+
+ # If the distance to the hole line is less than
+ # holeradius, we're not part of the spanner.
+ if dists[0] < holeradius:
+ continue
+ # If the distance to the head `line' is less than
+ # headradius, we are part of the spanner; likewise if
+ # the distance to the shaft line is less than
+ # shaftwidth _and_ the resulting shaft point isn't
+ # beyond the shaft end.
+ if dists[1] > headradius and \
+ (dists[2] > shaftwidth or x+vectors[2][0] >= shaftend):
+ continue
+
+ # We're part of the spanner. Now compute the highlight
+ # on this pixel. We do this by computing a `slope
+ # vector', which points from this pixel in the
+ # direction of its nearest edge. We store an array of
+ # slope vectors, in polar coordinates.
+ angles = [math.atan2(vy,vx) for (vx,vy) in vectors]
+ slopes = []
+ if dists[0] < holeradius + holehighlight:
+ slopes.append(((dists[0]-holeradius)/holehighlight,angles[0]))
+ if dists[1]/headradius < dists[2]/shaftwidth:
+ if dists[1] > headradius - headhighlight and dists[1] < headradius:
+ slopes.append(((headradius-dists[1])/headhighlight,math.pi+angles[1]))
+ else:
+ if dists[2] > shaftwidth - shafthighlight and dists[2] < shaftwidth:
+ slopes.append(((shaftwidth-dists[2])/shafthighlight,math.pi+angles[2]))
+ # Now we find the smallest distance in that array, if
+ # any, and that gives us a notional position on a
+ # sphere which we can use to compute the final
+ # highlight level.
+ bestdist = None
+ bestangle = 0
+ for dist, angle in slopes:
+ if bestdist == None or bestdist > dist:
+ bestdist = dist
+ bestangle = angle
+ if bestdist == None:
+ bestdist = 1.0
+ sx = (1.0-bestdist) * math.cos(bestangle)
+ sy = (1.0-bestdist) * math.sin(bestangle)
+ sz = math.sqrt(1.0 - sx*sx - sy*sy)
+ shade = sx-sy+sz / math.sqrt(3) # can range from -1 to +1
+ shade = 1.0 - (1-shade)/3
+
+ pixel(x, y, yellowpix(shade), canvas)
# And draw a border.
border(canvas, size, [])
return canvas
+def box(size, back):
+ canvas = {}
+
+ # The back side of the cardboard box in the installer icon.
+
+ boxwidth = round(15 * size)
+ boxheight = round(12 * size)
+ boxdepth = round(4 * size)
+ boxfrontflapheight = round(5 * size)
+ boxrightflapheight = round(3 * size)
+
+ # 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)
+ # 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
+ # right-hand side of the box. In other words, we want the pixel
+ # at (-1, boxwidth-1) to be black, and hence the one at (0,
+ # boxwidth) too.
+ parityadjust = int(boxwidth) % 2
+
+ # The entire back of the box.
+ if back:
+ for x in range(int(boxwidth + boxdepth)):
+ ytop = max(-x-1, -boxdepth-1)
+ ybot = min(boxheight, boxheight+boxwidth-1-x)
+ for y in range(int(ytop), int(ybot)):
+ pixel(x, y, dark[(x+y+parityadjust) % 2], canvas)
+
+ # Even when drawing the back of the box, we still draw the
+ # whole shape, because that means we get the right overall size
+ # (the flaps make the box front larger than the box back) and
+ # it'll all be overwritten anyway.
+
+ # The front face of the box.
+ for x in range(int(boxwidth)):
+ for y in range(int(boxheight)):
+ pixel(x, y, med[(x+y+parityadjust) % 2], canvas)
+ # The right face of the box.
+ for x in range(int(boxwidth), int(boxwidth+boxdepth)):
+ ybot = boxheight + boxwidth-x
+ ytop = ybot - boxheight
+ for y in range(int(ytop), int(ybot)):
+ pixel(x, y, dark[(x+y+parityadjust) % 2], canvas)
+ # The front flap of the box.
+ for y in range(int(boxfrontflapheight)):
+ xadj = int(round(-0.5*y))
+ for x in range(int(xadj), int(xadj+boxwidth)):
+ pixel(x, y, light[(x+y+parityadjust) % 2], canvas)
+ # The right flap of the box.
+ for x in range(int(boxwidth), int(boxwidth + boxdepth + boxrightflapheight + 1)):
+ ytop = max(boxwidth - 1 - x, x - boxwidth - 2*boxdepth - 1)
+ ybot = min(x - boxwidth - 1, boxwidth + 2*boxrightflapheight - 1 - x)
+ for y in range(int(ytop), int(ybot+1)):
+ pixel(x, y, med[(x+y+parityadjust) % 2], canvas)
+
+ # And draw a border.
+ border(canvas, size, [(0, int(boxheight)-1, BL)])
+
+ return canvas
+
+def boxback(size):
+ return box(size, 1)
+def boxfront(size):
+ return box(size, 0)
+
# Functions to draw entire icons by composing the above components.
-def xybolt(c1, c2, size, boltoffx=0, boltoffy=0):
+def xybolt(c1, c2, size, boltoffx=0, boltoffy=0, aux={}):
# Two unspecified objects and a lightning bolt.
canvas = {}
bb = bbox(c2)
assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
overlay(c2, w-bb[2], 0-bb[1], canvas)
+ aux["c2pos"] = (w-bb[2], 0-bb[1])
# Position c1 against the bottom left of the icon.
bb = bbox(c1)
assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
overlay(c1, 0-bb[0], h-bb[3], canvas)
+ aux["c1pos"] = (0-bb[0], h-bb[3])
# Place the lightning bolt artistically off-centre. (The
# rationale for this positioning is that it's centred on the
# midpoint between the centres of the two monitors in the PuTTY
def pscp_icon(size):
return xybolt(document(size), computer(size), size)
+def puttyins_icon(size):
+ aret = {}
+ # The box back goes behind the lightning bolt.
+ canvas = xybolt(boxback(size), computer(size), size, boltoffx=-2, boltoffy=+1, aux=aret)
+ # But the box front goes over the top, so that the lightning
+ # bolt appears to come _out_ of the box. Here it's useful to
+ # know the exact coordinates where xybolt placed the box back,
+ # so we can overlay the box front exactly on top of it.
+ c1x, c1y = aret["c1pos"]
+ overlay(boxfront(size), c1x, c1y, canvas)
+ return canvas
+
def pterm_icon(size):
# Just a really big computer.
hty = topy(ht)
yrelmin = None
for cx in cty.keys():
- hx = cx - xrel
- assert hty.has_key(hx)
- yrel = cty[cx] - hty[hx]
- if yrelmin == None:
- yrelmin = yrel
- else:
- yrelmin = min(yrelmin, yrel)
+ hx = cx - xrel
+ assert hty.has_key(hx)
+ yrel = cty[cx] - hty[hx]
+ if yrelmin == None:
+ yrelmin = yrel
+ else:
+ yrelmin = min(yrelmin, yrel)
# Overlay the hat on the computer.
overlay(ht, xrel, yrelmin, c)
def testrun(func, fname):
canvases = []
for size in [0.5, 0.6, 1.0, 1.2, 1.5, 4.0]:
- canvases.append(func(size))
+ canvases.append(func(size))
wid = 0
ht = 0
for canvas in canvases:
- minx, miny, maxx, maxy = bbox(canvas)
- wid = max(wid, maxx-minx+4)
- ht = ht + maxy-miny+4
+ minx, miny, maxx, maxy = bbox(canvas)
+ wid = max(wid, maxx-minx+4)
+ ht = ht + maxy-miny+4
block = []
for canvas in canvases:
- minx, miny, maxx, maxy = bbox(canvas)
- block.extend(render(canvas, minx-2, miny-2, minx-2+wid, maxy+2))
+ 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))
+ 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()
def drawicon(func, width, fname, orangebackground = 0):
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))
+ 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()
args = sys.argv[1:]
realargs = []
for arg in args:
if doingargs and arg[0] == "-":
- if arg == "-t":
- test = 1
- elif arg == "-it":
- orangebackground = 1
- elif arg == "-2":
- colours = 0
- elif arg == "-T":
- colours = 2
- elif arg == "--":
- doingargs = 0
- else:
- sys.stderr.write("unrecognised option '%s'\n" % arg)
- sys.exit(1)
+ if arg == "-t":
+ test = 1
+ elif arg == "-it":
+ orangebackground = 1
+ elif arg == "-2":
+ colours = 0
+ elif arg == "-T":
+ colours = 2
+ elif arg == "--":
+ doingargs = 0
+ else:
+ sys.stderr.write("unrecognised option '%s'\n" % arg)
+ sys.exit(1)
else:
- realargs.append(arg)
+ realargs.append(arg)
if colours == 0:
# Monochrome.
cY=cy=cW = 1
cT = -1
def greypix(value):
- return [cK,cW][int(round(value))]
+ return [cK,cW][int(round(value))]
def yellowpix(value):
- return [cK,cW][int(round(value))]
+ return [cK,cW][int(round(value))]
def bluepix(value):
- return cK
+ return cK
def dark(value):
- return [cT,cK][int(round(value))]
+ return [cT,cK][int(round(value))]
def blend(col1, col2):
- if col1 == cT:
- return col2
- else:
- return col1
+ if col1 == cT:
+ return col2
+ else:
+ return col1
pixvals = [
(0x00, 0x00, 0x00, 0xFF), # cK
(0xFF, 0xFF, 0xFF, 0xFF), # cW
(0x00, 0x00, 0x00, 0x00), # cT
]
def outpix(colour):
- return pixvals[colour]
+ return pixvals[colour]
def finalisepix(colour):
- return colour
+ return colour
+ def halftone(col1, col2):
+ return (col1, col2)
elif colours == 1:
# Windows 16-colour palette.
cK,cr,cg,cy,cb,cm,cc,cP,cw,cR,cG,cY,cB,cM,cC,cW = range(16)
cT = -1
cD = -2 # special translucent half-darkening value used internally
def greypix(value):
- return [cK,cw,cw,cP,cW][int(round(4*value))]
+ return [cK,cw,cw,cP,cW][int(round(4*value))]
def yellowpix(value):
- return [cK,cy,cY][int(round(2*value))]
+ return [cK,cy,cY][int(round(2*value))]
def bluepix(value):
- return [cK,cb,cB][int(round(2*value))]
+ return [cK,cb,cB][int(round(2*value))]
def dark(value):
- return [cT,cD,cK][int(round(2*value))]
+ return [cT,cD,cK][int(round(2*value))]
def blend(col1, col2):
- if col1 == cT:
- return col2
- elif col1 == cD:
- return [cK,cK,cK,cK,cK,cK,cK,cw,cK,cr,cg,cy,cb,cm,cc,cw,cD,cD][col2]
- else:
- return col1
+ if col1 == cT:
+ return col2
+ elif col1 == cD:
+ return [cK,cK,cK,cK,cK,cK,cK,cw,cK,cr,cg,cy,cb,cm,cc,cw,cD,cD][col2]
+ else:
+ return col1
pixvals = [
(0x00, 0x00, 0x00, 0xFF), # cK
(0x80, 0x00, 0x00, 0xFF), # cr
(0x00, 0x00, 0x00, 0x00), # cT
]
def outpix(colour):
- return pixvals[colour]
+ return pixvals[colour]
def finalisepix(colour):
- # cD is used internally, but can't be output. Convert to cK.
- if colour == cD:
- return cK
- return colour
+ # cD is used internally, but can't be output. Convert to cK.
+ if colour == cD:
+ return cK
+ return colour
+ def halftone(col1, col2):
+ return (col1, col2)
else:
# True colour.
cK = (0x00, 0x00, 0x00, 0xFF)
cD = (0x00, 0x00, 0x00, 0x80)
cT = (0x00, 0x00, 0x00, 0x00)
def greypix(value):
- value = max(min(value, 1), 0)
- return (int(round(0xFF*value)),) * 3 + (0xFF,)
+ value = max(min(value, 1), 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)
+ value = max(min(value, 1), 0)
+ 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)
+ value = max(min(value, 1), 0)
+ 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)))
+ value = max(min(value, 1), 0)
+ return (0, 0, 0, int(round(0xFF*value)))
def blend(col1, col2):
- r1,g1,b1,a1 = col1
- r2,g2,b2,a2 = col2
- r = int(round((r1*a1 + r2*(0xFF-a1)) / 255.0))
- g = int(round((g1*a1 + g2*(0xFF-a1)) / 255.0))
- b = int(round((b1*a1 + b2*(0xFF-a1)) / 255.0))
- a = int(round((255*a1 + a2*(0xFF-a1)) / 255.0))
- return r, g, b, a
+ r1,g1,b1,a1 = col1
+ r2,g2,b2,a2 = col2
+ r = int(round((r1*a1 + r2*(0xFF-a1)) / 255.0))
+ g = int(round((g1*a1 + g2*(0xFF-a1)) / 255.0))
+ b = int(round((b1*a1 + b2*(0xFF-a1)) / 255.0))
+ a = int(round((255*a1 + a2*(0xFF-a1)) / 255.0))
+ return r, g, b, a
def outpix(colour):
- return colour
+ return colour
if colours == 2:
- # True colour with no alpha blending: we still have to
- # finalise half-dark pixels to black.
- def finalisepix(colour):
- if colour[3] > 0:
- return colour[:3] + (0xFF,)
- return colour
+ # True colour with no alpha blending: we still have to
+ # finalise half-dark pixels to black.
+ def finalisepix(colour):
+ if colour[3] > 0:
+ return colour[:3] + (0xFF,)
+ return colour
else:
- def finalisepix(colour):
- return colour
+ def finalisepix(colour):
+ return colour
+ def halftone(col1, col2):
+ r1,g1,b1,a1 = col1
+ r2,g2,b2,a2 = col2
+ colret = (int(r1+r2)/2, int(g1+g2)/2, int(b1+b2)/2, int(a1+a2)/2)
+ return (colret, colret)
if test:
testrun(eval(realargs[0]), realargs[1])