Module 2: Supplemental Material


More image transformations

Let's look at some code that does image rotation:

Here's the program:
import java.awt.*;
import java.awt.image.*;

public class ImageRotationReflection {

    static ImageTool imTool = new ImageTool ();

    public static void main (String[] argv)
    {
        Image image = imTool.readImageFile ("device.jpg");
        imTool.showImage (image, "original");
        Image rotatedImage = rotateRight (image);
    }
    
    static Image rotateRight (Image image)
    {
	// Extract pixels and dimensions.
        int[][][] pixels = imTool.imageToPixels (image);
        int numRows = pixels.length;
        int numCols = pixels[0].length;

	// Storage for new image.
        int[][][] rotatedPixels = new int [numCols][numRows][4];

        for (int i=0; i < numRows; i++) {
            for (int j=0; j < numCols; j++) {
                // Copy from pixels[i][j] to rotatedPixels[j][end-i]
		for (int k=0; k < 4; k++) {
		    rotatedPixels[j][numRows-1-i][k] = pixels[i][j][k];
		}
            }
        }
        
        Image rotatedImage = imTool.pixelsToImage (rotatedPixels);
        return rotatedImage;
    }

}

Exercise 1: The line in the innermost loop

		    rotatedPixels[j][numRows-1-i][k] = pixels[i][j][k];
  
is what makes it happen. Think through this and explain why it works. Download the above program, along with device.jpg and execute. Can you identify the device in the image?

Exercise 2: Reflection about the diagonal is similar to rotation except that the first row becomes the first column, the second row becomes the second column etc. How would you modify the above code to implement reflection? Download ImageReflection.java and implement your code for reflection.


2D array of chars

Let's look at a simple example with chars:

Here's the program:
public class WordSearch {

    public static void main (String[] argv)
    {
        char[][] puzzle = {
            {'n', 'o', 'h', 't', 'y', 'p', 's'},
            {'m', 'i', 'a', 'r', 'y', 'c', 'c'},
            {'l', 'l', 'e', 'k', 's', 'a', 'h'},
            {'r', 'u', 'b', 'y', 'v', 'm', 'e'},
            {'e', 'h', 'h', 'a', 'l', 'l', 'm'},
            {'p', 'c', 'j', 'n', 'i', 'c', 'e'},
            {'r', 'e', 'e', 'k', 'b', 'i', 'p'}
        };

        String result = findWord (puzzle, "ruby");
	System.out.println (result);
    }


    static String findWord (char[][] puzzle, String word)
    {
	// First convert the String into a char array.
	char[] letters = word.toCharArray ();

	// Now try every possible starting point in the puzzle array.
	for (int i=0; i < puzzle.length; i++) {
	    for (int j=0; j < puzzle[i].length; j++) {

		// Use (i,j) as the starting point.
		boolean found = true;

		// Try to find the given word's letters.
		for (int k=0; k < letters.length; k++) {
		    if ( (j+k >= puzzle[i].length) || (letters[k] != puzzle[i][j+k]) ) {
			// Not a match.
			found = false;
			break;
		    }
		}

		// If we went the whole length of the word, we found it.
		if (found) {
		    return "String " + word + " found in row=" + i + " col=" +j;
		}

	    }
	}

	return "String " + word + " not found";
    }

}
Note:

Exercise 3: Clearly, ruby and java are two programming languages in the puzzle. There are seven more - can you see them?

There are two points of control flow worth examining in the above program:

Here's how we could do it:
public class WordSearch2 {

    public static void main (String[] argv)
    {
        char[][] puzzle = {
            {'n', 'o', 'h', 't', 'y', 'p', 's'},
            {'m', 'i', 'a', 'r', 'y', 'c', 'c'},
            {'l', 'l', 'e', 'k', 's', 'a', 'h'},
            {'r', 'u', 'b', 'y', 'v', 'm', 'e'},
            {'e', 'h', 'h', 'a', 'l', 'l', 'm'},
            {'p', 'c', 'j', 'n', 'i', 'c', 'e'},
            {'r', 'e', 'e', 'k', 'b', 'i', 'p'}
        };

	// Now findWord() doesn't return anything.
        findWord (puzzle, "ruby");
    }


