1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
#!/usr/bin/env python
# $URL: http://pypng.googlecode.com/svn/trunk/code/pipcomposite $
# $Rev: 208 $
# pipcomposite
# Image alpha compositing.
"""
pipcomposite [--background #rrggbb] file.png
Composite an image onto a background and output the result. The
background colour is specified with an HTML-style triple (3, 6, or 12
hex digits), and defaults to black (#000).
The output PNG has no alpha channel.
It is valid for the input to have no alpha channel, but it doesn't
make much sense: the output will equal the input.
"""
import sys
def composite(out, inp, background):
import png
p = png.Reader(file=inp)
w,h,pixel,info = p.asRGBA()
outinfo = dict(info)
outinfo['alpha'] = False
outinfo['planes'] -= 1
outinfo['interlace'] = 0
# Convert to tuple and normalise to same range as source.
background = rgbhex(background)
maxval = float(2**info['bitdepth'] - 1)
background = map(lambda x: int(0.5 + x*maxval/65535.0),
background)
# Repeat background so that it's a whole row of sample values.
background *= w
def iterrow():
for row in pixel:
# Remove alpha from row, then create a list with one alpha
# entry _per channel value_.
# Squirrel the alpha channel away (and normalise it).
t = map(lambda x: x/maxval, row[3::4])
row = list(row)
del row[3::4]
alpha = row[:]
for i in range(3):
alpha[i::3] = t
assert len(alpha) == len(row) == len(background)
yield map(lambda a,v,b: int(0.5 + a*v + (1.0-a)*b),
alpha, row, background)
w = png.Writer(**outinfo)
w.write(out, iterrow())
def rgbhex(s):
"""Take an HTML style string of the form "#rrggbb" and return a
colour (R,G,B) triple. Following the initial '#' there can be 3, 6,
or 12 digits (for 4-, 8- or 16- bits per channel). In all cases the
values are expanded to a full 16-bit range, so the returned values
are all in range(65536).
"""
assert s[0] == '#'
s = s[1:]
assert len(s) in (3,6,12)
# Create a target list of length 12, and expand the string s to make
# it length 12.
l = ['z']*12
if len(s) == 3:
for i in range(4):
l[i::4] = s
if len(s) == 6:
for i in range(2):
l[i::4] = s[i::2]
l[i+2::4] = s[i::2]
if len(s) == 12:
l[:] = s
s = ''.join(l)
return map(lambda x: int(x, 16), (s[:4], s[4:8], s[8:]))
class Usage(Exception):
pass
def main(argv=None):
import getopt
import sys
if argv is None:
argv = sys.argv
argv = argv[1:]
try:
try:
opt,arg = getopt.getopt(argv, '',
['background='])
except getopt.error, msg:
raise Usage(msg)
background = '#000'
for o,v in opt:
if o in ['--background']:
background = v
except Usage, err:
print >>sys.stderr, __doc__
print >>sys.stderr, str(err)
return 2
if len(arg) > 0:
f = open(arg[0], 'rb')
else:
f = sys.stdin
return composite(sys.stdout, f, background)
if __name__ == '__main__':
main()
|