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 225, 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 225, 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)
    Make sure to map the other two also. 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. The following is not the modifier, but is an English expression of what it needs to do:

def each_pixel(pix, row, col):
    if near the left  or  near the right  or  near the top  or  near the bottom:
        turn the pixel blue
If you were to copy the above modifier into mimp, you would quickly discover it does not work. We need to convert it to Python so the computer can run it. Each of the four pixel position tests must be converted to a mathematical expression using row and column, while the instruction to “turn the pixel blue” must be changed to appropriate assignments of the pixel components.

You should by now be able to figure out how to make a pixel blue. The new part is translating the if test to Python. First, recall that the border is five pixels wide, so when the test asks if a pixel is “near” something, it means five pixels or fewer away: within the border width. So the test that a pixel is near the left is just col < 5. Since the column number is always a non-negative integer, that means the the pixel must be in column 0, 1, 2, 3 or 4.

The left-most column is always zero, so we can test for leftness by just seeing if the column is small. The number of the right-most column always changes with the image size, but the modifier program tells us what we need to know. There is a pre-defined variable width giving the width of the image, so the right-most pixel is always in column width-1, so the five right-most columns are width-5 through width-1. We can see if our pixel is among them (on the right) with the test col >= width-5.

The tests for the top and bottom are similar, except that you are testing the pixel's row number, and you need to know the number of the bottom row of the image. The pre-defined variable height will be helpful. Substitute mathematical versions of the four tests (keep the words if and or, and the colon), and substitute the correct assignments to turn the pixel blue, and you will have a fine border-creating modifier.

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 your instructor. The submit 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. These tests are similar to the ones for the border, but they are more difficult, since you must separate each area completely. The four border areas overlap in the corners, but it's not a problem to turn each of those pixels blue twice. Darkening or lightening must be done exactly once.