1 #!/usr/bin/env python
\r
5 # Python code which draws the PuTTY icon components at a range of
\r
11 # - use of alpha blending
\r
12 # + try for variable-transparency borders
\r
14 # - can we integrate the Mac icons into all this? Do we want to?
\r
16 def pixel(x, y, colour, canvas):
\r
17 canvas[(int(x),int(y))] = colour
\r
19 def overlay(src, x, y, dst):
\r
22 for (sx, sy), colour in src.items():
\r
23 dst[sx+x, sy+y] = blend(colour, dst.get((sx+x, sy+y), cT))
\r
25 def finalise(canvas):
\r
26 for k in canvas.keys():
\r
27 canvas[k] = finalisepix(canvas[k])
\r
30 minx, miny, maxx, maxy = None, None, None, None
\r
31 for (x, y) in canvas.keys():
\r
33 minx, miny, maxx, maxy = x, y, x+1, y+1
\r
37 maxx = max(maxx, x+1)
\r
38 maxy = max(maxy, y+1)
\r
39 return (minx, miny, maxx, maxy)
\r
43 for (x, y) in canvas.keys():
\r
44 miny[x] = min(miny.get(x, y), y)
\r
47 def render(canvas, minx, miny, maxx, maxy):
\r
52 ret.append([outpix(cT)] * w)
\r
53 for (x, y), colour in canvas.items():
\r
54 if x >= minx and x < maxx and y >= miny and y < maxy:
\r
55 ret[y-miny][x-minx] = outpix(colour)
\r
58 # Code to actually draw pieces of icon. These don't generally worry
\r
59 # about positioning within a canvas; they just draw at a standard
\r
60 # location, return some useful coordinates, and leave composition
\r
61 # to other pieces of code.
\r
64 def memoisedsqrt(x):
\r
65 if not sqrthash.has_key(x):
\r
66 sqrthash[x] = math.sqrt(x)
\r
69 BR, TR, BL, TL = range(4) # enumeration of quadrants for border()
\r
71 def border(canvas, thickness, squarecorners, out={}):
\r
72 # I haven't yet worked out exactly how to do borders in a
\r
73 # properly alpha-blended fashion.
\r
75 # When you have two shades of dark available (half-dark H and
\r
76 # full-dark F), the right sequence of circular border sections
\r
77 # around a pixel x starts off with these two layouts:
\r
83 # Where it goes after that I'm not entirely sure, but I'm
\r
84 # absolutely sure those are the right places to start. However,
\r
85 # every automated algorithm I've tried has always started off
\r
86 # with the two layouts
\r
92 # which looks much worse. This is true whether you do
\r
93 # pixel-centre sampling (define an inner circle and an outer
\r
94 # circle with radii differing by 1, set any pixel whose centre
\r
95 # is inside the inner circle to F, any pixel whose centre is
\r
96 # outside the outer one to nothing, interpolate between the two
\r
97 # and round sensibly), _or_ whether you plot a notional circle
\r
98 # of a given radius and measure the actual _proportion_ of each
\r
99 # pixel square taken up by it.
\r
101 # It's not clear what I should be doing to prevent this. One
\r
102 # option is to attempt error-diffusion: Ian Jackson proved on
\r
103 # paper that if you round each pixel's ideal value to the
\r
104 # nearest of the available output values, then measure the
\r
105 # error at each pixel, propagate that error outwards into the
\r
106 # original values of the surrounding pixels, and re-round
\r
107 # everything, you do get the correct second stage. However, I
\r
108 # haven't tried it at a proper range of radii.
\r
110 # Another option is that the automated mechanisms described
\r
111 # above would be entirely adequate if it weren't for the fact
\r
112 # that the human visual centres are adapted to detect
\r
113 # horizontal and vertical lines in particular, so the only
\r
114 # place you have to behave a bit differently is at the ends of
\r
115 # the top and bottom row of pixels in the circle, and the top
\r
116 # and bottom of the extreme columns.
\r
118 # For the moment, what I have below is a very simple mechanism
\r
119 # which always uses only one alpha level for any given border
\r
120 # thickness, and which seems to work well enough for Windows
\r
121 # 16-colour icons. Everything else will have to wait.
\r
123 thickness = memoisedsqrt(thickness)
\r
125 if thickness < 0.9:
\r
129 if thickness < 1: thickness = 1
\r
130 thickness = round(thickness - 0.5) + 0.3
\r
132 out["borderthickness"] = thickness
\r
134 dmax = int(round(thickness))
\r
135 if dmax < thickness: dmax = dmax + 1
\r
137 cquadrant = [[0] * (dmax+1) for x in range(dmax+1)]
\r
138 squadrant = [[0] * (dmax+1) for x in range(dmax+1)]
\r
140 for x in range(dmax+1):
\r
141 for y in range(dmax+1):
\r
142 if max(x, y) < thickness:
\r
143 squadrant[x][y] = darkness
\r
144 if memoisedsqrt(x*x+y*y) < thickness:
\r
145 cquadrant[x][y] = darkness
\r
148 for (x, y), colour in canvas.items():
\r
149 for dx in range(-dmax, dmax+1):
\r
150 for dy in range(-dmax, dmax+1):
\r
151 quadrant = 2 * (dx < 0) + (dy < 0)
\r
152 if (x, y, quadrant) in squarecorners:
\r
153 bval = squadrant[abs(dx)][abs(dy)]
\r
155 bval = cquadrant[abs(dx)][abs(dy)]
\r
156 if bvalues.get((x+dx,y+dy),0) < bval:
\r
157 bvalues[(x+dx,y+dy)] = bval
\r
159 for (x, y), value in bvalues.items():
\r
160 if not canvas.has_key((x,y)):
\r
161 canvas[(x,y)] = dark(value)
\r
163 def sysbox(size, out={}):
\r
166 # The system box of the computer.
\r
168 height = int(round(3.6*size))
\r
169 width = int(round(16.51*size))
\r
170 depth = int(round(2*size))
\r
171 highlight = int(round(1*size))
\r
172 bothighlight = int(round(1*size))
\r
174 out["sysboxheight"] = height
\r
176 floppystart = int(round(19*size)) # measured in half-pixels
\r
177 floppyend = int(round(29*size)) # measured in half-pixels
\r
178 floppybottom = height - bothighlight
\r
179 floppyrheight = 0.7 * size
\r
180 floppyheight = int(round(floppyrheight))
\r
181 if floppyheight < 1:
\r
183 floppytop = floppybottom - floppyheight
\r
185 # The front panel is rectangular.
\r
186 for x in range(width):
\r
187 for y in range(height):
\r
189 if x < highlight or y < highlight:
\r
191 if x >= width-highlight or y >= height-bothighlight:
\r
193 if y < highlight and x >= width-highlight:
\r
194 v = (highlight-1-y) - (x-(width-highlight))
\r
199 if y >= floppytop and y < floppybottom and \
\r
200 2*x+2 > floppystart and 2*x < floppyend:
\r
201 if 2*x >= floppystart and 2*x+2 <= floppyend and \
\r
202 floppyrheight >= 0.7:
\r
206 pixel(x, y, greypix(grey/4.0), canvas)
\r
208 # The side panel is a parallelogram.
\r
209 for x in range(depth):
\r
210 for y in range(height):
\r
211 pixel(x+width, y-(x+1), greypix(0.5), canvas)
\r
213 # The top panel is another parallelogram.
\r
214 for x in range(width-1):
\r
215 for y in range(depth):
\r
217 if x >= width-1 - highlight:
\r
219 pixel(x+(y+1), -(y+1), greypix(grey/4.0), canvas)
\r
221 # And draw a border.
\r
222 border(canvas, size, [], out)
\r
229 # The computer's monitor.
\r
231 height = int(round(9.55*size))
\r
232 width = int(round(11.49*size))
\r
233 surround = int(round(1*size))
\r
234 botsurround = int(round(2*size))
\r
235 sheight = height - surround - botsurround
\r
236 swidth = width - 2*surround
\r
237 depth = int(round(2*size))
\r
238 highlight = int(round(math.sqrt(size)))
\r
239 shadow = int(round(0.55*size))
\r
241 # The front panel is rectangular.
\r
242 for x in range(width):
\r
243 for y in range(height):
\r
244 if x >= surround and y >= surround and \
\r
245 x < surround+swidth and y < surround+sheight:
\r
247 sx = (float(x-surround) - swidth/3) / swidth
\r
248 sy = (float(y-surround) - sheight/3) / sheight
\r
249 shighlight = 1.0 - (sx*sx+sy*sy)*0.27
\r
250 pix = bluepix(shighlight)
\r
251 if x < surround+shadow or y < surround+shadow:
\r
252 pix = blend(cD, pix) # sharp-edged shadow on top and left
\r
254 # Complicated double bevel on the screen surround.
\r
256 # First, the outer bevel. We compute the distance
\r
257 # from this pixel to each edge of the front
\r
265 # Now sort the list to find the distance to the
\r
266 # _nearest_ edge, or the two joint nearest.
\r
268 # If there's one nearest edge, that determines our
\r
269 # bevel colour. If there are two joint nearest, our
\r
270 # bevel colour is their shared one if they agree,
\r
271 # and neutral otherwise.
\r
273 if list[0][0] < list[1][0] or list[0][1] == list[1][1]:
\r
274 if list[0][0] < highlight:
\r
275 outerbevel = list[0][1]
\r
277 # Now, the inner bevel. We compute the distance
\r
278 # from this pixel to each edge of the screen
\r
281 (surround-1-x, -1),
\r
282 (surround-1-y, -1),
\r
283 (x-(surround+swidth), +1),
\r
284 (y-(surround+sheight), +1)
\r
286 # Now we sort to find the _maximum_ distance, which
\r
287 # conveniently ignores any less than zero.
\r
289 # And now the strategy is pretty much the same as
\r
290 # above, only we're working from the opposite end
\r
293 if list[-1][0] > list[-2][0] or list[-1][1] == list[-2][1]:
\r
294 if list[-1][0] >= 0 and list[-1][0] < highlight:
\r
295 innerbevel = list[-1][1]
\r
297 # Now we know the adjustment we want to make to the
\r
298 # pixel's overall grey shade due to the outer
\r
299 # bevel, and due to the inner one. We break a tie
\r
300 # in favour of a light outer bevel, but otherwise
\r
303 if outerbevel > 0 or outerbevel == innerbevel:
\r
305 grey = grey + outerbevel + innerbevel
\r
307 pix = greypix(grey / 4.0)
\r
309 pixel(x, y, pix, canvas)
\r
311 # The side panel is a parallelogram.
\r
312 for x in range(depth):
\r
313 for y in range(height):
\r
314 pixel(x+width, y-x, greypix(0.5), canvas)
\r
316 # The top panel is another parallelogram.
\r
317 for x in range(width):
\r
318 for y in range(depth-1):
\r
319 pixel(x+(y+1), -(y+1), greypix(0.75), canvas)
\r
321 # And draw a border.
\r
322 border(canvas, size, [(0,int(height-1),BL)])
\r
326 def computer(size):
\r
327 # Monitor plus sysbox.
\r
330 s = sysbox(size, out)
\r
331 x = int(round((2+size/(size+1))*size))
\r
332 y = int(out["sysboxheight"] + out["borderthickness"])
\r
335 xoff = sb[0] - mb[0] + x
\r
336 yoff = sb[3] - mb[3] - y
\r
337 overlay(m, xoff, yoff, s)
\r
340 def lightning(size):
\r
343 # The lightning bolt motif.
\r
345 # We always want this to be an even number of pixels in height,
\r
346 # and an odd number in width.
\r
347 width = round(7*size) * 2 - 1
\r
348 height = round(8*size) * 2
\r
350 # The outer edge of each side of the bolt goes to this point.
\r
351 outery = round(8.4*size)
\r
352 outerx = round(11*size)
\r
354 # And the inner edge goes to this point.
\r
355 innery = height - 1 - outery
\r
356 innerx = round(7*size)
\r
358 for y in range(int(height)):
\r
361 list.append(width-1-int(outerx * float(y) / outery + 0.3))
\r
363 list.append(width-1-int(innerx * float(y) / innery + 0.3))
\r
366 list.append(int(outerx * float(y0) / outery + 0.3))
\r
368 list.append(int(innerx * float(y0) / innery + 0.3))
\r
370 for x in range(int(list[0]), int(list[-1]+1)):
\r
371 pixel(x, y, cY, canvas)
\r
373 # And draw a border.
\r
374 border(canvas, size, [(int(width-1),0,TR), (0,int(height-1),BL)])
\r
378 def document(size):
\r
381 # The document used in the PSCP/PSFTP icon.
\r
383 width = round(13*size)
\r
384 height = round(16*size)
\r
386 lineht = round(1*size)
\r
387 if lineht < 1: lineht = 1
\r
388 linespc = round(0.7*size)
\r
389 if linespc < 1: linespc = 1
\r
390 nlines = int((height-linespc)/(lineht+linespc))
\r
391 height = nlines*(lineht+linespc)+linespc # round this so it fits better
\r
393 # Start by drawing a big white rectangle.
\r
394 for y in range(int(height)):
\r
395 for x in range(int(width)):
\r
396 pixel(x, y, cW, canvas)
\r
398 # Now draw lines of text.
\r
399 for line in range(nlines):
\r
400 # Decide where this line of text begins.
\r
402 start = round(4*size)
\r
403 elif line < 5*nlines/7:
\r
404 start = round((line - (nlines/7)) * size)
\r
406 start = round(1*size)
\r
407 if start < round(1*size):
\r
408 start = round(1*size)
\r
409 # Decide where it ends.
\r
410 endpoints = [10, 8, 11, 6, 5, 7, 5]
\r
411 ey = line * 6.0 / (nlines-1)
\r
412 eyf = math.floor(ey)
\r
413 eyc = math.ceil(ey)
\r
414 exf = endpoints[int(eyf)]
\r
415 exc = endpoints[int(eyc)]
\r
419 end = exf * (eyc-ey) + exc * (ey-eyf)
\r
420 end = round(end * size)
\r
422 liney = height - (lineht+linespc) * (line+1)
\r
423 for x in range(int(start), int(end)):
\r
424 for y in range(int(lineht)):
\r
425 pixel(x, y+liney, cK, canvas)
\r
427 # And draw a border.
\r
428 border(canvas, size, \
\r
429 [(0,0,TL),(int(width-1),0,TR),(0,int(height-1),BL), \
\r
430 (int(width-1),int(height-1),BR)])
\r
437 # The secret-agent hat in the Pageant icon.
\r
439 topa = [6]*9+[5,3,1,0,0,1,2,2,1,1,1,9,9,10,10,11,11,12,12]
\r
440 topa = [round(x*size) for x in topa]
\r
441 botl = round(topa[0]+2.4*math.sqrt(size))
\r
442 botr = round(topa[-1]+2.4*math.sqrt(size))
\r
443 width = round(len(topa)*size)
\r
445 # Line equations for the top and bottom of the hat brim, in the
\r
446 # form y=mx+c. c, of course, needs scaling by size, but m is
\r
447 # independent of size.
\r
449 brimtopc = round(4*size/3)
\r
450 brimbotc = round(10*size/3)
\r
452 for x in range(int(width)):
\r
453 xs = float(x) * (len(topa)-1) / (width-1)
\r
454 xf = math.floor(xs)
\r
456 topf = topa[int(xf)]
\r
457 topc = topa[int(xc)]
\r
461 top = topf * (xc-xs) + topc * (xs-xf)
\r
462 top = math.floor(top)
\r
463 bot = round(botl + (botr-botl) * x/(width-1))
\r
465 for y in range(int(top), int(bot)):
\r
466 pixel(x, y, cK, canvas)
\r
468 # Now draw the brim.
\r
469 for x in range(int(width)):
\r
470 brimtop = brimtopc + brimm * x
\r
471 brimbot = brimbotc + brimm * x
\r
472 for y in range(int(math.floor(brimtop)), int(math.ceil(brimbot))):
\r
473 tophere = max(min(brimtop - y, 1), 0)
\r
474 bothere = max(min(brimbot - y, 1), 0)
\r
475 grey = bothere - tophere
\r
476 # Only draw brim pixels over pixels which are (a) part
\r
477 # of the main hat, and (b) not right on its edge.
\r
478 if canvas.has_key((x,y)) and \
\r
479 canvas.has_key((x,y-1)) and \
\r
480 canvas.has_key((x,y+1)) and \
\r
481 canvas.has_key((x-1,y)) and \
\r
482 canvas.has_key((x+1,y)):
\r
483 pixel(x, y, greypix(grey), canvas)
\r
490 # The key in the PuTTYgen icon.
\r
492 keyheadw = round(9.5*size)
\r
493 keyheadh = round(12*size)
\r
494 keyholed = round(4*size)
\r
495 keyholeoff = round(2*size)
\r
496 # Ensure keyheadh and keyshafth have the same parity.
\r
497 keyshafth = round((2*size - (int(keyheadh)&1)) / 2) * 2 + (int(keyheadh)&1)
\r
498 keyshaftw = round(18.5*size)
\r
499 keyhead = [round(x*size) for x in [12,11,8,10,9,8,11,12]]
\r
503 # Ellipse for the key head, minus an off-centre circular hole.
\r
504 for y in range(int(keyheadh)):
\r
505 dy = (y-(keyheadh-1)/2.0) / (keyheadh/2.0)
\r
506 dyh = (y-(keyheadh-1)/2.0) / (keyholed/2.0)
\r
507 for x in range(int(keyheadw)):
\r
508 dx = (x-(keyheadw-1)/2.0) / (keyheadw/2.0)
\r
509 dxh = (x-(keyheadw-1)/2.0-keyholeoff) / (keyholed/2.0)
\r
510 if dy*dy+dx*dx <= 1 and dyh*dyh+dxh*dxh > 1:
\r
511 pixel(x + keyshaftw, y, cy, canvas)
\r
513 # Rectangle for the key shaft, extended at the bottom for the
\r
515 for x in range(int(keyshaftw)):
\r
516 top = round((keyheadh - keyshafth) / 2)
\r
517 bot = round((keyheadh + keyshafth) / 2)
\r
518 xs = float(x) * (len(keyhead)-1) / round((len(keyhead)-1)*size)
\r
519 xf = math.floor(xs)
\r
522 if xc < len(keyhead):
\r
524 yf = keyhead[int(xf)]
\r
525 yc = keyhead[int(xc)]
\r
529 bot = yf * (xc-xs) + yc * (xs-xf)
\r
530 for y in range(int(top),int(bot)):
\r
531 pixel(x, y, cy, canvas)
\r
535 squarepix.append((x, int(top), TL))
\r
537 squarepix.append(last + (BL,))
\r
538 if last != None and not in_head:
\r
539 squarepix.append(last + (BR,))
\r
542 # And draw a border.
\r
543 border(canvas, size, squarepix)
\r
547 def linedist(x1,y1, x2,y2, x,y):
\r
548 # Compute the distance from the point x,y to the line segment
\r
549 # joining x1,y1 to x2,y2. Returns the distance vector, measured
\r
550 # with x,y at the origin.
\r
554 # Special case: if x1,y1 and x2,y2 are the same point, we
\r
555 # don't attempt to extrapolate it into a line at all.
\r
556 if x1 != x2 or y1 != y2:
\r
557 # First, find the nearest point to x,y on the infinite
\r
558 # projection of the line segment. So we construct a vector
\r
559 # n perpendicular to that segment...
\r
562 # ... compute the dot product of (x1,y1)-(x,y) with that
\r
564 nd = (x1-x)*nx + (y1-y)*ny
\r
565 # ... multiply by the vector we first thought of...
\r
568 # ... and divide twice by the length of n.
\r
569 ndx = ndx / (nx*nx+ny*ny)
\r
570 ndy = ndy / (nx*nx+ny*ny)
\r
571 # That gives us a displacement vector from x,y to the
\r
572 # nearest point. See if it's within the range of the line
\r
576 if cx >= min(x1,x2) and cx <= max(x1,x2) and \
\r
577 cy >= min(y1,y2) and cy <= max(y1,y2):
\r
578 vectors.append((ndx,ndy))
\r
580 # Now we have up to three candidate result vectors: (ndx,ndy)
\r
581 # as computed just above, and the two vectors to the ends of
\r
582 # the line segment, (x1-x,y1-y) and (x2-x,y2-y). Pick the
\r
584 vectors = vectors + [(x1-x,y1-y), (x2-x,y2-y)]
\r
585 bestlen, best = None, None
\r
587 vlen = v[0]*v[0]+v[1]*v[1]
\r
588 if bestlen == None or bestlen > vlen:
\r
596 # The spanner in the config box icon.
\r
598 headcentre = 0.5 + round(4*size)
\r
599 headradius = headcentre + 0.1
\r
600 headhighlight = round(1.5*size)
\r
601 holecentre = 0.5 + round(3*size)
\r
602 holeradius = round(2*size)
\r
603 holehighlight = round(1.5*size)
\r
604 shaftend = 0.5 + round(25*size)
\r
605 shaftwidth = round(2*size)
\r
606 shafthighlight = round(1.5*size)
\r
607 cmax = shaftend + shaftwidth
\r
609 # Define three line segments, such that the shortest distance
\r
610 # vectors from any point to each of these segments determines
\r
611 # everything we need to know about where it is on the spanner
\r
614 ((0,0), (holecentre, holecentre)),
\r
615 ((headcentre, headcentre), (headcentre, headcentre)),
\r
616 ((headcentre+headradius/math.sqrt(2), headcentre+headradius/math.sqrt(2)),
\r
620 for y in range(int(cmax)):
\r
621 for x in range(int(cmax)):
\r
622 vectors = [linedist(a,b,c,d,x,y) for ((a,b),(c,d)) in segments]
\r
623 dists = [memoisedsqrt(vx*vx+vy*vy) for (vx,vy) in vectors]
\r
625 # If the distance to the hole line is less than
\r
626 # holeradius, we're not part of the spanner.
\r
627 if dists[0] < holeradius:
\r
629 # If the distance to the head `line' is less than
\r
630 # headradius, we are part of the spanner; likewise if
\r
631 # the distance to the shaft line is less than
\r
632 # shaftwidth _and_ the resulting shaft point isn't
\r
633 # beyond the shaft end.
\r
634 if dists[1] > headradius and \
\r
635 (dists[2] > shaftwidth or x+vectors[2][0] >= shaftend):
\r
638 # We're part of the spanner. Now compute the highlight
\r
639 # on this pixel. We do this by computing a `slope
\r
640 # vector', which points from this pixel in the
\r
641 # direction of its nearest edge. We store an array of
\r
642 # slope vectors, in polar coordinates.
\r
643 angles = [math.atan2(vy,vx) for (vx,vy) in vectors]
\r
645 if dists[0] < holeradius + holehighlight:
\r
646 slopes.append(((dists[0]-holeradius)/holehighlight,angles[0]))
\r
647 if dists[1]/headradius < dists[2]/shaftwidth:
\r
648 if dists[1] > headradius - headhighlight and dists[1] < headradius:
\r
649 slopes.append(((headradius-dists[1])/headhighlight,math.pi+angles[1]))
\r
651 if dists[2] > shaftwidth - shafthighlight and dists[2] < shaftwidth:
\r
652 slopes.append(((shaftwidth-dists[2])/shafthighlight,math.pi+angles[2]))
\r
653 # Now we find the smallest distance in that array, if
\r
654 # any, and that gives us a notional position on a
\r
655 # sphere which we can use to compute the final
\r
659 for dist, angle in slopes:
\r
660 if bestdist == None or bestdist > dist:
\r
663 if bestdist == None:
\r
665 sx = (1.0-bestdist) * math.cos(bestangle)
\r
666 sy = (1.0-bestdist) * math.sin(bestangle)
\r
667 sz = math.sqrt(1.0 - sx*sx - sy*sy)
\r
668 shade = sx-sy+sz / math.sqrt(3) # can range from -1 to +1
\r
669 shade = 1.0 - (1-shade)/3
\r
671 pixel(x, y, yellowpix(shade), canvas)
\r
673 # And draw a border.
\r
674 border(canvas, size, [])
\r
678 def box(size, back):
\r
681 # The back side of the cardboard box in the installer icon.
\r
683 boxwidth = round(15 * size)
\r
684 boxheight = round(12 * size)
\r
685 boxdepth = round(4 * size)
\r
686 boxfrontflapheight = round(5 * size)
\r
687 boxrightflapheight = round(3 * size)
\r
689 # Three shades of basically acceptable brown, all achieved by
\r
690 # halftoning between two of the Windows-16 colours. I'm quite
\r
691 # pleased that was feasible at all!
\r
692 dark = halftone(cr, cK)
\r
693 med = halftone(cr, cy)
\r
694 light = halftone(cr, cY)
\r
695 # We define our halftoning parity in such a way that the black
\r
696 # pixels along the RHS of the visible part of the box back
\r
697 # match up with the one-pixel black outline around the
\r
698 # right-hand side of the box. In other words, we want the pixel
\r
699 # at (-1, boxwidth-1) to be black, and hence the one at (0,
\r
701 parityadjust = int(boxwidth) % 2
\r
703 # The entire back of the box.
\r
705 for x in range(int(boxwidth + boxdepth)):
\r
706 ytop = max(-x-1, -boxdepth-1)
\r
707 ybot = min(boxheight, boxheight+boxwidth-1-x)
\r
708 for y in range(int(ytop), int(ybot)):
\r
709 pixel(x, y, dark[(x+y+parityadjust) % 2], canvas)
\r
711 # Even when drawing the back of the box, we still draw the
\r
712 # whole shape, because that means we get the right overall size
\r
713 # (the flaps make the box front larger than the box back) and
\r
714 # it'll all be overwritten anyway.
\r
716 # The front face of the box.
\r
717 for x in range(int(boxwidth)):
\r
718 for y in range(int(boxheight)):
\r
719 pixel(x, y, med[(x+y+parityadjust) % 2], canvas)
\r
720 # The right face of the box.
\r
721 for x in range(int(boxwidth), int(boxwidth+boxdepth)):
\r
722 ybot = boxheight + boxwidth-x
\r
723 ytop = ybot - boxheight
\r
724 for y in range(int(ytop), int(ybot)):
\r
725 pixel(x, y, dark[(x+y+parityadjust) % 2], canvas)
\r
726 # The front flap of the box.
\r
727 for y in range(int(boxfrontflapheight)):
\r
728 xadj = int(round(-0.5*y))
\r
729 for x in range(int(xadj), int(xadj+boxwidth)):
\r
730 pixel(x, y, light[(x+y+parityadjust) % 2], canvas)
\r
731 # The right flap of the box.
\r
732 for x in range(int(boxwidth), int(boxwidth + boxdepth + boxrightflapheight + 1)):
\r
733 ytop = max(boxwidth - 1 - x, x - boxwidth - 2*boxdepth - 1)
\r
734 ybot = min(x - boxwidth - 1, boxwidth + 2*boxrightflapheight - 1 - x)
\r
735 for y in range(int(ytop), int(ybot+1)):
\r
736 pixel(x, y, med[(x+y+parityadjust) % 2], canvas)
\r
738 # And draw a border.
\r
739 border(canvas, size, [(0, int(boxheight)-1, BL)])
\r
744 return box(size, 1)
\r
745 def boxfront(size):
\r
746 return box(size, 0)
\r
748 # Functions to draw entire icons by composing the above components.
\r
750 def xybolt(c1, c2, size, boltoffx=0, boltoffy=0, aux={}):
\r
751 # Two unspecified objects and a lightning bolt.
\r
754 w = h = round(32 * size)
\r
756 bolt = lightning(size)
\r
758 # Position c2 against the top right of the icon.
\r
760 assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
\r
761 overlay(c2, w-bb[2], 0-bb[1], canvas)
\r
762 aux["c2pos"] = (w-bb[2], 0-bb[1])
\r
763 # Position c1 against the bottom left of the icon.
\r
765 assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
\r
766 overlay(c1, 0-bb[0], h-bb[3], canvas)
\r
767 aux["c1pos"] = (0-bb[0], h-bb[3])
\r
768 # Place the lightning bolt artistically off-centre. (The
\r
769 # rationale for this positioning is that it's centred on the
\r
770 # midpoint between the centres of the two monitors in the PuTTY
\r
771 # icon proper, but it's not really feasible to _base_ the
\r
772 # calculation here on that.)
\r
774 assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
\r
775 overlay(bolt, (w-bb[0]-bb[2])/2 + round(boltoffx*size), \
\r
776 (h-bb[1]-bb[3])/2 + round((boltoffy-2)*size), canvas)
\r
780 def putty_icon(size):
\r
781 return xybolt(computer(size), computer(size), size)
\r
783 def puttycfg_icon(size):
\r
784 w = h = round(32 * size)
\r
786 canvas = putty_icon(size)
\r
787 # Centre the spanner.
\r
789 overlay(s, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas)
\r
792 def puttygen_icon(size):
\r
793 return xybolt(computer(size), key(size), size, boltoffx=2)
\r
795 def pscp_icon(size):
\r
796 return xybolt(document(size), computer(size), size)
\r
798 def puttyins_icon(size):
\r
800 # The box back goes behind the lightning bolt.
\r
801 canvas = xybolt(boxback(size), computer(size), size, boltoffx=-2, boltoffy=+1, aux=aret)
\r
802 # But the box front goes over the top, so that the lightning
\r
803 # bolt appears to come _out_ of the box. Here it's useful to
\r
804 # know the exact coordinates where xybolt placed the box back,
\r
805 # so we can overlay the box front exactly on top of it.
\r
806 c1x, c1y = aret["c1pos"]
\r
807 overlay(boxfront(size), c1x, c1y, canvas)
\r
810 def pterm_icon(size):
\r
811 # Just a really big computer.
\r
814 w = h = round(32 * size)
\r
816 c = computer(size * 1.4)
\r
818 # Centre c in the return canvas.
\r
820 assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
\r
821 overlay(c, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas)
\r
825 def ptermcfg_icon(size):
\r
826 w = h = round(32 * size)
\r
828 canvas = pterm_icon(size)
\r
829 # Centre the spanner.
\r
831 overlay(s, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas)
\r
834 def pageant_icon(size):
\r
835 # A biggish computer, in a hat.
\r
838 w = h = round(32 * size)
\r
840 c = computer(size * 1.2)
\r
846 # Determine the relative y-coordinates of the computer and hat.
\r
847 # We just centre the one on the other.
\r
848 xrel = (cbb[0]+cbb[2]-hbb[0]-hbb[2])/2
\r
850 # Determine the relative y-coordinates of the computer and hat.
\r
851 # We do this by sitting the hat as low down on the computer as
\r
852 # possible without any computer showing over the top. To do
\r
853 # this we first have to find the minimum x coordinate at each
\r
854 # y-coordinate of both components.
\r
858 for cx in cty.keys():
\r
860 assert hty.has_key(hx)
\r
861 yrel = cty[cx] - hty[hx]
\r
862 if yrelmin == None:
\r
865 yrelmin = min(yrelmin, yrel)
\r
867 # Overlay the hat on the computer.
\r
868 overlay(ht, xrel, yrelmin, c)
\r
870 # And centre the result in the main icon canvas.
\r
872 assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
\r
873 overlay(c, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas)
\r
877 # Test and output functions.
\r
882 def testrun(func, fname):
\r
884 for size in [0.5, 0.6, 1.0, 1.2, 1.5, 4.0]:
\r
885 canvases.append(func(size))
\r
888 for canvas in canvases:
\r
889 minx, miny, maxx, maxy = bbox(canvas)
\r
890 wid = max(wid, maxx-minx+4)
\r
891 ht = ht + maxy-miny+4
\r
893 for canvas in canvases:
\r
894 minx, miny, maxx, maxy = bbox(canvas)
\r
895 block.extend(render(canvas, minx-2, miny-2, minx-2+wid, maxy+2))
\r
896 p = os.popen("convert -depth 8 -size %dx%d rgb:- %s" % (wid,ht,fname), "w")
\r
897 assert len(block) == ht
\r
899 assert len(line) == wid
\r
900 for r, g, b, a in line:
\r
901 # Composite on to orange.
\r
902 r = int(round((r * a + 255 * (255-a)) / 255.0))
\r
903 g = int(round((g * a + 128 * (255-a)) / 255.0))
\r
904 b = int(round((b * a + 0 * (255-a)) / 255.0))
\r
905 p.write("%c%c%c" % (r,g,b))
\r
908 def drawicon(func, width, fname, orangebackground = 0):
\r
909 canvas = func(width / 32.0)
\r
911 minx, miny, maxx, maxy = bbox(canvas)
\r
912 assert minx >= 0 and miny >= 0 and maxx <= width and maxy <= width
\r
914 block = render(canvas, 0, 0, width, width)
\r
915 p = os.popen("convert -depth 8 -size %dx%d rgba:- %s" % (width,width,fname), "w")
\r
916 assert len(block) == width
\r
918 assert len(line) == width
\r
919 for r, g, b, a in line:
\r
920 if orangebackground:
\r
921 # Composite on to orange.
\r
922 r = int(round((r * a + 255 * (255-a)) / 255.0))
\r
923 g = int(round((g * a + 128 * (255-a)) / 255.0))
\r
924 b = int(round((b * a + 0 * (255-a)) / 255.0))
\r
926 p.write("%c%c%c%c" % (r,g,b,a))
\r
929 args = sys.argv[1:]
\r
931 orangebackground = test = 0
\r
932 colours = 1 # 0=mono, 1=16col, 2=truecol
\r
937 if doingargs and arg[0] == "-":
\r
941 orangebackground = 1
\r
949 sys.stderr.write("unrecognised option '%s'\n" % arg)
\r
952 realargs.append(arg)
\r
956 cK=cr=cg=cb=cm=cc=cP=cw=cR=cG=cB=cM=cC=cD = 0
\r
959 def greypix(value):
\r
960 return [cK,cW][int(round(value))]
\r
961 def yellowpix(value):
\r
962 return [cK,cW][int(round(value))]
\r
963 def bluepix(value):
\r
966 return [cT,cK][int(round(value))]
\r
967 def blend(col1, col2):
\r
973 (0x00, 0x00, 0x00, 0xFF), # cK
\r
974 (0xFF, 0xFF, 0xFF, 0xFF), # cW
\r
975 (0x00, 0x00, 0x00, 0x00), # cT
\r
977 def outpix(colour):
\r
978 return pixvals[colour]
\r
979 def finalisepix(colour):
\r
981 def halftone(col1, col2):
\r
982 return (col1, col2)
\r
984 # Windows 16-colour palette.
\r
985 cK,cr,cg,cy,cb,cm,cc,cP,cw,cR,cG,cY,cB,cM,cC,cW = range(16)
\r
987 cD = -2 # special translucent half-darkening value used internally
\r
988 def greypix(value):
\r
989 return [cK,cw,cw,cP,cW][int(round(4*value))]
\r
990 def yellowpix(value):
\r
991 return [cK,cy,cY][int(round(2*value))]
\r
992 def bluepix(value):
\r
993 return [cK,cb,cB][int(round(2*value))]
\r
995 return [cT,cD,cK][int(round(2*value))]
\r
996 def blend(col1, col2):
\r
1000 return [cK,cK,cK,cK,cK,cK,cK,cw,cK,cr,cg,cy,cb,cm,cc,cw,cD,cD][col2]
\r
1004 (0x00, 0x00, 0x00, 0xFF), # cK
\r
1005 (0x80, 0x00, 0x00, 0xFF), # cr
\r
1006 (0x00, 0x80, 0x00, 0xFF), # cg
\r
1007 (0x80, 0x80, 0x00, 0xFF), # cy
\r
1008 (0x00, 0x00, 0x80, 0xFF), # cb
\r
1009 (0x80, 0x00, 0x80, 0xFF), # cm
\r
1010 (0x00, 0x80, 0x80, 0xFF), # cc
\r
1011 (0xC0, 0xC0, 0xC0, 0xFF), # cP
\r
1012 (0x80, 0x80, 0x80, 0xFF), # cw
\r
1013 (0xFF, 0x00, 0x00, 0xFF), # cR
\r
1014 (0x00, 0xFF, 0x00, 0xFF), # cG
\r
1015 (0xFF, 0xFF, 0x00, 0xFF), # cY
\r
1016 (0x00, 0x00, 0xFF, 0xFF), # cB
\r
1017 (0xFF, 0x00, 0xFF, 0xFF), # cM
\r
1018 (0x00, 0xFF, 0xFF, 0xFF), # cC
\r
1019 (0xFF, 0xFF, 0xFF, 0xFF), # cW
\r
1020 (0x00, 0x00, 0x00, 0x80), # cD
\r
1021 (0x00, 0x00, 0x00, 0x00), # cT
\r
1023 def outpix(colour):
\r
1024 return pixvals[colour]
\r
1025 def finalisepix(colour):
\r
1026 # cD is used internally, but can't be output. Convert to cK.
\r
1030 def halftone(col1, col2):
\r
1031 return (col1, col2)
\r
1034 cK = (0x00, 0x00, 0x00, 0xFF)
\r
1035 cr = (0x80, 0x00, 0x00, 0xFF)
\r
1036 cg = (0x00, 0x80, 0x00, 0xFF)
\r
1037 cy = (0x80, 0x80, 0x00, 0xFF)
\r
1038 cb = (0x00, 0x00, 0x80, 0xFF)
\r
1039 cm = (0x80, 0x00, 0x80, 0xFF)
\r
1040 cc = (0x00, 0x80, 0x80, 0xFF)
\r
1041 cP = (0xC0, 0xC0, 0xC0, 0xFF)
\r
1042 cw = (0x80, 0x80, 0x80, 0xFF)
\r
1043 cR = (0xFF, 0x00, 0x00, 0xFF)
\r
1044 cG = (0x00, 0xFF, 0x00, 0xFF)
\r
1045 cY = (0xFF, 0xFF, 0x00, 0xFF)
\r
1046 cB = (0x00, 0x00, 0xFF, 0xFF)
\r
1047 cM = (0xFF, 0x00, 0xFF, 0xFF)
\r
1048 cC = (0x00, 0xFF, 0xFF, 0xFF)
\r
1049 cW = (0xFF, 0xFF, 0xFF, 0xFF)
\r
1050 cD = (0x00, 0x00, 0x00, 0x80)
\r
1051 cT = (0x00, 0x00, 0x00, 0x00)
\r
1052 def greypix(value):
\r
1053 value = max(min(value, 1), 0)
\r
1054 return (int(round(0xFF*value)),) * 3 + (0xFF,)
\r
1055 def yellowpix(value):
\r
1056 value = max(min(value, 1), 0)
\r
1057 return (int(round(0xFF*value)),) * 2 + (0, 0xFF)
\r
1058 def bluepix(value):
\r
1059 value = max(min(value, 1), 0)
\r
1060 return (0, 0, int(round(0xFF*value)), 0xFF)
\r
1062 value = max(min(value, 1), 0)
\r
1063 return (0, 0, 0, int(round(0xFF*value)))
\r
1064 def blend(col1, col2):
\r
1065 r1,g1,b1,a1 = col1
\r
1066 r2,g2,b2,a2 = col2
\r
1067 r = int(round((r1*a1 + r2*(0xFF-a1)) / 255.0))
\r
1068 g = int(round((g1*a1 + g2*(0xFF-a1)) / 255.0))
\r
1069 b = int(round((b1*a1 + b2*(0xFF-a1)) / 255.0))
\r
1070 a = int(round((255*a1 + a2*(0xFF-a1)) / 255.0))
\r
1072 def outpix(colour):
\r
1075 # True colour with no alpha blending: we still have to
\r
1076 # finalise half-dark pixels to black.
\r
1077 def finalisepix(colour):
\r
1079 return colour[:3] + (0xFF,)
\r
1082 def finalisepix(colour):
\r
1084 def halftone(col1, col2):
\r
1085 r1,g1,b1,a1 = col1
\r
1086 r2,g2,b2,a2 = col2
\r
1087 colret = (int(r1+r2)/2, int(g1+g2)/2, int(b1+b2)/2, int(a1+a2)/2)
\r
1088 return (colret, colret)
\r
1091 testrun(eval(realargs[0]), realargs[1])
\r
1093 drawicon(eval(realargs[0]), int(realargs[1]), realargs[2], orangebackground)
\r