# -*- 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
#
import struct
import drawing
import gtk.gdk
class IsfBin( object ):
'''Base class to encoder and decoder, represents
the information read from tobestored import
a binary Isf file, as well as the functions
necessary to do so '''
def __init__( self, data = '' ):
self.data = data
self.pointer = 0
self.bit = 0
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 ):
'''Reads formatted data from thestruct.Supports import
in addition to anthing supported by struct, mbuint32/64,
and mbsint32'''
if format.startswith('mbuint32'):
next = True
temp = 0
bitcount = 0
while next:
byte = struct.unpack( '<B', self.pop(1) )[0]
num = byte & 0x7f
temp *= pow(2, 7)
temp = temp | num
bitcount += 7
if byte > 0x7f and bitcount < 29:
next = True
else:
next = False
return temp
elif format.startswith('mbuint64'):
next = True
temp = 0
bitcount = 0
while next:
byte = struct.unpack( '<B', self.pop(1) )[0]
num = byte & 0x7f
temp *= pow(2, 7)
temp = temp | num
bitcount += 7
if byte > 0x7f and bitcount < 57:
next = True
else:
next = False
return temp
# The number of points in a stroke seems to use this strange
# format of integer. It is essentially a multi-byte integer
# where the least significant bits come first in the stream.
elif format.startswith('weirdoint'):
next = True
temp = 0
bitcount = 0
while next:
byte = struct.unpack( '<B', self.pop(1) )[0]
num = byte & 0x7f
temp = temp + num * pow(2, bitcount)
bitcount += 7
if byte > 0x7f:
next = True
else:
next = False
return temp
elif format.startswith('mbsint32'):
temp = self.pops('mbuint64')
if temp & 1:
temp *= -1
return int(temp / 2)
else:
# pop struct: get size, pop(), unpack()
size = struct.calcsize(format)
return struct.unpack( format, self.pop(size) )
def push( self, newdata, insert = False ):
'''adds $newdata to the data stack, if insert is True then
it adds it to the beginning of the stack'''
if insert:
self.data = newdata + self.data
else:
self.data += newdata
def pushs( self, format, *vars ):
'''Pushes formatted data onto the struct. Supports, in
in addition to anthing supported by struct, mbuint32/64,
and mbsint32'''
# TODO: I need a way to insert things at the beginning
# for now insert is always False
insert = False
if format.startswith('mbuint32'):
if len(vars) > 1:
print 'Only one value per', format
value = vars[0]
if value > 0xfffffff or value < 0:
print 'Value not within', format, 'range'
new = True
cont = False
num = 0
values = []
while new or cont:
for i in range(7):
bit = value % 2
value = int(value / 2)
if bit:
num += pow(2, i)
if value > 0:
cont = True
else:
cont = False
if new:
new = False
elif cont:
num += pow(2, 7)
values.insert(0, num)
num = 0
if insert:
for val in values.reverse():
self.push( struct.pack( '<B', val, insert = True ) )
else:
for val in values:
self.push( struct.pack( '<B', val ) )
elif format.startswith('mbuint64'):
if len(vars) > 1:
print 'Only one value per', format
value = vars[0]
if value > 0xfffffffffffffff or value < 0:
print 'Value not within', format, 'range'
new = True
cont = False
num = 0
values = []
while new or cont:
for i in range(7):
bit = value % 2
value = int(value / 2)
if bit and i < 7:
num += pow(2, i)
if value > 0:
cont = True
else:
cont = False
if new:
new = False
else:
num += pow(2, 7)
values.insert(0, num)
num = 0
if insert:
for val in values.reverse():
self.push( struct.pack( '<B', val, insert = True ) )
else:
for val in values:
self.push( struct.pack( '<B', val ) )
elif format.startswith('mbsint32'):
if len(vars) > 1:
print 'Only one value per', format
value = vars[0]
if abs(value) > 0xfffffff:
print 'Value not within', format, 'range'
if value < 0:
sign = 1
value = abs(value)
else:
sign = 0
value = value * 2 + sign
self.pushs('mbuint64', value)
elif format.startswith('weirdoint'):
if len(vars) > 1:
print 'Only one value per', format
value = vars[0]
new = True
cont = False
num = 0
values = []
while new or cont:
for i in range(7):
bit = value % 2
value = int(value / 2)
if bit and i < 7:
num += pow(2, i)
if value > 0:
cont = True
else:
cont = False
if new:
new = False
else:
num += pow(2, 7)
values.append(num)
num = 0
if insert:
for val in values.reverse():
self.push( struct.pack( '<B', val, insert = True ) )
else:
for val in values:
self.push( struct.pack( '<B', val ) )
else:
# push struct: adds a packed struct to the data stack
self.push(struct.pack(format, *vars))
class ImageDescriptor( object ):
'''A class that represents a single image'''
# The following two arrays, taglist and tagformat are organized
# so the index is the tag's index, and the format strings are
# the type of payload following the tag
# Array of tag strings
taglist = [
'INK_SPACE_RECT', 'GUID_TABLE', 'DRAW_ATTRS_TABLE',
'DRAW_ATTRS_BLOCK', 'STROKE_DESC_TABLE', 'STROKE_DESC_BLOCK',
'BUTTONS', 'NO_X', 'NO_Y', 'DIDX', 'STROKE', 'STROKE_PROPERTY_LIST',
'POINT_PROPERTY', 'SIDX', 'COMPRESSION_HEADER', 'TRANSFORM_TABLE',
'TRANSFORM', 'TRANSFORM_ISOTROPIC_SCALE', 'TRANSFORM_ANISOTROPIC_SCALE',
'TRANSFORM_ROTATE', 'TRANSFORM_TRANSLATE', 'TRANSFORM_SCALE_AND_TRANSLATE',
'TRANSFORM_QUAD', 'TIDX', 'METRIC_TABLE', 'METRIC_BLOCK', 'MIDX',
'MANTISSA', 'PERSISTENT_FORMAT', 'HIMETRIC_SIZE', 'STROKE_IDS']
# Array of format stings; a V means an mbuint64 containing
# the payload size follows
tagformat = [
'pass', 'V', 'V', 'V', 'V', 'V', 'pass', 'pass', 'pass', 'mbuint32', 'V',
'pass', 'pass', 'mbuint32', 'pass', 'V', '<ffffff', '<f', '<ff', 'mbuint32',
'<ff', '<ffff', '<IIIIII', 'mbuint32', 'V', 'V', 'pass', 'mbuint64', 'V',
'V', 'V']
# Array of bitamounts used in huffman decompression
bitamounts = [
[0, 1, 2, 4, 6, 8, 12, 16, 24, 32], [0, 1, 1, 2, 4, 8, 12, 16, 24, 32],
[0, 1, 1, 1, 2, 4, 8, 14, 22, 32], [0, 2, 2, 3, 5, 8, 12, 16, 24, 32],
[0, 3, 4, 5, 8, 12, 16, 24, 32], [0, 4, 6, 8, 12, 16, 24, 32],
[0, 6, 8, 12, 16, 24, 32], [0, 7, 8, 12, 16, 24, 32]]
def __init__( self ):
self.paths = []
self.isf_tag = 0
self.isf_size = 0
self.himetric_size = [0, 0]
self.indices = {
'DIDX':0,
'TIDX':0,
'MIDX':0,
'SIDX':0 }
self.transforms = []
self.draw_attrs_table = []
self.size = 8
def print_info( self ):
'''Prints some information about the isf file'''
print 'ISFTag:', self.isf_tag
print 'ISFSize:', self.isf_size
print 'Image Size:', self.himetric_size[0], 'x', self.himetric_size[1]
print 'Transforms:'
for trans in self.transforms:
print trans
print 'Paths:'
for path in self.paths:
path.print_info()
print 'Indices:', self.indices
class IsfEncoder( ImageDescriptor ):
'''encodes an isf file'''
def __init__( self, paths, debug=False):
self.paths = paths
self.enc = IsfBin()
self.fill()
def fill( self ):
'''Fills the data array'''
for path in self.paths:
stroke = self.enc_stroke(path)
self.enc.pushs('mbuint32', taglist.index('STROKE'))
self.enc.pushs('mbuint64', len(stroke))
self.enc.data += stroke
self.enc.pointer += len(stroke)
self.add_headers()
def add_headers( self ):
'''Adds basic information to the top of the file'''
head = IsfBin()
head.pushs('mbuint32', 0)
head.pushs('mbuint64', len(self.enc.data))
self.enc.data = head.data + self.enc.data
self.enc.pointer += len(head.data)
def enc_stroke(self, path, enc):
num_pts = len(path._points)
stroke = IsfBin()
stroke.pushs('weirdoint', num_pts)
guid_x, guid_y = [], []
for point in path._points:
guid_x.append(point.x)
guid_y.append(point.y)
if num_pts > 1:
packet_x = enc_huffman(guid_x)
packet_y = enc_huffman(guid_y)
tag = '\x82'
else:
packet_x = enc_gorilla(guid_x)
packet_y = enc_gorilla(guid_y)
stroke.data += tag + packet_x + tag + packet_y
stroke.data = struct.pack('<B', len(stroke.data)) + stroke.data
return stroke.data
def enc_huffman(self, guid):
stream = Bitstream()
# Initial point to the stream
enc_huffvalue(guid[0], stream)
prevdelta = 0
curdelta = 0
i = 1
while i < len(guid):
curdelta = guid[i] - guid[i-1]
diff = curdelta - prevdelta
prevdelta = curdelta
enc_huffvalue(diff, stream)
def enc_huffvalue(self, val, stream):
temp = val
length = 0
while temp:
# Get the number of bits needed to represent
length += 1
temp /=2
# Find the next smallest length in bitamounts
while not bitamounts[2].count(length):
length += 1
# Get the index of that length
index = bitamounts[2].index(length)
# Push that index to the stream
stream.push_count(index)
# Push the actual value of length length
stream.push_int(val, length)
class transform( object ):
def __init__( self):
self.a = 1
class IsfDecoder( ImageDescriptor ):
'''decodes an isf file'''
def __init__( self, data, debug=False):
ImageDescriptor.__init__( self )
# Dictionary of functions associated with tags
self.tagdict = {
'STROKE':self.dec_stroke,
'DIDX':self.dec_index,
'SIDX':self.dec_index,
'TIDX':self.dec_index,
'MIDX':self.dec_index,
'DRAW_ATTRS_BLOCK':self.dec_dablock,
'DRAW_ATTRS_TABLE':self.dec_datable
}
dec = IsfBin(data)
self.debug = debug
self.fill( dec )
def fill( self, dec ):
'''reads the data and fills each field of the file'''
dec.pointer = 0
self.isf_tag = dec.pops('mbuint32')
if self.debug:
print 'ISFTag', self.isf_tag
self.isf_size = dec.pops('mbuint64')
if self.debug:
print 'ISFSize', self.isf_size
# read tags
while True:
try:
tagnum = dec.pops('mbuint32')
tag = self.taglist[tagnum]
if self.debug:
print 'Tag', tag
format = self.tagformat[tagnum]
except IndexError:
if self.debug:
print 'Uknown Tag Value'
except struct.error:
if self.debug:
print 'End of file'
return
except:
raise
else:
prnt = 1
if format == 'pass':
pass
elif format == 'V':
payload_size = dec.pops('mbuint64')
if self.debug:
print '\tPayload Size', payload_size
payload = dec.data[dec.pointer:dec.pointer+payload_size]
if self.tagdict.has_key(tag):
prnt = 0
self.tagdict[tag](tag, payload)
if self.debug and prnt:
for char in payload:
print '\t', hex(struct.unpack('<B', char)[0])
dec.pointer += payload_size
else:
payload = dec.pops(format)
if self.tagdict.has_key(tag):
prnt = 0
self.tagdict[tag](tag, payload)
if self.debug and prnt:
print '\tPayload', payload
def process_tag(self, tag, payload):
'''Performs specific operations with supported tags'''
if tag == 'STROKE_IDS':
dat = IsfBin(payload)
num_pts = dat.pops('mbuint32')
stream = Bitstream(payload[dat.pointer:])
ids = self.dec_packet(stream, num_pts)
if self.debug:
print '\t', ids
elif tag == 'HIMETRIC_SIZE':
dat = IsfBin(payload)
width = dat.pops('mbsint32')
height = dat.pops('mbsint32')
self.himetric_size = [width, height]
else: return 1
def dec_datable( self, tag, payload ):
dat = IsfBin(payload)
index = 0
while True:
try:
block_size = dat.pops('mbuint32')
self.dec_dablock(tag, dat.data[dat.pointer:dat.pointer + block_size], index)
dat.pointer += block_size
except struct.error:
if self.debug:
print 'End of DRAW_ATTR_TABLE'
for table in self.draw_attrs_table:
print table.color.to_string()
return
except:
raise
index += 1
def dec_dablock( self, tag, payload, index = 0 ):
dat = IsfBin(payload)
if index >= len(self.draw_attrs_table):
self.draw_attrs_table.append(drawing.DrawAttributes())
print 'Appended'
color = gtk.gdk.Color(0, 0, 0)
while True:
try:
guid = dat.pops('mbuint32')
except struct.error:
if self.debug:
print 'End of DRAW_ATTR_BLOCK'
self.draw_attrs_table[index].color = color
return
except:
raise
else:
print '\tGUID', hex(guid)
if guid != 0x57:
data = dat.pops('mbuint32')
print '\t\tData', hex(data)
else:
data = dat.pops('<BBBB')
if guid == 0x44:
print hex(data)
red = data & 0x7f
#red = 0xff - red
red = (red * 65535) / 127
green = (data & 0x3f80) / 0x80
#green = 0xff - green
green = (green * 65535) / 127
blue = (data & 0x1f6000) / 0x4000
#blue = 0xff - blue
blue = (blue * 65535) / 127
color = gtk.gdk.Color(green, red, blue)
def dec_index( self, tag, payload ):
self.indices[tag] = payload
print '\tIndices:', self.indices
def dec_stroke( self, tag, payload ):
'''Decodes stroke data from thepayloadincluding import
the number of data points, the x values, and the
y values'''
# Get number of points
dat = IsfBin(payload)
num_pts = dat.pops('weirdoint')
num_bytes = dat.pointer
dat = None
if self.debug:
print '\tNumPoints:', num_pts
# Create the bit stream
stream = Bitstream( payload[num_bytes:] )
# Decode the two packets
guid_x = self.dec_packet(stream, num_pts)
guid_y = self.dec_packet(stream, num_pts)
if guid_x == -1 or guid_y == -1:
guid_x = [0]
guid_y = [0]
path = drawing.BrushPath(drawing.Point(guid_x[0], guid_y[0]),
self.draw_attrs_table[self.indices['DIDX']])
for i in range(1, len(guid_x)):
path.add(drawing.Point(guid_x[i], guid_y[i]))
self.paths.append(path)
def dec_packet(self, stream, num_pts):
'''Function that decodes *ISF/PACKET* formatted data from import
a Bitstream class object, and the number of points
to be decoded'''
# Get the tag byte
tag = stream.pop_byte()
if tag & 0xc0 == 0x80:
# Adaptive-Huffman Compression
if self.debug:
print '\tAdaptive-Huffman Compression'
index = tag & 0x1f
try:
pts = self.dec_huffman(stream, index, num_pts)
except:
if self.debug == 0:
pts = -1
else:
raise
else:
# Gorilla Compression
if self.debug:
print '\tGorilla Compression'
# TODO: Figure out what the gorilla transformation is
transformation = tag & 0x20
if transformation == 0x20:
print "\tError: Gorilla transformation unimplemented", hex(tag)
width = tag & 0x1f
if not width:
width = 32
try:
pts = self.dec_gorilla(stream, width, num_pts)
except:
if self.debug == 0:
pts = -1
else:
raise
return pts
def dec_huffman( self, stream, index, num_pts ):
'''Decodes num_pts values from aBistreamobjectusing import
bitamounts[index] to decode it using an Adaptive-Huffman
coding scheme'''
huffbase = []
bitamount = self.bitamounts[index]
# Create huffbase array
huffbase = [0]
base = 1
for value in bitamount:
if value != 0:
huffbase.append(base)
base += pow(2, value-1)
# Decompress data
n_count = 0
value = []
val = 0
while len(value) < num_pts:
bit = stream.pop_bit()
if bit:
n_count += 1
else:
if n_count == 0:
val = 0
elif n_count < len(bitamount):
offset = stream.pop_bits_to_int(bitamount[n_count])
sign = offset & 0x1
offset /= 2
val = huffbase[n_count] + offset
if sign:
val = val * -1
elif n_count == len(bitamount):
#TODO: Implement 64-bit value decompression
print '64-bit'
val = 0
else:
print 'error'
val = 0
n_count = 0
value.append(val)
# Done reading stream, go to the next byte, unless already
# at start of new byte
if stream.bit != 0:
stream.next_byte()
# Delta-delta inverse transform
curdelta = 0
prevdelta = 0
results = []
for val in value:
newdelta = curdelta*2 - prevdelta + val
prevdelta = curdelta
curdelta = newdelta
results.append(newdelta)
return results
def dec_gorilla( self, stream, width, num_pts ):
'''Decides num_pts values from the Bistream object 'stream',
using a fixed width of width'''
mask = ( 0xffffffff * pow(2, width-1) ) & 0xffffffff
values = []
while len(values) < num_pts:
value = stream.pop_bits_to_int(width)
if value & mask:
value = value | mask
string = struct.pack('q', value)
values.append(struct.unpack('q', string)[0])
stream.next_byte()
return values
class Bitstream( object ):
'''Takes a character string and turns it into a bitstream'''
def __init__( self, data = '\x00'):
self.data = data
self.bit = 0
self.index = 0
def pop_byte( self ):
'''Removes a byte from the stream, and increments the index'''
byte = struct.unpack('<B', self.data[self.index])
self.next_byte()
return byte[0]
def pop_bit( self ):
'''Returns a bit from the stream, incrementing the counters'''
mask = pow(2, 7-self.bit)
bit = struct.unpack('<B', self.data[self.index])[0] & mask
self.__increment()
ret = False
if bit:
ret = True
return ret
def push_bit( self, bit ):
'''Pushes the value of the bit passed to the stream'''
if bit:
val = ((0xff - (pow(2, 8 - self.bit) - 1)) & \
struct.unpack('<B', self.data[self.index])[0]) + pow(2, 7 - self.bit)
self.data = self.data[0:self.index] + struct.pack('<B', val)
self.__increment()
def __increment( self ):
'''Increments the bit counter, or the index if at last bit of byte'''
if self.bit < 7:
self.bit += 1
else:
self.bit = 0
self.index += 1
if self.index == len(self.data):
self.data += '\x00'
return
def next_byte( self ):
'''Resets the bit counter to 0, and increments the index'''
self.index += 1
self.bit = 0
if self.index == len(self.data):
self.data += '\x00'
def pop_bits_to_int( self, length ):
'''Pops length bits from thestreamreturnsalittle import
endian decoded integer value'''
bitcount = 0
value = 0
while bitcount < length:
if self.pop_bit():
value += pow(2, length-bitcount-1)
bitcount += 1
return value
def push_int( self, value, length=0 ):
'''Pushes the minimum number of bits possible to represent
value to the stream, or a specific length if length set'''
bitlist = []
while value:
bitlist.insert(0, value % 2)
value /= 2
if length >= len(bitlist):
num = length - len(bitlist)
for i in range(num):
self.push_bit(0)
else:
raise ValueError
for bit in bitlist:
self.push_bit(bit)
def push_count( self, count ):
'''Pushes count # 1's to the bitstream, followed by a 0'''
for count in range(count):
self.push_bit(1)
self.push_bit(0)
|