pygif.py :  » Network » emesene » emesene-1.6.2 » pygif » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Network » emesene 
emesene » emesene 1.6.2 » pygif » pygif.py
# -*- coding: utf-8 -*-
#
#   This file is part of emesene.
#
#    Emesene is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    Emesene is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with emesene; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
#   The Graphics Interchange Format(c) is the Copyright property of
#   CompuServe Incorporated. GIF(sm) is a Service Mark property of
#   CompuServe Incorporated.
#
# The unisys/lzw patent has expired, yes. If anyone puts another patent
# over this code, you must *burn* this file.

'''pygif: gif implementation in python

I (dx) have designed this to encode the handwritten messages for emesene.
While learning the format, I had to write that simple decoder -- so, it isn't
a complete gif89a decoder. If anyone needs to use it, contact me for help:
dx dxzone com ar.

Special credit for Jan de Mooij that fixed the giflzw encoder.'''


import struct
import math


KNOWN_FORMATS = ('GIF87a', 'GIF89a')


class Gif(object):
    '''Base class to encoder and decoder'''
    
    # struct format strings
        
    #17,18:
    FMT_HEADER = '<6sHHBBB'
    #20:
    FMT_IMGDESC = '<HHHHB'

    IMAGE_SEPARATOR = 0x2C
    EXTENSION_INTRODUCER = 0x21
    GIF_TRAILER = 0x3b

    LABEL_GRAPHIC_CONTROL = 0xF9
    LABEL_COMMENT = 0xFE
    LABEL_PLAINTEXT = 0x01

    FMT_EXT_GRAPHIC_CONTROL = '<BBHB' #89a
    
    def __init__( self, data, debug ):
        self.data = data
        self.pointer = 0
        
        # default data for an empty file
        self.header = 'GIF87a'
        self.ls_width = 0
        self.ls_height = 0
        self.flags = 0
        self.color_resolution = 0
        self.sort_flag = 0
        self.color_table_flag = 0
        self.global_color_table_size = 0
        self.background_color = 0
        self.aspect_ratio = 0
        # greyscale pallete by default
        self.pallete = [(x, x, x) for x in range(0, 256)]
        self.images = []

        self.debug_enabled = debug
        
    def pop( self, length=1 ):
        '''gets the next $len chars from thedatastack import 
        and increment the pointer'''
        
        start = self.pointer
        end = self.pointer + length
        self.pointer += length

        return self.data[start:end]
        
    def pops( self, format ):
        '''pop struct: get size, pop(), unpack()'''
        size = struct.calcsize(format) 
        return struct.unpack( format, self.pop(size) )

    def push( self, newdata ):
        '''adds $newdata to the data stack'''
        if self.debug_enabled:
            print repr(newdata), len(newdata)
        self.data += newdata
    
    def pushs( self, format, *vars ):
        '''push struct: adds a packed struct to the data stack'''
        self.push(struct.pack(format, *vars))
        
    def pop_bits( self, length ):
        '''return a list of bytes represented as bit lists'''
        bytes = self.pops( '<' + str(length) + 'B' )
        plain = []
        for byte in bytes:
            for bit in get_bits(byte):
                plain.append(bit)
        return plain
        
    def print_info( self ):
        '''prints out some useful info (..debug?)'''
        
        print "Version: %s" % self.header
        print "Logical screen width: %d" % self.ls_width
        print "Logical screen height: %d" % self.ls_height
        print "Flags: %s" % repr(self.flags)
        print " "*6,"Color resolution: %d" % self.color_resolution
        print " "*6,"Sort flag: %s" % str(self.sort_flag)
        print " "*6,"Global color table flag: %s" % str(self.color_table_flag)
        print " "*22,"...size: %d (%d bytes)" % \
            (self.global_color_table_size, self.global_color_table_size * 3)
        print "Background color: %d" % self.background_color
        print "Aspect ratio info: %d" % self.aspect_ratio

    def new_image( self, header=None ):
        '''adds a new image descriptor'''
        image = ImageDescriptor(self, header)
        self.images.append(image)
        return image
    
class ImageDescriptor(object):
    '''A class that represents a single image'''

    def __init__( self, parent, header=None ):
        # this will be set when needed
        self.codesize = 0

        # compressed output codes
        self.lzwcode = ''
        
        # uncompressed pixels (decoded)
        self.pixels = []
        
        # we assume a "fullscreen" image
        self.left = self.top = 0
        self.width = parent.ls_width
        self.height = parent.ls_height
        
        # yes, these default flags work...
        self.flags = [False for x in range(8)]
        self.local_color_table_flag = False
        self.interlace_flag = False
        self.sort_flag = False
        self.local_color_table_size = 0
        self.pallete = []
        
        if header:
            self.setup_header(header)

    def setup_header( self, header ):
        '''takes a header tuple and fills the attributes'''

        self.left = header[0]
        self.top = header[1]
        self.width = header[2]
        self.height = header[3]

        self.flags = get_bits( header[4] )
        
        self.local_color_table_flag = self.flags[7]
        assert self.local_color_table_flag == False, \
            "Local color tables not implemented" # TODO
        self.interlace_flag = self.flags[6]
        self.sort_flag = self.flags[5]
        #-- flags 4 and 3 are reserved
        self.local_color_table_size =  2 ** (pack_bits(self.flags[:2]) + 1)


    def get_header(self):
        '''builds a header dynamically'''
        flags = [False for x in range(8)]
        flags[7] = self.local_color_table_flag
        flags[6] = self.interlace_flag
        flags[5] = self.sort_flag
        
        # useless!
        flags[2], flags[1], flags[0] = get_bits(len(self.pallete), bits=3)

        return (self.left, self.top, self.width, self.height, pack_bits(flags))

    header = property(fget=get_header)
        

class GifDecoder( Gif ):
    '''decodes a gif file into.. something.. else..'''
    def __init__( self, data, debug=False ):
        Gif.__init__( self, data, debug )
        self.fill()
        
    def fill( self ):
        '''reads the data and fills each field of the file'''
        
        # start reading from the beggining of the file
        self.pointer = 0
        
        #17. Header.
        #18. Logical Screen Descriptor.
        data = self.pops( Gif.FMT_HEADER )
        
        self.header = data[0]
        self.ls_width = data[1]
        self.ls_height = data[2]
        self.background_color = data[4]
        self.aspect_ratio = data[5]
        
        # flags field
        self.flags = get_bits( data[3] )
        #1 bit
        self.color_table_flag = self.flags[7]
        self.sort_flag = self.flags[3]
        #3 bit
        self.color_resolution = pack_bits(self.flags[4:7]) # 7 not included
        #3 bit
        self.global_color_table_size = 2 ** (pack_bits(self.flags[:3]) + 1)
        
        #19. Global Color Table.
        if self.color_table_flag:
            size = (self.global_color_table_size) * 3
            self.pallete = self.get_color_table(size)
        else:
            # generate a greyscale pallete
            self.pallete = [(x, x, x) for x in range(256)]
        
        # blocks
        while True:
            try:
                nextbyte = self.pops('<B')[0]
            except:
                nextbyte = 0x3b # force end
            
            #20. Image Descriptor
            if nextbyte == Gif.IMAGE_SEPARATOR:
                descriptor = self.pops(Gif.FMT_IMGDESC)
                image = self.new_image(descriptor)
                image.codesize = self.pops('<B')[0]
                image.lzwcode = ''

                while True:
                    try:
                        blocksize = self.pops('<B')[0]
                    except:
                        break
                    if blocksize == 0:
                        break   # no more image data
                    lzwdata = self.pop(blocksize)
                    image.lzwcode += lzwdata
                
                if self.debug_enabled:
                    print 'LZW length:', len(image.lzwcode)

                image.pixels = self.lzw_decode(image.lzwcode, image.codesize, \
                    self.global_color_table_size)
            
            # Extensions
            elif nextbyte == Gif.EXTENSION_INTRODUCER:
                pass
            # Gif trailer
            elif nextbyte == Gif.GIF_TRAILER:
                return
            
            # "No Idea What Is This"
            else:
                pass

    def get_color_table( self, size ):
        '''Returns a color table in the format [(r,g,b),(r,g,b), ...]'''
         
        raw_color_table = self.pops("<%dB" % size)
        pos = 0
        pallete = []
        
        while pos + 3 < (size+1):
            red = raw_color_table[pos]
            green = raw_color_table[pos+1]
            blue = raw_color_table[pos+2]
            pallete.append((red, green, blue))
            pos += 3
        return pallete

    def lzw_decode(self, input, initial_codesize, color_table_size):
        '''Decodes a lzw stream from input import 
        Returns list of ints (pixel values)'''
        string_table = {}
        output = []
        old = ''
        index = 0
        
        codesize = initial_codesize + 1
        clearcode, end_of_info = color_table_size, color_table_size + 1
        bits = string_to_bits(input)

        def pop(size):
            '''Pops <size> bits from <bits>'''
            out = []
            for i in range(size):
                out.append(bits.pop(0))
            return out
        
        def clear():
            '''Called on clear code'''
            string_table.clear()
            for index in range(color_table_size):
                string_table[index] = chr(index)
            index = end_of_info + 1
            return index
        
        index = clear()
        
        # skip first (clear)code
        bits = bits[codesize:]

        # read first code, append to output
        code = bits_to_int(pop(codesize))
        output = [ord(string_table[code])]
        
        old = string_table[code]
        
        while len(bits) > 0:
            # read next code
            code = bits_to_int(pop(codesize))

            # special code?
            if code == clearcode:
                index = clear()
                
                codesize = initial_codesize + 1
                code = bits_to_int(pop(codesize))
                
                output.append(ord(string_table[code]))
                old = string_table[code]
                continue
                   
            elif code == end_of_info:
                break
            
            # code in stringtable?
            if code in string_table:
                c = string_table[code]
                string_table[index] = old + c[0]
            else:
                c = old + old[0]
                string_table[code] = c
     
            index += 1
            old = c
            output += [ord(x) for x in c]
            
            if index == 2 ** codesize:
                codesize += 1
                if codesize == 13:
                    codesize = 12
                    print 'decoding error, missed a clearcode?'
                    print 'index:', index
                    #exit()
            
        if self.debug_enabled:
            print 'Output stream len: %d' % len(output)
        return output

