Image Lab 2
Introduction Lab 1 Lab 2
Another lab, another two manipulators. If you need them, download the image manipulator program some test images.

Create a modifier which enhances the contrast of an image. The idea is to spread out the values of the pixels so that they occupy a wider range. This tends to make bright pixels brighter and dark pixels darker, thus improving the contrast of the image. The diagram at the left explains how the pixel values are modified.

Original Level
← 196 Levels →

← 256 Levels →
Modified Level

Pixels less than 30 are reduced to zero. Pixels above 225 (which is 255 − 30) are changed to 255. In the range from 30 to 255, pixel values are spread out to fill the range from 0 to 255. The diagram shows several example levels at the top, and where they map on the bottom. This improves the contrast of the image by spreading out the central pixel values to cover a larger range. For levels in the range 30 to 255, the following formula is used:
newlevel = (level − 30) 255
195
The formula takes a level from that original range (30 to 225) and scales it by the ratio of the ranges to fall at the correct spot on the expanded range.

Here is a python function which makes that transformation:

def map(level):
    if level < 30:
        return 0
    else:
        if level > 225:
            return 255
        else:
            return round((level - 30) * 255.0 / 195.0)
A function is just a set of instructions with a name. We can use the name to run the instructions (see below). You can see that map takes some level and uses an if to decide which range it belongs to. If it is below 30, the function gives 0; above 225 then 255. Between those values, we compute the formula given above.

Some notes about the Python version of the formula: The round operator is a function provided by the system which rounds to the nearest integer value. Of course, 195 = 255 − 30 − 30. The fact that the constants 255 and 195 are given as 255.0 and 195.0 matters. We'll talk about why in lecture.

So do this:

  1. Open the image tool and create a modifier button for the contrast enhancement. Edit it and add the map function given above. Place it above the each_pixel function.
  2. Replace each level assignment in each_pixel with one that maps that particular level. The first one will be
    pix.r = map(pix.r)
    The value of pix.r is sent to map, where it becomes the value of level. Level is kind of variable called a parameter. Values which are sent to parameters are called arguments. The map function receives the argument value, and executes the instructions in the body. When a return statement is executed, the value it computes is sent back and assigned to the variable on the left.
  3. Save your modifier and try it out. The modification is not as dramatic as adding bright violet lines, but you should be able to see the change.
You can can press the button several times to increase the amount of contrast enhancement. Eventually, though, the picture just looks ugly.

Interestingly enough, if you push the contrast button enough times, the picture will stop changing. Can you figure out why?

Our contrast modifier assumes that the pixels we are interested in have levels in the range 30 to 225. Will this work well on an image that starts out very dark or very light?

A more thorough version would first analyze the levels in the picture, and then compute low and high cutoff values which reflect the actual image. For instance, the program might set limits such that 3% of the actual pixels are below the low limit, and 3% above the high. If the image is dark, the range will be shifted to the low end, say 10 to 190. This would do a better job of contrast improvement in a dark image than our limits of 30 to 225.

Our contrast modifier computes each color without considering the brightness of the pixel as a whole. Is that reasonable?

For our second modifier, we'll place a pure blue border, five pixels wide, around the picture. The modifier for this is much like the one for the bars from the last lab. It has a similar form:

def each_pixel(pix, row, col):
    if the pixel is within 5 columns of the left side or \
            the pixel is within 5 columns of the right side or \
            the pixel is within 5 rows from the top or \
            the pixel is within 5 rows from the bottom:
        turn it blue
(You actually need those backward slash things if you want to write the test on more than one line.)

You should by now be able to figure out how to make a pixel blue. The key is translating the if test to Python. The left side and top should be simple enough. Column numbers start with 0, so the pixel will be within five of the left edge if col < 5. Likewise for rows near the top.

For the right side and bottom, you need to know that the system provides variables called width and height which always contain the width and height of the image in pixels. The largest column will be width - 1, and the largest row height - 1. Therefore, a pixel is near the right side of the image when:

col >= width - 5
Or, equivalently, if you like it better:
col + 5 >= width
Pixels are likewise near the bottom when they pass height - 5. So put these four tests together, and you can turn the border blue.

When you have your modifiers are ready, use the export function to save them on the desktop. Right click on each button, and choose the Export operation from the menu. This brings up a file save dialog that will let you save your modifier on the desktop. Save them, then send them to me using this form. The form has spots for each of the required modifiers, and one extra in case you create something else you just like.

If you have some extra time, here are a couple of interesting challenges:

The first one sets each pixel to black or white, whichever it's closer to. You would first compute the gray value, as we did in the previous lab, then set the pixel to pure black or pure white depending on whether the gray level is closer to black (less than 128) or white (128 or above).

The bevel is a nice effect. This one is ten pixels wide, consisting of four trapezoidal areas along the image edges. The upper area is brightened by 60, and the bottom one darkened by 60. The right area is lightened by 30, and the left one darkened the same amount. This creates the illusion of a 3-d object by assuming a light source above and a little to the right: the top edge is brightened most, the right a bit less, and the left and bottom are in shadow. The code has a series of if tests which figure out if the pixel is within one of the four regions, and lightens or darkens it appropriately.