CSCI 150: Lab 4

Pictures and Images
Due: 10PM on Tuesday, February 28

The purpose of this lab is to:

  • Explore exceptions
  • Get a lot more practice using and creating functions
  • Express your creativity!
  • Get (much more) practice with nested loops
  • Implement a basic photo editor

Before you begin, please create a folder called lab04 inside your cs150 folder (for a refresher, here's how you did this on lab 02). This is where you should put all files made for this lab.

Part 1 - Using Exceptions

monte.py: 5 points.

Implement a Design

Copy your monte.py program from lab 02 to your lab04 directory and modify it to handle at least 3 exceptions (such as if the user enters n=0).

Handin:

Please handin what you have of your lab so far.

Part 2 - Picture This

sketchy.py: 10 points.

Implement a Design

Create a program called sketchy.py that draws the picture you designed on your prelab using the picture module.

You may want to refer to the previous lab03 if you can't remember how to import the picture module, construct a picture object, or call functions associated with your picture object. Once you've got that set up, here are a few of the things you can do:

  • setPenWidth(w) sets the pen and border width
  • setPosition(x,y) sets the pen position
  • setDirection(theta) and rotate(theta) changes the direction of the pen
  • drawForward(d) draws a line of the current color with current width in the current direction for the specified distance
  • drawCircle(x,y,r), drawCircleFill(x,y,r), drawRect(x,y,w,h), drawRectFill(x,y,w,h), etc. draw shapes, filled or not.
  • setFillColor(r,g,b) changes the fill color used when creating shapes
  • setOutlineColor(r,g,b) changes the color of shape edges and pen lines.
  • Don't forget to use the display() function followed by an input() function call so your image gets displayed, and gives you time to savor your creation before closing the window.

Handin:

Please handin what you have of your lab so far.

Important Note

We would love to add some decoration to the two lab rooms, and your art work could really make the place feel more like home. If you do not want you art work printed and displayed in the labs, please indicate so in your README file. Otherwise, we will try to print it out and add it to the labs!

Part 3 - Image Manipulation

imageEdit.py: 25 points, partner allowed (and recommended).

If you choose to work with a partner, only one of you should submit a solution, but both of you should indicate your partner and who submitted a solution in your README files. You should also both read the Recurse Center's guide on pair programming before you begin. You may not simply split up the functions and complete them separately. All code should be written with both partners working together at a single machine. Do not commit to working with a partner until you're sure your schedules are compatible for working together outside of scheduled lab time.

In this (major) portion of the lab you'll create a nifty program that reads in an image and does a sequence of modifications to that image, as specified by the user. Things like inverting the image, mirroring it, increasing or decreasing the contrast, etc. In doing so, you'll get more practice with nested for loops, while loops, creating methods and using objects.

It turns out that Python 3 doesn't support sufficiently fast image manipulation for our purposes, so for this portion of the lab we're going to be stepping down to Python 2 temporarily. The impact of this should be pretty minimal for you. The changes you'll see should be limited to the following:

(1) To compile, you'll use the command

    python myProgram.py
                  
rather than
    python3 myProgram.py
                  

(2) Print statements don't have parentheses. So you'd write


    print "The value of x is",x
                  
rather than


    print("The value of x is",x)
                  

(3) The input function works a little differently, so to get a string form the user use raw_input. That is, use


    fileName = raw_input("Please enter the image file you'd like loaded: ")
                  
rather than


    fileName = input("Please enter the image file you'd like loaded: ")
                  

(4) We'll be using a different (though very similar) image module called picture2.

Describe the Problem:

Write a program called imageEdit.py that provides the user with image editing functionality. The user should first be prompted to enter a file name. That file should be loaded and displayed. Then the user should be presented with a list of filters that can be applied.

Understand the Problem:

Your program should be capable of the following operations:

  1. Make Negative
  2. Make Grayscale
  3. Flip Horizontally
  4. Scroll Horizontally
  5. Zoom
  6. Your Choice: (Pick one -- Blur, Posterize, Increase Contrast, etc.)


Examples of these operations are given below.
Original Image:
original
Negative: Greyscale:
flip mirror
Flip: Scroll:
scroll negative
Zoom:
zoom
Other Ideas
Posterize: Blur:
poster scroll
Find Edges: Tiled:
find edges tiling
Shear: Bizarre:
shear bizarre
Contrast:
negative

Design an Algorithm:

Your program should begin by printing a welcome to the user, informing them just how fortunate they are to have stumbled upon your very own image editor. You should then prompt the user in the console to pick a file to load in. As will be standard from here on out, you'll want to be robust against bad user input, so if the user enters something that isn't a valid file and an exception is thrown, you can catch it and prompt them again for a file.

Once you have an image loaded, display it. Then use a while loop to repeatedly print a table of possible operations, prompt the user to select one of these operations to apply to their image, apply the selected operation (create a distinct function for each operation), and display the resulting image. Thus, the user might choose to reflect the image, then increase the contrast (of the now reflected image), and then blur (the now reflected and contrasty image). At each step, the user should be able to see the resulting image. Again, be sure to handle bad input from the user.

Of course, this still leaves out the details of each operation, for which you will want to write pseudocode before you start writing functions. Details for some of these operations are given below.

Negative: The negative of an image is creating by inverting each color channel. So if the red value of a pixel were 255, it should become 0. If it were 254, it should become 1, and so on, down to 0, which should become 255. Similarly for green and blue.

Grayscale: Shades of gray have the same red, green and blue value. To convert an image to grayscale, you want to set the red, green and blue values all to the average value of the three channels of the original pixel.

Scrolling: Scrolling should ask the user to specify some number of pixels, and should then shift the image that many to the right. Pixels that would fall off the edge of the image should wrap around to the other size. Modular arithmetic may come in handy here.

Zoom: This method result in an image of the same size as the original, but consist of the center of the image blown up by a factor of 2. So if the image has width w and height h, zooming should expand the middle w/2 by h/2 region to fill the whole picture.

Blur: When you blur an image, you set the color of each pixel to be the average of the 9 pixels in the 3 x 3 square centered at that pixel (i.e. the average of the original pixel and its original 8 neighbors). You'll probably want to create a new Picture object; otherwise, you'll be adjusting pixel values that you'll need for subsequent calculations. Be careful at the borders, not all pixels have 8 neighbors!

Posterize: A typical pixel can have one of 256 value for each color channel. In a posterized image, this number is drastically decreased. Each color channel value should be rounded to the nearest multiple of 32.

Increase Contrast: When increasing the contrast, color values at 128 should be unchanged. For any other value x, the difference between x and 128 should be scaled a factor of 2. For example, a color value of 129 (1 above 128) would become 130 (2 above 128). 125 (3 below 128) would become 122 (6 below 128). Just remember that you'll need to stay between 0 and 255.

Implement a Design:

This program will be a good deal larger than those you've created on previous labs. Making a single large function that does everything will most likely end in tears. As such, think carefully about how to break your program into logical and managable pieces using functions. It is very important that you test your code incrementally as you build your program -- don't try to write the whole thing before you start testing. It is also critical that you use comments to explain what each method you create does.

Since we're creating an image manipulation program, you'll want to save picture2.py into your working directory. To create a new picture object, first add an import picture2 statement at the top of your program. Then you'll be able to use


  pic = picture2.Picture("crayons.bmp")
                      
where pic is just a variable name for the picture object (we used canvas last time, but you can use whatever name you want).

This causes a new picture object called pic to be created, but rather than starting as a blank image, pic is initialized to match the image in the file crayons.bmp. To create a new blank image (which may be useful if you need to create a copy of the current image), you'll use almost the same syntax we had for picture.py, minus one set of parens. In particular,


  pic = picture2.Picture(w,h)
                      
will create a new blank image with width w and height h.

Whichever image file you use should be saved in your working directory. You can use crayons.bmp or an image of your choice, although I suggest sticking with images which are in .bmp format. If you're looking for files on Google images, you can add filetype:bmp to your query to restrict hits to this file type.

Some Important Methods

The following methods will be useful in completing this lab. For starters, you often won't know the height and width of the image you read in. To find out, the methods getHeight() and getWidth() can be used. Both return an integer. Remember that to call these methods, you'll use the object name, followed by a period, followed by the method invocation. For example, w = pic.getWidth().

Keep in mind that if the width of the image is w, then the x-coordinates of all pixels range from 0 to w-1. Trying to access or modify a pixel with an x-coordinate of w or greater will cause an error. Similarly for the height.

Since we'll be doing pixel-by-pixel modifications, we need to be able to read and set the three color channels of any given pixel. The method getPixelRed(x, y) returns the red value of the pixel at location (x,y). The method setPixelRed(x, y, v) assigns the pixel at (x,y) a red value of v. This method does not return a value.

The methods getPixelGreen(x,y), setPixelGreen(x,y,v), getPixelBlue(x,y) and setPixelBlue(x,y,v) behave as you'd expect. Keep in mind that when setting any color value, you must use an integer in the range from 0 to 255 (inclusive).

It'll often simplify your code to use getPixelColor(x,y) (which returns three integers, one per color channel) and setPixelColor(x,y,r,g,b).

As we've seen, the method display() can be used to pop open a window with the current contents of your Picture object.

Note About Copying Picture Objects

For some of the operations listed above, it's helpful (if not necessary) to create a duplicate of a Picture Object.

If you have a picture object pic containing your original image, the assignment statement picCopy = pic will not suffice to create a duplicate image. All this will do is give you two variable names pointing to the exact same picture object, and any changes to either will be reflected in both. Instead, you'll need to create a new Picture object of the same size, through all pixels of pic and copy the corresponding color channels into picCopy. You may want to create a copy(pic) function specifically for this task, that takes in a picture object, makes a new picture object of the same size with the same pixel values, and returns it. Only call display on your original picture object pic. Any function that makes use of a copy of your picture should create a new variable, say, pic2, using your copy function. Then use that copy to modify pic. Don't assign pic2 to pic, and don't display pic2.

Test the Program:

This is a big one, so hopefully you've been testing as you go along. Make sure each individual operation does what it is supposed to, and then make sure that combinations work as well.

Handin

If you followed the Honor Code in this assignment, insert a paragraph attesting to the fact in a README file.

I affirm that I have adhered to the Honor Code in this assignment.

You now just need to electronically handin all your files. As a reminder

 
  % cd             # changes to your home directory
  % cd cs150       # goes to your cs150 folder
  % handin         # starts the handin program
                  # class is 150
                  # assignment is 4
                  # file/directory is lab04
  % lshand         # should show that you've handed in something
                

You can also specify the options to handin from the command line

 
  % cd ~/cs150     # goes to your cs150 folder
  % handin -c 150 -a 4 lab04
                

File Checklist


You should have submitted the following files:
  README
  monte.py
  sketchy.py
  imageEdit.py (unless you worked with a partner and your partner submitted)
  picture.py   (for ease of grading)
  picture2.py  (for ease of grading)
  crayons.bmp	(for ease of grading)
                


A. Eck, T. Wexler, A. Sharp.