pipstack 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. #!/usr/bin/env python
  2. # $URL: http://pypng.googlecode.com/svn/trunk/code/pipstack $
  3. # $Rev: 190 $
  4. # pipstack
  5. # Combine input PNG files into a multi-channel output PNG.
  6. """
  7. pipstack file1.png [file2.png ...]
  8. pipstack can be used to combine 3 greyscale PNG files into a colour, RGB,
  9. PNG file. In fact it is slightly more general than that. The number of
  10. channels in the output PNG is equal to the sum of the numbers of
  11. channels in the input images. It is an error if this sum exceeds 4 (the
  12. maximum number of channels in a PNG image is 4, for an RGBA image). The
  13. output colour model corresponds to the number of channels: 1 -
  14. greyscale; 2 - greyscale+alpha; 3 - RGB; 4 - RGB+alpha.
  15. In this way it is possible to combine 3 greyscale PNG files into an RGB
  16. PNG (a common expected use) as well as more esoteric options: rgb.png +
  17. grey.png = rgba.png; grey.png + grey.png = greyalpha.png.
  18. Color Profile, Gamma, and so on.
  19. [This is not implemented yet]
  20. If an input has an ICC Profile (``iCCP`` chunk) then the output will
  21. have an ICC Profile, but only if it is possible to combine all the input
  22. ICC Profiles. It is possible to combine all the input ICC Profiles
  23. only when: they all use the same Profile Connection Space; the PCS white
  24. point is the same (specified in the header; should always be D50);
  25. possibly some other things I haven't thought of yet.
  26. If some of the inputs have a ``gAMA`` chunk (specifying gamma) and
  27. an output ICC Profile is being generated, then the gamma information
  28. will be incorporated into the ICC Profile.
  29. When the output is an RGB colour type and the output ICC Profile is
  30. synthesized, it is necessary to supply colorant tags (``rXYZ`` and so
  31. on). These are taken from ``sRGB``.
  32. If the input images have ``gAMA`` chunks and no input image has an ICC
  33. Profile then the output image will have a ``gAMA`` chunk, but only if
  34. all the ``gAMA`` chunks specify the same value. Otherwise a warning
  35. will be emitted and no ``gAMA`` chunk. It is possible to add or replace
  36. a ``gAMA`` chunk using the ``pipchunk`` tool.
  37. gAMA, pHYs, iCCP, sRGB, tIME, any other chunks.
  38. """
  39. class Error(Exception):
  40. pass
  41. def stack(out, inp):
  42. """Stack the input PNG files into a single output PNG."""
  43. from array import array
  44. import itertools
  45. # Local module
  46. import png
  47. if len(inp) < 1:
  48. raise Error("Required input is missing.")
  49. l = map(png.Reader, inp)
  50. # Let data be a list of (pixel,info) pairs.
  51. data = map(lambda p: p.asDirect()[2:], l)
  52. totalchannels = sum(map(lambda x: x[1]['planes'], data))
  53. if not (0 < totalchannels <= 4):
  54. raise Error("Too many channels in input.")
  55. alpha = totalchannels in (2,4)
  56. greyscale = totalchannels in (1,2)
  57. bitdepth = []
  58. for b in map(lambda x: x[1]['bitdepth'], data):
  59. try:
  60. if b == int(b):
  61. bitdepth.append(b)
  62. continue
  63. except (TypeError, ValueError):
  64. pass
  65. # Assume a tuple.
  66. bitdepth += b
  67. # Currently, fail unless all bitdepths equal.
  68. if len(set(bitdepth)) > 1:
  69. raise NotImplemented("Cannot cope when bitdepths differ - sorry!")
  70. bitdepth = bitdepth[0]
  71. arraytype = 'BH'[bitdepth > 8]
  72. size = map(lambda x: x[1]['size'], data)
  73. # Currently, fail unless all images same size.
  74. if len(set(size)) > 1:
  75. raise NotImplemented("Cannot cope when sizes differ - sorry!")
  76. size = size[0]
  77. # Values per row
  78. vpr = totalchannels * size[0]
  79. def iterstack():
  80. # the izip call creates an iterator that yields the next row
  81. # from all the input images combined into a tuple.
  82. for irow in itertools.izip(*map(lambda x: x[0], data)):
  83. row = array(arraytype, [0]*vpr)
  84. # output channel
  85. och = 0
  86. for i,arow in enumerate(irow):
  87. # ensure incoming row is an array
  88. arow = array(arraytype, arow)
  89. n = data[i][1]['planes']
  90. for j in range(n):
  91. row[och::totalchannels] = arow[j::n]
  92. och += 1
  93. yield row
  94. w = png.Writer(size[0], size[1],
  95. greyscale=greyscale, alpha=alpha, bitdepth=bitdepth)
  96. w.write(out, iterstack())
  97. def main(argv=None):
  98. import sys
  99. if argv is None:
  100. argv = sys.argv
  101. argv = argv[1:]
  102. arg = argv[:]
  103. return stack(sys.stdout, arg)
  104. if __name__ == '__main__':
  105. main()