pipcomposite 3.2 KB

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