    static void findWord (char[][] puzzle, String word)
    {
	char[] letters = word.toCharArray ();

	// We'll need two "flag" variables.
	boolean over = false;
	boolean found = false;

	// Note the second condition in each for-loop:
	for (int i=0; (i < puzzle.length) && (!over); i++) {
	    for (int j=0; (j < puzzle[i].length) && (!over); j++) {

		// Use (i,j) as the starting point.
		found = true;

		// Note the additional condition:
		for (int k=0; (k < letters.length) && (found); k++) {
		    if ( (j+k >= puzzle[i].length) || (letters[k] != puzzle[i][j+k]) ) {
			// Not a match.
			found = false;
		    }
		}

		// If we went the whole length of the word, we found it.
		if (found) {
		    System.out.println ("String " + word + " found in row=" + i + " col=" +j);
		    // Mark the end of the double-for here.
		    over = true;
		}

	    }
	}

	// We print outside the double-for.
	if (! found) {
	    System.out.println ("String " + word + " not found");
	}
    }

}
Note:

It is possible, with a little thinking, to use a single flag for both purposes:

    static void findWord (char[][] puzzle, String word)
    {
	char[] letters = word.toCharArray ();

	// Use a single flag variable.
	boolean found = false;

	for (int i=0; (i < puzzle.length) && (!found); i++) {
	    for (int j=0; (j < puzzle[i].length) && (!found); j++) {

		// Use (i,j) as the starting point.
		found = true;

		// Try to find the given word's letters.
		for (int k=0; (k < letters.length) && (found); k++) {
		    if ( (j+k >= puzzle[i].length) || (letters[k] != puzzle[i][j+k]) ) {
			// Not a match.
			found = false;
		    }
		}

		// If we went the whole length of the word, we found it.
		if (found) {
		    System.out.println ("String " + word + " found in row=" + i + " col=" +j);
		}

	    }
	}

	if (! found) {
	    System.out.println ("String " + word + " not found");
	}
    }

Sometimes it's hard to read code with complicated for-loop conditions. An alternative is to use an additional if-statement:

    static void findWord (char[][] puzzle, String word)
    {
	char[] letters = word.toCharArray ();

	boolean found = false;

	for (int i=0; i < puzzle.length; i++) {
	    for (int j=0; j < puzzle[i].length; j++) {
		if (! found) {
		
		    // ... same as before ...

		} // end-if
	    }
	}

	if (! found) {
	    System.out.println ("String " + word + " not found");
	}
    }

For comparison, let's look at how to implement the outer loops using while:

    static void findWord (char[][] puzzle, String word)
    {
	char[] letters = word.toCharArray ();

	boolean found = false;

	int i = 0;
	while ( (i < puzzle.length) && (! found) ) {
	    int j = 0;
	    while ( (j < puzzle[i].length) && (! found) ) {

	        // ... same as before ...
		
		// Must increment j
		j ++;
	    }
	    
	    // Must rememeber to increment i.
	    i ++;
	}

	if (! found) {
	    System.out.println ("String " + word + " not found");
	}
    }
