"""mems.instrument.server.codecs

Contains various codecs for sending out a (possibly-large) image
as a series of packets.

Codecs present a consistent interface.  A *Serializer class takes a
PIL image object and outputs a stream of packets, and an
*Unserializer class assembles an image from a stream of packets.

Serializer classes must have an __init__ method to set up their state,
and a .next() method which returns a string; this series of strings
will be sent out as the individual packets making up an image.

Unserializer classes have an __init__ method, and a .process() method
which takes a string and adds its contents to the image being built
up.  Two necessary attributes are .num_frames, a counter of the number
of frames that have been processed, and .processing_time, which
accumulates the time required to create PIL objects from a string.
These values are used to calculate how many frames per second a client
can handle.

"""

# created 2001/10/03, AMK

__revision__ = "$Id: codecs.py,v 1.11 2002/02/21 20:24:45 akuchlin Exp $"

import time
from PIL import Image
from cStringIO import StringIO

from mems.instrument.protocols import make_packet

#
# Trivial codec to just send and receive an entire image
#

class NullSerializer:
    def __init__ (self, image=None):
        self.image = image
        
    def set_image (self, image):
        self.image = image
        
    def next (self):
        if self.image is None:
            return None

        io = StringIO()
        self.image.save(io, 'JPEG')
        data = io.getvalue()
        w, h = self.image.size
        packet = make_packet('null', data,
                             width=w, height=h,
                             timestamp = time.time())
        return packet
        

class NullUnserializer:
    codec_format = 'null'
    
    def __init__ (self):
        self.image = None
        self.processing_time = 1.0
        self.num_frames = 0
        
    def process (self, data, **keywords):
        t1 = time.time()
        io = StringIO(data)
        image = Image.open(io)
        self.handle_image(image)
        self.processing_time = time.time() - t1
        self.num_frames += 1
        
    def handle_image(self, image, **kw):
        if self.image is not None and image.size == self.image.size:
            self.image.paste(image, (0,0) )
        else:
            self.image = image

    def get_image (self):
        return self.image
    
        
#
# Codec that sends out an image in 100-pixel-square tiles
#

class TiledSerializer:
    TILE_SIZE = 100

    def __init__ (self, image=None):
        self.x, self.y = 0,0
        self.image = None
        
        if image is not None:
            self.set_image(image)

    def set_image (self, image):
        self.image = image
        
    def get_next_slice (self):
        sx, sy = self.x, self.y
        w,h = self.image.size

        sx += self.TILE_SIZE
        if sx >= w:
            sx = 0
            sy += self.TILE_SIZE
            if sy >= h:
                sx = sy = 0
        self.x, self.y = sx, sy
        ex, ey = min(sx + self.TILE_SIZE, w), min(sy+self.TILE_SIZE, h)
        i2 = self.image.crop( (sx,sy,ex,ey) )
        return (sx,sy), i2

    def next (self):
        if self.image is None:
            return None
        
        (sx, sy), image = self.get_next_slice()

        io = StringIO()
        image.save(io, 'JPEG')
        data = io.getvalue()
        w, h = self.image.size
        packet = make_packet('tile', data,
                             width=w, height=h,
                             xoff=sx, yoff=sy,
                             timestamp = time.time())
        return packet
        

class TiledUnserializer:

    codec_format = 'tile'
    
    def __init__ (self):
        self.image = None
        self.processing_time = 1.0
        self.num_frames = 0

    def process (self, data, **keywords):
        t1 = time.time()
        width, height = keywords['width'], keywords['height']
        io = StringIO(data)
        image = Image.open(io)
        
        self.handle_image(image, **keywords)
        self.processing_time = time.time() - t1
        self.num_frames += 1

        
    def handle_image(self, image, width, height, xoff, yoff, **kw):
        if (self.image is None or (width,height) != self.image.size):
            self.image = Image.new('RGB', (width, height))

        self.image.paste(image, (xoff, yoff))

    def get_image (self):
        return self.image
    

# Machinery for registering codec unserializers

# Dictionary mapping format names to the corresponding class
_codec = {}
def register (codec_class):
    _codec[codec_class.codec_format] = codec_class

register(NullUnserializer)
register(TiledUnserializer)

def lookup (codec_format):
    return _codec.get(codec_format)


