OSDN Git Service

Add PuTTY 0.61 to contrib directory.
[ffftp/ffftp.git] / contrib / putty / ICONS / MKICON.PY
diff --git a/contrib/putty/ICONS/MKICON.PY b/contrib/putty/ICONS/MKICON.PY
new file mode 100644 (file)
index 0000000..122eaaa
--- /dev/null
@@ -0,0 +1,1093 @@
+#!/usr/bin/env python\r
+\r
+import math\r
+\r
+# Python code which draws the PuTTY icon components at a range of\r
+# sizes.\r
+\r
+# TODO\r
+# ----\r
+#\r
+#  - use of alpha blending\r
+#     + try for variable-transparency borders\r
+#\r
+#  - can we integrate the Mac icons into all this? Do we want to?\r
+\r
+def pixel(x, y, colour, canvas):\r
+    canvas[(int(x),int(y))] = colour\r
+\r
+def overlay(src, x, y, dst):\r
+    x = int(x)\r
+    y = int(y)\r
+    for (sx, sy), colour in src.items():\r
+        dst[sx+x, sy+y] = blend(colour, dst.get((sx+x, sy+y), cT))\r
+\r
+def finalise(canvas):\r
+    for k in canvas.keys():\r
+        canvas[k] = finalisepix(canvas[k])\r
+\r
+def bbox(canvas):\r
+    minx, miny, maxx, maxy = None, None, None, None\r
+    for (x, y) in canvas.keys():\r
+        if minx == None:\r
+            minx, miny, maxx, maxy = x, y, x+1, y+1\r
+        else:\r
+            minx = min(minx, x)\r
+            miny = min(miny, y)\r
+            maxx = max(maxx, x+1)\r
+            maxy = max(maxy, y+1)\r
+    return (minx, miny, maxx, maxy)\r
+\r
+def topy(canvas):\r
+    miny = {}\r
+    for (x, y) in canvas.keys():\r
+        miny[x] = min(miny.get(x, y), y)\r
+    return miny\r
+\r
+def render(canvas, minx, miny, maxx, maxy):\r
+    w = maxx - minx\r
+    h = maxy - miny\r
+    ret = []\r
+    for y in range(h):\r
+        ret.append([outpix(cT)] * w)\r
+    for (x, y), colour in canvas.items():\r
+        if x >= minx and x < maxx and y >= miny and y < maxy:\r
+            ret[y-miny][x-minx] = outpix(colour)\r
+    return ret\r
+\r
+# Code to actually draw pieces of icon. These don't generally worry\r
+# about positioning within a canvas; they just draw at a standard\r
+# location, return some useful coordinates, and leave composition\r
+# to other pieces of code.\r
+\r
+sqrthash = {}\r
+def memoisedsqrt(x):\r
+    if not sqrthash.has_key(x):\r
+        sqrthash[x] = math.sqrt(x)\r
+    return sqrthash[x]\r
+\r
+BR, TR, BL, TL = range(4) # enumeration of quadrants for border()\r
+\r
+def border(canvas, thickness, squarecorners, out={}):\r
+    # I haven't yet worked out exactly how to do borders in a\r
+    # properly alpha-blended fashion.\r
+    #\r
+    # When you have two shades of dark available (half-dark H and\r
+    # full-dark F), the right sequence of circular border sections\r
+    # around a pixel x starts off with these two layouts:\r
+    #\r
+    #   H    F\r
+    #  HxH  FxF\r
+    #   H    F\r
+    #\r
+    # Where it goes after that I'm not entirely sure, but I'm\r
+    # absolutely sure those are the right places to start. However,\r
+    # every automated algorithm I've tried has always started off\r
+    # with the two layouts\r
+    #\r
+    #   H   HHH\r
+    #  HxH  HxH\r
+    #   H   HHH\r
+    #\r
+    # which looks much worse. This is true whether you do\r
+    # pixel-centre sampling (define an inner circle and an outer\r
+    # circle with radii differing by 1, set any pixel whose centre\r
+    # is inside the inner circle to F, any pixel whose centre is\r
+    # outside the outer one to nothing, interpolate between the two\r
+    # and round sensibly), _or_ whether you plot a notional circle\r
+    # of a given radius and measure the actual _proportion_ of each\r
+    # pixel square taken up by it.\r
+    #\r
+    # It's not clear what I should be doing to prevent this. One\r
+    # option is to attempt error-diffusion: Ian Jackson proved on\r
+    # paper that if you round each pixel's ideal value to the\r
+    # nearest of the available output values, then measure the\r
+    # error at each pixel, propagate that error outwards into the\r
+    # original values of the surrounding pixels, and re-round\r
+    # everything, you do get the correct second stage. However, I\r
+    # haven't tried it at a proper range of radii.\r
+    #\r
+    # Another option is that the automated mechanisms described\r
+    # above would be entirely adequate if it weren't for the fact\r
+    # that the human visual centres are adapted to detect\r
+    # horizontal and vertical lines in particular, so the only\r
+    # place you have to behave a bit differently is at the ends of\r
+    # the top and bottom row of pixels in the circle, and the top\r
+    # and bottom of the extreme columns.\r
+    #\r
+    # For the moment, what I have below is a very simple mechanism\r
+    # which always uses only one alpha level for any given border\r
+    # thickness, and which seems to work well enough for Windows\r
+    # 16-colour icons. Everything else will have to wait.\r
+\r
+    thickness = memoisedsqrt(thickness)\r
+\r
+    if thickness < 0.9:\r
+        darkness = 0.5\r
+    else:\r
+        darkness = 1\r
+    if thickness < 1: thickness = 1\r
+    thickness = round(thickness - 0.5) + 0.3\r
+\r
+    out["borderthickness"] = thickness\r
+\r
+    dmax = int(round(thickness))\r
+    if dmax < thickness: dmax = dmax + 1\r
+\r
+    cquadrant = [[0] * (dmax+1) for x in range(dmax+1)]\r
+    squadrant = [[0] * (dmax+1) for x in range(dmax+1)]\r
+\r
+    for x in range(dmax+1):\r
+        for y in range(dmax+1):\r
+            if max(x, y) < thickness:\r
+                squadrant[x][y] = darkness\r
+            if memoisedsqrt(x*x+y*y) < thickness:\r
+                cquadrant[x][y] = darkness\r
+\r
+    bvalues = {}\r
+    for (x, y), colour in canvas.items():\r
+        for dx in range(-dmax, dmax+1):\r
+            for dy in range(-dmax, dmax+1):\r
+                quadrant = 2 * (dx < 0) + (dy < 0)\r
+                if (x, y, quadrant) in squarecorners:\r
+                    bval = squadrant[abs(dx)][abs(dy)]\r
+                else:\r
+                    bval = cquadrant[abs(dx)][abs(dy)]\r
+                if bvalues.get((x+dx,y+dy),0) < bval:\r
+                    bvalues[(x+dx,y+dy)] = bval\r
+\r
+    for (x, y), value in bvalues.items():\r
+        if not canvas.has_key((x,y)):\r
+            canvas[(x,y)] = dark(value)\r
+\r
+def sysbox(size, out={}):\r
+    canvas = {}\r
+\r
+    # The system box of the computer.\r
+\r
+    height = int(round(3.6*size))\r
+    width = int(round(16.51*size))\r
+    depth = int(round(2*size))\r
+    highlight = int(round(1*size))\r
+    bothighlight = int(round(1*size))\r
+\r
+    out["sysboxheight"] = height\r
+\r
+    floppystart = int(round(19*size)) # measured in half-pixels\r
+    floppyend = int(round(29*size)) # measured in half-pixels\r
+    floppybottom = height - bothighlight\r
+    floppyrheight = 0.7 * size\r
+    floppyheight = int(round(floppyrheight))\r
+    if floppyheight < 1:\r
+        floppyheight = 1\r
+    floppytop = floppybottom - floppyheight\r
+\r
+    # The front panel is rectangular.\r
+    for x in range(width):\r
+        for y in range(height):\r
+            grey = 3\r
+            if x < highlight or y < highlight:\r
+                grey = grey + 1\r
+            if x >= width-highlight or y >= height-bothighlight:\r
+                grey = grey - 1\r
+            if y < highlight and x >= width-highlight:\r
+                v = (highlight-1-y) - (x-(width-highlight))\r
+                if v < 0:\r
+                    grey = grey - 1\r
+                elif v > 0:\r
+                    grey = grey + 1\r
+            if y >= floppytop and y < floppybottom and \\r
+            2*x+2 > floppystart and 2*x < floppyend:\r
+                if 2*x >= floppystart and 2*x+2 <= floppyend and \\r
+                floppyrheight >= 0.7:\r
+                    grey = 0\r
+                else:\r
+                    grey = 2\r
+            pixel(x, y, greypix(grey/4.0), canvas)\r
+\r
+    # The side panel is a parallelogram.\r
+    for x in range(depth):\r
+        for y in range(height):\r
+            pixel(x+width, y-(x+1), greypix(0.5), canvas)\r
+\r
+    # The top panel is another parallelogram.\r
+    for x in range(width-1):\r
+        for y in range(depth):\r
+            grey = 3\r
+            if x >= width-1 - highlight:\r
+                grey = grey + 1         \r
+            pixel(x+(y+1), -(y+1), greypix(grey/4.0), canvas)\r
+\r
+    # And draw a border.\r
+    border(canvas, size, [], out)\r
+\r
+    return canvas\r
+\r
+def monitor(size):\r
+    canvas = {}\r
+\r
+    # The computer's monitor.\r
+\r
+    height = int(round(9.55*size))\r
+    width = int(round(11.49*size))\r
+    surround = int(round(1*size))\r
+    botsurround = int(round(2*size))\r
+    sheight = height - surround - botsurround\r
+    swidth = width - 2*surround\r
+    depth = int(round(2*size))\r
+    highlight = int(round(math.sqrt(size)))\r
+    shadow = int(round(0.55*size))\r
+\r
+    # The front panel is rectangular.\r
+    for x in range(width):\r
+        for y in range(height):\r
+            if x >= surround and y >= surround and \\r
+            x < surround+swidth and y < surround+sheight:\r
+                # Screen.\r
+                sx = (float(x-surround) - swidth/3) / swidth\r
+                sy = (float(y-surround) - sheight/3) / sheight\r
+                shighlight = 1.0 - (sx*sx+sy*sy)*0.27\r
+                pix = bluepix(shighlight)\r
+                if x < surround+shadow or y < surround+shadow:\r
+                    pix = blend(cD, pix) # sharp-edged shadow on top and left\r
+            else:\r
+                # Complicated double bevel on the screen surround.\r
+\r
+                # First, the outer bevel. We compute the distance\r
+                # from this pixel to each edge of the front\r
+                # rectangle.\r
+                list = [\r
+                (x, +1),\r
+                (y, +1),\r
+                (width-1-x, -1),\r
+                (height-1-y, -1)\r
+                ]\r
+                # Now sort the list to find the distance to the\r
+                # _nearest_ edge, or the two joint nearest.\r
+                list.sort()\r
+                # If there's one nearest edge, that determines our\r
+                # bevel colour. If there are two joint nearest, our\r
+                # bevel colour is their shared one if they agree,\r
+                # and neutral otherwise.\r
+                outerbevel = 0\r
+                if list[0][0] < list[1][0] or list[0][1] == list[1][1]:\r
+                    if list[0][0] < highlight:\r
+                        outerbevel = list[0][1]\r
+\r
+                # Now, the inner bevel. We compute the distance\r
+                # from this pixel to each edge of the screen\r
+                # itself.\r
+                list = [\r
+                (surround-1-x, -1),\r
+                (surround-1-y, -1),\r
+                (x-(surround+swidth), +1),\r
+                (y-(surround+sheight), +1)\r
+                ]\r
+                # Now we sort to find the _maximum_ distance, which\r
+                # conveniently ignores any less than zero.\r
+                list.sort()\r
+                # And now the strategy is pretty much the same as\r
+                # above, only we're working from the opposite end\r
+                # of the list.\r
+                innerbevel = 0\r
+                if list[-1][0] > list[-2][0] or list[-1][1] == list[-2][1]:\r
+                    if list[-1][0] >= 0 and list[-1][0] < highlight:\r
+                        innerbevel = list[-1][1]\r
+\r
+                # Now we know the adjustment we want to make to the\r
+                # pixel's overall grey shade due to the outer\r
+                # bevel, and due to the inner one. We break a tie\r
+                # in favour of a light outer bevel, but otherwise\r
+                # add.\r
+                grey = 3\r
+                if outerbevel > 0 or outerbevel == innerbevel:\r
+                    innerbevel = 0\r
+                grey = grey + outerbevel + innerbevel\r
+\r
+                pix = greypix(grey / 4.0)\r
+\r
+            pixel(x, y, pix, canvas)\r
+\r
+    # The side panel is a parallelogram.\r
+    for x in range(depth):\r
+        for y in range(height):\r
+            pixel(x+width, y-x, greypix(0.5), canvas)\r
+\r
+    # The top panel is another parallelogram.\r
+    for x in range(width):\r
+        for y in range(depth-1):\r
+            pixel(x+(y+1), -(y+1), greypix(0.75), canvas)\r
+\r
+    # And draw a border.\r
+    border(canvas, size, [(0,int(height-1),BL)])\r
+\r
+    return canvas\r
+\r
+def computer(size):\r
+    # Monitor plus sysbox.\r
+    out = {}\r
+    m = monitor(size)\r
+    s = sysbox(size, out)\r
+    x = int(round((2+size/(size+1))*size))\r
+    y = int(out["sysboxheight"] + out["borderthickness"])\r
+    mb = bbox(m)\r
+    sb = bbox(s)\r
+    xoff = sb[0] - mb[0] + x\r
+    yoff = sb[3] - mb[3] - y\r
+    overlay(m, xoff, yoff, s)\r
+    return s\r
+\r
+def lightning(size):\r
+    canvas = {}\r
+\r
+    # The lightning bolt motif.\r
+\r
+    # We always want this to be an even number of pixels in height,\r
+    # and an odd number in width.\r
+    width = round(7*size) * 2 - 1\r
+    height = round(8*size) * 2\r
+\r
+    # The outer edge of each side of the bolt goes to this point.\r
+    outery = round(8.4*size)\r
+    outerx = round(11*size)\r
+\r
+    # And the inner edge goes to this point.\r
+    innery = height - 1 - outery\r
+    innerx = round(7*size)\r
+\r
+    for y in range(int(height)):\r
+        list = []\r
+        if y <= outery:\r
+            list.append(width-1-int(outerx * float(y) / outery + 0.3))\r
+        if y <= innery:\r
+            list.append(width-1-int(innerx * float(y) / innery + 0.3))\r
+        y0 = height-1-y\r
+        if y0 <= outery:\r
+            list.append(int(outerx * float(y0) / outery + 0.3))\r
+        if y0 <= innery:\r
+            list.append(int(innerx * float(y0) / innery + 0.3))\r
+        list.sort()\r
+        for x in range(int(list[0]), int(list[-1]+1)):\r
+            pixel(x, y, cY, canvas)\r
+\r
+    # And draw a border.\r
+    border(canvas, size, [(int(width-1),0,TR), (0,int(height-1),BL)])\r
+\r
+    return canvas\r
+\r
+def document(size):\r
+    canvas = {}\r
+\r
+    # The document used in the PSCP/PSFTP icon.\r
+\r
+    width = round(13*size)\r
+    height = round(16*size)\r
+\r
+    lineht = round(1*size)\r
+    if lineht < 1: lineht = 1\r
+    linespc = round(0.7*size)\r
+    if linespc < 1: linespc = 1\r
+    nlines = int((height-linespc)/(lineht+linespc))\r
+    height = nlines*(lineht+linespc)+linespc # round this so it fits better\r
+\r
+    # Start by drawing a big white rectangle.\r
+    for y in range(int(height)):\r
+        for x in range(int(width)):\r
+            pixel(x, y, cW, canvas)\r
+\r
+    # Now draw lines of text.\r
+    for line in range(nlines):\r
+        # Decide where this line of text begins.\r
+        if line == 0:\r
+            start = round(4*size)\r
+        elif line < 5*nlines/7:\r
+            start = round((line - (nlines/7)) * size)\r
+        else:\r
+            start = round(1*size)\r
+        if start < round(1*size):\r
+            start = round(1*size)\r
+        # Decide where it ends.\r
+        endpoints = [10, 8, 11, 6, 5, 7, 5]\r
+        ey = line * 6.0 / (nlines-1)\r
+        eyf = math.floor(ey)\r
+        eyc = math.ceil(ey)\r
+        exf = endpoints[int(eyf)]\r
+        exc = endpoints[int(eyc)]\r
+        if eyf == eyc:\r
+            end = exf\r
+        else:\r
+            end = exf * (eyc-ey) + exc * (ey-eyf)\r
+        end = round(end * size)\r
+\r
+        liney = height - (lineht+linespc) * (line+1)\r
+        for x in range(int(start), int(end)):\r
+            for y in range(int(lineht)):\r
+                pixel(x, y+liney, cK, canvas)\r
+\r
+    # And draw a border.\r
+    border(canvas, size, \\r
+    [(0,0,TL),(int(width-1),0,TR),(0,int(height-1),BL), \\r
+    (int(width-1),int(height-1),BR)])\r
+\r
+    return canvas\r
+\r
+def hat(size):\r
+    canvas = {}\r
+\r
+    # The secret-agent hat in the Pageant icon.\r
+\r
+    topa = [6]*9+[5,3,1,0,0,1,2,2,1,1,1,9,9,10,10,11,11,12,12]\r
+    topa = [round(x*size) for x in topa]\r
+    botl = round(topa[0]+2.4*math.sqrt(size))\r
+    botr = round(topa[-1]+2.4*math.sqrt(size))\r
+    width = round(len(topa)*size)\r
+\r
+    # Line equations for the top and bottom of the hat brim, in the\r
+    # form y=mx+c. c, of course, needs scaling by size, but m is\r
+    # independent of size.\r
+    brimm = 1.0 / 3.75\r
+    brimtopc = round(4*size/3)\r
+    brimbotc = round(10*size/3)\r
+\r
+    for x in range(int(width)):\r
+        xs = float(x) * (len(topa)-1) / (width-1)\r
+        xf = math.floor(xs)\r
+        xc = math.ceil(xs)\r
+        topf = topa[int(xf)]\r
+        topc = topa[int(xc)]\r
+        if xf == xc:\r
+            top = topf\r
+        else:\r
+            top = topf * (xc-xs) + topc * (xs-xf)\r
+        top = math.floor(top)\r
+        bot = round(botl + (botr-botl) * x/(width-1))\r
+\r
+        for y in range(int(top), int(bot)):\r
+            pixel(x, y, cK, canvas)\r
+\r
+    # Now draw the brim.\r
+    for x in range(int(width)):\r
+        brimtop = brimtopc + brimm * x\r
+        brimbot = brimbotc + brimm * x\r
+        for y in range(int(math.floor(brimtop)), int(math.ceil(brimbot))):\r
+            tophere = max(min(brimtop - y, 1), 0)\r
+            bothere = max(min(brimbot - y, 1), 0)\r
+            grey = bothere - tophere\r
+            # Only draw brim pixels over pixels which are (a) part\r
+            # of the main hat, and (b) not right on its edge.\r
+            if canvas.has_key((x,y)) and \\r
+            canvas.has_key((x,y-1)) and \\r
+            canvas.has_key((x,y+1)) and \\r
+            canvas.has_key((x-1,y)) and \\r
+            canvas.has_key((x+1,y)):\r
+                pixel(x, y, greypix(grey), canvas)\r
+\r
+    return canvas\r
+\r
+def key(size):\r
+    canvas = {}\r
+\r
+    # The key in the PuTTYgen icon.\r
+\r
+    keyheadw = round(9.5*size)\r
+    keyheadh = round(12*size)\r
+    keyholed = round(4*size)\r
+    keyholeoff = round(2*size)\r
+    # Ensure keyheadh and keyshafth have the same parity.\r
+    keyshafth = round((2*size - (int(keyheadh)&1)) / 2) * 2 + (int(keyheadh)&1)\r
+    keyshaftw = round(18.5*size)\r
+    keyhead = [round(x*size) for x in [12,11,8,10,9,8,11,12]]\r
+\r
+    squarepix = []\r
+\r
+    # Ellipse for the key head, minus an off-centre circular hole.\r
+    for y in range(int(keyheadh)):\r
+        dy = (y-(keyheadh-1)/2.0) / (keyheadh/2.0)\r
+        dyh = (y-(keyheadh-1)/2.0) / (keyholed/2.0)\r
+        for x in range(int(keyheadw)):\r
+            dx = (x-(keyheadw-1)/2.0) / (keyheadw/2.0)\r
+            dxh = (x-(keyheadw-1)/2.0-keyholeoff) / (keyholed/2.0)\r
+            if dy*dy+dx*dx <= 1 and dyh*dyh+dxh*dxh > 1:\r
+                pixel(x + keyshaftw, y, cy, canvas)\r
+\r
+    # Rectangle for the key shaft, extended at the bottom for the\r
+    # key head detail.\r
+    for x in range(int(keyshaftw)):\r
+        top = round((keyheadh - keyshafth) / 2)\r
+        bot = round((keyheadh + keyshafth) / 2)\r
+        xs = float(x) * (len(keyhead)-1) / round((len(keyhead)-1)*size)\r
+        xf = math.floor(xs)\r
+        xc = math.ceil(xs)\r
+        in_head = 0\r
+        if xc < len(keyhead):\r
+            in_head = 1\r
+            yf = keyhead[int(xf)]\r
+            yc = keyhead[int(xc)]\r
+            if xf == xc:\r
+                bot = yf\r
+            else:\r
+                bot = yf * (xc-xs) + yc * (xs-xf)\r
+        for y in range(int(top),int(bot)):\r
+            pixel(x, y, cy, canvas)\r
+            if in_head:\r
+                last = (x, y)\r
+        if x == 0:\r
+            squarepix.append((x, int(top), TL))\r
+        if x == 0:\r
+            squarepix.append(last + (BL,))\r
+        if last != None and not in_head:\r
+            squarepix.append(last + (BR,))\r
+            last = None\r
+\r
+    # And draw a border.\r
+    border(canvas, size, squarepix)\r
+\r
+    return canvas\r
+\r
+def linedist(x1,y1, x2,y2, x,y):\r
+    # Compute the distance from the point x,y to the line segment\r
+    # joining x1,y1 to x2,y2. Returns the distance vector, measured\r
+    # with x,y at the origin.\r
+\r
+    vectors = []\r
+\r
+    # Special case: if x1,y1 and x2,y2 are the same point, we\r
+    # don't attempt to extrapolate it into a line at all.\r
+    if x1 != x2 or y1 != y2:\r
+        # First, find the nearest point to x,y on the infinite\r
+        # projection of the line segment. So we construct a vector\r
+        # n perpendicular to that segment...\r
+        nx = y2-y1\r
+        ny = x1-x2\r
+        # ... compute the dot product of (x1,y1)-(x,y) with that\r
+        # vector...\r
+        nd = (x1-x)*nx + (y1-y)*ny\r
+        # ... multiply by the vector we first thought of...\r
+        ndx = nd * nx\r
+        ndy = nd * ny\r
+        # ... and divide twice by the length of n.\r
+        ndx = ndx / (nx*nx+ny*ny)\r
+        ndy = ndy / (nx*nx+ny*ny)\r
+        # That gives us a displacement vector from x,y to the\r
+        # nearest point. See if it's within the range of the line\r
+        # segment.\r
+        cx = x + ndx\r
+        cy = y + ndy\r
+        if cx >= min(x1,x2) and cx <= max(x1,x2) and \\r
+        cy >= min(y1,y2) and cy <= max(y1,y2):\r
+            vectors.append((ndx,ndy))\r
+\r
+    # Now we have up to three candidate result vectors: (ndx,ndy)\r
+    # as computed just above, and the two vectors to the ends of\r
+    # the line segment, (x1-x,y1-y) and (x2-x,y2-y). Pick the\r
+    # shortest.\r
+    vectors = vectors + [(x1-x,y1-y), (x2-x,y2-y)]\r
+    bestlen, best = None, None\r
+    for v in vectors:\r
+        vlen = v[0]*v[0]+v[1]*v[1]\r
+        if bestlen == None or bestlen > vlen:\r
+            bestlen = vlen\r
+            best = v\r
+    return best\r
+\r
+def spanner(size):\r
+    canvas = {}\r
+\r
+    # The spanner in the config box icon.\r
+\r
+    headcentre = 0.5 + round(4*size)\r
+    headradius = headcentre + 0.1\r
+    headhighlight = round(1.5*size)\r
+    holecentre = 0.5 + round(3*size)\r
+    holeradius = round(2*size)\r
+    holehighlight = round(1.5*size)\r
+    shaftend = 0.5 + round(25*size)\r
+    shaftwidth = round(2*size)\r
+    shafthighlight = round(1.5*size)\r
+    cmax = shaftend + shaftwidth\r
+\r
+    # Define three line segments, such that the shortest distance\r
+    # vectors from any point to each of these segments determines\r
+    # everything we need to know about where it is on the spanner\r
+    # shape.\r
+    segments = [\r
+    ((0,0), (holecentre, holecentre)),\r
+    ((headcentre, headcentre), (headcentre, headcentre)),\r
+    ((headcentre+headradius/math.sqrt(2), headcentre+headradius/math.sqrt(2)),\r
+    (cmax, cmax))\r
+    ]\r
+\r
+    for y in range(int(cmax)):\r
+        for x in range(int(cmax)):\r
+            vectors = [linedist(a,b,c,d,x,y) for ((a,b),(c,d)) in segments]\r
+            dists = [memoisedsqrt(vx*vx+vy*vy) for (vx,vy) in vectors]\r
+\r
+            # If the distance to the hole line is less than\r
+            # holeradius, we're not part of the spanner.\r
+            if dists[0] < holeradius:\r
+                continue\r
+            # If the distance to the head `line' is less than\r
+            # headradius, we are part of the spanner; likewise if\r
+            # the distance to the shaft line is less than\r
+            # shaftwidth _and_ the resulting shaft point isn't\r
+            # beyond the shaft end.\r
+            if dists[1] > headradius and \\r
+            (dists[2] > shaftwidth or x+vectors[2][0] >= shaftend):\r
+                continue\r
+\r
+            # We're part of the spanner. Now compute the highlight\r
+            # on this pixel. We do this by computing a `slope\r
+            # vector', which points from this pixel in the\r
+            # direction of its nearest edge. We store an array of\r
+            # slope vectors, in polar coordinates.\r
+            angles = [math.atan2(vy,vx) for (vx,vy) in vectors]\r
+            slopes = []\r
+            if dists[0] < holeradius + holehighlight:\r
+                slopes.append(((dists[0]-holeradius)/holehighlight,angles[0]))\r
+            if dists[1]/headradius < dists[2]/shaftwidth:\r
+                if dists[1] > headradius - headhighlight and dists[1] < headradius:\r
+                    slopes.append(((headradius-dists[1])/headhighlight,math.pi+angles[1]))\r
+            else:\r
+                if dists[2] > shaftwidth - shafthighlight and dists[2] < shaftwidth:\r
+                    slopes.append(((shaftwidth-dists[2])/shafthighlight,math.pi+angles[2]))\r
+            # Now we find the smallest distance in that array, if\r
+            # any, and that gives us a notional position on a\r
+            # sphere which we can use to compute the final\r
+            # highlight level.\r
+            bestdist = None\r
+            bestangle = 0\r
+            for dist, angle in slopes:\r
+                if bestdist == None or bestdist > dist:\r
+                    bestdist = dist\r
+                    bestangle = angle\r
+            if bestdist == None:\r
+                bestdist = 1.0\r
+            sx = (1.0-bestdist) * math.cos(bestangle)\r
+            sy = (1.0-bestdist) * math.sin(bestangle)\r
+            sz = math.sqrt(1.0 - sx*sx - sy*sy)\r
+            shade = sx-sy+sz / math.sqrt(3) # can range from -1 to +1\r
+            shade = 1.0 - (1-shade)/3\r
+\r
+            pixel(x, y, yellowpix(shade), canvas)\r
+\r
+    # And draw a border.\r
+    border(canvas, size, [])\r
+\r
+    return canvas\r
+\r
+def box(size, back):\r
+    canvas = {}\r
+\r
+    # The back side of the cardboard box in the installer icon.\r
+\r
+    boxwidth = round(15 * size)\r
+    boxheight = round(12 * size)\r
+    boxdepth = round(4 * size)\r
+    boxfrontflapheight = round(5 * size)\r
+    boxrightflapheight = round(3 * size)\r
+\r
+    # Three shades of basically acceptable brown, all achieved by\r
+    # halftoning between two of the Windows-16 colours. I'm quite\r
+    # pleased that was feasible at all!\r
+    dark = halftone(cr, cK)\r
+    med = halftone(cr, cy)\r
+    light = halftone(cr, cY)\r
+    # We define our halftoning parity in such a way that the black\r
+    # pixels along the RHS of the visible part of the box back\r
+    # match up with the one-pixel black outline around the\r
+    # right-hand side of the box. In other words, we want the pixel\r
+    # at (-1, boxwidth-1) to be black, and hence the one at (0,\r
+    # boxwidth) too.\r
+    parityadjust = int(boxwidth) % 2\r
+\r
+    # The entire back of the box.\r
+    if back:\r
+        for x in range(int(boxwidth + boxdepth)):\r
+            ytop = max(-x-1, -boxdepth-1)\r
+            ybot = min(boxheight, boxheight+boxwidth-1-x)\r
+            for y in range(int(ytop), int(ybot)):\r
+                pixel(x, y, dark[(x+y+parityadjust) % 2], canvas)\r
+\r
+    # Even when drawing the back of the box, we still draw the\r
+    # whole shape, because that means we get the right overall size\r
+    # (the flaps make the box front larger than the box back) and\r
+    # it'll all be overwritten anyway.\r
+\r
+    # The front face of the box.\r
+    for x in range(int(boxwidth)):\r
+        for y in range(int(boxheight)):\r
+            pixel(x, y, med[(x+y+parityadjust) % 2], canvas)\r
+    # The right face of the box.\r
+    for x in range(int(boxwidth), int(boxwidth+boxdepth)):\r
+        ybot = boxheight + boxwidth-x\r
+        ytop = ybot - boxheight\r
+        for y in range(int(ytop), int(ybot)):\r
+            pixel(x, y, dark[(x+y+parityadjust) % 2], canvas)\r
+    # The front flap of the box.\r
+    for y in range(int(boxfrontflapheight)):\r
+        xadj = int(round(-0.5*y))\r
+        for x in range(int(xadj), int(xadj+boxwidth)):\r
+            pixel(x, y, light[(x+y+parityadjust) % 2], canvas)\r
+    # The right flap of the box.\r
+    for x in range(int(boxwidth), int(boxwidth + boxdepth + boxrightflapheight + 1)):\r
+        ytop = max(boxwidth - 1 - x, x - boxwidth - 2*boxdepth - 1)\r
+        ybot = min(x - boxwidth - 1, boxwidth + 2*boxrightflapheight - 1 - x)\r
+        for y in range(int(ytop), int(ybot+1)):\r
+            pixel(x, y, med[(x+y+parityadjust) % 2], canvas)\r
+\r
+    # And draw a border.\r
+    border(canvas, size, [(0, int(boxheight)-1, BL)])\r
+\r
+    return canvas\r
+\r
+def boxback(size):\r
+    return box(size, 1)\r
+def boxfront(size):\r
+    return box(size, 0)\r
+\r
+# Functions to draw entire icons by composing the above components.\r
+\r
+def xybolt(c1, c2, size, boltoffx=0, boltoffy=0, aux={}):\r
+    # Two unspecified objects and a lightning bolt.\r
+\r
+    canvas = {}\r
+    w = h = round(32 * size)\r
+\r
+    bolt = lightning(size)\r
+\r
+    # Position c2 against the top right of the icon.\r
+    bb = bbox(c2)\r
+    assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h\r
+    overlay(c2, w-bb[2], 0-bb[1], canvas)\r
+    aux["c2pos"] = (w-bb[2], 0-bb[1])\r
+    # Position c1 against the bottom left of the icon.\r
+    bb = bbox(c1)\r
+    assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h\r
+    overlay(c1, 0-bb[0], h-bb[3], canvas)\r
+    aux["c1pos"] = (0-bb[0], h-bb[3])\r
+    # Place the lightning bolt artistically off-centre. (The\r
+    # rationale for this positioning is that it's centred on the\r
+    # midpoint between the centres of the two monitors in the PuTTY\r
+    # icon proper, but it's not really feasible to _base_ the\r
+    # calculation here on that.)\r
+    bb = bbox(bolt)\r
+    assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h\r
+    overlay(bolt, (w-bb[0]-bb[2])/2 + round(boltoffx*size), \\r
+    (h-bb[1]-bb[3])/2 + round((boltoffy-2)*size), canvas)\r
+\r
+    return canvas\r
+\r
+def putty_icon(size):\r
+    return xybolt(computer(size), computer(size), size)\r
+\r
+def puttycfg_icon(size):\r
+    w = h = round(32 * size)\r
+    s = spanner(size)\r
+    canvas = putty_icon(size)\r
+    # Centre the spanner.\r
+    bb = bbox(s)\r
+    overlay(s, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas)\r
+    return canvas\r
+\r
+def puttygen_icon(size):\r
+    return xybolt(computer(size), key(size), size, boltoffx=2)\r
+\r
+def pscp_icon(size):\r
+    return xybolt(document(size), computer(size), size)\r
+\r
+def puttyins_icon(size):\r
+    aret = {}\r
+    # The box back goes behind the lightning bolt.\r
+    canvas = xybolt(boxback(size), computer(size), size, boltoffx=-2, boltoffy=+1, aux=aret)\r
+    # But the box front goes over the top, so that the lightning\r
+    # bolt appears to come _out_ of the box. Here it's useful to\r
+    # know the exact coordinates where xybolt placed the box back,\r
+    # so we can overlay the box front exactly on top of it.\r
+    c1x, c1y = aret["c1pos"]\r
+    overlay(boxfront(size), c1x, c1y, canvas)\r
+    return canvas\r
+\r
+def pterm_icon(size):\r
+    # Just a really big computer.\r
+\r
+    canvas = {}\r
+    w = h = round(32 * size)\r
+\r
+    c = computer(size * 1.4)\r
+\r
+    # Centre c in the return canvas.\r
+    bb = bbox(c)\r
+    assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h\r
+    overlay(c, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas)\r
+\r
+    return canvas\r
+\r
+def ptermcfg_icon(size):\r
+    w = h = round(32 * size)\r
+    s = spanner(size)\r
+    canvas = pterm_icon(size)\r
+    # Centre the spanner.\r
+    bb = bbox(s)\r
+    overlay(s, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas)\r
+    return canvas\r
+\r
+def pageant_icon(size):\r
+    # A biggish computer, in a hat.\r
+\r
+    canvas = {}\r
+    w = h = round(32 * size)\r
+\r
+    c = computer(size * 1.2)\r
+    ht = hat(size)\r
+\r
+    cbb = bbox(c)\r
+    hbb = bbox(ht)\r
+\r
+    # Determine the relative y-coordinates of the computer and hat.\r
+    # We just centre the one on the other.\r
+    xrel = (cbb[0]+cbb[2]-hbb[0]-hbb[2])/2\r
+\r
+    # Determine the relative y-coordinates of the computer and hat.\r
+    # We do this by sitting the hat as low down on the computer as\r
+    # possible without any computer showing over the top. To do\r
+    # this we first have to find the minimum x coordinate at each\r
+    # y-coordinate of both components.\r
+    cty = topy(c)\r
+    hty = topy(ht)\r
+    yrelmin = None\r
+    for cx in cty.keys():\r
+        hx = cx - xrel\r
+        assert hty.has_key(hx)\r
+        yrel = cty[cx] - hty[hx]\r
+        if yrelmin == None:\r
+            yrelmin = yrel\r
+        else:\r
+            yrelmin = min(yrelmin, yrel)\r
+\r
+    # Overlay the hat on the computer.\r
+    overlay(ht, xrel, yrelmin, c)\r
+\r
+    # And centre the result in the main icon canvas.\r
+    bb = bbox(c)\r
+    assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h\r
+    overlay(c, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas)\r
+\r
+    return canvas\r
+\r
+# Test and output functions.\r
+\r
+import os\r
+import sys\r
+\r
+def testrun(func, fname):\r
+    canvases = []\r
+    for size in [0.5, 0.6, 1.0, 1.2, 1.5, 4.0]:\r
+        canvases.append(func(size))\r
+    wid = 0\r
+    ht = 0\r
+    for canvas in canvases:\r
+        minx, miny, maxx, maxy = bbox(canvas)\r
+        wid = max(wid, maxx-minx+4)\r
+        ht = ht + maxy-miny+4\r
+    block = []\r
+    for canvas in canvases:\r
+        minx, miny, maxx, maxy = bbox(canvas)\r
+        block.extend(render(canvas, minx-2, miny-2, minx-2+wid, maxy+2))\r
+    p = os.popen("convert -depth 8 -size %dx%d rgb:- %s" % (wid,ht,fname), "w")\r
+    assert len(block) == ht\r
+    for line in block:\r
+        assert len(line) == wid\r
+        for r, g, b, a in line:\r
+            # Composite on to orange.\r
+            r = int(round((r * a + 255 * (255-a)) / 255.0))\r
+            g = int(round((g * a + 128 * (255-a)) / 255.0))\r
+            b = int(round((b * a +   0 * (255-a)) / 255.0))\r
+            p.write("%c%c%c" % (r,g,b))\r
+    p.close()\r
+\r
+def drawicon(func, width, fname, orangebackground = 0):\r
+    canvas = func(width / 32.0)\r
+    finalise(canvas)\r
+    minx, miny, maxx, maxy = bbox(canvas)\r
+    assert minx >= 0 and miny >= 0 and maxx <= width and maxy <= width\r
+\r
+    block = render(canvas, 0, 0, width, width)\r
+    p = os.popen("convert -depth 8 -size %dx%d rgba:- %s" % (width,width,fname), "w")\r
+    assert len(block) == width\r
+    for line in block:\r
+        assert len(line) == width\r
+        for r, g, b, a in line:\r
+            if orangebackground:\r
+                # Composite on to orange.\r
+                r = int(round((r * a + 255 * (255-a)) / 255.0))\r
+                g = int(round((g * a + 128 * (255-a)) / 255.0))\r
+                b = int(round((b * a +   0 * (255-a)) / 255.0))\r
+                a = 255\r
+            p.write("%c%c%c%c" % (r,g,b,a))\r
+    p.close()\r
+\r
+args = sys.argv[1:]\r
+\r
+orangebackground = test = 0\r
+colours = 1 # 0=mono, 1=16col, 2=truecol\r
+doingargs = 1\r
+\r
+realargs = []\r
+for arg in args:\r
+    if doingargs and arg[0] == "-":\r
+        if arg == "-t":\r
+            test = 1\r
+        elif arg == "-it":\r
+            orangebackground = 1\r
+        elif arg == "-2":\r
+            colours = 0\r
+        elif arg == "-T":\r
+            colours = 2\r
+        elif arg == "--":\r
+            doingargs = 0\r
+        else:\r
+            sys.stderr.write("unrecognised option '%s'\n" % arg)\r
+            sys.exit(1)\r
+    else:\r
+        realargs.append(arg)\r
+\r
+if colours == 0:\r
+    # Monochrome.\r
+    cK=cr=cg=cb=cm=cc=cP=cw=cR=cG=cB=cM=cC=cD = 0\r
+    cY=cy=cW = 1\r
+    cT = -1\r
+    def greypix(value):\r
+        return [cK,cW][int(round(value))]\r
+    def yellowpix(value):\r
+        return [cK,cW][int(round(value))]\r
+    def bluepix(value):\r
+        return cK\r
+    def dark(value):\r
+        return [cT,cK][int(round(value))]\r
+    def blend(col1, col2):\r
+        if col1 == cT:\r
+            return col2\r
+        else:\r
+            return col1\r
+    pixvals = [\r
+    (0x00, 0x00, 0x00, 0xFF), # cK\r
+    (0xFF, 0xFF, 0xFF, 0xFF), # cW\r
+    (0x00, 0x00, 0x00, 0x00), # cT\r
+    ]\r
+    def outpix(colour):\r
+        return pixvals[colour]\r
+    def finalisepix(colour):\r
+        return colour\r
+    def halftone(col1, col2):\r
+        return (col1, col2)\r
+elif colours == 1:\r
+    # Windows 16-colour palette.\r
+    cK,cr,cg,cy,cb,cm,cc,cP,cw,cR,cG,cY,cB,cM,cC,cW = range(16)\r
+    cT = -1\r
+    cD = -2 # special translucent half-darkening value used internally\r
+    def greypix(value):\r
+        return [cK,cw,cw,cP,cW][int(round(4*value))]\r
+    def yellowpix(value):\r
+        return [cK,cy,cY][int(round(2*value))]\r
+    def bluepix(value):\r
+        return [cK,cb,cB][int(round(2*value))]\r
+    def dark(value):\r
+        return [cT,cD,cK][int(round(2*value))]\r
+    def blend(col1, col2):\r
+        if col1 == cT:\r
+            return col2\r
+        elif col1 == cD:\r
+            return [cK,cK,cK,cK,cK,cK,cK,cw,cK,cr,cg,cy,cb,cm,cc,cw,cD,cD][col2]\r
+        else:\r
+            return col1\r
+    pixvals = [\r
+    (0x00, 0x00, 0x00, 0xFF), # cK\r
+    (0x80, 0x00, 0x00, 0xFF), # cr\r
+    (0x00, 0x80, 0x00, 0xFF), # cg\r
+    (0x80, 0x80, 0x00, 0xFF), # cy\r
+    (0x00, 0x00, 0x80, 0xFF), # cb\r
+    (0x80, 0x00, 0x80, 0xFF), # cm\r
+    (0x00, 0x80, 0x80, 0xFF), # cc\r
+    (0xC0, 0xC0, 0xC0, 0xFF), # cP\r
+    (0x80, 0x80, 0x80, 0xFF), # cw\r
+    (0xFF, 0x00, 0x00, 0xFF), # cR\r
+    (0x00, 0xFF, 0x00, 0xFF), # cG\r
+    (0xFF, 0xFF, 0x00, 0xFF), # cY\r
+    (0x00, 0x00, 0xFF, 0xFF), # cB\r
+    (0xFF, 0x00, 0xFF, 0xFF), # cM\r
+    (0x00, 0xFF, 0xFF, 0xFF), # cC\r
+    (0xFF, 0xFF, 0xFF, 0xFF), # cW\r
+    (0x00, 0x00, 0x00, 0x80), # cD\r
+    (0x00, 0x00, 0x00, 0x00), # cT\r
+    ]\r
+    def outpix(colour):\r
+        return pixvals[colour]\r
+    def finalisepix(colour):\r
+        # cD is used internally, but can't be output. Convert to cK.\r
+        if colour == cD:\r
+            return cK\r
+        return colour\r
+    def halftone(col1, col2):\r
+        return (col1, col2)\r
+else:\r
+    # True colour.\r
+    cK = (0x00, 0x00, 0x00, 0xFF)\r
+    cr = (0x80, 0x00, 0x00, 0xFF)\r
+    cg = (0x00, 0x80, 0x00, 0xFF)\r
+    cy = (0x80, 0x80, 0x00, 0xFF)\r
+    cb = (0x00, 0x00, 0x80, 0xFF)\r
+    cm = (0x80, 0x00, 0x80, 0xFF)\r
+    cc = (0x00, 0x80, 0x80, 0xFF)\r
+    cP = (0xC0, 0xC0, 0xC0, 0xFF)\r
+    cw = (0x80, 0x80, 0x80, 0xFF)\r
+    cR = (0xFF, 0x00, 0x00, 0xFF)\r
+    cG = (0x00, 0xFF, 0x00, 0xFF)\r
+    cY = (0xFF, 0xFF, 0x00, 0xFF)\r
+    cB = (0x00, 0x00, 0xFF, 0xFF)\r
+    cM = (0xFF, 0x00, 0xFF, 0xFF)\r
+    cC = (0x00, 0xFF, 0xFF, 0xFF)\r
+    cW = (0xFF, 0xFF, 0xFF, 0xFF)\r
+    cD = (0x00, 0x00, 0x00, 0x80)\r
+    cT = (0x00, 0x00, 0x00, 0x00)\r
+    def greypix(value):\r
+        value = max(min(value, 1), 0)\r
+        return (int(round(0xFF*value)),) * 3 + (0xFF,)\r
+    def yellowpix(value):\r
+        value = max(min(value, 1), 0)\r
+        return (int(round(0xFF*value)),) * 2 + (0, 0xFF)\r
+    def bluepix(value):\r
+        value = max(min(value, 1), 0)\r
+        return (0, 0, int(round(0xFF*value)), 0xFF)\r
+    def dark(value):\r
+        value = max(min(value, 1), 0)\r
+        return (0, 0, 0, int(round(0xFF*value)))\r
+    def blend(col1, col2):\r
+        r1,g1,b1,a1 = col1\r
+        r2,g2,b2,a2 = col2\r
+        r = int(round((r1*a1 + r2*(0xFF-a1)) / 255.0))\r
+        g = int(round((g1*a1 + g2*(0xFF-a1)) / 255.0))\r
+        b = int(round((b1*a1 + b2*(0xFF-a1)) / 255.0))\r
+        a = int(round((255*a1 + a2*(0xFF-a1)) / 255.0))\r
+        return r, g, b, a\r
+    def outpix(colour):\r
+        return colour\r
+    if colours == 2:\r
+        # True colour with no alpha blending: we still have to\r
+        # finalise half-dark pixels to black.\r
+        def finalisepix(colour):\r
+            if colour[3] > 0:\r
+                return colour[:3] + (0xFF,)\r
+            return colour\r
+    else:\r
+        def finalisepix(colour):\r
+            return colour\r
+    def halftone(col1, col2):\r
+        r1,g1,b1,a1 = col1\r
+        r2,g2,b2,a2 = col2\r
+        colret = (int(r1+r2)/2, int(g1+g2)/2, int(b1+b2)/2, int(a1+a2)/2)\r
+        return (colret, colret)\r
+\r
+if test:\r
+    testrun(eval(realargs[0]), realargs[1])\r
+else:\r
+    drawicon(eval(realargs[0]), int(realargs[1]), realargs[2], orangebackground)\r