The while-loops are a little more difficult to read, and one must be careful to increment (otherwise, we'd have an infinite loop).

Next, suppose we had to write the method findWord to return the actual location where the word is found:

Here's the program:
class Coord {
    int i, j;
}

public class WordSearch6 {

    public static void main (String[] argv)
    {
        char[][] puzzle = {
            {'n', 'o', 'h', 't', 'y', 'p', 's'},
            {'m', 'i', 'a', 'r', 'y', 'c', 'c'},
            {'l', 'l', 'e', 'k', 's', 'a', 'h'},
            {'r', 'u', 'b', 'y', 'v', 'm', 'e'},
            {'e', 'h', 'h', 'a', 'l', 'l', 'm'},
            {'p', 'c', 'j', 'n', 'i', 'c', 'e'},
            {'r', 'e', 'e', 'k', 'b', 'i', 'p'}
        };

        Coord c = findWord (puzzle, "ruby");
	if (c == null) {
	    System.out.println ("Not found");
	}
	else {
	    System.out.println ("Found in row " + c.i + " column " + c.j);
	}
    }


    static Coord findWord (char[][] puzzle, String word)
    {
	// First convert the String into a char array.
	char[] letters = word.toCharArray ();

	// Now try every possible starting point in the puzzle array.
	for (int i=0; i < puzzle.length; i++) {
	    for (int j=0; j < puzzle[i].length; j++) {

		// Use (i,j) as the starting point.
		boolean found = true;

		// Try to find the given word's letters.
		for (int k=0; k < letters.length; k++) {
		    if ( (j+k >= puzzle[i].length) || (letters[k] != puzzle[i][j+k]) ) {
			// Not a match.
			found = false;
			break;
		    }
		}

		// If we went the whole length of the word, we found it.
		if (found) {
		    // Make an instance of the Coord class, set it's values and return it.
		    Coord c = new Coord ();
		    c.i = i;
		    c.j = j;
		    return c;
		}

	    }
	}

	return null;
    }

}

As an alternative to creating our own class, we could use the Point class in Java's library:

Then, findWord would be used like this:
import java.awt.*;

public class WordSearch7 {

    public static void main (String[] argv)
    {
        // ... puzzle array ...

        Point p = findWord (puzzle, "ruby");
	if (p == null) {
	    System.out.println ("Not found");
	}
	else {
	    System.out.println ("Found in row " + p.x + " column " + p.y);
	}
    }


    static Point findWord (char[][] puzzle, String word)
    {
        // ... similar to WordSearch6.java, except for using Point 
        // instead of Coord ...
    }

}


A matrix: 2D array of double's

What is a matrix?

Here's the program:
public class Matrices {

    public static void main (String[] argv)
    {
        // Make two samples matrices.
	double[][] A = {
	    {1, 3, 5},
	    {7, 9, 11},
	    {13, 15, 17},
        };

	double[][] B = {
	    {1, 0, 0},
	    {0, 1, 0},
	    {0, 0, 1},
	};

        // Addition.
	double[][] C = add (A, B);
	System.out.println ("Sum C = A + B: ");
	print (C);

        // Multiplication.
	double[][] D = multiply (A, B);
	System.out.println ("Product D = A * B: ");
	print (D);

    }


    static void print (double[][] X)
    {
	for (int i=0; i < X.length; i++) {
	    for (int j=0; j < X[i].length; j++) {
		System.out.printf (" %4.1f", X[i][j]);
	    }
	    System.out.println ();
	}
    }


    static double[][] add (double[][] X, double[][] Y) 
    {
        // See if the matrices are compatible.
	if ( (X.length != Y.length) || (X[0].length != Y[0].length) ) {
	    System.out.println ("Incompatible matrices");
	    System.exit(0);
	}

        // Create space for the result.
	double[][] Z = new double [X.length][X[0].length];

        // Simple element-by-element addition.
	for (int i=0; i < X.length; i++) {
	    for (int j=0; j < X[i].length; j++) {
		Z[i][j] = X[i][j] + Y[i][j];
	    }
	}

	return Z;
    }


    static double[][] multiply (double[][] X, double[][] Y) 
    {
        // Check that we can multiply.
	if ( (X.length != Y[0].length) && (X[0].length != Y.length) ) {
	    System.out.println ("Incompatible matrices");
	    System.exit(0);
	}

        // Space for result.
	double[][] Z = new double [X.length][Y[0].length];

        // For (i,j)-th element of Z, multiply i-row of X by j-th row of Y.
	for (int i=0; i < Z.length; i++) {
	    for (int j=0; j < Z[i].length; j++) {

                // This is the row x col result.
		double sum = 0;
		for (int k=0; k < X[0].length; k++) {
		    sum += X[i][k] * Y[k][j];
		}

		Z[i][j] = sum;
	    }
	}

	return Z;
    }

}

Exercise 3: Just as we define powers of a single number such as 23 = 8, we can define powers of a matrix A:

  • First, A needs to be a square matrix. (Why?)
  • The zero-eth power of a matrix A is the identity matrix, consisting of all zeroes, except for 1's along the diagonal. For example, this is an identity matrix of size 3:
                |  1  0  0  |
          I  =  |  0  1  0  |
                |  0  0  1  |
        
  • The square is the product of A with itself: A * A.
  • You get the n-th power by successively multiplying A by itself, n times.
Download Matrices2.java and implement the method power() to raise a matrix to the n-th power.


2D array of Integer's

We'll examine how to work with 2D arrays of Integer's using a simple application:

  • The goal is to compute a 2D histogram.

  • First, let's look at a 1D histogram:
    • Suppose I have 10 data points in the range 0-4:
               0.4, 0.6, 1.1, 1.3, 1.4, 1.5, 1.6, 2.2, 3.1, 3.3
             
    • Let's say I want a histogram of size 4 that counts how many points lie in the interval [0,1], how many points in [1,2] etc.
    • The intervals are:
                first interval: [0, 1]
                second:         [1, 2]
                third:          [2, 3]
                fourth:         [3, 4]
             
    • Clearly, the histogram we desire:
               histogram[0] = 2;   // 2 points lie in [0, 1]
               histogram[1] = 5;   // 5 points lie in [1, 2]
               histogram[2] = 1;   // 1 point lies in [2, 3]
               histogram[3] = 2;   // 2 points lie in [3, 4]
             

  • In a 2D histogram, the data are points generated in the X-Y plane. For example:

  • A 2D histogram divides the plane into cells, and counts how many points lie in each cell:

  • A histogram provides an approximate view of how points are distributed.

  • We'll write code to compute a histogram below.
Here's the program
import java.util.*;
import java.awt.*;

// A class to hold an (x,y) pair where both are double's.
class Pointd {
    double x, y;
}


public class Histogram2D {

    public static void main (String[] argv)
    {
        // Number of points to generate.
	int numSamples = 1000;
	Pointd[] points = new Pointd [1000];

        // Generate random points from a Gaussian distribution
        // where both x and y are in the range [0,4].
	for (int n=0; n < numSamples; n++) {
	    points[n] = nextGaussian ();
	}
        
        // Compute and print the histogram.
	Integer[][] gaussianHist = makeHistogram (points);
	print (gaussianHist);

        // Do the same for points generated from a uniform distribution.
	for (int n=0; n < numSamples; n++) {
	    points[n] = nextUniform ();
	}
	Integer[][] uniform = makeHistogram (points);
	print (uniform);
    }


    static void print (Integer[][] H)
    {
	System.out.println ("Histogram ");
	for (int i=0; i < H.length; i++) {
	    for (int j=0; j < H[i].length; j++) {
                // Note: use of printf for formatting.
		System.out.printf (" %3d", H[i][j]);
	    }
	    System.out.println ();
	}
    }

    
    static Integer[][] makeHistogram (Pointd[] points)
    {
        // Size of histogram.
	int size = 10;

        // Make the space for it, using instances of java.lang.Integer.
	Integer[][] hist = new Integer [size][size];

        // Initialize: must create an instance.
	for (int i=0; i < size; i++) {
	    for (int j=0; j < size; j++) {
		hist[i][j] = new Integer (0);
	    }
	}

        // Size of each cell.
	double interval = 4.0 / size;

        // Now count.
	for (int n=0; n < points.length; n++) {
	    int i = (int) (points[n].x / interval);
	    int j = (int) (points[n].y / interval);

            // Note: increment operator works for Integer's.
	    hist[i][j] ++;
	}

	return hist;
    }


    // Class Random is in java.util. Needed below
    static Random rand = new Random ();

    static Pointd nextGaussian ()
    {
	// ... We don't need to understand how this works... 
    }

    static Pointd nextUniform ()
    {
	// ... We don't need to understand how this works... 
    }

}
Note:
  • Our histogram is now a 2D array of Integer's instead of int's (for the sake of illustration).

  • Unlike int's where
           int[][] hist = new int [size][size];
         
    creates the space needed, we need one more step with objects like Integer's:
            // This creates a 2D array of pointers, each initialized to null.
    	Integer[][] hist = new Integer [size][size];
    
            // Each such pointer must be made to point to an Integer instance.
    	for (int i=0; i < size; i++) {
    	    for (int j=0; j < size; j++) {
    		hist[i][j] = new Integer (0);
    	    }
    	}
         
    Here:
    • Object variables are really pointers.
    • Thus the array creation
      	Integer[][] hist = new Integer [size][size];
              
      merely creates space for size*size pointers.
    • We must actually create the object instances ourselves, in the for-loop.

  • About the Integer class:
    • This is a special class in the Java library with many useful methods related to integers.
    • In most places, you can use an Integer instance interchangeably with an int.
    • Above, we saw that the increment operator can be used with an Integer instance:
      	    hist[i][j] ++;
             

Finally, since we've discussed Integer's, let's look at an example that shows how Java lets you mix Integer's and int's:

public class IntegerExamples {

    public static void main (String[] argv)
    {
        // Instance of Integer:
        Integer I = new Integer (5);

        // Ordinary int:
        int i = 6;
        
        // Integer + int, with result stored in an int.
        int n = I + i;

        // The same with the result stored in an Integer.
        Integer N = I + i;

        // Comparison operator across types.
        if (N == n) {
            System.out.println ("N=" + N + "  n=" + n);
        }

        // See how the add method is declared.
        Integer S = add (i, I);
        System.out.println (I + "+" + i + "=" + S);
    }

    static int add (int a, int b)
    {
        return (a + b);
    }
    
}



© 2006-2020, Rahul Simha & James Taylor (revised 2020)