# image_array.py # Dexter Kozen (dck10) and Walker White (wmw2) # October 26, 2012 """This module provides the Model for our application. It contains a single class. Instances of this class support an image that can be modified.""" from kivy.graphics.texture import Texture import numpy import Image class ImageArray(object): """An instance maintains a row-major order array of pixels for an image. Use the methods get_pixel and set_pixel to get and set the various pixels in this image. Pixels are represented as 3-element tuples, with each element in the range 0..255. For example, red is (255,0,0). These pixels are not RGB objects, like in Assignment 3. They are tuples, which are lists that cannot be modified (so you can slice them to get new tuples, but not assign or append to them). Instance variables: rows (int): Number of rows in the image cols (int): Number of columns in the image size (int): Number of pixels in the image (== rows*cols) _data (list of 3-tuples): List with image data The variables rows, cols, and size are set only by __init__ and never change. The list _data (of length <size>) contains 3-tuples of integers in 0..255. """ def __init__(self, rows,cols,data=None): """Initialize an ImageArray of the given size. Parameters: rows: The number of rows in the new image. cols: The number of columns in the new image. data: The pixel array for this image, or None for a black image. Precondition: rows, cols >= 0. data is either None or it is an array of pixels (i.e. 3-element tuples, each element an int in 0..255) of size rows*cols Generally, you should leave the parameter 'data' as None. To create an image that isn't all zeroes, you can use the copy method or the load_file function.""" assert type(rows) == int and rows >= 0, `rows`+' is an invalid argument' assert type(cols) == int and cols >= 0, `cols`+' is an invalid argument' assert data is None or type(data) == list, `data`+' is an invalid buffer' if not data is None: assert len(data) == rows*cols, `data`+' is an invalid size' self.rows = rows self.cols = cols self.size = rows*cols if data is None: self._data = [(0,0,0)]*(rows*cols) else: self._data = data def copy(self): """Return: A copy of this ImageArray Once the copy is created, changes to the copy will not affect this instance.""" return ImageArray(rows=self.rows,cols=self.cols,data=self._data[:]) def get_pixel(self, row, col): """**Returns**: The pixel value at (row, col) Pre: 0 <= row < self.rows and 0 <= col < self.cols Value returned is an 3-element tuple (r,g,b). This method does not enforce the preconditions; that is the responsibility of the user.""" #assert type(row) == int and row >= 0 and row < self._rows, `row`+' is an invalid row' #assert type(col) == int and col >= 0 and col < self._cols, `col`+' is an invalid column' return self._data[row*self.cols + col] def set_pixel(self, row, col, pixel): """Sets the pixel value at (row, col) to pixel Pre: 0 <= row < self.rows and 0 <= col < self.cols pixel is a tuple of 3 values in 0..255. This method does not enforce the preconditions; that is the responsibility of the user.""" #assert type(row) == int and row >= 0 and row < self._rows, `row`+' is an invalid row' #assert type(col) == int and col >= 0 and col < self._cols, `col`+' is an invalid column' #assert type(pixel) == tuple and len(tuple) == 3, `pixel`+' is not a 3-element tuple' #assert type(pixel[0]) == int and 0 <= pixel[0] and pixel[0] < 256, `pixel[0]`+' is an invalid red value' #assert type(pixel[1]) == int and 0 <= pixel[1] and pixel[1] < 256, `pixel[1]`+' is an invalid green value' #assert type(pixel[2]) == int and 0 <= pixel[2] and pixel[2] < 256, `pixel[2]`+' is an invalid blue value' self._data[row*self.cols + col] = pixel def swap_pixels(self, row1, col1, row2, col2): """Swaps the pixel at (row1, col1) with the pixel at (row2, col2) Pre: 0 <= row1, row2 < self.rows and 0 <= col1, col2 < self.cols """ temp = self.get_pixel(row1, col1) self.set_pixel(row1, col1, self.get_pixel(row2, col2)) self.set_pixel(row2, col2, temp) def get_flat_pixel(self, n): """Return: Pixel number n of the image (in row major order) Pre: n in 0..(self.size- 1) This method is used when you want to treat an image as a flat, one-dimensional list rather than a 2-dimensional image. Value returned is a 3-element tuple (r,g,b). This method does not enforce the preconditions; that is the responsibility of the user.""" assert type(n) == int and n >=0 and n < self.size, `n`+' is an invalid pixel position' return self._data[n] def set_flat_pixel(self, n, pixel): """Sets pixel number n of the image (in row major order) to pixel Pre: n in 0..(self.size- 1); pixel is a tuple of 3 values in 0..255. This method is used when you want to treat an image as a flat, one-dimensional list rather than a 2-dimensional image. This method does not enforce the preconditions; that is the responsibility of the user.""" #assert type(n) == int and n >=0 and n < self.size, `n`+' is an invalid pixel position' #assert type(pixel) == tuple and len(tuple) == 3, `pixel`+' is not a 3-element tuple' #assert type(pixel[0]) == int and 0 <= pixel[0] and pixel[0] < 256, `pixel[0]`+' is an invalid red value' #assert type(pixel[1]) == int and 0 <= pixel[1] and pixel[1] < 256, `pixel[1]`+' is an invalid green value' #assert type(pixel[2]) == int and 0 <= pixel[2] and pixel[2] < 256, `pixel[2]`+' is an invalid blue value' self._data[n] = pixel def get_texture(self): """An OpenGL texture for this image (used for rendering).""" texture = Texture.create((self.cols,self.rows), colorfmt='rgb') buf = bytearray(0) for r in xrange(self.rows): for c in xrange(self.cols): pixel = self.get_pixel(r,c) pixel = (pixel[0],pixel[1],pixel[2]) buf.extend(pixel) buf = ''.join(map(chr, buf)) texture.blit_buffer(buf, colorfmt='rgb', bufferfmt='ubyte') return texture def get_image(self): """An Image object for this ImageArray. Used to save results.""" im = Image.new('RGBA',(self.cols,self.rows)) im.putdata(self._data) return im # Function for creating new ImageArrays; notice it is outside the class. def load_file(filename): """**Constructor** (alternate): Create an ImageArray for the given image file :param filename: image file to initialize array **Precondition**: a string representing an image file. If filename is not the name of a valid image file, this constructor will raise an exception.""" assert type(filename) == str, `filename`+' is not a string' im = Image.open(filename) (cols,rows) = im.size return ImageArray(rows,cols,data=map(lambda x: x[:3], im.getdata()))