# image_processor.py # YOUR NAME(S) AND NETID(S) HERE # DATE COMPLETED HERE from image_array import ImageArray """Secondary Controller module for Imager Application This module provides all of the image processing operations that are called whenever you press a button. All other controller functionality (loading files, etc.) is provided in imager.py""" # GLOBAL CONSTANTS GRAY = 0 SEPIA = 1 class ImageProcessor(object): """Instance is a collection of image transforms""" # Fields _current = None # Current image being manipulated _original = None # Original image, may not be changed. # Immutable Attributes @property def original(self): """The original state of the image in this processor. *This attribute is set by the constructor and may not be altered*""" return self._original # Mutable Attributes @property def current(self): """The original state of the image in this processor. **Invariant**: Must be an ImageArray""" return self._current @current.setter def current(self,value): assert isinstance(value,ImageArray), `value`+' is not an ImageArray' self._current = value # Built-in Methods def __init__(self, image_array): """**Constructor**: Create an ImageProcessor for the given image. :param image: The image to process. **Precondition**: an ImageArray object Attribute `original` is a direct reference to `image_array`. However, attribute `current` is a copy of that ImageArray.""" self._original = image_array self._current = ImageArray.Copy(image_array) def restore(self): """Restore the original image""" self._current = ImageArray.Copy(self.original) def invert(self): """Invert the current image, replacing each element with its color complement""" n = 0 # Invariant: pixels 0..n-1 have been inverted in self.current while n < self.current.len: rgb = self.current.getFlatPixel(n) red = 255 - rgb[0] green = 255 - rgb[1] blue = 255 - rgb[2] rgb = (red,green,blue) # New pixel value self.current.setFlatPixel(n, rgb) n = n + 1 # Remember to increment def transpose(self): """Transpose the current image Follow this plan: Create a new ImageArray ia, which has no data (it is an empty image), but which has the number of rows and columns swapped from their current values in self.current Store the transpose of self.current in ia, using self.current's `getPixel` function and ia's `setPixel` function. Assign ia to self.current. The transposed image will be drawn on the screen immediately afterwards.""" ia = ImageArray(rows=self.current.cols,cols=self.current.rows) r = 0 # Invariant: rows 0..r-1 have been copied to ia[.., 0..r-1] while r < ia.rows: c = 0 # Invariant: elements [r,0..c-1] have been copied to ia[0..c-1, r] while c < ia.cols: ia.setPixel(r, c, self.current.getPixel(c, r)) c = c + 1 # Remember to increment r = r + 1 # Remember to increment # Replace the image self.current = ia def horizReflect(self): """ Reflect the current image around a vertical line through the middle of the image.""" h = 0 k = self.current.cols-1 # Invariant: cols 0..h-1 and k+1.. have been swapped while h < k: r = 0 # Invariant: pixels 0..r-1 of cols h and k have been swapped while r < self.current.rows: self.current.swapPixels(r, h, r, k) r = r + 1 # Remember to increment # Must change two variables to satisfy invariant h = h + 1 # Move h forward k = k - 1 # Move k backward def rotateLeft(self): """Rotate the image left via a transpose, followed by a vertical reflection.""" self.transpose() self.vertReflect() def rotateRight(self): """Rotate the image right via a transpose, followed by a horizontal reflection.""" self.transpose() self.horizReflect() # Student defined def vertReflect(self): """ Reflect the current image around a horizontal line through the middle of the image.""" # IMPLEMENT ME pass def jail(self): """Put jail bars on the current image: Put 3-pixel-wide horizontal bars across top and bottom, Put 4-pixel vertical bars down left and right, and Put n 4-pixel vertical bars inside, where n is (number of columns - 8) / 50. The n+2 vertical bars should be as evenly spaced as possible.""" # IMPLEMENT ME pass def _drawHBar(self, row, pixel): """Helper function for jail. :param row: The start of the row to draw the bar **Precondition**: 0 <= row and row+2 < self.current.rows an int :param pixel: The pixel color to use **Precondition**: a 3-element tuple (r,g,b) where each value is 0..255 Draw a horizontal 3-pixel-wide bar at row `row` of the current image. So the bar is at rows row, row+1, and row+2. The bar uses the color given by the given rgb components.""" col = 0 # Invariant: pixels self.current[row..row+2][0..col-1] are color pixel while col < self.current.cols: self.current.setPixel(row, col, pixel) self.current.setPixel(row+1, col, pixel) self.current.setPixel(row+2, col, pixel) col = col + 1 # Remember to increment def monochromify(self, color): """Convert the current image to monochrome according to parameter color. :param color: Monochrome color choice **Precondition**: an int equal to either the global constant `GRAY` or the global constant `SEPIA` If color is `GRAY`, then remove all color from the image by setting the three color components of each pixel to (an int corresponding to) that pixel's overall brightness, defined as 0.3 * red + 0.6 * green + 0.1 * blue. If color is `SEPIA`, make the same computation but set green to int(0.6 * brightness) and blue to int(0.4 * brightness).""" assert color == GRAY or color == SEPIA, 'invalid color parameter' # IMPLEMENT ME pass def vignette(self): """Simulate vignetting (corner darkening) characteristic of antique lenses. Darken each pixel in the image by the factor (d / hfD)^2 where d is the distance from the pixel to the center of the image and hfD (for half diagonal) is the distance from the center of the image to any of the corners.""" rows = float(self.current.rows) # Computation on floats, not ints cols = float(self.current.cols) # Computation on floats, not ints # Remember that pixel values must, in the end, be 3-tuples of ints # IMPLEMENT ME pass def decode(self, p): """**Return**: the number n that is hidden in pixel p of the current image. :param p: a pixel position **Precondition**: pixel position is valid (i.e. 0 <= p < self.current.len) This function assumes that n is a 3-digit number encoded as the last digit in each color channel (i.e., red, green and blue).""" rgb = self.current.getFlatPixel(p) red = rgb[0] green = rgb[1] blue = rgb[2] return (red % 10) * 100 + (green % 10) * 10 + blue % 10 def encode(self, n, p): """Encode integer n in pixel number p of the current image. :param n: a number to hide **Precondition**: an int with 0 <= n < 1000 :param p: a pixel position **Precondition**: pixel position is valid (i.e. 0 <= p < self.current.len) This function encodes a three digit number by adding (or otherwise changing) a single digit to each color channel (i.e., red, green and blue).""" # IMPLEMENT ME pass def hide(self, text): """Hide message text in this image, using the ASCII representation of text's characters. **Return**: True if message hiding possible, and False if not. :param text: a message to hide **Precondition**: a string If m has more than 999999 characters or the picture does not have enough pixels, return False without storing the message.""" # IMPLEMENT ME pass def reveal(self): """**Return**: The secret message from the image array. Return None if no message detected.""" # IMPLEMENT ME pass def _pad3(self, n): """Returns a string value of n padded with 0s to be three characters. :param n: number to convert to string **Precondition**: a int, 0 <= n <= 999 This method does not assert its preconditions.""" if n < 10: return '00'+str(n) elif n < 100: return '0'+str(n) return str(n) def _pixel2str(self, pixel): """Helper function for getPixels to turn a pixel into a string. :param pixel: The pixel value **Precondition**: a 3-element tuple (r,g,b) where each value is 0..255 Pads all colors with 0s with to make them three digits. This makes them easier to 'line up'. This method does not assert its precondition.""" return self._pad3(pixel[0])+':'+self._pad3(pixel[1])+':'+self._pad3(pixel[2]) def getPixels(self, n): """**Return**: String that contains the first n pixels of the current image :param n: number of pixels to get **Precondition**: a positive int < self.current.len The pixels are shown 5 to a line, with annotation (i.e. something at the beginning to say what the string contains). To begin a new line, put an '\n' in the string. For example, type this at the Python interpreter and see what happens: 'ABCDE\nEFGH'. Use the function _pixel2str() to get the string representation of a pixel tuple.""" # IMPLEMENT ME return '' # Note: You do not have to write this procedure, and if you do write it, it # will not be graded. But it is instructive if you have the time! def fuzzify(self): """Change the current image so that every pixel that is not on one of the four edges of the image is replaced with the average of its current value and the current values of its eight neighboring pixels. When implementing this function: FIRST, make a copy of the image. Use the function ImageArray.Copy() THEN transform the copy using the values in the current image. THEN copy the entire transformed copy into the current image. Thus, the average will be the average of values in the "original" current image, NOT of values that have already been fuzzified.""" # IMPLEMENT ME pass