Section 4.3. Changing Color Values


[Page 91 (continued)]

4.3. Changing Color Values

The easiest thing to do with pictures is to change the color values of their pixels by changing the red, green, and blue components. You can get radically different effects by simply tweaking those values. Many of Adobe Photoshop's filters do just what we're going to be doing in this section.

The way that we're going to be manipulating colors is by computing a percentage of the original color. If we want 50% of the amount of red in the picture, we're going to set the red channel to 0.50 times whatever it is right now. If we want to increase the red by 25%, we're going to set the red to 1.25 times whatever it is right now. Recall that the asterisk (*) is the operator for multiplication in Java.


[Page 92]

4.3.1. Using a For-Each Loop

We know that we can use the getPixels() method to get an array of Pixel objects from a Picture object. We can use the geTRed() method to get the red value from a Pixel object, then we can multiply it by 0.5 to decrease the red value, and then we can use setRed() to set the red value of a Pixel object.

We will need to cast back to integer after we multiply the red value by 0.5. Remember that if the computer sees you using a double value it assumes that the result should be a double. However, pixel color values must be integers. We could write the code to change the first three pixels like this:

> String fName = "C:/intro-prog-java/mediasources/caterpillar.jpg"; > Picture pict = new Picture(fName); > pict.show(); > Pixel[] pixelArray = pict.getPixels(); > Pixel pixelObj = pixelArray[0]; > int red = pixelObj.getRed(); > red = (int) (red * 0.5); > pixelObj.setRed(red); > pixelObj = pixelArray[1]; > red = pixelObj.getRed(); > red = (int) (red * 0.5); > pixelObj.setRed(red); > pixelObj = pixelArray[2]; > red = pixelObj.getRed(); > red = (int) (red * 0.5); > pixelObj.setRed(red); > pict.explore();


This only changes the first three pixels. We don't want to write out statements like this to change all of the pixels in the array even for a small picture. We need some way to repeat the statements that get the red value, change it, and then set the red value for each pixel in the array. As of Java 5.0 (1.5) we can do that using a for-each loop. A loop is a way to repeat a statement or a block of statements. The syntax for a for-each loop is

for (Type variableName : array)


You can read this as "first declare a variable that will be used in the body of the loop," then "for each element in the array execute the body of the loop." The body of the loop can be either one statement or a series of statements inside of an open curly brace '{' and a close curly brace '}'. The statements in the body of the loop are indented to show that they are part of the loop. A method that will loop through all the pixels in the current picture and set the red value in each to half the original value is:

public void decreaseRed() {   Pixel[] pixelArray = this.getPixels();   int value = 0; 
[Page 93]
// loop through all the pixels in the array for (Pixel pixelObj : pixelArray) { // get the red value value = pixelObj.getRed(); // decrease the red value by 50% (1/2) value = (int) (value * 0.5); // set the red value of the current pixel to the new value pixelObj.setRed(value); } }


If you are using Java 5.0 (1.5) or above add the decreaseRed() method to the Picture.java file before the last closing curly brace '}'. Then click the COMPILE ALL button in DrJava to compile the file. You can try this method out by typing the following in the interactions pane.

> String fName = "C:/intro-prog-java/mediasources/caterpillar.jpg"; > Picture pict = new Picture(fName); > pict.explore(); > pict.decreaseRed(); > pict.explore();


You can compare the original picture with the changed picture. Use the picture explorer to check that the amount of red was decreased.

When you execute pict.decreaseRed() the Java runtime checks the Picture class to see if it has a decreaseRed() method. The Picture class does have this method so it will execute that method and implicitly pass in the Picture object the method was invoked on. The keyword this is used to refer to the object the method was invoked on (the one referred to by the variable pict).

The first time through the loop the pixelObj will refer to the first element of the array (the one at index 0). The second time through the loop the pixelObj will refer to the second element of the array (the one at index 1). The last time through the loop the pixelObj will refer to the last element of the array (the one at index (length 1)).

For-each loops are very useful for looping through each of the elements in an array. If you are still using Java 1.4, you can't use a for-each loop. You can use a while loop instead. Even if you are using Java 5.0 while loops can help you solve problems that for-each loops can't solve.

4.3.2. Using While Loops

A while loop executes a statement (command) or group of statements in a block (inside open and close curly braces). A while loop continues executing until a continuation test is false. When the continuation test is false execution continues with the statement following the while loop.


[Page 94]

The syntax for a while loop is:

while (test) {   /** commands to be done go here */ }


Let's talk through the pieces here.

  • First comes the required Java keyword while.

  • Next we have a required opening parenthesis

  • Next is the continuation test. While this test is true the loop will continue to be executed. When this test is false the loop will finish and the statement following the body of the loop will be executed.

  • Next is the required closing parenthesis.

  • Usually this is followed by a block of commands to be executed each time the expression following the while keyword is true. The block of commands is enclosed by curly braces. This is called the body of the loop. If there is only one command to be executed you may leave off the curly braces but you should still indent the command to show it is in the body of the while loop.

Tell someone to clap their hands 12 times. Did they do it right? How do you know? In order to tell if they did it right, you would have to count each time they clapped, and when they stopped clapping your count would be 12 if they did it right. A loop often needs a counter to count the number of times you want something done and an expression that stops when that count is reached. You wouldn't want to declare the count variable inside the while loop because you want it to change each time through the loop. Typically you declare the count variable just before the while loop and then increment it just before the end of the block of commands you want to repeat.

Computer Science Idea: Flowcharts

Figure 4.16 shows the flowchart of a while loop. A flowchart is a visual representation of the execution of a method or function. It shows the order in which statements are executed and branches or conditional execution. Normal statements are shown in rectangles. Tests are shown in diamonds and have a true branch which is executed when the test is true and a false branch that is executed when the test is false. A flowchart can help you understand what a method is doing.


Figure 4.16. Flowchart of a while loop.
(This item is displayed on page 95 in the print version)


A typical while loop will look like the following code.

int count = 0; while (count < target) {   // commands to be done inside loop   count = count + 1; }



[Page 95]

What if you want to write out the same sentence five times. You know how to print out a string using System.out.println("some string");. So, put this in the body of the loop. Start the count at 0 and increment it each time after the string is printed. When the count is 5 the string will have been printed five times, so stop the loop.

> int count = 0; > while (count < 5) {   System.out.println("This is a test.");   count = count + 1; } This is a test. This is a test. This is a test. This is a test. This is a test.


Debugging Tip: Stopping an Infinite Loop

If you forget to increment the count in the body of the while loop, or if you close the body of the while loop before the count is incremented you will have an infinite loop. An infinite loop is one that will never stop. You can tell that you are in an infinite loop in this case because many more than five copies of "This is a test." will be printed. To stop an infinite loop click on the RESET button near the top of the DrJava window.



[Page 96]

What if we want to change the color of all the pixels in a picture? Picture objects understand the method getPixels(), which returns a one-dimensional array of pixel objects. Even though the pixels are really in a two-dimensional array (a matrix), getPixels() puts the pixels in a one-dimensional array to make them easy to process if we just want to process all the pixels. We can get a pixel at a position in the array using pixelArray[index] with the index starting at 0 and changing each time through the loop by one until it is equal to the length of the array of pixels. Instead of calling the variable "count," we will call it "index" since that is what we are using it for. It doesn't matter to the computer, but it makes the code easier for people to understand.

Here is the while loop that simply sets each pixel's color to black in a picture.

> import java.awt.Color; > String fName = "C:/intro-prog-java/mediasources/caterpillar.jpg"; > Picture pict = new Picture(fName); > pict.show(); > Pixel[] pixelArray = pict.getPixels(); > Pixel pixel = null; > int index = 0; > while (index < pixelArray.length)   {     pixel = pixelArray[index];     pixel.setColor(Color.black);     index++;   } > pict.repaint();


Let's talk through this code.

  • We will be using the Color class so we need to either use the fully qualified name (java.awt.Color) or import the Color class using:

    import java.awt.Color;

  • Next we declare a variable with the name fileName to refer to the string object that has a particular file name stored in it:

    C:/intro-prog-java/mediasources/caterpillar.jpg

  • The variable pict is created and refers to the new Picture object created from the picture information in the file named by the variable fName.

  • We tell the Picture object to show (display) itself using pict.show();

  • Next we declare a variable pixelArray that references an array of Pixel objects (Pixel[]). We get the array of Pixel objects by asking the Picture object for them using the getPixels() method.

  • We declare an object variable, Pixel pixel, that will refer to a pixel object but initialize it to null to show that it isn't referring to any pixel object yet.

  • We declare a primitive variable index and initialize its value to 0.


  • [Page 97]
  • Next we have the while loop. First we test if the value of index is less than the length of the array of pixels with while (index < pixelArray.length). While it is, we set the variable pixel to refer to the pixel object at the current value of index in the array of pixel objects. Next we set the color of that pixel to the color black. Finally, we increment the variable index. Eventually the value of the variable index will equal the length of the array of pixels and then execution will continue after the body of the loop. Remember that in an array of five items the valid indexes are 04, so when the index is equal to the length of the array you need to stop the loop.

  • The statement after the body of the while loop will ask the Picture object pict to repaint so that we can see the color change.

Debugging Tip: Loops and Variable Declarations

Declare any variables that you will need before you start the loop. "While'' loops typically need some sort of counter or index declared outside the loop but changed inside the loop. If you forgot to change the counter or index, you will end up with a loop that never stops. This is called an infinite loop. Use the RESET button to stop if your code is in an infinite loop.


Now that we see how to get the computer to do thousands of commands without writing thousands of individual lines, let's do something useful with this.

4.3.3. Increasing/Decreasing Red (Green, Blue)

A common desire when working with digital pictures is to shift the redness (or greenness or bluenessbut most often, redness) of a picture. You might shift it higher to "warm" the picture, or to reduce it to "cool" the picture or deal with overly-red digital cameras.

The method below decreases the amount of red by 50% in the current picture.

Program 5. Decrease the Amount of Red in a Picture by 50%
(This item is displayed on pages 97 - 98 in the print version)

/**  * Method to decrease the red by half in the  * current picture  */ public void decreaseRed() {   Pixel[] pixelArray = this.getPixels();   Pixel pixel = null;   int value = 0;   int index = 0;   // loop through all the pixels   while(index < pixelArray.length)   {     // get the current pixel     pixel = pixelArray[index]; 
[Page 98]
// get the value value = pixel.getRed(); // decrease the red value by 50% (1/2) value = (int) (value * 0.5); // set the red value of the current pixel to the new value pixel.setRed(value); // increment the index index = index + 1; } }


Go ahead and type the above into your DrJava definitions pane before the last curly brace in the Picture.java file. Click COMPILE ALL to get DrJava to compile the new method. Why do we have to compile the file before we can use the new method? Computers don't understand the Java source code directly. We must compile it, which translates the class definition from something people can read and understand into something a computer can read and understand.

Common Bug: Methods with the Same Name

If you added the method decreaseRed with a for-each loop in it to your Picture.java source code you will get an error when you add this decreaseRed method and compile. You can't have two methods with the same name and parameter list in a class. Just rename the first decreaseRed method to decreaseRedForEach and compile again.


Unlike some other computer languages, Java doesn't compile into machine code, which is the language for the machine it is running on. When we compile Java source code we compile it into a language for a virtual machine, which is a machine that doesn't necessarily exist.

When we successfully compile a ClassName.java file the compiler outputs a ClassName.class file which contains the instructions that a Java virtual machine can understand. If our compile is not successful we will get error messages that explain what is wrong. We have to fix the errors and compile again before we can try out our new method.

When we execute a Java class the Java Virtual Machine will read the compiled code and map the instructions for the virtual machine to the machine it is currently executing on. This allows you to compile Java programs on one type of computer and run them on another without having to recompile.

Making it Work Tip: Comments in Java

You may notice that there are some interesting characters in the reduceRed method. The '/**' and '//' are comments in Java. Comments are descriptions of what your code is doing. Use comments to make the code easier to read and understand (not only for yourself but also for others). There are actually three kinds of comments in Java. The '//' starts a comment and tells the computer to ignore everything else till the end of the current line. You can use '/*' followed at some point by '*/' for a multi-line comment. The '/**' followed at some point by '*/' creates a JavaDoc comment. JavaDoc is a utility that pulls the JavaDoc comments from your class files and creates hyperlinked documentation from them. All of the Java class files written by Sun have JavaDoc comments in them and that is how the API documentation was created.


[Page 99]


This program works on a Picture objectthe one that we'll use to get the pixels from. To create a Picture object, we pass in the filename. After we ask the picture to decreaseRed(), we'll want to repaint the picture to see the effect. Therefore, the decreaseRed method can be used like this:

> String fName = "C:/intro-prog-java/mediasources/caterpillar.jpg"; > Picture picture = new Picture(fName); > picture.show(); > picture.decreaseRed(); > picture.repaint();


Common Bug: Patience: Loops can Take a Long Time

The most common bug with this kind of code is to give up and quit because you don't think the loop is working. It might take a full minute (or two!) for some of the manipulations we'll doespecially if your source image is large.


The original picture and its red-decreased version appear in Figure 4.17. 50% is obviously a lot of red to reduce! The picture looks like it was taken through a blue filter.

Figure 4.17. The original picture (left) and red-decreased version (right).
(This item is displayed on page 100 in the print version)


Computer Science Idea: Changing Memory Doesn't Change the File

If you create another Picture object from the same file will you get the original picture or the picture with red decreased? You will get the original picture. The Picture object picture was created by reading the file data into memory. The change to the Picture object was done in memory, but the file wasn't changed. If you want to save your changes write them out to a file using the method pictObj.write(String fileName); where pictObj is the name of the Picture object and fileName is the full path name of the file. So to save the changed Picture object above use picture.write("c:/caterpillarChanged.jpg");.


Tracing the program: How did that work?

Computer Science Idea: The Most Important Skill is Tracing

The most important skill that you can develop in programming is the ability to trace your program. This is also called stepping or walking through your program. To trace your program is to walk through it, line-by-line, and figure out what happens. Looking at a program, can you predict what it's going to do? You should be able to by thinking through what it does.



[Page 100]

Let's trace the method to decrease red and see how it worked. We want to start tracing at the point where we just called decreaseRed()

> String fileN = "C:/intro-prog-java/mediasources/caterpillar.jpg"; > Picture picture = new Picture(fileN); > picture.show(); > picture.decreaseRed();


What happens now? picture.decreaseRed() really means invoking the decreaseRed method which you have just added to the Picture.java file on the Picture object referred to by the variable picture. The picture object is implicitly passed to the decreaseRed method and can be referenced by the keyword this. What does "implicitly passed" mean? It means that even though decreaseRed doesn't have any parameters listed it is passed the Picture object it was invoked on. So, picture.decreaseRed() is like decreaseRed(Picture this). All object methods (methods without the keyword static in them) are implicitly passed the object that they are invoked on and that object can be referred to as this.

The first line we execute in Program 5 (page 97) is Pixel[] pixelArray = this.getPixels(). Let's break this down.

  • The Pixel[] pixelArray is a declaration of a variable pixelArray that references an array of Pixel objects. The '=' means that the variable pixelArray will be initialized to the result of the right side expression which is a call to the method this.getPixels() which returns a one-dimensional array of Pixel objects in the current Picture object.

  • The this is a keyword that represents the current object. Since the method declaration doesn't have the keyword static in it this is an object method. Object methods are always implicitly passed the current object (the object the method was invoked on). In this case the method decreaseRed() was invoked by picture.decreaseRed(); so the Picture object referenced by the variable picture is the current object. We could leave off the this and get the same result. If you don't reference any object when invoking a method the compiler will assume you mean the current object (referenced by the this keyword).


    [Page 101]
  • The this.getPixels() invokes the method getPixels() on the current object. This method returns a one-dimensional array of Pixel objects which are the pixels in the current Picture object.

So at the end of the first line we have a variable pixelArray that refers to an array of Pixel objects. The Pixel objects came from the Picture object which was referred to as picture in the interaction pane and as this in the method decreaseRed().

Next is a declaration of a couple of variables that we will need in the for loop. We will need something to represent the current Pixel object so we declare a variable pixel of type Pixel by Pixel pixel =. We start it off referring to nothing by using the defined value null. We also will need a variable to hold the current red value and we declare that as int value = 0;. We initialize the variable value to be 0. Finally we declare a variable to be the index into the array and the value that changes in the loop int index = 0;. Remember that array elements are indexed starting with 0 and ending at the length of the array minus one.

Variables that you declare inside methods are not automatically initialized for you, so you should initialize them when you declare them.

Computer Science Idea: Scope

The names inside a method like pixel and value are completely different than the names in the interactions pane or any other method. We say that they have a different scope. The scope of a variable is the area in which the variable is known. The variables that we declare inside of a method are only known from where they are declared until the end of the method. Variables declared in the interactions pane are known in the interactions pane until it is reset or until you exit DrJava.



[Page 102]

Next comes the loop while (index < pixelArray.length). This tests whether the value of the variable index is less than the length of the array of pixels referred to by pixelArray. If the test is true, the body of the loop will be executed. The body of the loop is all the code between the open and close curly braces following the test. If the test is false, execution continues after the body of the loop.

In the body of the loop we have pixel = pixelArray[index];. This will set the pixel variable to point to a Pixel object in the array of pixels with an index equal to the current value of index. Since index is initialized to 0 before the loop, the first time through this loop the pixel variable will point to the first Pixel object in the array.

Next in the body of the loop is value = pixel.getRed();. This sets the variable value to the amount of red in the current pixel. Remember that the amount of red can vary from a minimum of 0 to a maximum of 255.

Next in the body of the loop is value = (int) (value * 0.5);. This sets the variable value to the integer amount that you get from multiplying the current contents of value by 0.5. The (int) is a cast to integer so that the compiler doesn't complain about losing precision since we are storing a floating point number in an integer number. Any numbers after the decimal point will be discarded. We do this because colors are represented as integers. The (int) (value * 0.5) is needed because the variable value is declared of type int and yet the calculation of (value * 0.5) contains a floating point number and so will automatically be done in floating point. However, a floating point result (say of 1.5) won't fit into a variable of type int. So, the compiler won't let us do this without telling it that we really want it to by including the (int). This is called casting and is required whenever a larger value is being placed into a smaller variable. So if the result of a multiplication has a fractional part, that fractional part will just be thrown away so that the result can fit in an int.


[Page 103]

The next step in the body of the loop is pixel.setRed(value);. This changes the amount of red in the current pixel to be the same as what is stored in variable value. The current pixel is the first one, so we see that the red value has changed from 252 to 126 after this line of code is executed.

After the statements in the body of the loop are executed the index = index + 1; will be executed which will add one to the current value of index. Since index was initialized to 0 this will result in index holding the value 1.

What happens next is very important. The loop starts over again. The continuation test will again check that the value in variable index is less than the length of the array of pixels, and since the value of index is less than the length of the array, the statements in the body of the loop will be executed again. The variable pixel will be set to the pixel object in the array of pixels at index 1. This is the second Pixel object in the array pixelArray.


[Page 104]

The variable value will be set to the red amount in the current pixel referred to by the variable pixel, which is 253.

The variable value will be set to the result of casting to integer the result of multiplying the amount in value by 0.5. This results in (253 * 0.5) = 126.5 and after we drop the digits after the decimal this is 126. We drop the digits after the decimal point because of the cast to the type int (integer). We cast to integer because colors are represented as integer values from 0 to 255.

The red value in the current pixel is set to the same amount as what is stored in value. So the value of red in the second pixel changes from 253 to 126.


[Page 105]

The variable index is set to the result of adding 1 to its current value. This adds 1 to 1, resulting in 2.

At the end of the loop body we go back to the continuation test. The test will be evaluated and if the result is true the commands in the loop body will be executed again. If the continuation test evaluates to false execution will continue with the first statement after the body of the loop.

Eventually, we get Figure 4.17 (and at Figure 4.18). We keep going through all the pixels in the sequence and changing all the red values.

Figure 4.18. Using the picture explorer to convince ourselves that the red was decreased.


Testing the program: Did that really work?

How do we know that that really worked? Sure, something happened to the picture, but did we really decrease the red? By 50%?

Making it Work Tip: Don't Just Trust Your Programs!

It's easy to mislead yourself that your programs worked. After all, you told the computer to do a particular thing, you shouldn't be surprised if the computer did what you wanted. But computers are really stupidthey can't figure out what you want. They only do what you actually tell them to do. It's pretty easy to get it almost right. Actually check.


We can check it several ways. One way is with the picture explorer. Create two Picture objects: Picture p = new Picture(FileChooser.pickAFile()); and Picture p2 = new Picture(FileChooser.pickAFile()); and pick the same picture each time. Decrease red in one of them. Then open a picture explorer on each of the Picture objects using p.explore(); and p2.explore();.


[Page 106]

We can also use the methods that we know in the Interactions pane to check the red values of individual pixels.

> String fName = "C:/intro-prog-java/mediasources/caterpillar.jpg"; > Picture pict = new Picture(fName); > Pixel pixel = pict.getPixel(0,0); > System.out.println(pixel); Pixel red=252 green=254 blue=251 > pict.decreaseRed(); > Pixel newPixel = pict.getPixel(0,0); > System.out.println(newPixel); Pixel red=126 green=254 blue=251 > System.out.println( 252 * 0.5); 126.0


Increasing red

Let's increase the red in the picture now. If multiplying the red component by 0.5 decreased it, multiplying it by something over 1.0 should increase it. I'm going to apply the increase to the exact same picture, to see if we can reduce the blue (Figure 4.19).

Figure 4.19. Overly blue (left) and red increased by 30% (right).


Program 6. Increase the Red Component by 30%
(This item is displayed on pages 106 - 107 in the print version)

/**  * Method to increase the amount of red by 30%  */ public void increaseRed() {   Pixel[] pixelArray = this.getPixels();   Pixel pixel = null; 
[Page 107]
int value = 0; int index = 0; // loop through all the pixels while (index < pixelArray.length) { // get the current pixel pixel = pixelArray[index]; // get the value value = pixel.getRed(); // change the value to 1.3 times what it was value = (int) (value * 1.3); // set the red value to 1.3 times what it was pixel.setRed(value); // increment the index index++; } }


This method works much the same way as the method decreaseRed. We set up some variables that we will need such as the array of pixel objects, the current pixel, the current value, and the current index. We loop through all the pixels in the array of pixels and change the red value for each pixel to 1.3 times its original value.

Making it Work Tip: Shortcuts for Increment and Decrement

Adding one or subtracting one from a current value is something that is done frequently in programs. Programmers have to do lots of typing, so they try to reduce the amount of typing they have to do for things they do frequently. Notice the index++; in the increase red program. This has the same result as index = index + 1; and can also be written as ++index;. You can also use index--; or --index; which will have the same result as index = index - 1;. Be careful about using this when you are also assigning the result to a variable. If you do int x = index++; x will be assigned the original value of index and then index will be incremented. If you do int x = ++index; first index will be incremented and then the value assigned to x.


Compile the new method increaseRed and first use decreaseRed and then increaseRed on the same picture. Explore the picture objects to check that increaseRed worked. Remember that the method explore makes a copy of the picture and allows you to check the color values of individual pixels.

> String fName = "C:/intro-prog-java/mediasources/caterpillar.jpg"; > Picture picture = new Picture(fName); > picture.decreaseRed(); > picture.explore(); > picture.increaseRed(); > picture.explore();



[Page 108]

We can even get rid of a color completely. The method below erases the blue component from a picture by setting the blue value to 0 in all pixels (Figure 4.20).

Figure 4.20. Original (left) and blue erased (right).


Program 7. Clear the Blue Component from a Picture

/**  * Method to clear the blue from the picture (set  * the blue to 0 for all pixels)  */ public void clearBlue() {   Pixel[] pixelArray = this.getPixels();   Pixel pixel = null;   int index = 0;   // loop through all the pixels   while (index < pixelArray.length)   {     // get the current pixel     pixel = pixelArray[index];     // set the blue on the pixel to 0     pixel.setBlue(0);     // increment index     index++;   } }


Compile the new method clearBlue and invoke it on a Picture object. Explore the picture object to check that all the blue values are indeed 0.

> String fName = "C:/intro-prog-java/mediasources/caterpillar.jpg"; > Picture picture = new Picture(fName); 
[Page 109]
> picture.explore(); > picture.clearBlue(); > picture.explore();


This method is also similar to the decreaseRed and increaseRed methods except that we don't need to get out the current blue value since we are simply setting all the blue values to 0.

4.3.4. Creating a Sunset

We can certainly do more than one color manipulation at once. Mark wanted to try to generate a sunset out of a beach scene. His first attempt was to increase the red, but that doesn't always work. Some of the red values in a given picture are pretty high. If you go past 255 for a channel value it will keep the value at 255.

His second thought was that maybe what happens in a sunset is that there is less blue and green, thus emphasizing the red, without actually increasing it. Here was the program that he wrote for that:

Program 8. Making a Sunset

/**  * Method to simulate a sunset by decreasing the green  * and blue  */ public void makeSunset() {   Pixel[] pixelArray = this.getPixels();   Pixel pixel = null;   int value = 0;   int i = 0;   // loop through all the pixels   while (i < pixelArray.length)   {     // get the current pixel     pixel = pixelArray[i];     // change the blue value     value = pixel.getBlue();     pixel.setBlue((int) (value * 0.7));     // change the green value     value = pixel.getGreen();     pixel.setGreen((int) (value * 0.7));     // increment the index     i++;   } }



[Page 110]

Making it Work Tip: Using Short Variable Names for Loop Counters

Notice that instead of using index as the counter for the loop we are using i. Again, programmers like to reduce the amount of typing, and so the simple variable name i is commonly used to represent the counter or index for a loop.


Compile the new method makeSunset and invoke it on a Picture object. Explore the picture object to check that the blue and green values have been decreased.

> String fName = "C:/intro-prog-java/mediasources/beach-smaller.jpg"; > Picture picture = new Picture(fName); > picture.explore(); > picture.makeSunset(); > picture.explore();


What we see happening in Program 8 (page 109) is that we're changing both the blue and green channelsreducing each by 30%. The effect works pretty well, as seen in Figure 4.21.

Figure 4.21. Original beach scene (left) and at (fake) sunset (right).


4.3.5. Making Sense of Methods

You probably have lots of questions about methods at this point. Why did we write these methods this way? How is that we're reusing variable names like pixel in every method? Are there other ways to write these methods? Is there such a thing as a better or worse method?

Since we're always picking a file (or typing in a filename) then making a picture, before calling one of our picture manipulation methods, and then showing or repainting the picture, it's a natural question why we're not building those in. Why doesn't every method have String fileName = FileChooser.pickAFile(); and new Picture(fileName); in it?


[Page 111]

We actually want to write the methods to make them more general and reusable. We want our methods to do one and only one thing, so that we can use the method again in a new context where we need that one thing done. An example might make that clearer. Consider the program to make a sunset (Program 8 (page 109)). That works by reducing the green and blue, each by 30%. What if we rewrote that method so that it called two smaller methods that just did the two pieces of the manipulation? We'd end up with something like Program 9 (page 111).

Program 9. Making a Sunset as Three Methods
(This item is displayed on pages 111 - 112 in the print version)

/**  * Method to decrease the green in the picture by 30%  */ public void decreaseGreen() {   Pixel[] pixelArray = this.getPixels();   Pixel pixel = null;   int value = 0;   int i = 0;   // loop through all the pixels in the array   while (i < pixelArray.length)   {     // get the current pixel     pixel = pixelArray[i];     // get the value     value = pixel.getGreen();     // set the green value to 70% of what it was     pixel.setGreen((int) (value * 0.7));     // increment the index     i++;   } } /**  * Method to decrease the blue in the picture by 30%  */ public void decreaseBlue() {   Pixel[] pixelArray = this.getPixels();   Pixel pixel = null;   int value = 0;   int i = 0;    // loop through all the pixels in the array   while (i < pixelArray.length)   { 
[Page 112]
// get the current pixel pixel = pixelArray[i]; // get the value value = pixel.getBlue(); // set the blue value to 70% of what it was pixel.setBlue((int) (value * 0.7)); } } /** * Method to make a picture look like it was taken at sunset * by reducing the blue and green to make it look more red */ public void makeSunset2() { decreaseGreen(); decreaseBlue(); }


The first thing to note is that this actually does work. makeSunset2() does the same thing here as in the previous method. It's perfectly okay to have one method (makeSunset2() in this case) use other methods in the same class (decreaseBlue() and decreaseGreen()). You use makeSunset2() just as you had before. It's the same algorithm (it tells the computer to do the same thing), but with different methods. The earlier program did everything in one method, and this one does it in three. In fact, you can also use decreaseBlue() and decreaseGreen() by themselves toomake a picture in the Command Area and invoke either method on the Picture object. They work just like decreaseRed().

What's different is that the method makeSunset2() is much simpler to read. That's very important.

Computer Science Idea: Programs are for People

Computers don't care about how a program looks. Programs are written to communicate with people. Making programs easy to read and understand means that they are more easily changed and reused, and they more effectively communicate process to other humans.


What if we had written decreaseBlue() and decreaseGreen() so that each asked you to pick a file and created the picture before changing the color. We would be asked to pick a file twiceonce in each method. Because we wrote these methods to only decrease the blue and decrease the green ("one and only one thing") in the implicitly passed Picture object, we can use them in new methods like makeSunset().

There is an issue that the new makeSunset2() will take twice as long to finish as the original makeSunset(), since every pixel gets changed twice. We address that issue in Chapter 15 on speed and complexity. The important issue is still to write the code readably first, and worry about efficiency later. However, this could also be handled by a method that changes each color by some passed in amount. This would be a very general and reusable method.


[Page 113]

Now, let's say that we asked you to pick a picture and created the picture in makeSunset2() before calling the other methods. The methods decreaseBlue() and decreaseGreen() are completely flexible and reusable again. But the method makeSunset2() is now less flexible and reusable. Is that a big deal? No, not if you only care about having the ability to give a sunset look to a single picked picture. But what if you later want to build a movie with a few hundred frames of Picture objects, to each of which you want to add a sunset look? Do you really want to pick out each of those few hundred frames? Or would you rather write a method to go through each of the frames (which we'll learn how to do in a few chapters) and invoke makeSunset2() on each Picture object. That's why we make methods general and reusableyou never know when you're going to want to use that method again, in a larger context.

Making it Work Tip: Don't Start by Trying to Write Applications

There's a tendency for new programmers to want to write complete applications that a non-technical user can use. You might want to write a makeSunset() application that goes out and fetches a picture for a user and generates a sunset for them. Building good user interfaces that anyone can use is hard work. Start out more slowly. It's hard enough to make a method that just does something to a picture. You can work on user interfaces later.


Even larger methods, like makeSunset(), do "one and only one thing." The method makeSunset() makes a sunset-looking picture. It does that by decreasing green and decreasing blue. It calls two other methods to do that. What we end up with is a hierarchy of goalsthe "one and only one thing" that is being done. makeSunset() does its one thing, by asking two other methods to do their one thing. We call this hierarchical decomposition (breaking down a problem into smaller parts, and then breaking down those smaller parts until you get something that you can easily program), and it's very powerful for creating complex programs out of pieces that you understand. This is also called top-down refinement or problem decomposition.

4.3.6. Variable Name Scope

Names in methods are completely separate from names in the interactions pane and also from names in other methods. We say that they have different scope. Scope is the area where a name is known by the computer. Variables declared inside of a method have method scope and only apply inside that method. That is why we can use the same variable names in several methods. Variables declared inside the Interactions Pane are known inside the Interactions Pane until it is reset. This is why you get Error: Redefinition of 'picture' when you declare a variable that is already declared in the Interactions Pane.


[Page 114]

The only way to get any data (pictures, sounds, filenames, numbers) from the interactions pane into a method is by passing it in as input to the method. Within the method, you can use any names you wantnames that you first define within the method (like pixel in the last example) or names that you use to stand for the input data (like fileName) only exist while the method is running. When the method is done, those variable names literally do not exist anymore.

This is really an advantage. Earlier, we said that naming is very important to computer scientists: We name everything, from data to methods to classes. But if each name could mean one and only one thing ever, we'd run out of names. In natural language, words mean different things in different contexts (e.g., "What do you mean?" and "You are being mean!"). A method is a different contextnames can mean something different than they do outside of that method.

Sometimes, you will compute something inside a method that you want to return to the interactions pane or to a calling method. We've already seen methods that output a value, like FileChooser.pickAFile() which outputs a filename. If you created a Picture object using new Picture(fileName) inside a method, you should return it so that it can be used. You can do that by using the return keyword.

The name that you give to a method's input can be thought of as a placeholder. Whenever the placeholder appears, imagine the input data appearing instead. So, in a method like:

Program 10. General Change Red by a Passed Amount
(This item is displayed on pages 114 - 115 in the print version)

/**  * Method to change the red by an amount  * @param amount the amount to change the red by  */ public void changeRed(double amount) {   Pixel[] pixelArray = this.getPixels();   Pixel pixel = null;   int value = 0;   int i = 0;   // loop through all the pixels   while( i < pixelArray.length)   {     // get the current pixel     pixel = pixelArray[i];     // get the value     value = pixel.getRed();     /* set the red value to the original value     * times the passed amount     */     pixel.setRed((int) (value * amount)); 
[Page 115]
// increment i i++; } }


When you call (invoke) the method changeRed with a specific amount such as picture.changeRed(0.7); it will decrease the red by 30%. In the method changeRed the input parameter amount is set to 0.7. This is similar to declaring a variable inside the method like this double amount = 0.7;. Just like any variable declared in the method the parameter amount is known inside the method. It has method scope.

Call changeRed with an amount less than one to decrease the amount of red in a picture. Call changeRed with an amount greater than one to increase the amount of red in a picture. Remember that the amount of red must be between 0 and 255. If you try to set the amount of red less than 0 it will be set to 0. If you try to set the amount of red greater than 255 it will be set to 255.

We've talked about different ways of writing the same methodsome better, some worse. There are others that are pretty much equivalent, and others that are much better. Let's consider a few more ways that we can write methods.

We can pass in more than one input at a time. Consider the following:

Program 11. Change all Pixel Colors by the Passed Amounts
(This item is displayed on pages 115 - 116 in the print version)

/**  * Method to change the color of each pixel in the picture  * object by passed in amounts.  * @param redAmount the amount to change the red value  * @param greenAmount the amount to change the green value  * @param blueAmount the amount to change the blue value  */ public void changeColors(double redAmount,                          double greenAmount,                          double blueAmount) {   Pixel[] pixelArray = this.getPixels();   Pixel pixel = null;   int value = 0;   int i = 0;   // loop through all the pixels   while( i < pixelArray.length)   {     // get the current pixel     pixel = pixelArray[i];     // change the red value     value = pixel.getRed();     pixel.setRed((int) (redAmount * value)); 
[Page 116]
// change the green value value = pixel.getGreen(); pixel.setGreen((int) (greenAmount * value)); // change the blue value value = pixel.getBlue(); pixel.setBlue((int) (blueAmount * value)); // increment i i++; } }


We could use this method as shown below:

> String fName = "C:/intro-prog-java/mediasources/beach-smaller.jpg"; > Picture picture = new Picture(fName); > picture.changeColors(1.0,0.7,0.7); > picture.show();


The above code would have the same result as makeSunset(). It keeps the red values the same and decreases the green and blue values 30%. That's a pretty useful and powerful method.

Recall seeing in Program 7 (page 108) this code:

/**  * Method to clear the blue from the picture (set  * the blue to 0 for all pixels)  */ public void clearBlue() {   Pixel[] pixelArray = this.getPixels();   Pixel pixel = null;   int index = 0;   // loop through all the pixels   while (index < pixelArray.length)   {     // get the current pixel     pixel = pixelArray[index];     // set the blue on the pixel to 0     pixel.setBlue(0);     // increment index     index++;   } }



[Page 117]

We could also write that same algorithm like this:

/**  * Method to clear the blue from the picture (set  * the blue to 0 for all pixels)  */ public void clearBlue2() {   Pixel[] pixelArray = this.getPixels();   int i = 0;   // loop through all the pixels   while(i < pixelArray.length)   {       pixelArray[i].setBlue(0);       i++;   } }


It's important to note that this method achieves the exact same thing as the earlier method did. Both set the blue channel of all pixels to zero. An advantage of the second method is that it is shorter and doesn't require a variable declaration for a pixel. However, it may be harder for someone to understand. A shorter method isn't necessarily better.

4.3.7. Using a For Loop

You may have had the problem that you forgot to declare the index variable before you tried to use it in your while loop. You may also have had the problem of forgetting to increment the index variable before the end of the loop body. This happens often enough that another kind of loop is usually used when you want to loop a set number of times. It is called a for loop.

A for loop executes a command or group of commands in a block. A for loop allows for declaration and/or initialization of variables before the loop body is first executed. A for loop continues executing the loop body while the continuation test is true. After the end of the body of the loop and before the continuation test one or more variables can be changed.

The syntax for a for loop is:

for (initialization area; continuation test; change area) {   /* commands in body of the loop */ }


Let's talk through the pieces here.

  • First comes the required Java keyword for.

  • Next we have a required opening parenthesis.

  • Next is the initialization area. You can declare and initialize variables here. For example, you can have int i=0 which declares a variable i of the primitive type int and initializes it to 0. You can initialize more than one variable here by separating the initializations with commas. You are not required to have any initializations here.


    [Page 118]
  • Next comes the required semicolon.

  • Next is the continuation test. This holds an expression that returns true or false. When this expression is true the loop will continue to be executed. When this test is false the loop will finish and the statement following the body of the loop will be executed.

  • Next comes the required semicolon.

  • Next is the change area. Here you usually increment or decrement variables, such as i++ to increment i. The statements in the change area actually take place after each execution of the body of the loop.

  • Next is the required closing parenthesis.

If you just want to execute one statement (command) in the body of the loop, it can just follow on the next line. It is normally indented to show that it is part of the for loop. If you want to execute more than one statement in the body of the for loop, you will need to enclose the statements in a block (a set of open and close curly braces).

Common Bug: Change Loop Variables in One Place!

When you specify how to change the loop variables in the change area of the for loop this will actually happen at the end of the body of the loop. So don't also change the loop variables in the loop or you will change them twice and probably not get the desired result.


Compare the flowchart (Figure 4.22) for a for loop with the flowchart for a while loop (Figure 4.16). They look the same because for loops and while loops execute in the same way even though the code looks different. Any code can be written using either. The syntax of the for loop just makes it easier to remember to declare a variable for use in the loop and to change it each time through the loop since all of that is written at the same time that you write the test. To change clearBlue() to use a for loop simply move the declaration and initialization of the index variable i to be done in the initialization area and the increment of i to be done in the change area.

Figure 4.22. Flowchart of a for loop.
(This item is displayed on page 119 in the print version)


Program 12. Another Clear Blue Method
(This item is displayed on pages 118 - 119 in the print version)

/**  * Method to clear the blue from the picture (set  * the blue to 0 for all pixels)  */ public void clearBlue3() {   Pixel[] pixelArray = this.getPixels();   // loop through all the pixels 
[Page 119]
for (int i=0; i < pixelArray.length; i++) pixelArray[i].setBlue(0); }


4.3.8. Lightening and Darkening

To lighten or darken a picture is pretty simple. It's the same pattern as we saw previously, but instead of changing a color component, you change the overall color. Here's lightening and then darkening as methods. Figure 4.23 shows the lighter and darker versions of the original picture seen earlier.

Figure 4.23. Original picture, lightened picture, and darkened picture.
(This item is displayed on page 120 in the print version)


Program 13. Lighten the Picture
(This item is displayed on pages 119 - 120 in the print version)

/**  * Method to lighten the colors in the picture  */ public void lighten() {   Pixel[] pixelArray = this.getPixels();   Color color = null;   Pixel pixel = null;   // loop through all the pixels   for (int i = 0; i < pixelArray.length; i++)   {     // get the current pixel     pixel = pixelArray[i]; 
[Page 120]
// get the current color color = pixel.getColor(); // get a lighter color color = color.brighter(); // set the pixel color to the lighter color pixel.setColor(color); } }


Program 14. Darken the Picture

/**  * Method to darken the color in the picture  */ public void darken() {   Pixel[] pixelArray = this.getPixels();   Color color = null;   Pixel pixel = null;   // loop through all the pixels   for (int i = 0; i < pixelArray.length; i++)   {     // get the current pixel     pixel = pixelArray[i];     // get the current color     color = pixel.getColor();     // get a darker color     color = color.darker();     // set the pixel color to the darker color     pixel.setColor(color);   } }



[Page 121]

4.3.9. Creating a Negative

Creating a negative image of a picture is much easier than you might think at first. Let's think it through. What we want is the opposite of each of the current values for red, green, and blue. It's easiest to understand at the extremes. If we have a red component of 0, we want 255 instead. If we have 255, we want the negative to have a zero.

Now let's consider the middle ground. If the red component is slightly red (say, 50), we want something that is almost completely redwhere the "almost" is the same amount of redness in the original picture. We want the maximum red (255), but 50 less than that. We want a red component of 255 50 = 205. In general, the negative should be 255 original. We need to compute the negative of each of the red, green, and blue components, then create a new negative color, and set the pixel to the negative color.

Here's the program that does it, and you can see from the image that it really does work (Figure 4.24).

Figure 4.24. Negative of the image.


Program 15. Create the Negative of the Original Picture
(This item is displayed on pages 121 - 122 in the print version)

/**  * Method to negate the picture  */ public void negate() {   Pixel[] pixelArray = this.getPixels();   Pixel pixel = null;   int redValue, blueValue, greenValue = 0;   // loop through all the pixels   for (int i = 0; i < pixelArray.length; i++)   {     // get the current pixel     pixel = pixelArray[i]; 
[Page 122]
// get the current red, green, and blue values redValue = pixel.getRed(); greenValue = pixel.getGreen(); blueValue = pixel.getBlue(); // set the pixel's color to the new color pixel.setColor(new Color(255 - redValue, 255 - greenValue, 255 - blueValue)); } }


4.3.10. Converting to Grayscale

Converting to grayscale is a fun program. It's short, not hard to understand, and yet has such a nice visual effect. It's a really nice example of what one can do easily yet powerfully by manipulating pixel color values.

Recall that the resultant color is gray whenever the red component, green component, and blue component have the same value. That means that our RGB encoding supports 256 levels of gray from, (0, 0, 0) (black) to (1, 1, 1) through (100, 100, 100) and finally (255, 255, 255). The tricky part is figuring out what the replicated value should be.

What we want is a sense of the intensity of the color. It turns out that it's pretty easy to compute: We average the three component colors. Since there are three components, the formula for intensity is:


This leads us to the following simple program and Figure 4.25.

Figure 4.25. Color picture converted to grayscale.



[Page 123]

Program 16. Convert to Grayscale

/**  * Method to change the picture to grayscale  */ public void grayscale() {   Pixel[] pixelArray = this.getPixels();   Pixel pixel = null;   int intensity = 0;   // loop through all the pixels   for (int i = 0; i < pixelArray.length; i++)   {     // get the current pixel     pixel = pixelArray[i];     // compute the intensity of the pixel (average value)     intensity = (int) ((pixel.getRed() + pixel.getGreen() +                    pixel.getBlue()) / 3);     // set the pixel color to the new color     pixel.setColor(new Color(intensity,intensity,intensity));   } }


This is an overly simply notion of grayscale. Below is a program that takes into account how the human eye perceives luminance. Remember that we consider blue to be darker than red, even if there's the same amount of light reflected off. So we weight blue lower, and red more, when computing the average.

Program 17. Convert to Grayscale with More Careful Control of Luminance
(This item is displayed on pages 123 - 124 in the print version)

/**  * Method to change the picture to grayscale with luminance  */ public void grayscaleWithLuminance() {   Pixel[] pixelArray = this.getPixels();   Pixel pixel = null;   int luminance = 0;   double redValue = 0;   double greenValue = 0;   double blueValue = 0;   // loop through all the pixels   for (int i = 0; i < pixelArray.length; i++)   { 
[Page 124]
// get the current pixel pixel = pixelArray[i]; // get the corrected red, green, and blue values redValue = pixel.getRed() * 0.299; greenValue = pixel.getGreen() * 0.587; blueValue = pixel.getBlue() * 0.114; // compute the intensity of the pixel (average value) luminance = (int) (redValue + greenValue + blueValue); // set the pixel color to the new color pixel.setColor(new Color(luminance,luminance,luminance)); } }




Introduction to Computing & Programming Algebra in Java(c) A Multimedia Approach
Introduction to Computing & Programming Algebra in Java(c) A Multimedia Approach
ISBN: N/A
EAN: N/A
Year: 2007
Pages: 191

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net