""" This module is part of the 'blup' package and contains the definition of a basic Frame class. """ class FrameDimension(object): """ This class serves as container for the dimensions of a frame. """ def __init__(self, width=18, height=8, depth=2, channels=1): self.width = width self.height = height self.depth = depth self.channels = channels def size(self): return (self.width, self.height) def depth(self): return self.depth def channels(self): return self.channels def __eq__(self, other): if ( self.width == other.width and self.height == other.height and self.depth == other.depth and self.channels == other.channels ): return True else: return False def __ne__(self, other): return not self.__eq__(other) def __str__(self): return "(%d, %d, %d, %d)" % (self.width, self.height, self.depth, self.channels) class Frame(object): """ This class describes a single frame. """ def __init__(self, dimension): """ Initialize the frame with the given dimensions. The 'depth' parameter describes, how many different values a single pixel can have. """ self.__dimension = dimension width = dimension.width height = dimension.height if dimension.channels == 1: self.__pixels = [ [ 0 ] * width for i in range(height) ] else: self.__pixels = [ [ tuple([0]*dimension.channels) ] * width for i in range(height) ] @property def dimension(self): return self.__dimension @property def size(self): return (self.__dimension.width, self.__dimension.height) @property def depth(self): return self.__dimension.depth @property def channels(self): return self.__dimension.channels @property def pixels(self): return self.__pixels @pixels.setter def pixels(self, pixels): if len(pixels) != self.__dimension.height: raise ValueError('not enough rows given') widths = set(map(lambda r: len(r), pixels)) if len(widths) != 1: raise ValueError('rows are not of the same length') if widths.pop() != self.__dimension.width: raise ValueError('rows are too short') # TODO check depth # TODO check number of channels self.__pixels = pixels def transform(self, depth): """ Transform the frame to a frame with a different depth. """ oldmaxval = self.__dimension.depth - 1 newmaxval = depth - 1 factor = (newmaxval*1.0) / (oldmaxval*1.0) newdim = FrameDimension(self.__dimension.width, self.__dimension.height, depth, self.__dimension.channels) newframe = Frame(newdim) transformfkt = lambda p: int(round(p * factor)) for y in range(self.__dimension.height): if self.__dimension.channels == 1: newframe.__pixels[y] = map(transformfkt, self.__pixels[y]) else: for x in range(self.__dimension.width): newframe.__pixels[y][x] = tuple(map(transformfkt, p)) return newframe def getPixel(self, x, y): """ Return the pixel value at (x, y). """ return self.__pixels[y][x] def setPixel(self, x, y, value): """ Set the pixel value at (x, y). """ if self.__dimension.channels == 1: if value < 0 or value >= self.__dimension.depth: raise ValueError('pixel value not in depth range') else: if len(value) != self.__dimension.channels: raise ValueError('channel count does not match') for v in value: if v < 0 or v >= self.__dimension.depth: raise ValueError('pixel value not in depth range') self.__pixels[y][x] = value def getPixelsAsBytes(self): """ Return the frame as a byte string. """ if self.__dimension.channels != 1: raise Exception('cannot convert a multi-channel frame') bytes = '' for y in range(self.__dimension.width): for x in range(self.__dimension.height): bytes += chr(self.__pixels[x][y]) return bytes def invert(self): """ Ivert the frame. """ for y in range(self.__dimension.height): for x in range(self.__dimension.width): if self.__dimension.channels == 1: self.__pixels[x][y] = ((self.__dimension.depth - 1) - self.__pixels[x][y]) else: self.__pixels[y][x] = tuple(map( lambda x: ((self.depth - 1) - x), self.__pixels[y][x]))