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
):
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 dmax
= int(round(thickness
))
133 if dmax
< thickness
: dmax
= dmax
+ 1
135 cquadrant
= [[0] * (dmax
+1) for x
in range(dmax
+1)]
136 squadrant
= [[0] * (dmax
+1) for x
in range(dmax
+1)]
138 for x
in range(dmax
+1):
139 for y
in range(dmax
+1):
140 if max(x
, y
) < thickness
:
141 squadrant
[x
][y
] = darkness
142 if memoisedsqrt(x
*x
+y
*y
) < thickness
:
143 cquadrant
[x
][y
] = darkness
146 for (x
, y
), colour
in canvas
.items():
147 for dx
in range(-dmax
, dmax
+1):
148 for dy
in range(-dmax
, dmax
+1):
149 quadrant
= 2 * (dx
< 0) + (dy
< 0)
150 if (x
, y
, quadrant
) in squarecorners
:
151 bval
= squadrant
[abs(dx
)][abs(dy
)]
153 bval
= cquadrant
[abs(dx
)][abs(dy
)]
154 if bvalues
.get((x
+dx
,y
+dy
),0) < bval
:
155 bvalues
[(x
+dx
,y
+dy
)] = bval
157 for (x
, y
), value
in bvalues
.items():
158 if not canvas
.has_key((x
,y
)):
159 canvas
[(x
,y
)] = dark(value
)
164 # The system box of the computer.
166 height
= int(round(3*size
))
167 width
= int(round(17*size
))
168 depth
= int(round(2*size
))
169 highlight
= int(round(1*size
))
170 bothighlight
= int(round(0.49*size
))
172 floppystart
= int(round(19*size
)) # measured in half-pixels
173 floppyend
= int(round(29*size
)) # measured in half-pixels
174 floppybottom
= height
- bothighlight
175 floppyrheight
= 0.7 * size
176 floppyheight
= int(round(floppyrheight
))
179 floppytop
= floppybottom
- floppyheight
181 # The front panel is rectangular.
182 for x
in range(width
):
183 for y
in range(height
):
185 if x
< highlight
or y
< highlight
:
187 if x
>= width
-highlight
or y
>= height
-bothighlight
:
189 if y
< highlight
and x
>= width
-highlight
:
190 v
= (highlight
-1-y
) - (x
-(width
-highlight
))
195 if y
>= floppytop
and y
< floppybottom
and \
196 2*x
+2 > floppystart
and 2*x
< floppyend
:
197 if 2*x
>= floppystart
and 2*x
+2 <= floppyend
and \
198 floppyrheight
>= 0.7:
202 pixel(x
, y
, greypix(grey
/4.0), canvas
)
204 # The side panel is a parallelogram.
205 for x
in range(depth
):
206 for y
in range(height
+1):
207 pixel(x
+width
, y
-(x
+1), greypix(0.5), canvas
)
209 # The top panel is another parallelogram.
210 for x
in range(width
-1):
211 for y
in range(depth
):
213 if x
>= width
-1 - highlight
:
215 pixel(x
+(y
+1), -(y
+1), greypix(grey
/4.0), canvas
)
218 border(canvas
, size
, [])
225 # The computer's monitor.
227 height
= int(round(9.55*size
))
228 width
= int(round(11*size
))
229 surround
= int(round(1*size
))
230 botsurround
= int(round(2*size
))
231 sheight
= height
- surround
- botsurround
232 swidth
= width
- 2*surround
233 depth
= int(round(2*size
))
234 highlight
= int(round(math
.sqrt(size
)))
235 shadow
= int(round(0.55*size
))
237 # The front panel is rectangular.
238 for x
in range(width
):
239 for y
in range(height
):
240 if x
>= surround
and y
>= surround
and \
241 x
< surround
+swidth
and y
< surround
+sheight
:
243 sx
= (float(x
-surround
) - swidth
/3) / swidth
244 sy
= (float(y
-surround
) - sheight
/3) / sheight
245 shighlight
= 1.0 - (sx
*sx
+sy
*sy
)*0.27
246 pix
= bluepix(shighlight
)
247 if x
< surround
+shadow
or y
< surround
+shadow
:
248 pix
= blend(cD
, pix
) # sharp-edged shadow on top and left
250 # Complicated double bevel on the screen surround.
252 # First, the outer bevel. We compute the distance
253 # from this pixel to each edge of the front
261 # Now sort the list to find the distance to the
262 # _nearest_ edge, or the two joint nearest.
264 # If there's one nearest edge, that determines our
265 # bevel colour. If there are two joint nearest, our
266 # bevel colour is their shared one if they agree,
267 # and neutral otherwise.
269 if list[0][0] < list[1][0] or list[0][1] == list[1][1]:
270 if list[0][0] < highlight
:
271 outerbevel
= list[0][1]
273 # Now, the inner bevel. We compute the distance
274 # from this pixel to each edge of the screen
279 (x
-(surround
+swidth
), +1),
280 (y
-(surround
+sheight
), +1)
282 # Now we sort to find the _maximum_ distance, which
283 # conveniently ignores any less than zero.
285 # And now the strategy is pretty much the same as
286 # above, only we're working from the opposite end
289 if list[-1][0] > list[-2][0] or list[-1][1] == list[-2][1]:
290 if list[-1][0] >= 0 and list[-1][0] < highlight
:
291 innerbevel
= list[-1][1]
293 # Now we know the adjustment we want to make to the
294 # pixel's overall grey shade due to the outer
295 # bevel, and due to the inner one. We break a tie
296 # in favour of a light outer bevel, but otherwise
299 if outerbevel
> 0 or outerbevel
== innerbevel
:
301 grey
= grey
+ outerbevel
+ innerbevel
303 pix
= greypix(grey
/ 4.0)
305 pixel(x
, y
, pix
, canvas
)
307 # The side panel is a parallelogram.
308 for x
in range(depth
):
309 for y
in range(height
):
310 pixel(x
+width
, y
-x
, greypix(0.5), canvas
)
312 # The top panel is another parallelogram.
313 for x
in range(width
):
314 for y
in range(depth
-1):
315 pixel(x
+(y
+1), -(y
+1), greypix(0.75), canvas
)
318 border(canvas
, size
, [(0,int(height
-1),BL
)])
323 # Monitor plus sysbox.
326 x
= int(round((2+size
/(size
+1))*size
))
327 y
= int(round(4*size
))
330 xoff
= sb
[0] - mb
[0] + x
331 yoff
= sb
[3] - mb
[3] - y
332 overlay(m
, xoff
, yoff
, s
)
338 # The lightning bolt motif.
340 # We always want this to be an even number of pixels in span.
341 width
= round(7*size
) * 2
342 height
= round(8*size
) * 2
344 # The outer edge of each side of the bolt goes to this point.
345 outery
= round(8.4*size
)
346 outerx
= round(11*size
)
348 # And the inner edge goes to this point.
349 innery
= height
- 1 - outery
350 innerx
= round(7*size
)
352 for y
in range(int(height
)):
355 list.append(width
-1-int(outerx
* float(y
) / outery
+ 0.3))
357 list.append(width
-1-int(innerx
* float(y
) / innery
+ 0.3))
360 list.append(int(outerx
* float(y0
) / outery
+ 0.3))
362 list.append(int(innerx
* float(y0
) / innery
+ 0.3))
364 for x
in range(int(list[0]), int(list[-1]+1)):
365 pixel(x
, y
, cY
, canvas
)
368 border(canvas
, size
, [(int(width
-1),0,TR
), (0,int(height
-1),BL
)])
375 # The document used in the PSCP/PSFTP icon.
377 width
= round(13*size
)
378 height
= round(16*size
)
380 lineht
= round(1*size
)
381 if lineht
< 1: lineht
= 1
382 linespc
= round(0.7*size
)
383 if linespc
< 1: linespc
= 1
384 nlines
= int((height
-linespc
)/(lineht
+linespc
))
385 height
= nlines
*(lineht
+linespc
)+linespc
# round this so it fits better
387 # Start by drawing a big white rectangle.
388 for y
in range(int(height
)):
389 for x
in range(int(width
)):
390 pixel(x
, y
, cW
, canvas
)
392 # Now draw lines of text.
393 for line
in range(nlines
):
394 # Decide where this line of text begins.
396 start
= round(4*size
)
397 elif line
< 5*nlines
/7:
398 start
= round((line
- (nlines
/7)) * size
)
400 start
= round(1*size
)
401 if start
< round(1*size
):
402 start
= round(1*size
)
403 # Decide where it ends.
404 endpoints
= [10, 8, 11, 6, 5, 7, 5]
405 ey
= line
* 6.0 / (nlines
-1)
408 exf
= endpoints
[int(eyf
)]
409 exc
= endpoints
[int(eyc
)]
413 end
= exf
* (eyc
-ey
) + exc
* (ey
-eyf
)
414 end
= round(end
* size
)
416 liney
= height
- (lineht
+linespc
) * (line
+1)
417 for x
in range(int(start
), int(end
)):
418 for y
in range(int(lineht
)):
419 pixel(x
, y
+liney
, cK
, canvas
)
422 border(canvas
, size
, \
423 [(0,0,TL
),(int(width
-1),0,TR
),(0,int(height
-1),BL
), \
424 (int(width
-1),int(height
-1),BR
)])
431 # The secret-agent hat in the Pageant icon.
433 topa
= [6]*9+[5,3,1,0,0,1,2,2,1,1,1,9,9,10,10,11,11,12,12]
434 topa
= [round(x
*size
) for x
in topa
]
435 botl
= round(topa
[0]+2.4*math
.sqrt(size
))
436 botr
= round(topa
[-1]+2.4*math
.sqrt(size
))
437 width
= round(len(topa
)*size
)
439 # Line equations for the top and bottom of the hat brim, in the
440 # form y=mx+c. c, of course, needs scaling by size, but m is
441 # independent of size.
443 brimtopc
= round(4*size
/3)
444 brimbotc
= round(10*size
/3)
446 for x
in range(int(width
)):
447 xs
= float(x
) * (len(topa
)-1) / (width
-1)
455 top
= topf
* (xc
-xs
) + topc
* (xs
-xf
)
456 top
= math
.floor(top
)
457 bot
= round(botl
+ (botr
-botl
) * x
/(width
-1))
459 for y
in range(int(top
), int(bot
)):
460 pixel(x
, y
, cK
, canvas
)
463 for x
in range(int(width
)):
464 brimtop
= brimtopc
+ brimm
* x
465 brimbot
= brimbotc
+ brimm
* x
466 for y
in range(int(math
.floor(brimtop
)), int(math
.ceil(brimbot
))):
467 tophere
= max(min(brimtop
- y
, 1), 0)
468 bothere
= max(min(brimbot
- y
, 1), 0)
469 grey
= bothere
- tophere
470 # Only draw brim pixels over pixels which are (a) part
471 # of the main hat, and (b) not right on its edge.
472 if canvas
.has_key((x
,y
)) and \
473 canvas
.has_key((x
,y
-1)) and \
474 canvas
.has_key((x
,y
+1)) and \
475 canvas
.has_key((x
-1,y
)) and \
476 canvas
.has_key((x
+1,y
)):
477 pixel(x
, y
, greypix(grey
), canvas
)
484 # The key in the PuTTYgen icon.
486 keyheadw
= round(9.5*size
)
487 keyheadh
= round(12*size
)
488 keyholed
= round(4*size
)
489 keyholeoff
= round(2*size
)
490 # Ensure keyheadh and keyshafth have the same parity.
491 keyshafth
= round((2*size
- (int(keyheadh
)&1)) / 2) * 2 + (int(keyheadh
)&1)
492 keyshaftw
= round(18.5*size
)
493 keyhead
= [round(x
*size
) for x
in [12,11,8,10,9,8,11,12]]
497 # Ellipse for the key head, minus an off-centre circular hole.
498 for y
in range(int(keyheadh
)):
499 dy
= (y
-(keyheadh
-1)/2.0) / (keyheadh
/2.0)
500 dyh
= (y
-(keyheadh
-1)/2.0) / (keyholed
/2.0)
501 for x
in range(int(keyheadw
)):
502 dx
= (x
-(keyheadw
-1)/2.0) / (keyheadw
/2.0)
503 dxh
= (x
-(keyheadw
-1)/2.0-keyholeoff
) / (keyholed
/2.0)
504 if dy
*dy
+dx
*dx
<= 1 and dyh
*dyh
+dxh
*dxh
> 1:
505 pixel(x
+ keyshaftw
, y
, cy
, canvas
)
507 # Rectangle for the key shaft, extended at the bottom for the
509 for x
in range(int(keyshaftw
)):
510 top
= round((keyheadh
- keyshafth
) / 2)
511 bot
= round((keyheadh
+ keyshafth
) / 2)
512 xs
= float(x
) * (len(keyhead
)-1) / round((len(keyhead
)-1)*size
)
516 if xc
< len(keyhead
):
518 yf
= keyhead
[int(xf
)]
519 yc
= keyhead
[int(xc
)]
523 bot
= yf
* (xc
-xs
) + yc
* (xs
-xf
)
524 for y
in range(int(top
),int(bot
)):
525 pixel(x
, y
, cy
, canvas
)
529 squarepix
.append((x
, int(top
), TL
))
531 squarepix
.append(last
+ (BL
,))
532 if last
!= None and not in_head
:
533 squarepix
.append(last
+ (BR
,))
537 border(canvas
, size
, squarepix
)
541 def linedist(x1
,y1
, x2
,y2
, x
,y
):
542 # Compute the distance from the point x,y to the line segment
543 # joining x1,y1 to x2,y2. Returns the distance vector, measured
544 # with x,y at the origin.
548 # Special case: if x1,y1 and x2,y2 are the same point, we
549 # don't attempt to extrapolate it into a line at all.
550 if x1
!= x2
or y1
!= y2
:
551 # First, find the nearest point to x,y on the infinite
552 # projection of the line segment. So we construct a vector
553 # n perpendicular to that segment...
556 # ... compute the dot product of (x1,y1)-(x,y) with that
558 nd
= (x1
-x
)*nx
+ (y1
-y
)*ny
559 # ... multiply by the vector we first thought of...
562 # ... and divide twice by the length of n.
563 ndx
= ndx
/ (nx
*nx
+ny
*ny
)
564 ndy
= ndy
/ (nx
*nx
+ny
*ny
)
565 # That gives us a displacement vector from x,y to the
566 # nearest point. See if it's within the range of the line
570 if cx
>= min(x1
,x2
) and cx
<= max(x1
,x2
) and \
571 cy
>= min(y1
,y2
) and cy
<= max(y1
,y2
):
572 vectors
.append((ndx
,ndy
))
574 # Now we have up to three candidate result vectors: (ndx,ndy)
575 # as computed just above, and the two vectors to the ends of
576 # the line segment, (x1-x,y1-y) and (x2-x,y2-y). Pick the
578 vectors
= vectors
+ [(x1
-x
,y1
-y
), (x2
-x
,y2
-y
)]
579 bestlen
, best
= None, None
581 vlen
= v
[0]*v
[0]+v
[1]*v
[1]
582 if bestlen
== None or bestlen
> vlen
:
590 # The spanner in the config box icon.
592 headcentre
= 0.5 + round(4*size
)
593 headradius
= headcentre
+ 0.1
594 headhighlight
= round(1.5*size
)
595 holecentre
= 0.5 + round(3*size
)
596 holeradius
= round(2*size
)
597 holehighlight
= round(1.5*size
)
598 shaftend
= 0.5 + round(25*size
)
599 shaftwidth
= round(2*size
)
600 shafthighlight
= round(1.5*size
)
601 cmax
= shaftend
+ shaftwidth
603 # Define three line segments, such that the shortest distance
604 # vectors from any point to each of these segments determines
605 # everything we need to know about where it is on the spanner
608 ((0,0), (holecentre
, holecentre
)),
609 ((headcentre
, headcentre
), (headcentre
, headcentre
)),
610 ((headcentre
+headradius
/math
.sqrt(2), headcentre
+headradius
/math
.sqrt(2)),
614 for y
in range(int(cmax
)):
615 for x
in range(int(cmax
)):
616 vectors
= [linedist(a
,b
,c
,d
,x
,y
) for ((a
,b
),(c
,d
)) in segments
]
617 dists
= [memoisedsqrt(vx
*vx
+vy
*vy
) for (vx
,vy
) in vectors
]
619 # If the distance to the hole line is less than
620 # holeradius, we're not part of the spanner.
621 if dists
[0] < holeradius
:
623 # If the distance to the head `line' is less than
624 # headradius, we are part of the spanner; likewise if
625 # the distance to the shaft line is less than
626 # shaftwidth _and_ the resulting shaft point isn't
627 # beyond the shaft end.
628 if dists
[1] > headradius
and \
629 (dists
[2] > shaftwidth
or x
+vectors
[2][0] >= shaftend
):
632 # We're part of the spanner. Now compute the highlight
633 # on this pixel. We do this by computing a `slope
634 # vector', which points from this pixel in the
635 # direction of its nearest edge. We store an array of
636 # slope vectors, in polar coordinates.
637 angles
= [math
.atan2(vy
,vx
) for (vx
,vy
) in vectors
]
639 if dists
[0] < holeradius
+ holehighlight
:
640 slopes
.append(((dists
[0]-holeradius
)/holehighlight
,angles
[0]))
641 if dists
[1]/headradius
< dists
[2]/shaftwidth
:
642 if dists
[1] > headradius
- headhighlight
and dists
[1] < headradius
:
643 slopes
.append(((headradius
-dists
[1])/headhighlight
,math
.pi
+angles
[1]))
645 if dists
[2] > shaftwidth
- shafthighlight
and dists
[2] < shaftwidth
:
646 slopes
.append(((shaftwidth
-dists
[2])/shafthighlight
,math
.pi
+angles
[2]))
647 # Now we find the smallest distance in that array, if
648 # any, and that gives us a notional position on a
649 # sphere which we can use to compute the final
653 for dist
, angle
in slopes
:
654 if bestdist
== None or bestdist
> dist
:
659 sx
= (1.0-bestdist
) * math
.cos(bestangle
)
660 sy
= (1.0-bestdist
) * math
.sin(bestangle
)
661 sz
= math
.sqrt(1.0 - sx
*sx
- sy
*sy
)
662 shade
= sx
-sy
+sz
/ math
.sqrt(3) # can range from -1 to +1
663 shade
= 1.0 - (1-shade
)/3
665 pixel(x
, y
, yellowpix(shade
), canvas
)
668 border(canvas
, size
, [])
672 # Functions to draw entire icons by composing the above components.
674 def xybolt(c1
, c2
, size
, boltoffx
=0, boltoffy
=0):
675 # Two unspecified objects and a lightning bolt.
678 w
= h
= round(32 * size
)
680 bolt
= lightning(size
)
682 # Position c2 against the top right of the icon.
684 assert bb
[2]-bb
[0] <= w
and bb
[3]-bb
[1] <= h
685 overlay(c2
, w
-bb
[2], 0-bb
[1], canvas
)
686 # Position c1 against the bottom left of the icon.
688 assert bb
[2]-bb
[0] <= w
and bb
[3]-bb
[1] <= h
689 overlay(c1
, 0-bb
[0], h
-bb
[3], canvas
)
690 # Place the lightning bolt artistically off-centre. (The
691 # rationale for this positioning is that it's centred on the
692 # midpoint between the centres of the two monitors in the PuTTY
693 # icon proper, but it's not really feasible to _base_ the
694 # calculation here on that.)
696 assert bb
[2]-bb
[0] <= w
and bb
[3]-bb
[1] <= h
697 overlay(bolt
, (w
-bb
[0]-bb
[2])/2 - round((1-boltoffx
)*size
), \
698 (h
-bb
[1]-bb
[3])/2 - round((2-boltoffy
)*size
), canvas
)
702 def putty_icon(size
):
703 return xybolt(computer(size
), computer(size
), size
)
705 def puttycfg_icon(size
):
706 w
= h
= round(32 * size
)
708 canvas
= putty_icon(size
)
709 # Centre the spanner.
711 overlay(s
, (w
-bb
[0]-bb
[2])/2, (h
-bb
[1]-bb
[3])/2, canvas
)
714 def puttygen_icon(size
):
715 return xybolt(computer(size
), key(size
), size
, boltoffx
=2)
718 return xybolt(document(size
), computer(size
), size
, boltoffx
=1)
720 def pterm_icon(size
):
721 # Just a really big computer.
724 w
= h
= round(32 * size
)
726 c
= computer(size
* 1.4)
728 # Centre c in the return canvas.
730 assert bb
[2]-bb
[0] <= w
and bb
[3]-bb
[1] <= h
731 overlay(c
, (w
-bb
[0]-bb
[2])/2, (h
-bb
[1]-bb
[3])/2, canvas
)
735 def ptermcfg_icon(size
):
736 w
= h
= round(32 * size
)
738 canvas
= pterm_icon(size
)
739 # Centre the spanner.
741 overlay(s
, (w
-bb
[0]-bb
[2])/2, (h
-bb
[1]-bb
[3])/2, canvas
)
744 def pageant_icon(size
):
745 # A biggish computer, in a hat.
748 w
= h
= round(32 * size
)
750 c
= computer(size
* 1.3)
756 # Determine the relative y-coordinates of the computer and hat.
757 # We just centre the one on the other.
758 xrel
= (cbb
[0]+cbb
[2]-hbb
[0]-hbb
[2])/2
760 # Determine the relative y-coordinates of the computer and hat.
761 # We do this by sitting the hat as low down on the computer as
762 # possible without any computer showing over the top. To do
763 # this we first have to find the minimum x coordinate at each
764 # y-coordinate of both components.
768 for cx
in cty
.keys():
770 assert hty
.has_key(hx
)
771 yrel
= cty
[cx
] - hty
[hx
]
775 yrelmin
= min(yrelmin
, yrel
)
777 # Overlay the hat on the computer.
778 overlay(ht
, xrel
, yrelmin
, c
)
780 # And centre the result in the main icon canvas.
782 assert bb
[2]-bb
[0] <= w
and bb
[3]-bb
[1] <= h
783 overlay(c
, (w
-bb
[0]-bb
[2])/2, (h
-bb
[1]-bb
[3])/2, canvas
)
787 # Test and output functions.
792 def testrun(func
, fname
):
794 for size
in [0.5, 0.6, 1.0, 1.2, 1.5, 4.0]:
795 canvases
.append(func(size
))
798 for canvas
in canvases
:
799 minx
, miny
, maxx
, maxy
= bbox(canvas
)
800 wid
= max(wid
, maxx
-minx
+4)
801 ht
= ht
+ maxy
-miny
+4
803 for canvas
in canvases
:
804 minx
, miny
, maxx
, maxy
= bbox(canvas
)
805 block
.extend(render(canvas
, minx
-2, miny
-2, minx
-2+wid
, maxy
+2))
806 p
= os
.popen("convert -depth 8 -size %dx%d rgb:- %s" %
(wid
,ht
,fname
), "w")
807 assert len(block
) == ht
809 assert len(line
) == wid
810 for r
, g
, b
, a
in line
:
811 # Composite on to orange.
812 r
= int(round((r
* a
+ 255 * (255-a
)) / 255.0))
813 g
= int(round((g
* a
+ 128 * (255-a
)) / 255.0))
814 b
= int(round((b
* a
+ 0 * (255-a
)) / 255.0))
815 p
.write("%c%c%c" %
(r
,g
,b
))
818 def drawicon(func
, width
, fname
, orangebackground
= 0):
819 canvas
= func(width
/ 32.0)
821 minx
, miny
, maxx
, maxy
= bbox(canvas
)
822 assert minx
>= 0 and miny
>= 0 and maxx
<= width
and maxy
<= width
824 block
= render(canvas
, 0, 0, width
, width
)
825 p
= os
.popen("convert -depth 8 -size %dx%d rgba:- %s" %
(width
,width
,fname
), "w")
826 assert len(block
) == width
828 assert len(line
) == width
829 for r
, g
, b
, a
in line
:
831 # Composite on to orange.
832 r
= int(round((r
* a
+ 255 * (255-a
)) / 255.0))
833 g
= int(round((g
* a
+ 128 * (255-a
)) / 255.0))
834 b
= int(round((b
* a
+ 0 * (255-a
)) / 255.0))
836 p
.write("%c%c%c%c" %
(r
,g
,b
,a
))
841 orangebackground
= test
= 0
842 colours
= 1 # 0=mono, 1=16col, 2=truecol
847 if doingargs
and arg
[0] == "-":
859 sys
.stderr
.write("unrecognised option '%s'\n" % arg
)
866 cK
=cr
=cg
=cb
=cm
=cc
=cP
=cw
=cR
=cG
=cB
=cM
=cC
=cD
= 0
870 return [cK
,cW
][int(round(value
))]
871 def yellowpix(value
):
872 return [cK
,cW
][int(round(value
))]
876 return [cT
,cK
][int(round(value
))]
877 def blend(col1
, col2
):
883 (0x00, 0x00, 0x00, 0xFF), # cK
884 (0xFF, 0xFF, 0xFF, 0xFF), # cW
885 (0x00, 0x00, 0x00, 0x00), # cT
888 return pixvals
[colour
]
889 def finalisepix(colour
):
892 # Windows 16-colour palette.
893 cK
,cr
,cg
,cy
,cb
,cm
,cc
,cP
,cw
,cR
,cG
,cY
,cB
,cM
,cC
,cW
= range(16)
895 cD
= -2 # special translucent half-darkening value used internally
897 return [cK
,cw
,cw
,cP
,cW
][int(round(4*value
))]
898 def yellowpix(value
):
899 return [cK
,cy
,cY
][int(round(2*value
))]
901 return [cK
,cb
,cB
][int(round(2*value
))]
903 return [cT
,cD
,cK
][int(round(2*value
))]
904 def blend(col1
, col2
):
908 return [cK
,cK
,cK
,cK
,cK
,cK
,cK
,cw
,cK
,cr
,cg
,cy
,cb
,cm
,cc
,cw
,cD
,cD
][col2
]
912 (0x00, 0x00, 0x00, 0xFF), # cK
913 (0x80, 0x00, 0x00, 0xFF), # cr
914 (0x00, 0x80, 0x00, 0xFF), # cg
915 (0x80, 0x80, 0x00, 0xFF), # cy
916 (0x00, 0x00, 0x80, 0xFF), # cb
917 (0x80, 0x00, 0x80, 0xFF), # cm
918 (0x00, 0x80, 0x80, 0xFF), # cc
919 (0xC0, 0xC0, 0xC0, 0xFF), # cP
920 (0x80, 0x80, 0x80, 0xFF), # cw
921 (0xFF, 0x00, 0x00, 0xFF), # cR
922 (0x00, 0xFF, 0x00, 0xFF), # cG
923 (0xFF, 0xFF, 0x00, 0xFF), # cY
924 (0x00, 0x00, 0xFF, 0xFF), # cB
925 (0xFF, 0x00, 0xFF, 0xFF), # cM
926 (0x00, 0xFF, 0xFF, 0xFF), # cC
927 (0xFF, 0xFF, 0xFF, 0xFF), # cW
928 (0x00, 0x00, 0x00, 0x80), # cD
929 (0x00, 0x00, 0x00, 0x00), # cT
932 return pixvals
[colour
]
933 def finalisepix(colour
):
934 # cD is used internally, but can't be output. Convert to cK.
940 cK
= (0x00, 0x00, 0x00, 0xFF)
941 cr
= (0x80, 0x00, 0x00, 0xFF)
942 cg
= (0x00, 0x80, 0x00, 0xFF)
943 cy
= (0x80, 0x80, 0x00, 0xFF)
944 cb
= (0x00, 0x00, 0x80, 0xFF)
945 cm
= (0x80, 0x00, 0x80, 0xFF)
946 cc
= (0x00, 0x80, 0x80, 0xFF)
947 cP
= (0xC0, 0xC0, 0xC0, 0xFF)
948 cw
= (0x80, 0x80, 0x80, 0xFF)
949 cR
= (0xFF, 0x00, 0x00, 0xFF)
950 cG
= (0x00, 0xFF, 0x00, 0xFF)
951 cY
= (0xFF, 0xFF, 0x00, 0xFF)
952 cB
= (0x00, 0x00, 0xFF, 0xFF)
953 cM
= (0xFF, 0x00, 0xFF, 0xFF)
954 cC
= (0x00, 0xFF, 0xFF, 0xFF)
955 cW
= (0xFF, 0xFF, 0xFF, 0xFF)
956 cD
= (0x00, 0x00, 0x00, 0x80)
957 cT
= (0x00, 0x00, 0x00, 0x00)
959 value
= max(min(value
, 1), 0)
960 return (int(round(0xFF*value
)),) * 3 + (0xFF,)
961 def yellowpix(value
):
962 value
= max(min(value
, 1), 0)
963 return (int(round(0xFF*value
)),) * 2 + (0, 0xFF)
965 value
= max(min(value
, 1), 0)
966 return (0, 0, int(round(0xFF*value
)), 0xFF)
968 value
= max(min(value
, 1), 0)
969 return (0, 0, 0, int(round(0xFF*value
)))
970 def blend(col1
, col2
):
973 r
= int(round((r1
*a1
+ r2
*(0xFF-a1
)) / 255.0))
974 g
= int(round((g1
*a1
+ g2
*(0xFF-a1
)) / 255.0))
975 b
= int(round((b1
*a1
+ b2
*(0xFF-a1
)) / 255.0))
976 a
= int(round((255*a1
+ a2
*(0xFF-a1
)) / 255.0))
981 # True colour with no alpha blending: we still have to
982 # finalise half-dark pixels to black.
983 def finalisepix(colour
):
985 return colour
[:3] + (0xFF,)
988 def finalisepix(colour
):
992 testrun(eval(realargs
[0]), realargs
[1])
994 drawicon(eval(realargs
[0]), int(realargs
[1]), realargs
[2], orangebackground
)