Csci 210 Lab: Image Processing

(Laura Toma)

Overview

In this lab you will develop a packge for image processing. You will implement classes and methods for some of the basic functionality on images, like converting to grayscale, rotating, scaling, fading, and a couple of filters.

Your program will consist of one class, the Picture class, and a couple of helper classes and tester programs. The Picture class is responsible creating, modifying and rendering pictures. We'll describe it below.

The BufferedImage class

A picture is essentially a matrix of color pixels. Much like a grid terrain. Since the image formats are not as straightforward as the grid format, you will not write the code to read in the pixels from the picture file as you did for the grid. Instead you will use Java class BufferedImage that does the job for you.

Class BufferedImage is a Java class that can handle images in many of the standard formats and implements handy functions like loading an image from a file.

BufferedImage image; 
//assume file is the image file
try {
      image = ImageIO.read(file);
     } 
catch (IOException e) {
       System.out.println("Invalid image file: " + filename);
       System.exit(0);
}
The BufferedImage stores the image as a matrix of pixels, but you do not need to know the details as you should only communicate with a BufferedImage through its interface. Some useful methods of a BufferedImage are:

The Picture class

A Picture needs to know the following: Its main constructor takes a filename as parameter, opens the file associated with this filename, and loads the image from it.

Implement the needed getters and setters to make your code easier (height(), width(), get(i,j), set(i,j,color)).

The first thing you'll do with the Picture is to write a show() method that displays the image on the screen. To display the image you first need to create the JFrame object that will hold the image, set its title and whatever other defaults you usually set for a frame, add a menubar and the image to it, and then call repaint() on the frame.

public void show() {

    frame = new JFrame(); 

   //set frame title, set it visible, etc


   //add a menubar on the frame with a single option: saving the image


   //add the image to the frame

   //pait the frame
   frame.repaint();
}
To render the image in the frame we'll do a trick: we'll create a JLabel from the image, and set the JLabel as the ContentPane of the frame. Something like this:
 ImageIcon icon = new ImageIcon(image);
 frame.setContentPane(new JLabel(icon));

Converting an image to grayscale

Next you'll write a method that converts an image to grayscale. More precisely, it creates a new image, that is the grayscale version of the Picture object upon which it is called. I imagine its signature will look something like this:
Picture grayscale()
So I could do something like this:
Picture p1, p2;
p1= new Picture("mypic.jpg");
p1.show(); 
p2 = p1.grayscale();
p2.show();
Since grayscale() needs to return a new Picture, this means that inside grayscale() you need to create an empty picture of the same size and set it with values. To do this you'll create another constructor for the Picture class that creates an empty image of specified size.
 public Picture(int w, int h) {
      //filename=null; 
      image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
 }
Once you have an empty image of the right size, you simply loop through it and set the pixels:
// convert to grayscale
for (int i = 0; i < width; i++) {
     for (int j = 0; j < height; j++) {
          Color color = pic.get(i, j);
          Color gray = toGray(color);
          pic.set(i, j, gray);
      }
}
Now the only thing left is how to convert a Color to a gray shade. You should know by now that an RGB Color is represented as a triplet of values in 0..255 that represent the values of R,G and B. (0,0,0) represents White, and (255, 255, 255) represents Black. Any gray shade has the same values for R,G and B, so teh triplet looks like this (x, x, x). To transform an (r,g,b) value to a gray value you need to map the triplet to a value x between 0 and 255. Think of x as a function/combination of r, g and b. You can read and search about this, but here is one way to do it:
 int r = color.getRed();
 int g = color.getGreen();
 int b = color.getBlue();
 //the monochrome luminance of this color 
 double l = .299*r + .587*g + .114*b;
 Color gray = new Color((int)l, (int)l, (int)l);
 
Test your function.

Scaling an image

Scaling an image is one of the most common image processing tasks. Let's say we have a 100x100 image, and we want to scale it down to a 50x50: we simply delete half the rows and half the columns; or, in other words, we sample the original image picking every other pixel. If we want to scale the image up t oa 200x200, then we need to replace each pixel by 2x2=4 pixels of the same color.

Now you want to generalize this to scaling from w-by-h to x-by-y, and you can do both scaling up and down with the same code. Hint: think of the (i,j) pixel in the target image; what pixel does it correspond to in the source image?

Write a method that takes a targer height and width and returns a new Picture that is the scaled version of the original one.

Picture scale(int th, int tw)

Morphing/fading two images

In this method you will write the code the transform one image into another one (of the same size) through a set of k steps.
void morph(Picture p1, Picture p2, int k)
or
void morph(Picture p2, int k)
This method does not return anything. When called with two picture arguments p1 and p2, it simply computes and shows a set of k intermediate pictures P0, P1, P2, ..., Pk that are a combination of p1 and p2.

The picture at the i-th step has each pixel (i,j) colored with a color c as a linear function of the color c1 of pixel (i,j) in p1, and c2, the color of pixel (i,j) in p2: c = f(c1, c2, i). For i=0, c is p1, and for i=k c is p2.

In order to avoid a large number of frames popping up on the screen, I suggest that you keep all intermediate pictures in the same variable, and you render it every time. In other words you do not create k picture objects, but you create only one and you modify it and show it k times.

Rotating an image

Write a method that takes an angle a as parameter and produces a Picture of the same size that is the original picture rotated by a around the center of the image.
//angle a is in degrees
Picture rotate(int a) 
To rotate, copy the color of a pixel (i,j) into the rotated position of (i,j), which we denote by (i', j'). Assume that i represents the x coordinate and j represents the y coordinate.

It can be shown using elementary geometry and trigonometry, that a rotation by a around the origin maps point (x,y) to point (x', y') as follows:

x' = x cos a - y sin a
y' = x sin a + y cos a
This is the rotation of a point around the origin. We want to rotate around the center of the image. Let (Xc, Yc) be the coordinates of the center of the picture. We first need to translate each point (i,j) by (-Xc, -Yc) so that we bring the center of the image to coordinates (0,0). Then we rotate using the formula, then we add (Xc, Yc) to the rotated coordinates (x',y').

A swirl filter

Creating a swirl filter is similar to rotation, except that the angle changes as a function of the distance to the center. Use the same formula as for rotation, but compute the angle a as a function of (i,j):
a = normalized distance of (i,j) to center * 180  
Experiment with other values than 180.

Putting everything together: the Interface

You need to allow the user to run all the methods. I envision a couple of ways you could do this:

Comments, Suggestions

Each of the functions on images, taken separately, is pretty straightforward. The main part of this lab is to put everything together and get it to work (in a week). Design your methods so that they are short and simple. No method should be longer than 20 lines of code.

As usual, email me the folder (include the images that you use), and bring a hard copy to class.


Last modified: Tue Nov 18 19:19:46 EST 2008