Image Loops

< Code In The Browser

In this section we'll add the powerful "loop" construct, allowing us to work on the whole image instead of just one pixel at a time.

Accessing one pixel at a time, e.g. pixel at (0, 0), then the pixel at (1, 0), etc. is not a good way to work on an image which may have 10,000 or more pixels. We'd like to say something like "for exery pixel do this", and let the computer fiddle with the details of going through all the (x, y) values to look at each pixel once.

The very powerful "for loop" structure we'll learn here provides a "for every pixel do this" feature. The loop takes a few lines of our code, and runs those lines again and again, once for each pixel in the image. As a first example, the code below sets the red value of very pixel in the image to 0.


Since the yellow flowers are made with red + green light, blacking out the red results is greenish flowers. The line pixel.setRed(0); is run by the loop again and again, once for each pixel in the image. The image is 457 pixels wide by 360 pixels high, so that makes 457 * 360 = 164,520 pixels -- that's how many times the body of the loop is run. This shows how the for-loop is powerful for us -- we can write a few lines describing what effect we want, and let the computer do the bookeeping to run those lines over all the data.

The main feature of the for-loop to remember is that lines inside and outside the loop are very different. Here are the three main parts of the for-loop:

// 1. Before the loop -- the lines here run
// once from top to bottom, in the usual way.
image = newimpleImage("flowers.jpg");
print("Here we go");

for (pixel: image) {
  // 2. Inside the loop { ... }
  // the "body" lines here within the { ... } run again and again,
  // once for each pixel. Usually the body lines are indented.
  pixel.setRed(0);
}

