#!/usr/bin/python

# tz_png.py
#       --copyright--                   Copyright 2007 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       May 17, 2007            bar
#       June 26, 2007           bar     fix error conditions
#       November 18, 2007       bar     turn on doxygen
#       November 20, 2007       bar     comments
#       November 27, 2007       bar     insert boilerplate copyright
#       January 26, 2008        bar     allow a zero chunk crc (spurious error one night - from a file I generated using wxwidgets)
#       May 17, 2008            bar     email adr
#       --eodstamps--
##      \file
#
#
#       Do what I need to do with .png files.
#
#       http://www.w3.org/TR/PNG/
#
#


import  time

import  replace_file
import  tzlib




##                  The start of a .png file
PNG_MAGIC_BYTES =   "\x89PNG\r\n\x1A\n"


##                  Keywords in .png comments
PNG_KEYWORDS    =   [
                        "Title",            #   Short (one line) title or caption for image
                        "Author",           #   Name of image's creator
                        "Description",      #   Description of image (possibly long)
                        "Copyright",        #   Copyright notice
                        "Creation Time",    #   Time of original image creation
                        "Software",         #   Software used to create the image
                        "Disclaimer",       #   Legal disclaimer
                        "Warning",          #   Warning of nature of content
                        "Source",           #   Device used to create the image
                        "Comment",          #   Miscellaneous comment
                    ]



#
#   Note: this table is, in fact, that of tzlib's crc32.c, as opposed to pkcrc.c.
#

##  A .png CRC table is created (which we could simply copy the data from crc32.c)
crc_table   =   [ 0L ] * 256

for n in xrange(len(crc_table)) :
    c = n;
    for k in xrange(8) :
        if  c & 1 :
            c = 0xedb88320L ^ (c >> 1)
        else  :
            c = c >> 1
        pass

    crc_table[n] = c




def blkcrc(crc, blk, start = 0, end = 0) :
    """
        Return the CRC of the given data starting from the given CRC.
    """

    end         = end or len(blk)

    for i in xrange(start, end) :
        crc     = crc_table[(crc ^ ord(blk[i])) & 0xff] ^ (crc >> 8)

    return(crc & 0xFFFFffffL)




class a_png_chunk :
    """ Class whose objects contain .png file chunks. """

    def __init__(me, data, tag = "", start = 0, end = None) :
        """ Constructor. """

        end         = end or len(data)

        if  not tag :

            ln          = (long(ord(data[start])) << 24) | (ord(data[start + 1]) << 16) | (ord(data[start + 2]) << 8) | ord(data[start + 3])

            if  start + 12 + ln > end :
                raise ValueError("PNG chunk length too big. Offset:%u length:%u end:%u !" % ( start, ln, end ) )

            me.tag      = data[start + 4 : start + 8]
            i           = start + 8 + ln

            me.data     = data[start + 8 : i]
            me.crc      = (ord(data[i]) << 24) | (ord(data[i + 1]) << 16) | (ord(data[i + 2]) << 8) | ord(data[i + 3])

            crc         = blkcrc(0xFFFFffffL,  me.tag)
            crc         = blkcrc(crc, me.data)
            crc         = (~crc) & 0xFFFFffffL

            if  not me.crc :
                me.crc  = crc                               # oh, what the heck. Something seemed to have created a zero CRC late one night.
            elif   crc != me.crc :
                raise ValueError("PNG chunk tagged, %s, CRC bad %08x is not %08x!" % ( str(tzlib.ascii(me.tag)), me.crc, crc ) )
            pass

        elif len(tag) == 4 :

            me.tag      = tag

            me.data     = data

            me.crc      = blkcrc(0xFFFFffffL,  me.tag)
            me.crc      = blkcrc(me.crc, me.data)
            me.crc      = (~me.crc) & 0xFFFFffffL

        else :
            raise ValueError("Making PNG bad tag chunk [%s]!" % ( str(tag) ) )

        pass



    pass        # a_png_chunk




