IVI Practical 1 - Flip and Rotate

In this practical you will write programs which read in a JPEG image and then flip or rotate it. There are two flips (vertical and horizontal) and three rotations (by 90°, 180° or -90°).

Flipping an Image

Task: write a program or programs to flip an image horizontally and vertically.

Original Horizontal flip Vertical flip

There are (at least) two ways you can go about flipping an image. It is possible to flip an image 'in place', that is to read in an image and manipulate its data without creating a second image. This can cause problems, however, since you are writing over information in the image, which can lead to unexpected results (see below). The simpler (but more expensive in terms of memory) approach is to create a new image the same size as the first, and then write the flipped information into it. Taking this approach the pseudocode for flipping an image horizontally is:

  Read imageIn from file

  imageOut = new image the same size as imageIn

  for x = 0 to imageIn.width {
    for y = 0 to imageIn.height {
       imageOut(width-x, y) = imageIn(x, y)
    }
  }

  Write imageOut to file

You will, however, need to be careful with the image bounds, since the image co-ordinates go from 0 to one less than the width or height. This is the most likely problem if you get an ArrayIndexOutofBoundsException. Also you can't just copy pixels from one place to another, you need to move the red, green, and blue values separately.

To flip an image in place, we need to swap the left and right sides of the image. The pseudocode for this approach is

  Read image from file

  for x = 0 to width/2 {
    for y = 0 to height {
      swap image(x,y) and image(width-x,y)
    }
  }

  Write image to file

Note that the variable x only goes up to half the width of the image. If you don't do this then you will swap the pairs twice and end up where you started! Another mistake that people sometimes make is to copy one half to the other rather than swapping them. If you do this you end up with results like

These two approaches to flipping illustrate an basic principle in image processing programs - it is usually easiest and safest to make a new image rather than storing the results of processing in the original. Writing the results of an image processing operation over the original is possible in some cases, and requires less memory, but it does destroy the original information. Since memory is usually plentiful these days, it is usually best to be safe and put the results of any image processing you do into a new image created specifically for that purpose.

Rotating an Image

Task: write a program or programs to rotate an image by 90°, 180° and -90° (clockwise).

Original Rotated -90°

It is not generally possible to rotate an image in place since the size of the image will change - a 200 wide by 300 high image becomes 300 wide and 200 high on rotation by 90°. There are three simple rotation possibilities (90°, 180° or -90°), a more general rotation (say by 23°) is a much harder task. Pseudocode for one of the rotations is

  Read imageIn from a file

  imageOut = new image imageIn.width high and imageIn.height wide

  for x = 0 to imageIn.width {
    for y = 0 to imageIn.height {
      imageOut(y,x) = imageIn(imageIn.width-x,y)
    }
  }

Note that as well as swapping the x- and y-axes, one axis is flipped (we use imageIn.width-x rather than x). What happens if you don't do the flip?

Using Java Methods

For most small programs (including most of the ones you will need to write in this module) it is easiest to put all the code in the main method of a Java class. As your programs grow, however, it is easier to divide the problem up into sub-problems which are methods (or functions) of their own. It is a good practice to do this even when it seems that the problem you are trying to solve seems easy to do in one go. One reason for this is that it makes it easier to reuse bits of code, since methods or functions should be fairly self-contained.

Another advantage of using methods is that they can be used to do tasks again and again without worrying how they work too much. As an example of this, it is possible to do all five things (flip horizontally, flip vertically, and rotate by 90°, 180° or -90°) covered in this exercise using just one flip (either one) and a rotate by 90°. Suppose we had two functions written - one looks like

static JPEGImage flipHorizontal(JPEGImage inputImage) {
  JPEGImage outputImage;

  // Code to make outputImage into an image that is a 
  // horizontal flip of inputImage

  return outputImage;
}

and the other like

static JPEGImage rotate90(JPEGImage inputImage) {
  JPEGImage outputImage;

  // Code to make outputImage into an image that is a 
  // 90° rotation of inputImage

  return outputImage;
}

Then we could write a new function to rotate by 180° easily by

static JPEGImage rotate180(JPEGImage inputImage) {
  return rotate90(rotate90(inputImage));
}

Task: Download the file RotateAndFlip.java, which contains skeletons for the flipHorizontal and rotate90 functions, as well as implementations of the other three options. Fill in the details of flipHorizontal and rotate90. The other three rotate and flip methods are defined in terms of these (can you see how flipVertical works?) When run, this program reads the name of one JPEG file from the command line, and writes flipped and rotated images to the files rotate90.jpg, rotate180.jpg, rotate270,.jpg flipHorizontal.jpg, and flipVertical.jpg. Note that these files will be overwritten if they already exist.

Looking through te code of the main method of RotateAndFlip.java you may notice that the error handling code for writing images is repeated five times. This is a bit wasteful, and if we decided to change the behaviour on an error (say to continue rather than exit the program) we'd have to change the code in five places. It might be better to move this out to its own method as well - write a method to replace all of these bits of code. It should take in two parameters - a JPEGImage and a String that is the filename to write to - and write the image to the file, or exit if an exception occurs.

Note: The methods above are declared static because they need to be called from main, which is not a member of any instance of a class. More generally it is a good idea to group similar methods together into classes, and not to have too many static methods. For example we could create a class that includes the flip and rotate methods as member methods, and call them from an instance of this class, something like:

...
RotaterAndFlipper spinner = new RotaterAndFlipper();
JPEGImage rotatedImage = spinner.rotate90(originalImage);
...