128 строки
4.3 KiB
Python
128 строки
4.3 KiB
Python
#!/usr/bin/env python
|
|
# $URL: http://pypng.googlecode.com/svn/trunk/code/pipstack $
|
|
# $Rev: 190 $
|
|
|
|
# pipstack
|
|
# Combine input PNG files into a multi-channel output PNG.
|
|
|
|
"""
|
|
pipstack file1.png [file2.png ...]
|
|
|
|
pipstack can be used to combine 3 greyscale PNG files into a colour, RGB,
|
|
PNG file. In fact it is slightly more general than that. The number of
|
|
channels in the output PNG is equal to the sum of the numbers of
|
|
channels in the input images. It is an error if this sum exceeds 4 (the
|
|
maximum number of channels in a PNG image is 4, for an RGBA image). The
|
|
output colour model corresponds to the number of channels: 1 -
|
|
greyscale; 2 - greyscale+alpha; 3 - RGB; 4 - RGB+alpha.
|
|
|
|
In this way it is possible to combine 3 greyscale PNG files into an RGB
|
|
PNG (a common expected use) as well as more esoteric options: rgb.png +
|
|
grey.png = rgba.png; grey.png + grey.png = greyalpha.png.
|
|
|
|
Color Profile, Gamma, and so on.
|
|
|
|
[This is not implemented yet]
|
|
|
|
If an input has an ICC Profile (``iCCP`` chunk) then the output will
|
|
have an ICC Profile, but only if it is possible to combine all the input
|
|
ICC Profiles. It is possible to combine all the input ICC Profiles
|
|
only when: they all use the same Profile Connection Space; the PCS white
|
|
point is the same (specified in the header; should always be D50);
|
|
possibly some other things I haven't thought of yet.
|
|
|
|
If some of the inputs have a ``gAMA`` chunk (specifying gamma) and
|
|
an output ICC Profile is being generated, then the gamma information
|
|
will be incorporated into the ICC Profile.
|
|
|
|
When the output is an RGB colour type and the output ICC Profile is
|
|
synthesized, it is necessary to supply colorant tags (``rXYZ`` and so
|
|
on). These are taken from ``sRGB``.
|
|
|
|
If the input images have ``gAMA`` chunks and no input image has an ICC
|
|
Profile then the output image will have a ``gAMA`` chunk, but only if
|
|
all the ``gAMA`` chunks specify the same value. Otherwise a warning
|
|
will be emitted and no ``gAMA`` chunk. It is possible to add or replace
|
|
a ``gAMA`` chunk using the ``pipchunk`` tool.
|
|
|
|
gAMA, pHYs, iCCP, sRGB, tIME, any other chunks.
|
|
"""
|
|
|
|
class Error(Exception):
|
|
pass
|
|
|
|
def stack(out, inp):
|
|
"""Stack the input PNG files into a single output PNG."""
|
|
|
|
from array import array
|
|
import itertools
|
|
# Local module
|
|
import png
|
|
|
|
if len(inp) < 1:
|
|
raise Error("Required input is missing.")
|
|
|
|
l = map(png.Reader, inp)
|
|
# Let data be a list of (pixel,info) pairs.
|
|
data = map(lambda p: p.asDirect()[2:], l)
|
|
totalchannels = sum(map(lambda x: x[1]['planes'], data))
|
|
|
|
if not (0 < totalchannels <= 4):
|
|
raise Error("Too many channels in input.")
|
|
alpha = totalchannels in (2,4)
|
|
greyscale = totalchannels in (1,2)
|
|
bitdepth = []
|
|
for b in map(lambda x: x[1]['bitdepth'], data):
|
|
try:
|
|
if b == int(b):
|
|
bitdepth.append(b)
|
|
continue
|
|
except (TypeError, ValueError):
|
|
pass
|
|
# Assume a tuple.
|
|
bitdepth += b
|
|
# Currently, fail unless all bitdepths equal.
|
|
if len(set(bitdepth)) > 1:
|
|
raise NotImplemented("Cannot cope when bitdepths differ - sorry!")
|
|
bitdepth = bitdepth[0]
|
|
arraytype = 'BH'[bitdepth > 8]
|
|
size = map(lambda x: x[1]['size'], data)
|
|
# Currently, fail unless all images same size.
|
|
if len(set(size)) > 1:
|
|
raise NotImplemented("Cannot cope when sizes differ - sorry!")
|
|
size = size[0]
|
|
# Values per row
|
|
vpr = totalchannels * size[0]
|
|
def iterstack():
|
|
# the izip call creates an iterator that yields the next row
|
|
# from all the input images combined into a tuple.
|
|
for irow in itertools.izip(*map(lambda x: x[0], data)):
|
|
row = array(arraytype, [0]*vpr)
|
|
# output channel
|
|
och = 0
|
|
for i,arow in enumerate(irow):
|
|
# ensure incoming row is an array
|
|
arow = array(arraytype, arow)
|
|
n = data[i][1]['planes']
|
|
for j in range(n):
|
|
row[och::totalchannels] = arow[j::n]
|
|
och += 1
|
|
yield row
|
|
w = png.Writer(size[0], size[1],
|
|
greyscale=greyscale, alpha=alpha, bitdepth=bitdepth)
|
|
w.write(out, iterstack())
|
|
|
|
|
|
def main(argv=None):
|
|
import sys
|
|
|
|
if argv is None:
|
|
argv = sys.argv
|
|
argv = argv[1:]
|
|
arg = argv[:]
|
|
return stack(sys.stdout, arg)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|