]>
Commit | Line | Data |
---|---|---|
1c1af145 | 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) |