// 3. After the loop -- the lines here
// run once from top to bottom, after the many runs
// of the loop body.
print(image);
print("All done);

The loop begins with the syntax   for (pixel: image) {   which says that we want to use a variable named "pixel" to look, in turn, at every pixel in the image. The curly brace section "{ ... }", also known as the "body" of the loop is the most important part. The loop structure runs the code in the body again and again .. once for each pixel in the image, which can easily be over 100,000 times. For each run, or "iteration" of the loop body, the pixel variable will point to the next pixel in the image, with its own values for pixel.getGreen() etc. Note how the strings "Here we go" and "All done" are just printed once -- only the lines inside the loop {...} body are run many times. The lines inside the loop is usually indented 2 or more spaces, to emphasize visually which lines are inside and outside the loop.

Loops are common fixture in many programming languages. This for (pixel: image) { form is not actually standard part of Javascript, it's something I added for this course, although many other computer langauges have loops that look just like this (including Java, the language of the CS AP course).

Loop Code Examples


To see how the for loop works, try adding and running run code above for each of the problems below (solutions available).
Loop Example ProblemsSolution
Temporarily change the code to load "flowers-small.jpg" which is much smaller (width 100, height 78, so how many pixels is that?) Add the line
print("Here we go"); before the loop, then moved inside the loop, then after the loop to see the inside/outside loop differences.
For every pixel, set the green value to 0.
  pixel.setGreen(0);  // (in the loop body)
How to show the "red channel" of the image? For every pixel, set the red and blue values to 0.
  pixel.setRed(0);
  pixel.setBlue(0);
Back in black - set every pixel in the image to be perfectly black.
  pixel.setRed(0);
  pixel.setGreen(0);
  pixel.setBlue(0);


Image Loop Exercises

Exercise 1

Write code to set the blue value of every pixel to 200, giving the whole image a sort of blue light.


Exercise 2

Write code to show the blue channel of the image (as shown at the top of this doc).


Exercise 3 (optional)

The brown problem. What combination of red, green, and blue values makes brown? Experiment to find values that work, and set every pixel in the flowers image to use those values. Effectively, this writes over the original flowers image to create a flat brown rectangle.


Expressions

Many times, we have seen function calls where we pass in a value within the parenthesis. The value supplied to the function in this way is called an "argument" to the function, such as the 0 and 255 if this code snippet:

  pixel = image.getPixel(0, 0);
  pixel.setRed(255);

In place of a plain number like 255, the code also supports "expressions" which compute a value to use. For example you could write something like this:

  pixel.setRed(22 + 4);

When that line runs, it first computes 22 + 4, yielding 26. Then in effect it calls setRed(26), passing in the computed value. The "22 + 4" is called an "expression", a combination of values that the computer can evaluate to yield a single value. Any place where we have used a fixed number like 0 or 255 up to now, we could instead write an expression, letting the computer figure out a specific value when it runs that line.

We have not used them until now, but pixels support: pixel.getRed(), pixel.getGreen(), pixel.getBlue() functions which retrieve the color value 0..255 from a pixel. Here is a code snippet which uses getRed() and setRed() to increase the red value of a pixel by 100:

  red = pixel.getRed();
  pixel.setRed(red + 100);

In fact, the "red" variable is not necessary, and the snippet can be reduced to a single line:

  pixel.setRed(pixel.getRed() + 100);

The "pixel.getRed() + 100" is an expression, which is whatever the old red value was plus 100. This expression is evaluated first, resulting a number such as 150. Then in effect pixel.setRed(150); is called. The setRed() etc. functions automatically limit the value set to the range 0..255. If setRed() is called with a value a greater than 255, it just uses 255, and likewise if a value less than 0 is passed in, it just uses the value 0.

Loop Examples With Expressions

Before we could only express ideas like "set the red value to 200". Now we can express what new value we want in terms of the old value, like "set the red value to be 100 more than it was", or "double the red value".


Try adding and running run code above for each of the problems below (solutions available).
Example ProblemsSolution
For every pixel, decrease the green value by 100. How does this change the flowers?
  // (inside the loop)
  pixel.setGreen(pixel.getGreen() - 100);
Similar to the above, divide all the green values by 2.
  pixel.setGreen(pixel.getGreen() / 2);
Multiply the red values by 2.
  pixel.setRed(pixel.getRed() * 2);
Divide all three color values by 2 or a larger value like 3 or 5. What happens to the whole image?
  pixel.setRed(pixel.getRed() / 2);
  pixel.setGreen(pixel.getGreen() / 2);
  pixel.setBlue(pixel.getBlue() / 2);
Multiply all three color values by 2. What happens to the flowers?
  pixel.setRed(pixel.getRed() * 2);
  pixel.setGreen(pixel.getGreen() * 2);
  pixel.setBlue(pixel.getBlue() * 2);


Image Puzzle Exercises

1. Iron Image Puzzle

I must say, I really like this code puzzle as a way of getting a computing principles. This is a puzzle. You are given an image of something famous. However the image has been messed up: the real data is in the red channel, however the red channel values have all been divided by 10, so they are too small by a factor of 10. The blue and green channels are just meaningless random values ("noise") added to obscure the real image. You must undo all these steps to recover the real image. (The solution is available at the end of this page).

Hint: (a) First, set all the blue and green values to 0 to get them out of the way. Look at the result .. if you look very carefully, you may see the real image, although it is very very dark (way down towards 0). (b) Multiply each red value by 10, scaling it back up to its proper value.


2. Copper Image Puzzle

This is an image puzzle -- an image of something famous is hidden in the image "copper-puzzle.png". The real image is in the blue and green channels, however the blue and green values have all been divided by 20, so the values are very small. The red channel is just noise added on top to obscure things. Undo these obscuring factors to recover the original image.


3. Average Color Pixel

It is useful in a few situations to compute the "average" value for a pixel across the red/green/blue values. So for example, if a pixel has red=120, green=80, blue=100 ... in some sense the "average" value for that pixel is 100. The average is in effect a measure of how light/dark the pixel is (0 being dark, 255 being light), ignoring the pixel's color. Here is code to compute the average value for a pixel and store it in an "avg" variable:

  sum = pixel.getRed() + pixel.getGreen() + pixel.getBlue();
  avg = sum / 3;

Using the above code, write code below to compute the average for each pixel, and then set the three red/green/blue values to be that average. What is the visual effect?


Discussion question: why does the "sum = ... " code need to be inside the loop? It seems like it could be left on the outside of the loop.

Code like "avg = sum / 3;" does not set down some rule to be followed at all times. It is much more simple than that -- it evaluates the right hand side first to get a number, then stores that value into the variable "avg" at the moment that line is run. Notice that every time through the loop, the "pixel" variable is a different pixel, so pixel.getRed() etc. return different values every time through the loop. That's why we need to re-do the avg computation every time through the loop.

4. Cold Puzzle (optional)

There is an image hidden in cold-puzzle.png -- figure out code to change the pixels and reveal the image.



Iron puzzle solution:

  // Inside the loop
  pixel.setGreen(0);
  pixel.setBlue(0);
  pixel.setRed(pixel.getRed() * 10);

This work was created by Nick Parlante and is released under the Creative Commons Share-Alike license 3.0