CS 10: Winter 2016

Lecture 5, January 11

Code discussed in lecture

Short Assignment 3

Short Assignment 3 is due Wednesday.

How is an image represented?

A digital image is represented as a 2-dimensional grid of pixels, or picture elements. (Exactly how the image is represented is hidden inside the class BufferedImage.) Draw a rectangle, label upper left corner (0,0), talk about pixel at (x, y), where x is position in row, y is position in column. Note that y gets bigger as it goes down the image.

So what is a pixel? It is basically a representation of the color that should be drawn on the screen at that point. This color is represented in RGB, short for Red-Green-Blue. If you ask a BufferedImage for the color at a given pixel you get back an int. This value is not really treated as an int, though. Its 32 bits are broken into four 8-bit chunks. Each chunk represents a number between 0 and 255 in binary.

The leftmost 8 bits are the alpha value. This represents the amount of transparency, with 0 as totally transparent and 255 as totally opaque. The next 8 bits represent the amount of red (0 no red to 255 maximum red), the next 8 bits represent the amount of green, and the rightmost 8 bits represent the amount of blue. We won't be dealing with the alpha value much - all of the images we will be playing with have alpha = 255. (You can play around with it, though.)

Java has built-in graphics and image-handling classes and methods. We will see some soon. However, Barb Ericson at Georgia Tech has created a set of classes to allow you to represent and manipulate images more easily than using the built-in classes. We will use these classes to manipulate images. In particular you need to know about Picture, SimplePicture, Pixel, and FileChooser.

The JavaDocs for these four classes are in the file doc.zip. Unpack it and open index.html to get a directory to the JavaDocs for these four classes. You should not need to modify or even look at the code for any of the classes except Picture.

Picture is the class that is designed for you to add to or modify. Most of the work is done in a class SimplePicture, and Picture extends SimplePicture. Therefore any method defined in SimplePicture can be called on a Picture object and it will work correctly. She did this to allow you to use the SimplePicture class without having to look at its code. The methods from SimplePicture that you are likely to use are explore, getPixel, getPixels, getHeight, getWidth, setTitle, and show.

The Pixel class allows you get and set the colors within a single pixel in various ways. The methods that you are likely to use are getRed, getGreen, getBlue, getColor, setRed,setGreen, setBlue, setColor, correctValue, getX, and getY. Some of these methods use the Java library class Color. To learn about the this class go to the Get Help tag on course home page, click on the "Documentation for all supplied Java classes" link, scroll down through "All Classes" and click on "Color". One things that you might want to use from the Color class is the color constants for the various colors (Color.BLACK, Color.BLUE, Color.CYAN, etc). The methods getRed, getGreen, and getBlue will also be useful. You might expect methods to set red, green, and blue values, but Color objects are immutable and cannot be changed. You instead use the constructor Color(int r, int g, int b) to create a new Color out of the new red, green, and blue values rather than changing an existing color. There is also an equals method that determines if two Color objects represent the same color.

Finally, you may find FileChooser's PickAFile, getMediaPath and pickMediaPath useful.

Important note: The method FileChooser.pickAFile calls Java's JFileChooser method to pop up a dialog box and let you navigate to the appropriate file. It then returns the complete file name as a string and you may use it to open an new Picture as you did in SA0. It is very convenient, and for most people it works fine. However, for some Java installations (including mine) the second time FileChooser.pickAFile is called it goes into an infinite loop, requiring killing the program. The problem seems to be in the call to the JFileChooser method in the Java library. There are reports of this problem on the web page stackoverflow, but no easy solutions.

Fortunately you often only need to open one picture during a run of your program. If you need more pictures open and pickAFile does not work the second time you can call FileChooser.getMediaPath("filename"), where filename is the name of the picture file in your media-sources folder. This call returns same complete file name that FileChooser.pickAFile would return. This is less convenient, but it works.

Playing with the Picture class

Our first order of business is to go into DrJava and call FileChooser.pickMediaPath() from the Interactions window to select our media-sources folder. Once this is done, all of our searches for media will begin at this folder and FileChooser.getMediaPath will prepend this path the the file name that you supply. We only need to do this once, and it will be remembered in a file for future use. Reset the interactions window after doing this, because this method also calls JFileChooser.

Let's look at the JavaDoc for the Picture class and play with some of the functions. We will first figure out what they are doing, and then try to decide how they must work. Then we will see how the Java code does this.

We have a choice of 5 different constructors. Overloaded! The constructors that we will use are the one with a String parameter (the complete file name of the .jpg file), the one with 2 int parameters, and the copy constructor that takes a Picture as its parameter. It is a way to make a new copy of a picture. (The copy method is another way to do the same thing.)

pickAndShow gives an easy way to get a picture from an image file, but I prefer to call explore rather than show. Using explore I can examine the colors of individual pictures. Demo it. showNamed does the same, but from the file name. copy is fairly obvious, although the reason for it may not be. What advantage is there to copying rather than p2 = p? (Changing the copy won't change the original.)

So what does increaseRed do? Demo on a copy of a Picture made from beach.jpg. Looks like it ups the red value. Maxes out at 255, though. Do enough times every pixel is saturated with red. (Call explore to allow you to find RGB values for any pixel that you click on.) Why not all entirely red? Have green and blue components. If all are 255, get white! Also, note that if red was originally 0 it stays 0. Play with decreaseRed also. Looks like we need to go into the pixel repesentation and change the red value.

It would be a pain to go in and modify the int representing each color. Would have to pull out the correct 8 bits, treat them as a number, double it, and then put the new value for 8 bits back into the color representation. Not hard, but messy. Fortuately the Pixel class will simplify this for us. Go through the JavaDoc for this class. Note getRed, setRed, other useful functions.

Looking at the increaseRed we can see that it does precisely what we thought that it did. If calls this.getPixels() (the this is not needed) to get an array of all of the pixels. It then goes through each pixel in a foreach loop, calling getRed, doubling it, and using setRed to put the value back in the pixel. Note the two occurrences of pixelObj in the expression.

What does negate do? Makes it look like a photographic negative (for those of you who have seen photographic film). Click on pixels using explore(), note that the amount of color in a pixel in the original and the amount of the same color in the negative seem to add to 255. So subtract from 255 to get new color.

The code for negate does what we expect it to. It again gets an array of all the pixels, pulls out the red, green, and blue values, and calls setColor to store a new Color object with the appropriate values.

flip is fairly straightforward. How would you compute it? Reverse the order of the pixels in each row. Note the flip returns a Picture, so we can say p.flip().explore(). Work left to right in this expression. p is a Picture. Call flip on it, get another Picture. Call explore on that picture.

flip cannot easily use the approach of getting all of the pixels and processing them in a foreach loop. It instead goes through each pixel individually in a loop. It first creates a new Picture of the correct size and then does a double loop to handle copying pixels from one picture to the other. The outer loop does something unusual. It has two indices, going in opposite directions. These will be used for the column numbers (x-axis) to reverse the pixel order. (How would you do this with a single loop index?) The inner loop mirrors this construct, but it has two indices that always have the same value. Perhaps Prof. Ericson thought that this would be clearer. At any rate, it sets the color of the target pixel to the color of the corresponding source pixel, running through the rows in opposite directions.

decreaseRed does the same thing as increaseRed, except it halves the red value. It also uses a while loop instead of a foreach loop and spreads the computation over several lines. (I would have just done integer division by 2 instead of multiplying by 0.5 and casting.)

Look at more complex things. compose replaces part of a picture by another picture. If bigBen is the Picture created from the file bigben.jpg and beach is a Picture made from beach.jpg, try bigBen.compose(beach, 200, 20). Looks like it copies pixels from the first, saves them in the second, with upper left corner specified.

The code here does what is expected. It uses a double index in the for loop again, starting the srcX and srcY at 0 and the trgX and trgY

A similar idea appears in blueScreen. This is the method used by TV meterologists to make it appear that they are standing in front of a big weather display when in fact they are standing in front of a blue background. Demo using blue-mark.jpg. If beach is a Picture from beach.jpg, and blueMark is a picture from blue-mark.jpg, then try the call blueMark.blueScreen(beach, 100, 0). How does it work? Like compose, but only copy non-blue pixels (those with less blue than the sum of red and green).

reduceTo8 shows one way to "posterize" a picture by using a reduced number of colors. In Lab 1 you will implement a much better way. We will look at this as an example of how to use ArrayLists.

Look at the others yourself. The one that changes a picture to black and white (called gray scale) finds a weighted average of the three colors and creates a pixel with all three having that average. (Weighted average because gray scale only shows brightness, and the eye perceives the brightness of the colors differently.) Oil paint method replaces each pixel by the most common color in a rectangle around it, which makes for blotches of color.

ArrayLists

If you've programmed in C but not Python, then you're used to arrays. You can access the element at a given index quickly—in constant time, in fact. But if you've programmed in Python but not C, you're used to Python lists, which also allow you to dynamically append, insert, and delete elements at any location in the list. Arrays in Java do not allow you to do these operations unless you code them up yourself. In fact, just appending or inserting into a Java array can be a pain, because arrays in Java have a fixed length. If you want to insert into an array that is full, you need to allocate a new array, copy everything into it, and then insert. And if some other code has a reference to the array, then you need to update that reference. Yuck!

Instead, one of the available classes in Java is the ArrayList, which is a lot like a Python list. To use it, you have to add the line

import java.util.ArrayList;

to your .java file.

The reduceTo8 method has the declaration with initialization

    ArrayList<Pixel> lowValues = new ArrayList<Pixel>();  // a polymorphic ArrayList

When you declare an ArrayList, you have to say what kinds of objects will be in the ArrayList. You use Java's generics feature to do so, by putting a class name inside the angle brackets. Here, lowValues is a reference to an ArrayList object in which each item is a reference to a Pixel object.

Because an ArrayList is an object, we use Java's new operator to create it. We still have to use the generics feature when we create the ArrayList, saying new ArrayList<Pixel>(). The default constructor for ArrayList creates an empty ArrayList object.

reduceTo8 first gets all of the pixels in the picture and puts them in pixelArray. It then goes through these pixels and splits them into two groups, those with color value greater than a threshold (126, in this case) and those with color value less than or equal to that threshold. The call to add grows the corresponding ArrayList by adding the new value to the end. This has to be done for all three colors, so there is a loop where colorNum goes from 1 to 3. I wrote versions of getColor and setColor that take a colorNum and make the call corresponding to that number.

After finding the average high and low values in the ArrayLists via method calls, the code runs through the pixels (using a foreach loop for the ArrayList) and sets each pixel to have the appropriate average color value.

The method averageColors shows a normal for loop running though an ArrayList. Note that we have to use the method get instead of square subscript brackets to get a particular pixel out of the ArrayList. Also note that the number of elements in an ArrayList is found by calling the method size(). In contrast, an array uses the public instance variable length to get the number of places in the array.

The Java documentation tells you everything about the methods of the ArrayList class. The following methods are the ones you most frequently use. Here, E stands for the type that the ArrayList references (Pixel in our example).

A word about what the contains, indexOf, and remove methods do. Specifically, how does the ArrayList decide whether the object obj is at a particular position p in ArrayList list? You might think that the test would be:

obj == list.get(p)

However, that test would ask if they were the exact same object in memory. (That is, their addresses were the same.) But you would want to say that two of Java's Color objects are equal if they have the same red, green, blue, and alpha values. (Java does.) So we need a test different from ==.

Every class has a method named equals, which returns a boolean indicating whether two objects are "equal." The writer of the class gets to determine what "equal" means, with the default being "are the same object," i.e., the two references in question hold the same address. But you might decide that two distinct objects can be "equal" if, say, some of their instance variables are equal. For example, you could say that two Circle objects are equal if they have the same center and radius. Anyway, the contains, indexOf, and remove methods call the equals method of the class that obj references to decide whether there's a match.

Take a moment to look at the Java documentation to see what other methods the ArrayList class provides.

Wrapper classes and autoboxing

Notice how the ArrayList in the example held references to objects. Could it have held primitive types, such as an int? After all, an array can hold either primitive types or references. But an ArrayList cannot; it can hold only references.

But what if you really wanted an ArrayList that holds ints? Java provides wrapper classes that allow you to treat primitive types like objects. (If you recall the discussion of the Smalltalk language, it would need no such facility, since everything, and I mean everything, is an object.) The class names for the wrapper classes are Boolean, Character, Byte, Short, Integer, Long, Float, and Double. So, if you wanted an ArrayList holding integer values, you could declare

ArrayList<Integer> intList = new ArrayList<Integer>();

What's nice is that you can now treat this ArrayList as though each element was in fact an int, even though each element is really a reference to an Integer object. For example:

intList.add(7);
int n = intList.get(0);

Java has features called autoboxing and unboxing. When you use an int where an Integer object is expected, as in the call to add, Java automatically converts the int to an Integer for you. The Integer object holds that int value. That's autoboxing. When you use an Integer object where an int is expected, as in the assignment to n, Java automatically converts the Integer to an int; that's unboxing.

There are some subtleties that can mess you up. The following two calls do very different things:

intList.remove(3);
intList.remove(new Integer(3));

The first call removes the element at index 3. The second call removes the first element in the ArrayList whose value is 3.

A second case that could be confusing is the test: 3 == new Integer(3). Does Java first box the 3 and then compare the two Integer objects (which might then have different addresses) or does it unbox the Integer and then compare the two primitives? You can test to see which is the case, but your code should never depend on whether you box or unbox to make things the same type. The safe thing is to only use autoboxing and unboxing to put primitives into and take primitives out of data structures that expect objects.