class GifEncoder(Gif):
    '''Encodes a *something* into a gif'''
    
    def __init__( self, basedecoder=None, debug=False ):
        Gif.__init__( self, '', debug )
        if basedecoder:
            self.clone(basedecoder)

    def clone( self, decoder ):
        '''moves decoder data into this class'''
        self.header = decoder.header
        self.ls_width = decoder.ls_width
        self.ls_height = decoder.ls_height
        self.flags = decoder.flags
        self.background_color = decoder.background_color
        self.aspect_ratio = decoder.aspect_ratio

        self.color_table_flag = decoder.color_table_flag
        self.pallete = decoder.pallete
        self.color_resolution = decoder.color_resolution
        self.global_color_table_size = decoder.global_color_table_size
        
        self.images = decoder.images

    def write( self, filename ):
        '''rebuilds the gif stream and writes it to filename'''
        self.data = ''
        if self.debug_enabled:
            print "header, screen descriptor"
        #17. Header.
        #18. Logical Screen Descriptor.
        self.pushs( Gif.FMT_HEADER, self.header, self.ls_width, \
            self.ls_height, pack_bits(self.flags), \
            self.background_color, self.aspect_ratio )
        
        if self.debug_enabled:
            print "global color table"
        #19. Global Color Table.
        #print self.color_table_flag, self.pallete
        if self.color_table_flag:
            for red, green, blue in self.pallete:
                self.pushs("<BBB", red, green, blue)

        if self.debug_enabled:
            print "images"
        #20. Image Descriptor
        self.images = self.images[:1]
        for image in self.images:
            if self.debug_enabled:
                print "image:", image
            self.pushs('<B', Gif.IMAGE_SEPARATOR)
            self.pushs(Gif.FMT_IMGDESC, *image.header)
            self.pushs('<B', image.codesize)
            lzwcode = image.lzwcode
            while len(lzwcode) > 0:
                chunk, lzwcode = lzwcode[:254], lzwcode[254:]
                self.pushs('<B', len(chunk))
                self.push(chunk)

        # Trailer, end of file
        self.pushs('<B', 0)
        self.pushs('<B', Gif.GIF_TRAILER)
        
        rc = open(filename, 'wb')
        rc.write(self.data)
        
    def build_flags(self):
        '''builds the flag integer'''
        flaglist = [False for x in range(8)]
        # single bit flags
        flaglist[7] = bool(self.color_table_flag)
        flaglist[3] = bool(self.sort_flag)
        
        # color resolution
        colorbits = get_bits(self.color_resolution, bits=3)
        if self.debug_enabled:
            print 'reso: %d' % self.color_resolution
        flaglist[4], flaglist[5], flaglist[6] = colorbits

        if self.color_table_flag:
            # global color table size
            if self.debug_enabled:
                print 'pallete: %d' % len(self.pallete)
                print 'FIXME: is there an easier way?'
            val = int(math.ceil(math.log(len(self.pallete))/math.log(2)-1))
            gctsbits = get_bits(val, bits=3)
            flaglist[0], flaglist[1], flaglist[2] = gctsbits
        
        # self.flags is a list, not an int
        self.flags = flaglist

    def lzw_encode(self, pixels):
        '''Encodes a pixel stream (list of ints) in lzw code
        Returns tuple (initial code size, lzw string)'''

        if self.debug_enabled:
            print 'encoding %d pixels...' % len(pixels)
        
        string_table = {}
        output = []
        old = ''
        index = 0
        
        color_table_size = self.global_color_table_size
        clearcode = color_table_size
        end_of_info = color_table_size + 1
        # what?
        initial_codesize = \
            int(math.ceil( math.log(color_table_size) / math.log(2) - 1 )) + 1
        
        codesize = initial_codesize + 1
        bits = []

        def clear():
            string_table.clear()
            for index in range(color_table_size):
                string_table[chr(index)] = index
            index = end_of_info + 1
            return index
        
        # Initialize code table
        index = clear()
        #for i in string_table: print ord(i), '-', string_table[i]
        
        # add clear code
        add = get_bits(clearcode, bits=codesize)
        output += add
        
        c = chr(pixels.pop(0))
        counter = 0
        maxcount = len(pixels)
        
        while counter < maxcount:
            p = chr(pixels[counter])
            s = c + p
            
            counter += 1
            
            if s in string_table:
                c += p
            else:
                string_table[s] = index
                index += 1
                
                output += get_bits(string_table[c], bits=codesize)
                c = p
                
                if index == ((2 ** codesize)+1):
                    codesize += 1
                    if codesize == 13:
                        output += get_bits(clearcode, bits=12)
                        index = clear()
                        codesize = initial_codesize + 1

        if c != '':
            add = get_bits(string_table[c], bits=codesize)
            output += add

        add = get_bits(end_of_info, bits=codesize)
        output += add
        o = bits_to_string(output)
        return (initial_codesize, o)


def bits_to_int(bits):
    '''high level bit list packer'''
    i = 0
    c = 0
    bits.reverse()
    while c < len(bits):
        if bits[c]:
            i += 2 ** (len(bits) - c - 1)
        c += 1
    return i

def get_bits( flags, reverse=False, bits=8 ):
    '''return a list with $bits items, one for each enabled bit'''

    mybits = [ 1 << x for x in range(bits) ]
    
    ret = []
    for bit in mybits:
        ret.append(flags & bit != 0)

    if reverse:
        ret.reverse()

    return ret

def pack_bits( bits ):
    '''convert a bit (bool or int) tuple into a int'''
    packed = 0
    level = 0
    for bit in bits:
        if bit:
            packed += 2 ** level    
        #packed += int(bit) << level
        #print bit, packed, level
        level += 1
    return packed

def string_to_bits(string):
    '''high level string unpacker'''
    bits = []
    for byte in string:
        for bit in get_bits(ord(byte)):
            bits.append(bit)
    return bits

def bits_to_string(bits):
    '''high level bit list packer'''
    string = ''
    while len(bits)>0:
        code = pack_bits(bits[:8])
        bits = bits[8:]
        string += chr(code)
    return string

def readable(bool_list):
    '''Converts a list of booleans to a readable list of ints
    Useful for debug only'''
    return [int(x) for x in bool_list]

www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.