class a_png_tEXt_chunk(a_png_chunk) :
    """ Class whose objects contain tEXt chunks. """

    def __init__(me, key, arg) :
        """ Constructor. """

        if  not key :
            raise ValueError("No key for PNG tEXt chunk!")
        if  len(key) > 79 :
            raise IndexError("PNG key too long for tEXt cunk!")

        a_png_chunk.__init__(me, key + "\0" + arg, tag = "tEXt")

        pass




class a_png :
    """ Class whose objects are .png file data. """

    def __init__(me, fi) :
        """ Constructor. """

        me.chunks   = []

        if  fi :

            fn  = None
            if  isinstance(fi, basestring) :
                fn  = fi
                fi  = open(fi, "rb")

            es   = ""
            me.all  = fi.read()
            if  fn :
                fi.close()
                es  = fn

            if  not me.all.startswith(PNG_MAGIC_BYTES) :
                raise ValueError("File %s not a .png file!" % ( es ) )

            i   = 8
            while i < len(me.all) :
                me.chunks.append(a_png_chunk(me.all, None, i, len(me.all)))
                i  += len(me.chunks[-1].data) + 12

            if  len(me.chunks) < 3 :
                raise ValueError("Not enough chunks in .png file: %s!" % ( es ) )

            if  me.chunks[0].tag != 'IHDR' :
                raise ValueError("First .png file chunk not IHDR (%s) in file: %s!" % ( me.chunks[0].tag, es ) )

            if  me.chunks[-1].tag != 'IEND' :
                raise ValueError("Last .png file chunk not IEND (%s) in file: %s!" % ( me.chunks[-1].tag, es ) )

            pass

        pass


    def insert_tIME(me, t = None) :
        """ Put a tIME chunk in the .png data. """

        if  not t :
            t   = time.time()
        t       = time.gmtime(t)

        d       = chr((t.tm_year >> 8) & 0xff) + chr(t.tm_year & 0xff) + chr(t.tm_mon) + chr(t.tm_mday) + chr(t.tm_hour) + chr(t.tm_min) + chr(t.tm_sec)

        c       = a_png_chunk(d, "tIME")

        for i in xrange(len(me.chunks)) :
            if  me.chunks[i].tag == "tIME" :
                del(me.chunks[i])
                break
            pass

        me.chunks.insert(1, c)



    def save(me, fn) :
        """ Save the .png to the named file. """

        tfn = fn + ".tmp"
        fo  = open(tfn, "wb")

        fo.write(PNG_MAGIC_BYTES)

        for c in me.chunks :
            fo.write(chr((len(c.data) >> 24) & 0xff) + chr((len(c.data) >> 16) & 0xff) + chr((len(c.data) >> 8) & 0xff) + chr(len(c.data) & 0xff))
            fo.write(c.tag)
            fo.write(c.data)
            fo.write(chr((c.crc >> 24) & 0xff) + chr((c.crc >> 16) & 0xff) + chr((c.crc >> 8) & 0xff) + chr(c.crc & 0xff))

        fo.close()
        del(fo)

        replace_file.replace_file(fn, tfn, fn + ".bak")


    pass        #       a_png


if  __name__ == '__main__' :

    import  sys

    import  TZCommandLineAtFile


    del(sys.argv[0])
    TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv)

    while len(sys.argv) :
        fn  = sys.argv.pop(0)

        png = a_png(fn)

        print fn, len(png.chunks)

        png.chunks.insert(-1, a_png_tEXt_chunk("Comment", "blah blah"))
        png.chunks.insert(-1, a_png_tEXt_chunk("Creation Time", "May 18, 2007"))

        png.insert_tIME()

        c   = png.chunks.pop(-1)
        png.chunks.append(a_png_chunk(c.data, c.tag))

        c   = png.chunks.pop(0)
        png.chunks.insert(0, a_png_chunk(c.data, c.tag))

        png.save(fn + "_n.png")

    pass



#
#
#
# eof

