OSDN Git Service

Change OpenSSL context mode flags.
[ffftp/ffftp.git] / putty / ICONS / MKICON.PY
1 #!/usr/bin/env python\r
2 \r
3 import math\r
4 \r
5 # Python code which draws the PuTTY icon components at a range of\r
6 # sizes.\r
7 \r
8 # TODO\r
9 # ----\r
10 #\r
11 #  - use of alpha blending\r
12 #     + try for variable-transparency borders\r
13 #\r
14 #  - can we integrate the Mac icons into all this? Do we want to?\r
15 \r
16 def pixel(x, y, colour, canvas):\r
17     canvas[(int(x),int(y))] = colour\r
18 \r
19 def overlay(src, x, y, dst):\r
20     x = int(x)\r
21     y = int(y)\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
24 \r
25 def finalise(canvas):\r
26     for k in canvas.keys():\r
27         canvas[k] = finalisepix(canvas[k])\r
28 \r
29 def bbox(canvas):\r
30     minx, miny, maxx, maxy = None, None, None, None\r
31     for (x, y) in canvas.keys():\r
32         if minx == None:\r
33             minx, miny, maxx, maxy = x, y, x+1, y+1\r
34         else:\r
35             minx = min(minx, x)\r
36             miny = min(miny, y)\r
37             maxx = max(maxx, x+1)\r
38             maxy = max(maxy, y+1)\r
39     return (minx, miny, maxx, maxy)\r
40 \r
41 def topy(canvas):\r
42     miny = {}\r
43     for (x, y) in canvas.keys():\r
44         miny[x] = min(miny.get(x, y), y)\r
45     return miny\r
46 \r
47 def render(canvas, minx, miny, maxx, maxy):\r
48     w = maxx - minx\r
49     h = maxy - miny\r
50     ret = []\r
51     for y in range(h):\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
56     return ret\r
57 \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
62 \r
63 sqrthash = {}\r
64 def memoisedsqrt(x):\r
65     if not sqrthash.has_key(x):\r
66         sqrthash[x] = math.sqrt(x)\r
67     return sqrthash[x]\r
68 \r
69 BR, TR, BL, TL = range(4) # enumeration of quadrants for border()\r
70 \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
74     #\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
78     #\r
79     #   H    F\r
80     #  HxH  FxF\r
81     #   H    F\r
82     #\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
87     #\r
88     #   H   HHH\r
89     #  HxH  HxH\r
90     #   H   HHH\r
91     #\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
100     #\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
109     #\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
117     #\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
122 \r
123     thickness = memoisedsqrt(thickness)\r
124 \r
125     if thickness < 0.9:\r
126         darkness = 0.5\r
127     else:\r
128         darkness = 1\r
129     if thickness < 1: thickness = 1\r
130     thickness = round(thickness - 0.5) + 0.3\r
131 \r
132     out["borderthickness"] = thickness\r
133 \r
134     dmax = int(round(thickness))\r
135     if dmax < thickness: dmax = dmax + 1\r
136 \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
139 \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
146 \r
147     bvalues = {}\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
154                 else:\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
158 \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
162 \r
163 def sysbox(size, out={}):\r
164     canvas = {}\r
165 \r
166     # The system box of the computer.\r
167 \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
173 \r
174     out["sysboxheight"] = height\r
175 \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
182         floppyheight = 1\r
183     floppytop = floppybottom - floppyheight\r
184 \r
185     # The front panel is rectangular.\r
186     for x in range(width):\r
187         for y in range(height):\r
188             grey = 3\r
189             if x < highlight or y < highlight:\r
190                 grey = grey + 1\r
191             if x >= width-highlight or y >= height-bothighlight:\r
192                 grey = grey - 1\r
193             if y < highlight and x >= width-highlight:\r
194                 v = (highlight-1-y) - (x-(width-highlight))\r
195                 if v < 0:\r
196                     grey = grey - 1\r
197                 elif v > 0:\r
198                     grey = grey + 1\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
203                     grey = 0\r
204                 else:\r
205                     grey = 2\r
206             pixel(x, y, greypix(grey/4.0), canvas)\r
207 \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
212 \r
213     # The top panel is another parallelogram.\r
214     for x in range(width-1):\r
215         for y in range(depth):\r
216             grey = 3\r
217             if x >= width-1 - highlight:\r
218                 grey = grey + 1         \r
219             pixel(x+(y+1), -(y+1), greypix(grey/4.0), canvas)\r
220 \r
221     # And draw a border.\r
222     border(canvas, size, [], out)\r
223 \r
224     return canvas\r
225 \r
226 def monitor(size):\r
227     canvas = {}\r
228 \r
229     # The computer's monitor.\r
230 \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
240 \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
246                 # Screen.\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
253             else:\r
254                 # Complicated double bevel on the screen surround.\r
255 \r
256                 # First, the outer bevel. We compute the distance\r
257                 # from this pixel to each edge of the front\r
258                 # rectangle.\r
259                 list = [\r
260                 (x, +1),\r
261                 (y, +1),\r
262                 (width-1-x, -1),\r
263                 (height-1-y, -1)\r
264                 ]\r
265                 # Now sort the list to find the distance to the\r
266                 # _nearest_ edge, or the two joint nearest.\r
267                 list.sort()\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
272                 outerbevel = 0\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
276 \r
277                 # Now, the inner bevel. We compute the distance\r
278                 # from this pixel to each edge of the screen\r
279                 # itself.\r
280                 list = [\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
285                 ]\r
286                 # Now we sort to find the _maximum_ distance, which\r
287                 # conveniently ignores any less than zero.\r
288                 list.sort()\r
289                 # And now the strategy is pretty much the same as\r
290                 # above, only we're working from the opposite end\r
291                 # of the list.\r
292                 innerbevel = 0\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
296 \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
301                 # add.\r
302                 grey = 3\r
303                 if outerbevel > 0 or outerbevel == innerbevel:\r
304                     innerbevel = 0\r
305                 grey = grey + outerbevel + innerbevel\r
306 \r
307                 pix = greypix(grey / 4.0)\r
308 \r
309             pixel(x, y, pix, canvas)\r
310 \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
315 \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
320 \r
321     # And draw a border.\r
322     border(canvas, size, [(0,int(height-1),BL)])\r
323 \r
324     return canvas\r
325 \r
326 def computer(size):\r
327     # Monitor plus sysbox.\r
328     out = {}\r
329     m = monitor(size)\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
333     mb = bbox(m)\r
334     sb = bbox(s)\r
335     xoff = sb[0] - mb[0] + x\r
336     yoff = sb[3] - mb[3] - y\r
337     overlay(m, xoff, yoff, s)\r
338     return s\r
339 \r
340 def lightning(size):\r
341     canvas = {}\r
342 \r
343     # The lightning bolt motif.\r
344 \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
349 \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
353 \r
354     # And the inner edge goes to this point.\r
355     innery = height - 1 - outery\r
356     innerx = round(7*size)\r
357 \r
358     for y in range(int(height)):\r
359         list = []\r
360         if y <= outery:\r
361             list.append(width-1-int(outerx * float(y) / outery + 0.3))\r
362         if y <= innery:\r
363             list.append(width-1-int(innerx * float(y) / innery + 0.3))\r
364         y0 = height-1-y\r
365         if y0 <= outery:\r
366             list.append(int(outerx * float(y0) / outery + 0.3))\r
367         if y0 <= innery:\r
368             list.append(int(innerx * float(y0) / innery + 0.3))\r
369         list.sort()\r
370         for x in range(int(list[0]), int(list[-1]+1)):\r
371             pixel(x, y, cY, canvas)\r
372 \r
373     # And draw a border.\r
374     border(canvas, size, [(int(width-1),0,TR), (0,int(height-1),BL)])\r
375 \r
376     return canvas\r
377 \r
378 def document(size):\r
379     canvas = {}\r
380 \r
381     # The document used in the PSCP/PSFTP icon.\r
382 \r
383     width = round(13*size)\r
384     height = round(16*size)\r
385 \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
392 \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
397 \r
398     # Now draw lines of text.\r
399     for line in range(nlines):\r
400         # Decide where this line of text begins.\r
401         if line == 0:\r
402             start = round(4*size)\r
403         elif line < 5*nlines/7:\r
404             start = round((line - (nlines/7)) * size)\r
405         else:\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
416         if eyf == eyc:\r
417             end = exf\r
418         else:\r
419             end = exf * (eyc-ey) + exc * (ey-eyf)\r
420         end = round(end * size)\r
421 \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
426 \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
431 \r
432     return canvas\r
433 \r
434 def hat(size):\r
435     canvas = {}\r
436 \r
437     # The secret-agent hat in the Pageant icon.\r
438 \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
444 \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
448     brimm = 1.0 / 3.75\r
449     brimtopc = round(4*size/3)\r
450     brimbotc = round(10*size/3)\r
451 \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
455         xc = math.ceil(xs)\r
456         topf = topa[int(xf)]\r
457         topc = topa[int(xc)]\r
458         if xf == xc:\r
459             top = topf\r
460         else:\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
464 \r
465         for y in range(int(top), int(bot)):\r
466             pixel(x, y, cK, canvas)\r
467 \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
484 \r
485     return canvas\r
486 \r
487 def key(size):\r
488     canvas = {}\r
489 \r
490     # The key in the PuTTYgen icon.\r
491 \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
500 \r
501     squarepix = []\r
502 \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
512 \r
513     # Rectangle for the key shaft, extended at the bottom for the\r
514     # key head detail.\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
520         xc = math.ceil(xs)\r
521         in_head = 0\r
522         if xc < len(keyhead):\r
523             in_head = 1\r
524             yf = keyhead[int(xf)]\r
525             yc = keyhead[int(xc)]\r
526             if xf == xc:\r
527                 bot = yf\r
528             else:\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
532             if in_head:\r
533                 last = (x, y)\r
534         if x == 0:\r
535             squarepix.append((x, int(top), TL))\r
536         if x == 0:\r
537             squarepix.append(last + (BL,))\r
538         if last != None and not in_head:\r
539             squarepix.append(last + (BR,))\r
540             last = None\r
541 \r
542     # And draw a border.\r
543     border(canvas, size, squarepix)\r
544 \r
545     return canvas\r
546 \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
551 \r
552     vectors = []\r
553 \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
560         nx = y2-y1\r
561         ny = x1-x2\r
562         # ... compute the dot product of (x1,y1)-(x,y) with that\r
563         # vector...\r
564         nd = (x1-x)*nx + (y1-y)*ny\r
565         # ... multiply by the vector we first thought of...\r
566         ndx = nd * nx\r
567         ndy = nd * ny\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
573         # segment.\r
574         cx = x + ndx\r
575         cy = y + ndy\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
579 \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
583     # shortest.\r
584     vectors = vectors + [(x1-x,y1-y), (x2-x,y2-y)]\r
585     bestlen, best = None, None\r
586     for v in vectors:\r
587         vlen = v[0]*v[0]+v[1]*v[1]\r
588         if bestlen == None or bestlen > vlen:\r
589             bestlen = vlen\r
590             best = v\r
591     return best\r
592 \r
593 def spanner(size):\r
594     canvas = {}\r
595 \r
596     # The spanner in the config box icon.\r
597 \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
608 \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
612     # shape.\r
613     segments = [\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
617     (cmax, cmax))\r
618     ]\r
619 \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
624 \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
628                 continue\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
636                 continue\r
637 \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
644             slopes = []\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
650             else:\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
656             # highlight level.\r
657             bestdist = None\r
658             bestangle = 0\r
659             for dist, angle in slopes:\r
660                 if bestdist == None or bestdist > dist:\r
661                     bestdist = dist\r
662                     bestangle = angle\r
663             if bestdist == None:\r
664                 bestdist = 1.0\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
670 \r
671             pixel(x, y, yellowpix(shade), canvas)\r
672 \r
673     # And draw a border.\r
674     border(canvas, size, [])\r
675 \r
676     return canvas\r
677 \r
678 def box(size, back):\r
679     canvas = {}\r
680 \r
681     # The back side of the cardboard box in the installer icon.\r
682 \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
688 \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
700     # boxwidth) too.\r
701     parityadjust = int(boxwidth) % 2\r
702 \r
703     # The entire back of the box.\r
704     if back:\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
710 \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
715 \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
737 \r
738     # And draw a border.\r
739     border(canvas, size, [(0, int(boxheight)-1, BL)])\r
740 \r
741     return canvas\r
742 \r
743 def boxback(size):\r
744     return box(size, 1)\r
745 def boxfront(size):\r
746     return box(size, 0)\r
747 \r
748 # Functions to draw entire icons by composing the above components.\r
749 \r
750 def xybolt(c1, c2, size, boltoffx=0, boltoffy=0, aux={}):\r
751     # Two unspecified objects and a lightning bolt.\r
752 \r
753     canvas = {}\r
754     w = h = round(32 * size)\r
755 \r
756     bolt = lightning(size)\r
757 \r
758     # Position c2 against the top right of the icon.\r
759     bb = bbox(c2)\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
764     bb = bbox(c1)\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
773     bb = bbox(bolt)\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
777 \r
778     return canvas\r
779 \r
780 def putty_icon(size):\r
781     return xybolt(computer(size), computer(size), size)\r
782 \r
783 def puttycfg_icon(size):\r
784     w = h = round(32 * size)\r
785     s = spanner(size)\r
786     canvas = putty_icon(size)\r
787     # Centre the spanner.\r
788     bb = bbox(s)\r
789     overlay(s, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas)\r
790     return canvas\r
791 \r
792 def puttygen_icon(size):\r
793     return xybolt(computer(size), key(size), size, boltoffx=2)\r
794 \r
795 def pscp_icon(size):\r
796     return xybolt(document(size), computer(size), size)\r
797 \r
798 def puttyins_icon(size):\r
799     aret = {}\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
808     return canvas\r
809 \r
810 def pterm_icon(size):\r
811     # Just a really big computer.\r
812 \r
813     canvas = {}\r
814     w = h = round(32 * size)\r
815 \r
816     c = computer(size * 1.4)\r
817 \r
818     # Centre c in the return canvas.\r
819     bb = bbox(c)\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
822 \r
823     return canvas\r
824 \r
825 def ptermcfg_icon(size):\r
826     w = h = round(32 * size)\r
827     s = spanner(size)\r
828     canvas = pterm_icon(size)\r
829     # Centre the spanner.\r
830     bb = bbox(s)\r
831     overlay(s, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas)\r
832     return canvas\r
833 \r
834 def pageant_icon(size):\r
835     # A biggish computer, in a hat.\r
836 \r
837     canvas = {}\r
838     w = h = round(32 * size)\r
839 \r
840     c = computer(size * 1.2)\r
841     ht = hat(size)\r
842 \r
843     cbb = bbox(c)\r
844     hbb = bbox(ht)\r
845 \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
849 \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
855     cty = topy(c)\r
856     hty = topy(ht)\r
857     yrelmin = None\r
858     for cx in cty.keys():\r
859         hx = cx - xrel\r
860         assert hty.has_key(hx)\r
861         yrel = cty[cx] - hty[hx]\r
862         if yrelmin == None:\r
863             yrelmin = yrel\r
864         else:\r
865             yrelmin = min(yrelmin, yrel)\r
866 \r
867     # Overlay the hat on the computer.\r
868     overlay(ht, xrel, yrelmin, c)\r
869 \r
870     # And centre the result in the main icon canvas.\r
871     bb = bbox(c)\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
874 \r
875     return canvas\r
876 \r
877 # Test and output functions.\r
878 \r
879 import os\r
880 import sys\r
881 \r
882 def testrun(func, fname):\r
883     canvases = []\r
884     for size in [0.5, 0.6, 1.0, 1.2, 1.5, 4.0]:\r
885         canvases.append(func(size))\r
886     wid = 0\r
887     ht = 0\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
892     block = []\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
898     for line in block:\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
906     p.close()\r
907 \r
908 def drawicon(func, width, fname, orangebackground = 0):\r
909     canvas = func(width / 32.0)\r
910     finalise(canvas)\r
911     minx, miny, maxx, maxy = bbox(canvas)\r
912     assert minx >= 0 and miny >= 0 and maxx <= width and maxy <= width\r
913 \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
917     for line in block:\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
925                 a = 255\r
926             p.write("%c%c%c%c" % (r,g,b,a))\r
927     p.close()\r
928 \r
929 args = sys.argv[1:]\r
930 \r
931 orangebackground = test = 0\r
932 colours = 1 # 0=mono, 1=16col, 2=truecol\r
933 doingargs = 1\r
934 \r
935 realargs = []\r
936 for arg in args:\r
937     if doingargs and arg[0] == "-":\r
938         if arg == "-t":\r
939             test = 1\r
940         elif arg == "-it":\r
941             orangebackground = 1\r
942         elif arg == "-2":\r
943             colours = 0\r
944         elif arg == "-T":\r
945             colours = 2\r
946         elif arg == "--":\r
947             doingargs = 0\r
948         else:\r
949             sys.stderr.write("unrecognised option '%s'\n" % arg)\r
950             sys.exit(1)\r
951     else:\r
952         realargs.append(arg)\r
953 \r
954 if colours == 0:\r
955     # Monochrome.\r
956     cK=cr=cg=cb=cm=cc=cP=cw=cR=cG=cB=cM=cC=cD = 0\r
957     cY=cy=cW = 1\r
958     cT = -1\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
964         return cK\r
965     def dark(value):\r
966         return [cT,cK][int(round(value))]\r
967     def blend(col1, col2):\r
968         if col1 == cT:\r
969             return col2\r
970         else:\r
971             return col1\r
972     pixvals = [\r
973     (0x00, 0x00, 0x00, 0xFF), # cK\r
974     (0xFF, 0xFF, 0xFF, 0xFF), # cW\r
975     (0x00, 0x00, 0x00, 0x00), # cT\r
976     ]\r
977     def outpix(colour):\r
978         return pixvals[colour]\r
979     def finalisepix(colour):\r
980         return colour\r
981     def halftone(col1, col2):\r
982         return (col1, col2)\r
983 elif colours == 1:\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
986     cT = -1\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
994     def dark(value):\r
995         return [cT,cD,cK][int(round(2*value))]\r
996     def blend(col1, col2):\r
997         if col1 == cT:\r
998             return col2\r
999         elif col1 == cD:\r
1000             return [cK,cK,cK,cK,cK,cK,cK,cw,cK,cr,cg,cy,cb,cm,cc,cw,cD,cD][col2]\r
1001         else:\r
1002             return col1\r
1003     pixvals = [\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
1022     ]\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
1027         if colour == cD:\r
1028             return cK\r
1029         return colour\r
1030     def halftone(col1, col2):\r
1031         return (col1, col2)\r
1032 else:\r
1033     # True colour.\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
1061     def dark(value):\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
1071         return r, g, b, a\r
1072     def outpix(colour):\r
1073         return colour\r
1074     if colours == 2:\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
1078             if colour[3] > 0:\r
1079                 return colour[:3] + (0xFF,)\r
1080             return colour\r
1081     else:\r
1082         def finalisepix(colour):\r
1083             return 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
1089 \r
1090 if test:\r
1091     testrun(eval(realargs[0]), realargs[1])\r
1092 else:\r
1093     drawicon(eval(realargs[0]), int(realargs[1]), realargs[2], orangebackground)\r