| 1 | #!/usr/bin/env python |
| 2 | |
| 3 | import math |
| 4 | |
| 5 | # Python code which draws the PuTTY icon components at a range of |
| 6 | # sizes. |
| 7 | |
| 8 | # TODO |
| 9 | # ---- |
| 10 | # |
| 11 | # - use of alpha blending |
| 12 | # + try for variable-transparency borders |
| 13 | # |
| 14 | # - can we integrate the Mac icons into all this? Do we want to? |
| 15 | |
| 16 | def pixel(x, y, colour, canvas): |
| 17 | canvas[(int(x),int(y))] = colour |
| 18 | |
| 19 | def overlay(src, x, y, dst): |
| 20 | x = int(x) |
| 21 | y = int(y) |
| 22 | for (sx, sy), colour in src.items(): |
| 23 | dst[sx+x, sy+y] = blend(colour, dst.get((sx+x, sy+y), cT)) |
| 24 | |
| 25 | def finalise(canvas): |
| 26 | for k in canvas.keys(): |
| 27 | canvas[k] = finalisepix(canvas[k]) |
| 28 | |
| 29 | def bbox(canvas): |
| 30 | minx, miny, maxx, maxy = None, None, None, None |
| 31 | for (x, y) in canvas.keys(): |
| 32 | if minx == None: |
| 33 | minx, miny, maxx, maxy = x, y, x+1, y+1 |
| 34 | else: |
| 35 | minx = min(minx, x) |
| 36 | miny = min(miny, y) |
| 37 | maxx = max(maxx, x+1) |
| 38 | maxy = max(maxy, y+1) |
| 39 | return (minx, miny, maxx, maxy) |
| 40 | |
| 41 | def topy(canvas): |
| 42 | miny = {} |
| 43 | for (x, y) in canvas.keys(): |
| 44 | miny[x] = min(miny.get(x, y), y) |
| 45 | return miny |
| 46 | |
| 47 | def render(canvas, minx, miny, maxx, maxy): |
| 48 | w = maxx - minx |
| 49 | h = maxy - miny |
| 50 | ret = [] |
| 51 | for y in range(h): |
| 52 | ret.append([outpix(cT)] * w) |
| 53 | for (x, y), colour in canvas.items(): |
| 54 | if x >= minx and x < maxx and y >= miny and y < maxy: |
| 55 | ret[y-miny][x-minx] = outpix(colour) |
| 56 | return ret |
| 57 | |
| 58 | # Code to actually draw pieces of icon. These don't generally worry |
| 59 | # about positioning within a canvas; they just draw at a standard |
| 60 | # location, return some useful coordinates, and leave composition |
| 61 | # to other pieces of code. |
| 62 | |
| 63 | sqrthash = {} |
| 64 | def memoisedsqrt(x): |
| 65 | if not sqrthash.has_key(x): |
| 66 | sqrthash[x] = math.sqrt(x) |
| 67 | return sqrthash[x] |
| 68 | |
| 69 | BR, TR, BL, TL = range(4) # enumeration of quadrants for border() |
| 70 | |
| 71 | def border(canvas, thickness, squarecorners, out={}): |
| 72 | # I haven't yet worked out exactly how to do borders in a |
| 73 | # properly alpha-blended fashion. |
| 74 | # |
| 75 | # When you have two shades of dark available (half-dark H and |
| 76 | # full-dark F), the right sequence of circular border sections |
| 77 | # around a pixel x starts off with these two layouts: |
| 78 | # |
| 79 | # H F |
| 80 | # HxH FxF |
| 81 | # H F |
| 82 | # |
| 83 | # Where it goes after that I'm not entirely sure, but I'm |
| 84 | # absolutely sure those are the right places to start. However, |
| 85 | # every automated algorithm I've tried has always started off |
| 86 | # with the two layouts |
| 87 | # |
| 88 | # H HHH |
| 89 | # HxH HxH |
| 90 | # H HHH |
| 91 | # |
| 92 | # which looks much worse. This is true whether you do |
| 93 | # pixel-centre sampling (define an inner circle and an outer |
| 94 | # circle with radii differing by 1, set any pixel whose centre |
| 95 | # is inside the inner circle to F, any pixel whose centre is |
| 96 | # outside the outer one to nothing, interpolate between the two |
| 97 | # and round sensibly), _or_ whether you plot a notional circle |
| 98 | # of a given radius and measure the actual _proportion_ of each |
| 99 | # pixel square taken up by it. |
| 100 | # |
| 101 | # It's not clear what I should be doing to prevent this. One |
| 102 | # option is to attempt error-diffusion: Ian Jackson proved on |
| 103 | # paper that if you round each pixel's ideal value to the |
| 104 | # nearest of the available output values, then measure the |
| 105 | # error at each pixel, propagate that error outwards into the |
| 106 | # original values of the surrounding pixels, and re-round |
| 107 | # everything, you do get the correct second stage. However, I |
| 108 | # haven't tried it at a proper range of radii. |
| 109 | # |
| 110 | # Another option is that the automated mechanisms described |
| 111 | # above would be entirely adequate if it weren't for the fact |
| 112 | # that the human visual centres are adapted to detect |
| 113 | # horizontal and vertical lines in particular, so the only |
| 114 | # place you have to behave a bit differently is at the ends of |
| 115 | # the top and bottom row of pixels in the circle, and the top |
| 116 | # and bottom of the extreme columns. |
| 117 | # |
| 118 | # For the moment, what I have below is a very simple mechanism |
| 119 | # which always uses only one alpha level for any given border |
| 120 | # thickness, and which seems to work well enough for Windows |
| 121 | # 16-colour icons. Everything else will have to wait. |
| 122 | |
| 123 | thickness = memoisedsqrt(thickness) |
| 124 | |
| 125 | if thickness < 0.9: |
| 126 | darkness = 0.5 |
| 127 | else: |
| 128 | darkness = 1 |
| 129 | if thickness < 1: thickness = 1 |
| 130 | thickness = round(thickness - 0.5) + 0.3 |
| 131 | |
| 132 | out["borderthickness"] = thickness |
| 133 | |
| 134 | dmax = int(round(thickness)) |
| 135 | if dmax < thickness: dmax = dmax + 1 |
| 136 | |
| 137 | cquadrant = [[0] * (dmax+1) for x in range(dmax+1)] |
| 138 | squadrant = [[0] * (dmax+1) for x in range(dmax+1)] |
| 139 | |
| 140 | for x in range(dmax+1): |
| 141 | for y in range(dmax+1): |
| 142 | if max(x, y) < thickness: |
| 143 | squadrant[x][y] = darkness |
| 144 | if memoisedsqrt(x*x+y*y) < thickness: |
| 145 | cquadrant[x][y] = darkness |
| 146 | |
| 147 | bvalues = {} |
| 148 | for (x, y), colour in canvas.items(): |
| 149 | for dx in range(-dmax, dmax+1): |
| 150 | for dy in range(-dmax, dmax+1): |
| 151 | quadrant = 2 * (dx < 0) + (dy < 0) |
| 152 | if (x, y, quadrant) in squarecorners: |
| 153 | bval = squadrant[abs(dx)][abs(dy)] |
| 154 | else: |
| 155 | bval = cquadrant[abs(dx)][abs(dy)] |
| 156 | if bvalues.get((x+dx,y+dy),0) < bval: |
| 157 | bvalues[(x+dx,y+dy)] = bval |
| 158 | |
| 159 | for (x, y), value in bvalues.items(): |
| 160 | if not canvas.has_key((x,y)): |
| 161 | canvas[(x,y)] = dark(value) |
| 162 | |
| 163 | def sysbox(size, out={}): |
| 164 | canvas = {} |
| 165 | |
| 166 | # The system box of the computer. |
| 167 | |
| 168 | height = int(round(3.6*size)) |
| 169 | width = int(round(16.51*size)) |
| 170 | depth = int(round(2*size)) |
| 171 | highlight = int(round(1*size)) |
| 172 | bothighlight = int(round(1*size)) |
| 173 | |
| 174 | out["sysboxheight"] = height |
| 175 | |
| 176 | floppystart = int(round(19*size)) # measured in half-pixels |
| 177 | floppyend = int(round(29*size)) # measured in half-pixels |
| 178 | floppybottom = height - bothighlight |
| 179 | floppyrheight = 0.7 * size |
| 180 | floppyheight = int(round(floppyrheight)) |
| 181 | if floppyheight < 1: |
| 182 | floppyheight = 1 |
| 183 | floppytop = floppybottom - floppyheight |
| 184 | |
| 185 | # The front panel is rectangular. |
| 186 | for x in range(width): |
| 187 | for y in range(height): |
| 188 | grey = 3 |
| 189 | if x < highlight or y < highlight: |
| 190 | grey = grey + 1 |
| 191 | if x >= width-highlight or y >= height-bothighlight: |
| 192 | grey = grey - 1 |
| 193 | if y < highlight and x >= width-highlight: |
| 194 | v = (highlight-1-y) - (x-(width-highlight)) |
| 195 | if v < 0: |
| 196 | grey = grey - 1 |
| 197 | elif v > 0: |
| 198 | grey = grey + 1 |
| 199 | if y >= floppytop and y < floppybottom and \ |
| 200 | 2*x+2 > floppystart and 2*x < floppyend: |
| 201 | if 2*x >= floppystart and 2*x+2 <= floppyend and \ |
| 202 | floppyrheight >= 0.7: |
| 203 | grey = 0 |
| 204 | else: |
| 205 | grey = 2 |
| 206 | pixel(x, y, greypix(grey/4.0), canvas) |
| 207 | |
| 208 | # The side panel is a parallelogram. |
| 209 | for x in range(depth): |
| 210 | for y in range(height): |
| 211 | pixel(x+width, y-(x+1), greypix(0.5), canvas) |
| 212 | |
| 213 | # The top panel is another parallelogram. |
| 214 | for x in range(width-1): |
| 215 | for y in range(depth): |
| 216 | grey = 3 |
| 217 | if x >= width-1 - highlight: |
| 218 | grey = grey + 1 |
| 219 | pixel(x+(y+1), -(y+1), greypix(grey/4.0), canvas) |
| 220 | |
| 221 | # And draw a border. |
| 222 | border(canvas, size, [], out) |
| 223 | |
| 224 | return canvas |
| 225 | |
| 226 | def monitor(size): |
| 227 | canvas = {} |
| 228 | |
| 229 | # The computer's monitor. |
| 230 | |
| 231 | height = int(round(9.55*size)) |
| 232 | width = int(round(11.49*size)) |
| 233 | surround = int(round(1*size)) |
| 234 | botsurround = int(round(2*size)) |
| 235 | sheight = height - surround - botsurround |
| 236 | swidth = width - 2*surround |
| 237 | depth = int(round(2*size)) |
| 238 | highlight = int(round(math.sqrt(size))) |
| 239 | shadow = int(round(0.55*size)) |
| 240 | |
| 241 | # The front panel is rectangular. |
| 242 | for x in range(width): |
| 243 | for y in range(height): |
| 244 | if x >= surround and y >= surround and \ |
| 245 | x < surround+swidth and y < surround+sheight: |
| 246 | # Screen. |
| 247 | sx = (float(x-surround) - swidth/3) / swidth |
| 248 | sy = (float(y-surround) - sheight/3) / sheight |
| 249 | shighlight = 1.0 - (sx*sx+sy*sy)*0.27 |
| 250 | pix = bluepix(shighlight) |
| 251 | if x < surround+shadow or y < surround+shadow: |
| 252 | pix = blend(cD, pix) # sharp-edged shadow on top and left |
| 253 | else: |
| 254 | # Complicated double bevel on the screen surround. |
| 255 | |
| 256 | # First, the outer bevel. We compute the distance |
| 257 | # from this pixel to each edge of the front |
| 258 | # rectangle. |
| 259 | list = [ |
| 260 | (x, +1), |
| 261 | (y, +1), |
| 262 | (width-1-x, -1), |
| 263 | (height-1-y, -1) |
| 264 | ] |
| 265 | # Now sort the list to find the distance to the |
| 266 | # _nearest_ edge, or the two joint nearest. |
| 267 | list.sort() |
| 268 | # If there's one nearest edge, that determines our |
| 269 | # bevel colour. If there are two joint nearest, our |
| 270 | # bevel colour is their shared one if they agree, |
| 271 | # and neutral otherwise. |
| 272 | outerbevel = 0 |
| 273 | if list[0][0] < list[1][0] or list[0][1] == list[1][1]: |
| 274 | if list[0][0] < highlight: |
| 275 | outerbevel = list[0][1] |
| 276 | |
| 277 | # Now, the inner bevel. We compute the distance |
| 278 | # from this pixel to each edge of the screen |
| 279 | # itself. |
| 280 | list = [ |
| 281 | (surround-1-x, -1), |
| 282 | (surround-1-y, -1), |
| 283 | (x-(surround+swidth), +1), |
| 284 | (y-(surround+sheight), +1) |
| 285 | ] |
| 286 | # Now we sort to find the _maximum_ distance, which |
| 287 | # conveniently ignores any less than zero. |
| 288 | list.sort() |
| 289 | # And now the strategy is pretty much the same as |
| 290 | # above, only we're working from the opposite end |
| 291 | # of the list. |
| 292 | innerbevel = 0 |
| 293 | if list[-1][0] > list[-2][0] or list[-1][1] == list[-2][1]: |
| 294 | if list[-1][0] >= 0 and list[-1][0] < highlight: |
| 295 | innerbevel = list[-1][1] |
| 296 | |
| 297 | # Now we know the adjustment we want to make to the |
| 298 | # pixel's overall grey shade due to the outer |
| 299 | # bevel, and due to the inner one. We break a tie |
| 300 | # in favour of a light outer bevel, but otherwise |
| 301 | # add. |
| 302 | grey = 3 |
| 303 | if outerbevel > 0 or outerbevel == innerbevel: |
| 304 | innerbevel = 0 |
| 305 | grey = grey + outerbevel + innerbevel |
| 306 | |
| 307 | pix = greypix(grey / 4.0) |
| 308 | |
| 309 | pixel(x, y, pix, canvas) |
| 310 | |
| 311 | # The side panel is a parallelogram. |
| 312 | for x in range(depth): |
| 313 | for y in range(height): |
| 314 | pixel(x+width, y-x, greypix(0.5), canvas) |
| 315 | |
| 316 | # The top panel is another parallelogram. |
| 317 | for x in range(width): |
| 318 | for y in range(depth-1): |
| 319 | pixel(x+(y+1), -(y+1), greypix(0.75), canvas) |
| 320 | |
| 321 | # And draw a border. |
| 322 | border(canvas, size, [(0,int(height-1),BL)]) |
| 323 | |
| 324 | return canvas |
| 325 | |
| 326 | def computer(size): |
| 327 | # Monitor plus sysbox. |
| 328 | out = {} |
| 329 | m = monitor(size) |
| 330 | s = sysbox(size, out) |
| 331 | x = int(round((2+size/(size+1))*size)) |
| 332 | y = int(out["sysboxheight"] + out["borderthickness"]) |
| 333 | mb = bbox(m) |
| 334 | sb = bbox(s) |
| 335 | xoff = sb[0] - mb[0] + x |
| 336 | yoff = sb[3] - mb[3] - y |
| 337 | overlay(m, xoff, yoff, s) |
| 338 | return s |
| 339 | |
| 340 | def lightning(size): |
| 341 | canvas = {} |
| 342 | |
| 343 | # The lightning bolt motif. |
| 344 | |
| 345 | # We always want this to be an even number of pixels in height, |
| 346 | # and an odd number in width. |
| 347 | width = round(7*size) * 2 - 1 |
| 348 | height = round(8*size) * 2 |
| 349 | |
| 350 | # The outer edge of each side of the bolt goes to this point. |
| 351 | outery = round(8.4*size) |
| 352 | outerx = round(11*size) |
| 353 | |
| 354 | # And the inner edge goes to this point. |
| 355 | innery = height - 1 - outery |
| 356 | innerx = round(7*size) |
| 357 | |
| 358 | for y in range(int(height)): |
| 359 | list = [] |
| 360 | if y <= outery: |
| 361 | list.append(width-1-int(outerx * float(y) / outery + 0.3)) |
| 362 | if y <= innery: |
| 363 | list.append(width-1-int(innerx * float(y) / innery + 0.3)) |
| 364 | y0 = height-1-y |
| 365 | if y0 <= outery: |
| 366 | list.append(int(outerx * float(y0) / outery + 0.3)) |
| 367 | if y0 <= innery: |
| 368 | list.append(int(innerx * float(y0) / innery + 0.3)) |
| 369 | list.sort() |
| 370 | for x in range(int(list[0]), int(list[-1]+1)): |
| 371 | pixel(x, y, cY, canvas) |
| 372 | |
| 373 | # And draw a border. |
| 374 | border(canvas, size, [(int(width-1),0,TR), (0,int(height-1),BL)]) |
| 375 | |
| 376 | return canvas |
| 377 | |
| 378 | def document(size): |
| 379 | canvas = {} |
| 380 | |
| 381 | # The document used in the PSCP/PSFTP icon. |
| 382 | |
| 383 | width = round(13*size) |
| 384 | height = round(16*size) |
| 385 | |
| 386 | lineht = round(1*size) |
| 387 | if lineht < 1: lineht = 1 |
| 388 | linespc = round(0.7*size) |
| 389 | if linespc < 1: linespc = 1 |
| 390 | nlines = int((height-linespc)/(lineht+linespc)) |
| 391 | height = nlines*(lineht+linespc)+linespc # round this so it fits better |
| 392 | |
| 393 | # Start by drawing a big white rectangle. |
| 394 | for y in range(int(height)): |
| 395 | for x in range(int(width)): |
| 396 | pixel(x, y, cW, canvas) |
| 397 | |
| 398 | # Now draw lines of text. |
| 399 | for line in range(nlines): |
| 400 | # Decide where this line of text begins. |
| 401 | if line == 0: |
| 402 | start = round(4*size) |
| 403 | elif line < 5*nlines/7: |
| 404 | start = round((line - (nlines/7)) * size) |
| 405 | else: |
| 406 | start = round(1*size) |
| 407 | if start < round(1*size): |
| 408 | start = round(1*size) |
| 409 | # Decide where it ends. |
| 410 | endpoints = [10, 8, 11, 6, 5, 7, 5] |
| 411 | ey = line * 6.0 / (nlines-1) |
| 412 | eyf = math.floor(ey) |
| 413 | eyc = math.ceil(ey) |
| 414 | exf = endpoints[int(eyf)] |
| 415 | exc = endpoints[int(eyc)] |
| 416 | if eyf == eyc: |
| 417 | end = exf |
| 418 | else: |
| 419 | end = exf * (eyc-ey) + exc * (ey-eyf) |
| 420 | end = round(end * size) |
| 421 | |
| 422 | liney = height - (lineht+linespc) * (line+1) |
| 423 | for x in range(int(start), int(end)): |
| 424 | for y in range(int(lineht)): |
| 425 | pixel(x, y+liney, cK, canvas) |
| 426 | |
| 427 | # And draw a border. |
| 428 | border(canvas, size, \ |
| 429 | [(0,0,TL),(int(width-1),0,TR),(0,int(height-1),BL), \ |
| 430 | (int(width-1),int(height-1),BR)]) |
| 431 | |
| 432 | return canvas |
| 433 | |
| 434 | def hat(size): |
| 435 | canvas = {} |
| 436 | |
| 437 | # The secret-agent hat in the Pageant icon. |
| 438 | |
| 439 | topa = [6]*9+[5,3,1,0,0,1,2,2,1,1,1,9,9,10,10,11,11,12,12] |
| 440 | topa = [round(x*size) for x in topa] |
| 441 | botl = round(topa[0]+2.4*math.sqrt(size)) |
| 442 | botr = round(topa[-1]+2.4*math.sqrt(size)) |
| 443 | width = round(len(topa)*size) |
| 444 | |
| 445 | # Line equations for the top and bottom of the hat brim, in the |
| 446 | # form y=mx+c. c, of course, needs scaling by size, but m is |
| 447 | # independent of size. |
| 448 | brimm = 1.0 / 3.75 |
| 449 | brimtopc = round(4*size/3) |
| 450 | brimbotc = round(10*size/3) |
| 451 | |
| 452 | for x in range(int(width)): |
| 453 | xs = float(x) * (len(topa)-1) / (width-1) |
| 454 | xf = math.floor(xs) |
| 455 | xc = math.ceil(xs) |
| 456 | topf = topa[int(xf)] |
| 457 | topc = topa[int(xc)] |
| 458 | if xf == xc: |
| 459 | top = topf |
| 460 | else: |
| 461 | top = topf * (xc-xs) + topc * (xs-xf) |
| 462 | top = math.floor(top) |
| 463 | bot = round(botl + (botr-botl) * x/(width-1)) |
| 464 | |
| 465 | for y in range(int(top), int(bot)): |
| 466 | pixel(x, y, cK, canvas) |
| 467 | |
| 468 | # Now draw the brim. |
| 469 | for x in range(int(width)): |
| 470 | brimtop = brimtopc + brimm * x |
| 471 | brimbot = brimbotc + brimm * x |
| 472 | for y in range(int(math.floor(brimtop)), int(math.ceil(brimbot))): |
| 473 | tophere = max(min(brimtop - y, 1), 0) |
| 474 | bothere = max(min(brimbot - y, 1), 0) |
| 475 | grey = bothere - tophere |
| 476 | # Only draw brim pixels over pixels which are (a) part |
| 477 | # of the main hat, and (b) not right on its edge. |
| 478 | if canvas.has_key((x,y)) and \ |
| 479 | canvas.has_key((x,y-1)) and \ |
| 480 | canvas.has_key((x,y+1)) and \ |
| 481 | canvas.has_key((x-1,y)) and \ |
| 482 | canvas.has_key((x+1,y)): |
| 483 | pixel(x, y, greypix(grey), canvas) |
| 484 | |
| 485 | return canvas |
| 486 | |
| 487 | def key(size): |
| 488 | canvas = {} |
| 489 | |
| 490 | # The key in the PuTTYgen icon. |
| 491 | |
| 492 | keyheadw = round(9.5*size) |
| 493 | keyheadh = round(12*size) |
| 494 | keyholed = round(4*size) |
| 495 | keyholeoff = round(2*size) |
| 496 | # Ensure keyheadh and keyshafth have the same parity. |
| 497 | keyshafth = round((2*size - (int(keyheadh)&1)) / 2) * 2 + (int(keyheadh)&1) |
| 498 | keyshaftw = round(18.5*size) |
| 499 | keyhead = [round(x*size) for x in [12,11,8,10,9,8,11,12]] |
| 500 | |
| 501 | squarepix = [] |
| 502 | |
| 503 | # Ellipse for the key head, minus an off-centre circular hole. |
| 504 | for y in range(int(keyheadh)): |
| 505 | dy = (y-(keyheadh-1)/2.0) / (keyheadh/2.0) |
| 506 | dyh = (y-(keyheadh-1)/2.0) / (keyholed/2.0) |
| 507 | for x in range(int(keyheadw)): |
| 508 | dx = (x-(keyheadw-1)/2.0) / (keyheadw/2.0) |
| 509 | dxh = (x-(keyheadw-1)/2.0-keyholeoff) / (keyholed/2.0) |
| 510 | if dy*dy+dx*dx <= 1 and dyh*dyh+dxh*dxh > 1: |
| 511 | pixel(x + keyshaftw, y, cy, canvas) |
| 512 | |
| 513 | # Rectangle for the key shaft, extended at the bottom for the |
| 514 | # key head detail. |
| 515 | for x in range(int(keyshaftw)): |
| 516 | top = round((keyheadh - keyshafth) / 2) |
| 517 | bot = round((keyheadh + keyshafth) / 2) |
| 518 | xs = float(x) * (len(keyhead)-1) / round((len(keyhead)-1)*size) |
| 519 | xf = math.floor(xs) |
| 520 | xc = math.ceil(xs) |
| 521 | in_head = 0 |
| 522 | if xc < len(keyhead): |
| 523 | in_head = 1 |
| 524 | yf = keyhead[int(xf)] |
| 525 | yc = keyhead[int(xc)] |
| 526 | if xf == xc: |
| 527 | bot = yf |
| 528 | else: |
| 529 | bot = yf * (xc-xs) + yc * (xs-xf) |
| 530 | for y in range(int(top),int(bot)): |
| 531 | pixel(x, y, cy, canvas) |
| 532 | if in_head: |
| 533 | last = (x, y) |
| 534 | if x == 0: |
| 535 | squarepix.append((x, int(top), TL)) |
| 536 | if x == 0: |
| 537 | squarepix.append(last + (BL,)) |
| 538 | if last != None and not in_head: |
| 539 | squarepix.append(last + (BR,)) |
| 540 | last = None |
| 541 | |
| 542 | # And draw a border. |
| 543 | border(canvas, size, squarepix) |
| 544 | |
| 545 | return canvas |
| 546 | |
| 547 | def linedist(x1,y1, x2,y2, x,y): |
| 548 | # Compute the distance from the point x,y to the line segment |
| 549 | # joining x1,y1 to x2,y2. Returns the distance vector, measured |
| 550 | # with x,y at the origin. |
| 551 | |
| 552 | vectors = [] |
| 553 | |
| 554 | # Special case: if x1,y1 and x2,y2 are the same point, we |
| 555 | # don't attempt to extrapolate it into a line at all. |
| 556 | if x1 != x2 or y1 != y2: |
| 557 | # First, find the nearest point to x,y on the infinite |
| 558 | # projection of the line segment. So we construct a vector |
| 559 | # n perpendicular to that segment... |
| 560 | nx = y2-y1 |
| 561 | ny = x1-x2 |
| 562 | # ... compute the dot product of (x1,y1)-(x,y) with that |
| 563 | # vector... |
| 564 | nd = (x1-x)*nx + (y1-y)*ny |
| 565 | # ... multiply by the vector we first thought of... |
| 566 | ndx = nd * nx |
| 567 | ndy = nd * ny |
| 568 | # ... and divide twice by the length of n. |
| 569 | ndx = ndx / (nx*nx+ny*ny) |
| 570 | ndy = ndy / (nx*nx+ny*ny) |
| 571 | # That gives us a displacement vector from x,y to the |
| 572 | # nearest point. See if it's within the range of the line |
| 573 | # segment. |
| 574 | cx = x + ndx |
| 575 | cy = y + ndy |
| 576 | if cx >= min(x1,x2) and cx <= max(x1,x2) and \ |
| 577 | cy >= min(y1,y2) and cy <= max(y1,y2): |
| 578 | vectors.append((ndx,ndy)) |
| 579 | |
| 580 | # Now we have up to three candidate result vectors: (ndx,ndy) |
| 581 | # as computed just above, and the two vectors to the ends of |
| 582 | # the line segment, (x1-x,y1-y) and (x2-x,y2-y). Pick the |
| 583 | # shortest. |
| 584 | vectors = vectors + [(x1-x,y1-y), (x2-x,y2-y)] |
| 585 | bestlen, best = None, None |
| 586 | for v in vectors: |
| 587 | vlen = v[0]*v[0]+v[1]*v[1] |
| 588 | if bestlen == None or bestlen > vlen: |
| 589 | bestlen = vlen |
| 590 | best = v |
| 591 | return best |
| 592 | |
| 593 | def spanner(size): |
| 594 | canvas = {} |
| 595 | |
| 596 | # The spanner in the config box icon. |
| 597 | |
| 598 | headcentre = 0.5 + round(4*size) |
| 599 | headradius = headcentre + 0.1 |
| 600 | headhighlight = round(1.5*size) |
| 601 | holecentre = 0.5 + round(3*size) |
| 602 | holeradius = round(2*size) |
| 603 | holehighlight = round(1.5*size) |
| 604 | shaftend = 0.5 + round(25*size) |
| 605 | shaftwidth = round(2*size) |
| 606 | shafthighlight = round(1.5*size) |
| 607 | cmax = shaftend + shaftwidth |
| 608 | |
| 609 | # Define three line segments, such that the shortest distance |
| 610 | # vectors from any point to each of these segments determines |
| 611 | # everything we need to know about where it is on the spanner |
| 612 | # shape. |
| 613 | segments = [ |
| 614 | ((0,0), (holecentre, holecentre)), |
| 615 | ((headcentre, headcentre), (headcentre, headcentre)), |
| 616 | ((headcentre+headradius/math.sqrt(2), headcentre+headradius/math.sqrt(2)), |
| 617 | (cmax, cmax)) |
| 618 | ] |
| 619 | |
| 620 | for y in range(int(cmax)): |
| 621 | for x in range(int(cmax)): |
| 622 | vectors = [linedist(a,b,c,d,x,y) for ((a,b),(c,d)) in segments] |
| 623 | dists = [memoisedsqrt(vx*vx+vy*vy) for (vx,vy) in vectors] |
| 624 | |
| 625 | # If the distance to the hole line is less than |
| 626 | # holeradius, we're not part of the spanner. |
| 627 | if dists[0] < holeradius: |
| 628 | continue |
| 629 | # If the distance to the head `line' is less than |
| 630 | # headradius, we are part of the spanner; likewise if |
| 631 | # the distance to the shaft line is less than |
| 632 | # shaftwidth _and_ the resulting shaft point isn't |
| 633 | # beyond the shaft end. |
| 634 | if dists[1] > headradius and \ |
| 635 | (dists[2] > shaftwidth or x+vectors[2][0] >= shaftend): |
| 636 | continue |
| 637 | |
| 638 | # We're part of the spanner. Now compute the highlight |
| 639 | # on this pixel. We do this by computing a `slope |
| 640 | # vector', which points from this pixel in the |
| 641 | # direction of its nearest edge. We store an array of |
| 642 | # slope vectors, in polar coordinates. |
| 643 | angles = [math.atan2(vy,vx) for (vx,vy) in vectors] |
| 644 | slopes = [] |
| 645 | if dists[0] < holeradius + holehighlight: |
| 646 | slopes.append(((dists[0]-holeradius)/holehighlight,angles[0])) |
| 647 | if dists[1]/headradius < dists[2]/shaftwidth: |
| 648 | if dists[1] > headradius - headhighlight and dists[1] < headradius: |
| 649 | slopes.append(((headradius-dists[1])/headhighlight,math.pi+angles[1])) |
| 650 | else: |
| 651 | if dists[2] > shaftwidth - shafthighlight and dists[2] < shaftwidth: |
| 652 | slopes.append(((shaftwidth-dists[2])/shafthighlight,math.pi+angles[2])) |
| 653 | # Now we find the smallest distance in that array, if |
| 654 | # any, and that gives us a notional position on a |
| 655 | # sphere which we can use to compute the final |
| 656 | # highlight level. |
| 657 | bestdist = None |
| 658 | bestangle = 0 |
| 659 | for dist, angle in slopes: |
| 660 | if bestdist == None or bestdist > dist: |
| 661 | bestdist = dist |
| 662 | bestangle = angle |
| 663 | if bestdist == None: |
| 664 | bestdist = 1.0 |
| 665 | sx = (1.0-bestdist) * math.cos(bestangle) |
| 666 | sy = (1.0-bestdist) * math.sin(bestangle) |
| 667 | sz = math.sqrt(1.0 - sx*sx - sy*sy) |
| 668 | shade = sx-sy+sz / math.sqrt(3) # can range from -1 to +1 |
| 669 | shade = 1.0 - (1-shade)/3 |
| 670 | |
| 671 | pixel(x, y, yellowpix(shade), canvas) |
| 672 | |
| 673 | # And draw a border. |
| 674 | border(canvas, size, []) |
| 675 | |
| 676 | return canvas |
| 677 | |
| 678 | def box(size, back): |
| 679 | canvas = {} |
| 680 | |
| 681 | # The back side of the cardboard box in the installer icon. |
| 682 | |
| 683 | boxwidth = round(15 * size) |
| 684 | boxheight = round(12 * size) |
| 685 | boxdepth = round(4 * size) |
| 686 | boxfrontflapheight = round(5 * size) |
| 687 | boxrightflapheight = round(3 * size) |
| 688 | |
| 689 | # Three shades of basically acceptable brown, all achieved by |
| 690 | # halftoning between two of the Windows-16 colours. I'm quite |
| 691 | # pleased that was feasible at all! |
| 692 | dark = halftone(cr, cK) |
| 693 | med = halftone(cr, cy) |
| 694 | light = halftone(cr, cY) |
| 695 | # We define our halftoning parity in such a way that the black |
| 696 | # pixels along the RHS of the visible part of the box back |
| 697 | # match up with the one-pixel black outline around the |
| 698 | # right-hand side of the box. In other words, we want the pixel |
| 699 | # at (-1, boxwidth-1) to be black, and hence the one at (0, |
| 700 | # boxwidth) too. |
| 701 | parityadjust = int(boxwidth) % 2 |
| 702 | |
| 703 | # The entire back of the box. |
| 704 | if back: |
| 705 | for x in range(int(boxwidth + boxdepth)): |
| 706 | ytop = max(-x-1, -boxdepth-1) |
| 707 | ybot = min(boxheight, boxheight+boxwidth-1-x) |
| 708 | for y in range(int(ytop), int(ybot)): |
| 709 | pixel(x, y, dark[(x+y+parityadjust) % 2], canvas) |
| 710 | |
| 711 | # Even when drawing the back of the box, we still draw the |
| 712 | # whole shape, because that means we get the right overall size |
| 713 | # (the flaps make the box front larger than the box back) and |
| 714 | # it'll all be overwritten anyway. |
| 715 | |
| 716 | # The front face of the box. |
| 717 | for x in range(int(boxwidth)): |
| 718 | for y in range(int(boxheight)): |
| 719 | pixel(x, y, med[(x+y+parityadjust) % 2], canvas) |
| 720 | # The right face of the box. |
| 721 | for x in range(int(boxwidth), int(boxwidth+boxdepth)): |
| 722 | ybot = boxheight + boxwidth-x |
| 723 | ytop = ybot - boxheight |
| 724 | for y in range(int(ytop), int(ybot)): |
| 725 | pixel(x, y, dark[(x+y+parityadjust) % 2], canvas) |
| 726 | # The front flap of the box. |
| 727 | for y in range(int(boxfrontflapheight)): |
| 728 | xadj = int(round(-0.5*y)) |
| 729 | for x in range(int(xadj), int(xadj+boxwidth)): |
| 730 | pixel(x, y, light[(x+y+parityadjust) % 2], canvas) |
| 731 | # The right flap of the box. |
| 732 | for x in range(int(boxwidth), int(boxwidth + boxdepth + boxrightflapheight + 1)): |
| 733 | ytop = max(boxwidth - 1 - x, x - boxwidth - 2*boxdepth - 1) |
| 734 | ybot = min(x - boxwidth - 1, boxwidth + 2*boxrightflapheight - 1 - x) |
| 735 | for y in range(int(ytop), int(ybot+1)): |
| 736 | pixel(x, y, med[(x+y+parityadjust) % 2], canvas) |
| 737 | |
| 738 | # And draw a border. |
| 739 | border(canvas, size, [(0, int(boxheight)-1, BL)]) |
| 740 | |
| 741 | return canvas |
| 742 | |
| 743 | def boxback(size): |
| 744 | return box(size, 1) |
| 745 | def boxfront(size): |
| 746 | return box(size, 0) |
| 747 | |
| 748 | # Functions to draw entire icons by composing the above components. |
| 749 | |
| 750 | def xybolt(c1, c2, size, boltoffx=0, boltoffy=0, aux={}): |
| 751 | # Two unspecified objects and a lightning bolt. |
| 752 | |
| 753 | canvas = {} |
| 754 | w = h = round(32 * size) |
| 755 | |
| 756 | bolt = lightning(size) |
| 757 | |
| 758 | # Position c2 against the top right of the icon. |
| 759 | bb = bbox(c2) |
| 760 | assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h |
| 761 | overlay(c2, w-bb[2], 0-bb[1], canvas) |
| 762 | aux["c2pos"] = (w-bb[2], 0-bb[1]) |
| 763 | # Position c1 against the bottom left of the icon. |
| 764 | bb = bbox(c1) |
| 765 | assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h |
| 766 | overlay(c1, 0-bb[0], h-bb[3], canvas) |
| 767 | aux["c1pos"] = (0-bb[0], h-bb[3]) |
| 768 | # Place the lightning bolt artistically off-centre. (The |
| 769 | # rationale for this positioning is that it's centred on the |
| 770 | # midpoint between the centres of the two monitors in the PuTTY |
| 771 | # icon proper, but it's not really feasible to _base_ the |
| 772 | # calculation here on that.) |
| 773 | bb = bbox(bolt) |
| 774 | assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h |
| 775 | overlay(bolt, (w-bb[0]-bb[2])/2 + round(boltoffx*size), \ |
| 776 | (h-bb[1]-bb[3])/2 + round((boltoffy-2)*size), canvas) |
| 777 | |
| 778 | return canvas |
| 779 | |
| 780 | def putty_icon(size): |
| 781 | return xybolt(computer(size), computer(size), size) |
| 782 | |
| 783 | def puttycfg_icon(size): |
| 784 | w = h = round(32 * size) |
| 785 | s = spanner(size) |
| 786 | canvas = putty_icon(size) |
| 787 | # Centre the spanner. |
| 788 | bb = bbox(s) |
| 789 | overlay(s, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas) |
| 790 | return canvas |
| 791 | |
| 792 | def puttygen_icon(size): |
| 793 | return xybolt(computer(size), key(size), size, boltoffx=2) |
| 794 | |
| 795 | def pscp_icon(size): |
| 796 | return xybolt(document(size), computer(size), size) |
| 797 | |
| 798 | def puttyins_icon(size): |
| 799 | aret = {} |
| 800 | # The box back goes behind the lightning bolt. |
| 801 | canvas = xybolt(boxback(size), computer(size), size, boltoffx=-2, boltoffy=+1, aux=aret) |
| 802 | # But the box front goes over the top, so that the lightning |
| 803 | # bolt appears to come _out_ of the box. Here it's useful to |
| 804 | # know the exact coordinates where xybolt placed the box back, |
| 805 | # so we can overlay the box front exactly on top of it. |
| 806 | c1x, c1y = aret["c1pos"] |
| 807 | overlay(boxfront(size), c1x, c1y, canvas) |
| 808 | return canvas |
| 809 | |
| 810 | def pterm_icon(size): |
| 811 | # Just a really big computer. |
| 812 | |
| 813 | canvas = {} |
| 814 | w = h = round(32 * size) |
| 815 | |
| 816 | c = computer(size * 1.4) |
| 817 | |
| 818 | # Centre c in the return canvas. |
| 819 | bb = bbox(c) |
| 820 | assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h |
| 821 | overlay(c, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas) |
| 822 | |
| 823 | return canvas |
| 824 | |
| 825 | def ptermcfg_icon(size): |
| 826 | w = h = round(32 * size) |
| 827 | s = spanner(size) |
| 828 | canvas = pterm_icon(size) |
| 829 | # Centre the spanner. |
| 830 | bb = bbox(s) |
| 831 | overlay(s, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas) |
| 832 | return canvas |
| 833 | |
| 834 | def pageant_icon(size): |
| 835 | # A biggish computer, in a hat. |
| 836 | |
| 837 | canvas = {} |
| 838 | w = h = round(32 * size) |
| 839 | |
| 840 | c = computer(size * 1.2) |
| 841 | ht = hat(size) |
| 842 | |
| 843 | cbb = bbox(c) |
| 844 | hbb = bbox(ht) |
| 845 | |
| 846 | # Determine the relative y-coordinates of the computer and hat. |
| 847 | # We just centre the one on the other. |
| 848 | xrel = (cbb[0]+cbb[2]-hbb[0]-hbb[2])/2 |
| 849 | |
| 850 | # Determine the relative y-coordinates of the computer and hat. |
| 851 | # We do this by sitting the hat as low down on the computer as |
| 852 | # possible without any computer showing over the top. To do |
| 853 | # this we first have to find the minimum x coordinate at each |
| 854 | # y-coordinate of both components. |
| 855 | cty = topy(c) |
| 856 | hty = topy(ht) |
| 857 | yrelmin = None |
| 858 | for cx in cty.keys(): |
| 859 | hx = cx - xrel |
| 860 | assert hty.has_key(hx) |
| 861 | yrel = cty[cx] - hty[hx] |
| 862 | if yrelmin == None: |
| 863 | yrelmin = yrel |
| 864 | else: |
| 865 | yrelmin = min(yrelmin, yrel) |
| 866 | |
| 867 | # Overlay the hat on the computer. |
| 868 | overlay(ht, xrel, yrelmin, c) |
| 869 | |
| 870 | # And centre the result in the main icon canvas. |
| 871 | bb = bbox(c) |
| 872 | assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h |
| 873 | overlay(c, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas) |
| 874 | |
| 875 | return canvas |
| 876 | |
| 877 | # Test and output functions. |
| 878 | |
| 879 | import os |
| 880 | import sys |
| 881 | |
| 882 | def testrun(func, fname): |
| 883 | canvases = [] |
| 884 | for size in [0.5, 0.6, 1.0, 1.2, 1.5, 4.0]: |
| 885 | canvases.append(func(size)) |
| 886 | wid = 0 |
| 887 | ht = 0 |
| 888 | for canvas in canvases: |
| 889 | minx, miny, maxx, maxy = bbox(canvas) |
| 890 | wid = max(wid, maxx-minx+4) |
| 891 | ht = ht + maxy-miny+4 |
| 892 | block = [] |
| 893 | for canvas in canvases: |
| 894 | minx, miny, maxx, maxy = bbox(canvas) |
| 895 | block.extend(render(canvas, minx-2, miny-2, minx-2+wid, maxy+2)) |
| 896 | p = os.popen("convert -depth 8 -size %dx%d rgb:- %s" % (wid,ht,fname), "w") |
| 897 | assert len(block) == ht |
| 898 | for line in block: |
| 899 | assert len(line) == wid |
| 900 | for r, g, b, a in line: |
| 901 | # Composite on to orange. |
| 902 | r = int(round((r * a + 255 * (255-a)) / 255.0)) |
| 903 | g = int(round((g * a + 128 * (255-a)) / 255.0)) |
| 904 | b = int(round((b * a + 0 * (255-a)) / 255.0)) |
| 905 | p.write("%c%c%c" % (r,g,b)) |
| 906 | p.close() |
| 907 | |
| 908 | def drawicon(func, width, fname, orangebackground = 0): |
| 909 | canvas = func(width / 32.0) |
| 910 | finalise(canvas) |
| 911 | minx, miny, maxx, maxy = bbox(canvas) |
| 912 | assert minx >= 0 and miny >= 0 and maxx <= width and maxy <= width |
| 913 | |
| 914 | block = render(canvas, 0, 0, width, width) |
| 915 | p = os.popen("convert -depth 8 -size %dx%d rgba:- %s" % (width,width,fname), "w") |
| 916 | assert len(block) == width |
| 917 | for line in block: |
| 918 | assert len(line) == width |
| 919 | for r, g, b, a in line: |
| 920 | if orangebackground: |
| 921 | # Composite on to orange. |
| 922 | r = int(round((r * a + 255 * (255-a)) / 255.0)) |
| 923 | g = int(round((g * a + 128 * (255-a)) / 255.0)) |
| 924 | b = int(round((b * a + 0 * (255-a)) / 255.0)) |
| 925 | a = 255 |
| 926 | p.write("%c%c%c%c" % (r,g,b,a)) |
| 927 | p.close() |
| 928 | |
| 929 | args = sys.argv[1:] |
| 930 | |
| 931 | orangebackground = test = 0 |
| 932 | colours = 1 # 0=mono, 1=16col, 2=truecol |
| 933 | doingargs = 1 |
| 934 | |
| 935 | realargs = [] |
| 936 | for arg in args: |
| 937 | if doingargs and arg[0] == "-": |
| 938 | if arg == "-t": |
| 939 | test = 1 |
| 940 | elif arg == "-it": |
| 941 | orangebackground = 1 |
| 942 | elif arg == "-2": |
| 943 | colours = 0 |
| 944 | elif arg == "-T": |
| 945 | colours = 2 |
| 946 | elif arg == "--": |
| 947 | doingargs = 0 |
| 948 | else: |
| 949 | sys.stderr.write("unrecognised option '%s'\n" % arg) |
| 950 | sys.exit(1) |
| 951 | else: |
| 952 | realargs.append(arg) |
| 953 | |
| 954 | if colours == 0: |
| 955 | # Monochrome. |
| 956 | cK=cr=cg=cb=cm=cc=cP=cw=cR=cG=cB=cM=cC=cD = 0 |
| 957 | cY=cy=cW = 1 |
| 958 | cT = -1 |
| 959 | def greypix(value): |
| 960 | return [cK,cW][int(round(value))] |
| 961 | def yellowpix(value): |
| 962 | return [cK,cW][int(round(value))] |
| 963 | def bluepix(value): |
| 964 | return cK |
| 965 | def dark(value): |
| 966 | return [cT,cK][int(round(value))] |
| 967 | def blend(col1, col2): |
| 968 | if col1 == cT: |
| 969 | return col2 |
| 970 | else: |
| 971 | return col1 |
| 972 | pixvals = [ |
| 973 | (0x00, 0x00, 0x00, 0xFF), # cK |
| 974 | (0xFF, 0xFF, 0xFF, 0xFF), # cW |
| 975 | (0x00, 0x00, 0x00, 0x00), # cT |
| 976 | ] |
| 977 | def outpix(colour): |
| 978 | return pixvals[colour] |
| 979 | def finalisepix(colour): |
| 980 | return colour |
| 981 | def halftone(col1, col2): |
| 982 | return (col1, col2) |
| 983 | elif colours == 1: |
| 984 | # Windows 16-colour palette. |
| 985 | cK,cr,cg,cy,cb,cm,cc,cP,cw,cR,cG,cY,cB,cM,cC,cW = range(16) |
| 986 | cT = -1 |
| 987 | cD = -2 # special translucent half-darkening value used internally |
| 988 | def greypix(value): |
| 989 | return [cK,cw,cw,cP,cW][int(round(4*value))] |
| 990 | def yellowpix(value): |
| 991 | return [cK,cy,cY][int(round(2*value))] |
| 992 | def bluepix(value): |
| 993 | return [cK,cb,cB][int(round(2*value))] |
| 994 | def dark(value): |
| 995 | return [cT,cD,cK][int(round(2*value))] |
| 996 | def blend(col1, col2): |
| 997 | if col1 == cT: |
| 998 | return col2 |
| 999 | elif col1 == cD: |
| 1000 | return [cK,cK,cK,cK,cK,cK,cK,cw,cK,cr,cg,cy,cb,cm,cc,cw,cD,cD][col2] |
| 1001 | else: |
| 1002 | return col1 |
| 1003 | pixvals = [ |
| 1004 | (0x00, 0x00, 0x00, 0xFF), # cK |
| 1005 | (0x80, 0x00, 0x00, 0xFF), # cr |
| 1006 | (0x00, 0x80, 0x00, 0xFF), # cg |
| 1007 | (0x80, 0x80, 0x00, 0xFF), # cy |
| 1008 | (0x00, 0x00, 0x80, 0xFF), # cb |
| 1009 | (0x80, 0x00, 0x80, 0xFF), # cm |
| 1010 | (0x00, 0x80, 0x80, 0xFF), # cc |
| 1011 | (0xC0, 0xC0, 0xC0, 0xFF), # cP |
| 1012 | (0x80, 0x80, 0x80, 0xFF), # cw |
| 1013 | (0xFF, 0x00, 0x00, 0xFF), # cR |
| 1014 | (0x00, 0xFF, 0x00, 0xFF), # cG |
| 1015 | (0xFF, 0xFF, 0x00, 0xFF), # cY |
| 1016 | (0x00, 0x00, 0xFF, 0xFF), # cB |
| 1017 | (0xFF, 0x00, 0xFF, 0xFF), # cM |
| 1018 | (0x00, 0xFF, 0xFF, 0xFF), # cC |
| 1019 | (0xFF, 0xFF, 0xFF, 0xFF), # cW |
| 1020 | (0x00, 0x00, 0x00, 0x80), # cD |
| 1021 | (0x00, 0x00, 0x00, 0x00), # cT |
| 1022 | ] |
| 1023 | def outpix(colour): |
| 1024 | return pixvals[colour] |
| 1025 | def finalisepix(colour): |
| 1026 | # cD is used internally, but can't be output. Convert to cK. |
| 1027 | if colour == cD: |
| 1028 | return cK |
| 1029 | return colour |
| 1030 | def halftone(col1, col2): |
| 1031 | return (col1, col2) |
| 1032 | else: |
| 1033 | # True colour. |
| 1034 | cK = (0x00, 0x00, 0x00, 0xFF) |
| 1035 | cr = (0x80, 0x00, 0x00, 0xFF) |
| 1036 | cg = (0x00, 0x80, 0x00, 0xFF) |
| 1037 | cy = (0x80, 0x80, 0x00, 0xFF) |
| 1038 | cb = (0x00, 0x00, 0x80, 0xFF) |
| 1039 | cm = (0x80, 0x00, 0x80, 0xFF) |
| 1040 | cc = (0x00, 0x80, 0x80, 0xFF) |
| 1041 | cP = (0xC0, 0xC0, 0xC0, 0xFF) |
| 1042 | cw = (0x80, 0x80, 0x80, 0xFF) |
| 1043 | cR = (0xFF, 0x00, 0x00, 0xFF) |
| 1044 | cG = (0x00, 0xFF, 0x00, 0xFF) |
| 1045 | cY = (0xFF, 0xFF, 0x00, 0xFF) |
| 1046 | cB = (0x00, 0x00, 0xFF, 0xFF) |
| 1047 | cM = (0xFF, 0x00, 0xFF, 0xFF) |
| 1048 | cC = (0x00, 0xFF, 0xFF, 0xFF) |
| 1049 | cW = (0xFF, 0xFF, 0xFF, 0xFF) |
| 1050 | cD = (0x00, 0x00, 0x00, 0x80) |
| 1051 | cT = (0x00, 0x00, 0x00, 0x00) |
| 1052 | def greypix(value): |
| 1053 | value = max(min(value, 1), 0) |
| 1054 | return (int(round(0xFF*value)),) * 3 + (0xFF,) |
| 1055 | def yellowpix(value): |
| 1056 | value = max(min(value, 1), 0) |
| 1057 | return (int(round(0xFF*value)),) * 2 + (0, 0xFF) |
| 1058 | def bluepix(value): |
| 1059 | value = max(min(value, 1), 0) |
| 1060 | return (0, 0, int(round(0xFF*value)), 0xFF) |
| 1061 | def dark(value): |
| 1062 | value = max(min(value, 1), 0) |
| 1063 | return (0, 0, 0, int(round(0xFF*value))) |
| 1064 | def blend(col1, col2): |
| 1065 | r1,g1,b1,a1 = col1 |
| 1066 | r2,g2,b2,a2 = col2 |
| 1067 | r = int(round((r1*a1 + r2*(0xFF-a1)) / 255.0)) |
| 1068 | g = int(round((g1*a1 + g2*(0xFF-a1)) / 255.0)) |
| 1069 | b = int(round((b1*a1 + b2*(0xFF-a1)) / 255.0)) |
| 1070 | a = int(round((255*a1 + a2*(0xFF-a1)) / 255.0)) |
| 1071 | return r, g, b, a |
| 1072 | def outpix(colour): |
| 1073 | return colour |
| 1074 | if colours == 2: |
| 1075 | # True colour with no alpha blending: we still have to |
| 1076 | # finalise half-dark pixels to black. |
| 1077 | def finalisepix(colour): |
| 1078 | if colour[3] > 0: |
| 1079 | return colour[:3] + (0xFF,) |
| 1080 | return colour |
| 1081 | else: |
| 1082 | def finalisepix(colour): |
| 1083 | return colour |
| 1084 | def halftone(col1, col2): |
| 1085 | r1,g1,b1,a1 = col1 |
| 1086 | r2,g2,b2,a2 = col2 |
| 1087 | colret = (int(r1+r2)/2, int(g1+g2)/2, int(b1+b2)/2, int(a1+a2)/2) |
| 1088 | return (colret, colret) |
| 1089 | |
| 1090 | if test: |
| 1091 | testrun(eval(realargs[0]), realargs[1]) |
| 1092 | else: |
| 1093 | drawicon(eval(realargs[0]), int(realargs[1]), realargs[2], orangebackground) |