122 строки
3.2 KiB
Plaintext
122 строки
3.2 KiB
Plaintext
|
#!/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()
|