5 # Python code which draws the PuTTY icon components at a range of
11 # - use of alpha blending
12 # + try for variable-transparency borders
14 # - can we integrate the Mac icons into all this? Do we want to?
16 def pixel(x
, y
, colour
, canvas
):
17 canvas
[(int(x
),int(y
))] = colour
19 def overlay(src
, x
, y
, dst
):
22 for (sx
, sy
), colour
in src
.items():
23 dst
[sx
+x
, sy
+y
] = blend(colour
, dst
.get((sx
+x
, sy
+y
), cT
))
26 for k
in canvas
.keys():
27 canvas
[k
] = finalisepix(canvas
[k
])
30 minx
, miny
, maxx
, maxy
= None, None, None, None
31 for (x
, y
) in canvas
.keys():
33 minx
, miny
, maxx
, maxy
= x
, y
, x
+1, y
+1
39 return (minx
, miny
, maxx
, maxy
)
43 for (x
, y
) in canvas
.keys():
44 miny
[x
] = min(miny
.get(x
, y
), y
)
47 def render(canvas
, minx
, miny
, maxx
, maxy
):
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
)
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.
65 if not sqrthash
.has_key(x
):
66 sqrthash
[x
] = math
.sqrt(x
)
69 BR
, TR
, BL
, TL
= range(4) # enumeration of quadrants for border()
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.
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:
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
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.
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.
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.
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.
123 thickness
= memoisedsqrt(thickness
)
129 if thickness
< 1: thickness
= 1
130 thickness
= round(thickness
- 0.5) + 0.3
132 out
["borderthickness"] = thickness
134 dmax
= int(round(thickness
))
135 if dmax
< thickness
: dmax
= dmax
+ 1
137 cquadrant
= [[0] * (dmax
+1) for x
in range(dmax
+1)]
138 squadrant
= [[0] * (dmax
+1) for x
in range(dmax
+1)]
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
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
)]
155 bval
= cquadrant
[abs(dx
)][abs(dy
)]
156 if bvalues
.get((x
+dx
,y
+dy
),0) < bval
:
157 bvalues
[(x
+dx
,y
+dy
)] = bval
159 for (x
, y
), value
in bvalues
.items():
160 if not canvas
.has_key((x
,y
)):
161 canvas
[(x
,y
)] = dark(value
)
163 def sysbox(size
, out
={}):
166 # The system box of the computer.
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
))
174 out
["sysboxheight"] = height
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
))
183 floppytop
= floppybottom
- floppyheight
185 # The front panel is rectangular.
186 for x
in range(width
):
187 for y
in range(height
):
189 if x
< highlight
or y
< highlight
:
191 if x
>= width
-highlight
or y
>= height
-bothighlight
:
193 if y
< highlight
and x
>= width
-highlight
:
194 v
= (highlight
-1-y
) - (x
-(width
-highlight
))
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:
206 pixel(x
, y
, greypix(grey
/4.0), canvas
)
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
)
213 # The top panel is another parallelogram.
214 for x
in range(width
-1):
215 for y
in range(depth
):
217 if x
>= width
-1 - highlight
:
219 pixel(x
+(y
+1), -(y
+1), greypix(grey
/4.0), canvas
)
222 border(canvas
, size
, [], out
)
229 # The computer's monitor.
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
))
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
:
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
254 # Complicated double bevel on the screen surround.
256 # First, the outer bevel. We compute the distance
257 # from this pixel to each edge of the front
265 # Now sort the list to find the distance to the
266 # _nearest_ edge, or the two joint nearest.
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.
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]
277 # Now, the inner bevel. We compute the distance
278 # from this pixel to each edge of the screen
283 (x
-(surround
+swidth
), +1),
284 (y
-(surround
+sheight
), +1)
286 # Now we sort to find the _maximum_ distance, which
287 # conveniently ignores any less than zero.
289 # And now the strategy is pretty much the same as
290 # above, only we're working from the opposite end
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]
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
303 if outerbevel
> 0 or outerbevel
== innerbevel
:
305 grey
= grey
+ outerbevel
+ innerbevel
307 pix
= greypix(grey
/ 4.0)
309 pixel(x
, y
, pix
, canvas
)
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
)
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
)
322 border(canvas
, size
, [(0,int(height
-1),BL
)])
327 # Monitor plus sysbox.
330 s
= sysbox(size
, out
)
331 x
= int(round((2+size
/(size
+1))*size
))
332 y
= int(out
["sysboxheight"] + out
["borderthickness"])
335 xoff
= sb
[0] - mb
[0] + x
336 yoff
= sb
[3] - mb
[3] - y
337 overlay(m
, xoff
, yoff
, s
)
343 # The lightning bolt motif.
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
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
)
354 # And the inner edge goes to this point.
355 innery
= height
- 1 - outery
356 innerx
= round(7*size
)
358 for y
in range(int(height
)):
361 list.append(width
-1-int(outerx
* float(y
) / outery
+ 0.3))
363 list.append(width
-1-int(innerx
* float(y
) / innery
+ 0.3))
366 list.append(int(outerx
* float(y0
) / outery
+ 0.3))
368 list.append(int(innerx
* float(y0
) / innery
+ 0.3))
370 for x
in range(int(list[0]), int(list[-1]+1)):
371 pixel(x
, y
, cY
, canvas
)
374 border(canvas
, size
, [(int(width
-1),0,TR
), (0,int(height
-1),BL
)])
381 # The document used in the PSCP/PSFTP icon.
383 width
= round(13*size
)
384 height
= round(16*size
)
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
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
)
398 # Now draw lines of text.
399 for line
in range(nlines
):
400 # Decide where this line of text begins.
402 start
= round(4*size
)
403 elif line
< 5*nlines
/7:
404 start
= round((line
- (nlines
/7)) * size
)
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)
414 exf
= endpoints
[int(eyf
)]
415 exc
= endpoints
[int(eyc
)]
419 end
= exf
* (eyc
-ey
) + exc
* (ey
-eyf
)
420 end
= round(end
* size
)
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
)
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
)])
437 # The secret-agent hat in the Pageant icon.
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
)
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.
449 brimtopc
= round(4*size
/3)
450 brimbotc
= round(10*size
/3)
452 for x
in range(int(width
)):
453 xs
= float(x
) * (len(topa
)-1) / (width
-1)
461 top
= topf
* (xc
-xs
) + topc
* (xs
-xf
)
462 top
= math
.floor(top
)
463 bot
= round(botl
+ (botr
-botl
) * x
/(width
-1))
465 for y
in range(int(top
), int(bot
)):
466 pixel(x
, y
, cK
, canvas
)
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
)
490 # The key in the PuTTYgen icon.
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]]
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
)
513 # Rectangle for the key shaft, extended at the bottom for the
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
)
522 if xc
< len(keyhead
):
524 yf
= keyhead
[int(xf
)]
525 yc
= keyhead
[int(xc
)]
529 bot
= yf
* (xc
-xs
) + yc
* (xs
-xf
)
530 for y
in range(int(top
),int(bot
)):
531 pixel(x
, y
, cy
, canvas
)
535 squarepix
.append((x
, int(top
), TL
))
537 squarepix
.append(last
+ (BL
,))
538 if last
!= None and not in_head
:
539 squarepix
.append(last
+ (BR
,))
543 border(canvas
, size
, squarepix
)
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.
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...
562 # ... compute the dot product of (x1,y1)-(x,y) with that
564 nd
= (x1
-x
)*nx
+ (y1
-y
)*ny
565 # ... multiply by the vector we first thought of...
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
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
))
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
584 vectors
= vectors
+ [(x1
-x
,y1
-y
), (x2
-x
,y2
-y
)]
585 bestlen
, best
= None, None
587 vlen
= v
[0]*v
[0]+v
[1]*v
[1]
588 if bestlen
== None or bestlen
> vlen
:
596 # The spanner in the config box icon.
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
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
614 ((0,0), (holecentre
, holecentre
)),
615 ((headcentre
, headcentre
), (headcentre
, headcentre
)),
616 ((headcentre
+headradius
/math
.sqrt(2), headcentre
+headradius
/math
.sqrt(2)),
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
]
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
:
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
):
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
]
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]))
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
659 for dist
, angle
in slopes
:
660 if bestdist
== None or bestdist
> dist
:
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
671 pixel(x
, y
, yellowpix(shade
), canvas
)
674 border(canvas
, size
, [])
681 # The back side of the cardboard box in the installer icon.
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
)
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,
701 parityadjust
= int(boxwidth
) %
2
703 # The entire back of the box.
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
)
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.
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
)
739 border(canvas
, size
, [(0, int(boxheight
)-1, BL
)])
748 # Functions to draw entire icons by composing the above components.
750 def xybolt(c1
, c2
, size
, boltoffx
=0, boltoffy
=0, aux
={}):
751 # Two unspecified objects and a lightning bolt.
754 w
= h
= round(32 * size
)
756 bolt
= lightning(size
)
758 # Position c2 against the top right of the icon.
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.
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.)
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
)
780 def putty_icon(size
):
781 return xybolt(computer(size
), computer(size
), size
)
783 def puttycfg_icon(size
):
784 w
= h
= round(32 * size
)
786 canvas
= putty_icon(size
)
787 # Centre the spanner.
789 overlay(s
, (w
-bb
[0]-bb
[2])/2, (h
-bb
[1]-bb
[3])/2, canvas
)
792 def puttygen_icon(size
):
793 return xybolt(computer(size
), key(size
), size
, boltoffx
=2)
796 return xybolt(document(size
), computer(size
), size
)
798 def installer_icon(size
):
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
)
810 def pterm_icon(size
):
811 # Just a really big computer.
814 w
= h
= round(32 * size
)
816 c
= computer(size
* 1.4)
818 # Centre c in the return canvas.
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
)
825 def ptermcfg_icon(size
):
826 w
= h
= round(32 * size
)
828 canvas
= pterm_icon(size
)
829 # Centre the spanner.
831 overlay(s
, (w
-bb
[0]-bb
[2])/2, (h
-bb
[1]-bb
[3])/2, canvas
)
834 def pageant_icon(size
):
835 # A biggish computer, in a hat.
838 w
= h
= round(32 * size
)
840 c
= computer(size
* 1.2)
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
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.
858 for cx
in cty
.keys():
860 assert hty
.has_key(hx
)
861 yrel
= cty
[cx
] - hty
[hx
]
865 yrelmin
= min(yrelmin
, yrel
)
867 # Overlay the hat on the computer.
868 overlay(ht
, xrel
, yrelmin
, c
)
870 # And centre the result in the main icon canvas.
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
)
877 # Test and output functions.
882 def testrun(func
, fname
):
884 for size
in [0.5, 0.6, 1.0, 1.2, 1.5, 4.0]:
885 canvases
.append(func(size
))
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
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
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
))
908 def drawicon(func
, width
, fname
, orangebackground
= 0):
909 canvas
= func(width
/ 32.0)
911 minx
, miny
, maxx
, maxy
= bbox(canvas
)
912 assert minx
>= 0 and miny
>= 0 and maxx
<= width
and maxy
<= width
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
918 assert len(line
) == width
919 for r
, g
, b
, a
in line
:
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))
926 p
.write("%c%c%c%c" %
(r
,g
,b
,a
))
931 orangebackground
= test
= 0
932 colours
= 1 # 0=mono, 1=16col, 2=truecol
937 if doingargs
and arg
[0] == "-":
949 sys
.stderr
.write("unrecognised option '%s'\n" % arg
)
956 cK
=cr
=cg
=cb
=cm
=cc
=cP
=cw
=cR
=cG
=cB
=cM
=cC
=cD
= 0
960 return [cK
,cW
][int(round(value
))]
961 def yellowpix(value
):
962 return [cK
,cW
][int(round(value
))]
966 return [cT
,cK
][int(round(value
))]
967 def blend(col1
, col2
):
973 (0x00, 0x00, 0x00, 0xFF), # cK
974 (0xFF, 0xFF, 0xFF, 0xFF), # cW
975 (0x00, 0x00, 0x00, 0x00), # cT
978 return pixvals
[colour
]
979 def finalisepix(colour
):
981 def halftone(col1
, col2
):
984 # Windows 16-colour palette.
985 cK
,cr
,cg
,cy
,cb
,cm
,cc
,cP
,cw
,cR
,cG
,cY
,cB
,cM
,cC
,cW
= range(16)
987 cD
= -2 # special translucent half-darkening value used internally
989 return [cK
,cw
,cw
,cP
,cW
][int(round(4*value
))]
990 def yellowpix(value
):
991 return [cK
,cy
,cY
][int(round(2*value
))]
993 return [cK
,cb
,cB
][int(round(2*value
))]
995 return [cT
,cD
,cK
][int(round(2*value
))]
996 def blend(col1
, col2
):
1000 return [cK
,cK
,cK
,cK
,cK
,cK
,cK
,cw
,cK
,cr
,cg
,cy
,cb
,cm
,cc
,cw
,cD
,cD
][col2
]
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
1024 return pixvals
[colour
]
1025 def finalisepix(colour
):
1026 # cD is used internally, but can't be output. Convert to cK.
1030 def halftone(col1
, col2
):
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)
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)
1059 value
= max(min(value
, 1), 0)
1060 return (0, 0, int(round(0xFF*value
)), 0xFF)
1062 value
= max(min(value
, 1), 0)
1063 return (0, 0, 0, int(round(0xFF*value
)))
1064 def blend(col1
, 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))
1075 # True colour with no alpha blending: we still have to
1076 # finalise half-dark pixels to black.
1077 def finalisepix(colour
):
1079 return colour
[:3] + (0xFF,)
1082 def finalisepix(colour
):
1084 def halftone(col1
, col2
):
1087 colret
= (int(r1
+r2
)/2, int(g1
+g2
)/2, int(b1
+b2
)/2, int(a1
+a2
)/2)
1088 return (colret
, colret
)
1091 testrun(eval(realargs
[0]), realargs
[1])
1093 drawicon(eval(realargs
[0]), int(realargs
[1]), realargs
[2], orangebackground
)