#!/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()
