Module 2: Supplemental Material
More image transformations
Let's look at some code that does image rotation:
- Rotation is useful both for images as well as for geometric objects.
- We will, in effect, rotate a 2D array.
- The example shows a 90-degree rotation to the right, accomplished
by moving the pixels around appropriately.
- For a right rotation, the top row becomes the last column, the
second row becomes the second-last column etc.
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:
- This is the familiar Word-Search game in which
we are given a 2D array of characters, e.g,.
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
and a word such as "ruby" which we have to find inside the array:
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
- In this case, we will only consider horizontal left-to-right
occurences.
- Actual wordsearch games, of course, allow all eight directions, e.g.
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
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:
- The underlying algorithm is:
1. for each possible start location [i,j] of a word in the array
2. Walk along the array horizontally and check if word occurs;
3. if it occurs
4. return location;
5. endif
6. endfor
7. return "not found";
- We have to be careful to check that we don't go past the limits of
the 2D array:
for (int k=0; k < letters.length; k++) {
if ( (j+k >= puzzle[i].length) || ( ... ) ) {
// ... Not a match ...
}
}
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:
- When a mismatch occurs in the innermost for-loop, we break out of it:
for (int k=0; k < letters.length; k++) {
if ( ... ) {
// Not a match.
found = false;
break;
}
}
- Then, if we find the word, we return from the method
from inside the outer for-loops:
// 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 we went the whole length of the word, we found it.
if (found) {
return "String " + word + " found in row=" + i + " col=" +j;
}
}
}
- Suppose we wanted to print the results in the method itself.
⇒
This means we can't return inside the loop.
⇒
Need to keep track of whether the loop is completed.
- This "tracking" is a little more complicated to write.
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:
- We used a new boolean variable over to keep
track of whether to keep going in the outer-for-loops.
⇒
Set over = true when the word is found.
- Since the results are printed outside, we need found
to be declared outside the double-for-loop.
- Notice that the innermost for-loop uses the current
value of found to determine whether to continue.
- The code is a little more difficult to read. In general,
the earlier "return" version is preferred.
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:
- Recall, we can't write a method to return two integers.
- The best way to return multiple items is to return an
instance of a class that contains those items.
- To do this, we'll define the class
class Coord {
int i, j;
}
- This way, the method findWord will be written to be
used as follows:
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);
}
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:
- An instance of Point has space for two integers:
Point p = new Point ();
p.x = 5;
p.y = 6;
- We'll need to import from java.awt to use this class.
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?
- A matrix is a 2D array of real numbers, e.g,
- For example, this one has 2 rows and 3 columns:
1.0 2.45 2.1
0.76 1.3 7.72
And this one has 5 rows and 2 columns
1 2
4 5
7 8
10 11
13 14
- It is convenient to use symbols such as
| 1 2 | | 3 0 |
| 4 5 | | 1 1 |
A = | 7 8 | B = | 0 2.2 |
| 10 11 | | 0 1 |
| 13 14 | | 0.1 0 |
- If a matrix A has m rows and n
columns, we say it is an m x n matrix.
- Two matrices, A and B can be added or
subtracted if A and B have the same number
of rows and columns:
⇒ addition/subtraction is done term-by-term:
| 1 2 | | 3 0 | | 4 2 |
| 4 5 | | 1 1 | | 5 6 |
A + B = | 7 8 | + | 0 2.2 | = | 7 10.2 |
| 10 11 | | 0 1 | | 10 12 |
| 13 14 | | 0.1 0 | | 13.1 14 |
- Multiplication is a little more complicated:
- If A is m x n, we need B to be
n x m to perform the multiplication A * B.
- The product is m x m.
- To compute element i,j (i-th row, j-th
column) in the product, you multiply the i-th row of
A with the j-th row of B:
- How do you multiply a row by a column? Answer: Term--by-term.
- For example:
- Matrices are extremely useful in scientific and engineering
computations.
- Let's write some simple code to perform matrix addition and multiplication.
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)