CSCI 150: Lab 4

Images and Functions
Due: 11:59 PM on Tuesday, March 9 (Extension until 11:59 PM on Friday, March 12)

The purpose of this lab is to:

  • Implement a basic photo editor!
  • Gain practice using and creating functions
  • Get even more practice with nested loops

Optional Prelab

We have put together a set of optional prelab questions (with an answer key) that will help you work through some of the ideas related to this lab before you start doing any programming. In particular, we discuss how to create the pseudocode for several of the image filters so you can see some examples of how they work. You can find these questions here in Prelab 4. It is highly recommended that you read through the prelab before working on the lab.

Part 1 - Image Manipulation

imageEdit.py: 38 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 Google Form submission. 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 (e.g., by using Zoom). 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 lab, you will 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 functions and using images.

To be able to edit individual pixels, we will 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.)
  7. Quit
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. You should also use a try-except block to make sure that the file exists -- if a FileNotFoundError occurs, you should print a message to the user letting them know that the file does not exist.

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.

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 function 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 frustration. 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 function you create does.

New for this lab: your program should be split into different functions (including a main function), and you should practice not using any global variables (it will make the program easier to implement).

Since we're creating an image manipulation program, we will need a module for images. For us, this will be the picture2 module (which is already preloaded into your repl.it project). 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. 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 the results to this file type.

Some Important Functions

The following functions will be useful in completing this lab. For each one of them, the syntax we will use is variableName.functionName(arguments). For example, if our image is stored in a variable called pic, then we might call:

h = pic.getHeight()
w = pic.getWidth()

r = pic.getPixelRed(0, 0)
pic.setPixelRed(0, 0, 255 - r)
For starters, you often won't know the height and width of the image you read in. To find out, the functions getHeight() and getWidth() can be used. Both return an integer.

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 function getPixelRed(x, y) returns the red value of the pixel at location (x,y). The function setPixelRed(x, y, v) assigns the pixel at (x,y) a red value of v. This function does not return a value.

The functions 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).

Different from Lab 3: with the picture2 module, we need to use the function display() to draw our image to the screen after we first load it (using picture2.Picture(filename) and after we finish updating the image using each of our filters.

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, loop through all pixels of pic and copy the corresponding color channels into same pixel in 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.

Part 2 - Wrap Up

Congratulations! You have finished the fourth lab. As with every lab, your last job prior to submission is to complete a brief write-up by filling out a Google Form, which is also how you submit your Honor Code statement (so please do not forget to do this).

Handin

Finally, all you need to do is submit your solution to the assignment. To do so, please click on the "Submit" button in the top right of your programming environment. This will save all of your programs for myself and the graders to look at. You are welcome and encouraged to submit partial solutions to your assignment -- you can always resubmit by pushing the "Resubmit" button in the top right, which will save the latest version of your programs. We will only grade the latest version that you submitted. By submitting multiple times, you avoid accidentially forgetting to turn in any completed part of your assignment, especially in case you become busy around the lab due date.


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