CS 161 Lab F - Photoshopper

Due Fri Oct 18 at 9:00am

Overview

In this lab, you will be complete a program that lets you manipulate a graphical image, similar to the ever-popular Photoshop application. Your program will open an image file, display the image, and allow the user to apply a number of effects to that image. The basic functions you will be implementing are:

  1. Erasing (clearing) the image
  2. Turning the image into gray scale (i.e., making it black and white)
  3. Turning the image upside-down (i.e., flipping over the central horizontal axis)
  4. Turning the image into its "negative"
  5. Being able to "undo" the previous effect

Although this may sound complicated, I'll walk you through the steps needed. Make sure you read the lab directions carefully, think about the directions, and plan before you code.

When running, your program will look something like this (all examples are made using the eye.jpg file included in the project file):

I have provided the code to create the window with the buttons for you (we'll talk about how to do this yourself later!)

This lab will be completed in pairs. Be sure to review the pair programming guidelines. You also must work with a different partner than you have before!

Objectives

Necessary Files

You will need to download and extract the BlueJ project from the LabF.zip file. This project will supply you with the following classes:

The lab file also has a couple of images you can use to test your program, though the system will be able to work with any file with a .jpg extension (called a "jay-peg" file). Feel free to use your own pictures if you wish (but try and avoid using overly large pictures; it will slow down your program!)

Assignment Details

For this lab, you will be writing a new class called PhotoShopper (make sure to get the capitalization correct!) This class will do the actual image manipulation of your program, such as flipping the image, making it gray-scale, etc. When your class is finished, you will be able to construct (right-click in the BlueJ window) a PictureViewer. When the PictureViewer starts, it will look like this: Each of the buttons in the PictureViewer window will be associated with a method in your PhotoShopper---in effect, clicking a button will call a method you write to manipulate an image.

Colors and BufferedImages

Your PhotoShopper class will need to have an attribute that is of the type BufferedImage to represent the current image that is being modified. Be sure to look over the documentation for this class. Some details are below:

A BufferedImage is an object that represents a graphical image. Each image consists of a number of colored points, called 'pixels'. Each pixel has an x and y coordinate and a color. BufferedImages have lots of available methods, but for this lab we are mostly interested in finding out what the color of a pixel is, and changing the colors of the pixels.

Each pixel's color is represented by an integer. This integer is calculated based on three components (or channels): a red component, a green component and a blue component. Each component is an integer with a value from 0 to 255. If all components are 0, the color ends up being black. If all components are 255, the color is white. Other combinations give other colors. You can see what colors different combinations lead to here. This method of specifying colors (there are other methods too) is called 'RGB' for "red green blue".

To modify the pixel of an image, you need to get all of the RGB components, change them individually, and then set the appropriate pixel in your new image (see below) to that new color. However, it is not immediately obvious how to access the the individual components of an integer. One reasonable solution is to take that integer and use it to create a Color object, such that we've worked with before. Color objects have getters (e.g., getRed()) and setters that we can use for each individual component, as well as for the entire RGB integer (getRGB()). It also has a constructor that lets us specify the 3 channels as integers, making it easy to move back and forth between representing colors as ints and Color objects.

  • Take a moment and think about this idea of "representation." It's the same way we talked about using either a double or an int to represent a number; now we're using either an int or a Color to represent a color!

So the basic process is: get the RGB int and then make a Color object out of it. From the Color object, you can get the red, green and blue components as separate ints. After manipulating the red, green and blue components, make a new Color object from them, and then use the Color object to give you the appropriate rgb int for the pixel, and put that int back into the BufferedImage (whew!). Here is the process in code:

int oldRGB = oldImage.getRGB(x,y); //get the RGB for the current pixel 
Color oldColor = new Color(theRGB); //make a color so we can extract RGB components

// get the components 
int red = oldColor.getRed();
  ... //etc.

// process the color components here

Color newColor = new Color(newRed, newRreen, newBlue); //make the new Color object
int newRGB = newColor.getRGB(); //get the RGB for it as an int 
newImage.setRGB(x, y, newRGB); //set the pixel in the new image to be that color

Creating the PhotoShopper class

Modifying the Image

Each of the methods you will write manipulates the BufferedImage object by changing the pixels that make up the image. It then returns a new manipulated BufferedImage. Write one method at a time and test it. Once you have the first one done, the others will be much easier because most follow the same basic outline. The basic outline for each of these methods is this:

(1) get the dimensions of the current image
(2) make a new BufferedImage of the same size
(3) use a pair of nested for-loops to process each pixel, assigning the new color to your new BufferedImage
(4) save the new image in place of the old one
(5) return the new BufferedImage that you created

These steps are detailed below:

  1. Because you might be modifying different sized images, your methods will need to first get the width and height of the current image, so that you know how many pixels to look through. You can access this information by using the getWidth() and getHeight() methods of the BufferedImage class. Note that these are getters, just like every getter you've ever written or used!
  2. Next, you'll need to create a new BufferedImage to hold your modified image. We can't modify the image in place, because we might end up losing information. This new BufferedImage should be the same size as your previous one. Remember that the constructor for a BufferedImage looks like:
    BufferedImage newImage = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
  3. You'll need to use a pair of nested for-loops to process each pixel in your old image, in order to determine what corresponding (modified) pixel color to place in your new image.
    • Consider using "x" and "y" as the variables for your for loops--this will let you easily think about iterating over all of the (x,y) coordinate combinations!
    • Details on how you should modify pixels can be found in the below description of individual methods. Note that you will need to use the above conversion process to move between int and Color representations of colors.
    • NOTE: You want want to get the colors from the pixel in the old image, and use those to set the colors in the new image! Do not modify your old image at this point.
    • If you have any questions about this, ASK!
  4. After you've finished making the new image, you'll want to "save" that image inside your instance variable. You can do this by just assigning the new image to that instance variable!
  5. Finally, you'll need to return the new image (which should also be your instance variable at this point.

You can use nested for loops to process each pixel by making a for loop that iterates through each x-value. Within that loop, use another for loop to iterate through each y-value.

eraseImage()

This should be the first method you write. Note that it should have a method signature as described above.

This method should set all of the RGB components to 255 (making the pixel white). This is a great method to write first, since all of the other methods will follow this pattern. After writing this method, hitting the appropriate button should produce an image like:

grayScaleImage()

This method should take each pixel from the original image and figure out the average of the red, green, and blue components. Use that average for all of the red, green, and blue components of the corresponding pixel in the new image. After writing this method, hitting the appropriate button should produce an image like:

flipImage()

This method flips the image upside down!

This method should take each pixel from the original image and put it in the corresponding place in the new image by giving it a new y-coordinate. Think about where each pixel needs to go in order to flip the image upside down--what math will you need to perform? After writing this method, hitting the appropriate button should produce an image like:

negativeImage()

This method should take the red component and subtract it from 255 to get the new red component value. Do the same for the green and blue components. After writing this method, hitting the appropriate button should produce an image like:

undo()

Note that this method will require modifying all of the previous methods!

You can implement a simple "Undo" method by saving a copy of the image before changing it. Make a new instance variable (e.g., oldImage), and assign the current image to that variable at the start of each method before you make any changes. Initialize this instance variable as null in your constructor.

Then inside your undo() method, simply assign the oldImage instance variable to the current image instance variable, and then return the now current image.

getImage()

Save simplest for last! With this method, you just need to return the image (no need to process each pixel!) This method is used by the "Save image" button, and will let your program save the modified image to a file.

You will also need to include the signature for the blurImage extension so that your code compiles (though completing this method is optional).

Submitting

Make sure both your names are on the PhotoShopper class, and upload the entire project directory to the LabF submission folder on hedwig. Only one partner needs to upload the code. Make sure you upload your work to the correct folder! The lab is due at the start of class the morning after lab.

After you have submitted your solution, log onto Moodle and submit the Lab F Partner Evaluation. Both partners need to submit evaluations!

Extensions

As an extension, you might try adding functionality for the blurImage() button. Details below:

Optional: blurImage()

Grading

This assignment will be graded on approximately the following criteria: