A magic square of size N is an arrangement of the numbers 1,2, ..., N2 in a square array so that the row sums and column sums are all identical. For example, you can see that the rows and columns of this 3 x 3 square add up to 15:
|
|
|
|
|
|
|
|
|
About magic squares:
For odd squares, there is a simple algorithm called the Staircase Algorithm: (source file)
public class MagicSquare { public static void main (String[] argv) { // Some test cases. int[][] square = generateSquare (3); print (square); square = generateSquare (5); print (square); } // Current row/column and next row/column. static int row, col; static int nextRow, nextCol; static int[][] generateSquare (int size) { // This algorithm only works for odd-sizes. if (size % 2 != 1) { System.out.println ("size must be odd"); System.exit (0); } int[][] A = new int [size][size]; // Start with middle in top row. row = 0; col = size/2; A[row][col] = 1; for (int n=2; n<=size*size; n++) { // Go up diagonally to the right. computeNext (size); if (A[nextRow][nextCol] == 0) { // Place next number here if unoccupied. A[nextRow][nextCol] = n; } else { // Else, place directly below current number. nextRow = row + 1; nextCol = col; A[nextRow][nextCol] = n; } // Update. row = nextRow; col = nextCol; } //end-for return A; } static void computeNext (int size) { if (row == 0) { // If we're at the top, next row wraps around. nextRow = size - 1; } else { // Otherwise, go to previous row. nextRow = row - 1; } if (col == size-1) { // If we're at the rightmost col, wrap around to leftmost. nextCol = 0; } else { // Otherwise, next column is the one to the right. nextCol = col + 1; } } static void print (int[][] A) { System.out.println ("Square: "); for (int i=0; i < A.length; i++) { for (int j=0; j < A[i].length; j++) { System.out.printf ("%3d", A[i][j]); } } } }Note:
Square of size 3: 8 1 6 3 5 7 4 9 2 Square of size 5: 17 24 1 8 15 23 5 7 14 16 4 6 13 20 22 10 12 19 21 3 11 18 25 2 9
Let's look at a simpler example (source file)
public class MagicSquare2 { public static void main (String[] argv) { // Some test cases. int[][] square = { {8, 1, 6}, {3, 5, 7}, {4, 9, 2} }; print (square); } static void print (int[][] A) { // ... same as in MagicSquare.java ... } }Note:
int[][] square = { {8, 1, 6}, {3, 5, 7}, {4, 9, 2} };Here, the rows are enclosed in braces, and separated by commas.
Now let's consider a variation (source file)
public class MagicSquare3 { public static void main (String[] argv) { // Create space for the array, but not assign individual values: int[][] square = new int [3][3]; assignValues (square); print (square); } static void assignValues (int[][] A) { // ... the Staircase algorithm ... } static void print (int[][] A) { // ... same as before ... } }Note:
int[][] square = new int [3][3];
int[][] square; square = new int [3][3];
// This is the main pointer. int[][] square; // This creates the (unidimensional) array of row-pointers. square = new int [3][]; // For each row pointer, we create a row of three int's for (int i=0; i < 3; i++) { square[i] = new int [3]; }
Because arrays are implemented via pointers, we can create irregular arrays. (source file)
public class Triangle { public static void main (String[] argv) { // Different-length rows. int[][] A = { {1}, {2, 1}, {3, 2, 1}, {4, 3, 2, 1}, {5, 4, 3, 2, 1} }; print (A); } static void print (int[][] A) { // The number of rows is still A.length. for (int i=0; i < A.length; i++) { System.out.print ("Row i=" + i + " has " + A[i].length + " elements: "); // Note: we must use A[i].length, since they are all different. for (int j=0; j < A[i].length; j++) { System.out.print (" " + A[i][j]); } System.out.println (); } } }Note:
What is an image?
Image file formats:
Images in Java:
Useful methods in ImageTool:
ImageTool imTool = new ImageTool ();
Image image = imTool.readImageFile ("mymugshot.jpg");
int[][][] pixels = im.imageToPixels (image);
Image image2 = imTool.pixelsToImage (pixels);
imTool.writeToJPGFile (image, "doctoredMugshot.jpg");
imTool.showImage (image);
Notice that the pixels are a 3D array:
int[][][] pixels = imTool.imageToPixels (image);
int size = 500; pixels = new int [size][size][4]; for (int i=0; i < pixels.length; i++) { for (int j=0; j < pixels[0].length; j++) { for (int k=0; k < 4; k++) { pixels[i][j][k] = 255; } } }
int start = size/4; int end = (int) (3.0*size/4.0); for (int i=start; i <= end; i++) { for (int j=start; j <= end; j++) { for (int k=1; k < 4; k++) { pixels[i][j][k] = 0; } } }
ImageTool imTool = new ImageTool (); Image image = imTool.pixelsToImage (pixels); imTool.showImage (image);
Now let's manipulate some image pixels, starting with a "Thumbnail" application:
import java.awt.*; import java.awt.image.*; public class ImageShrinker { // We'll declare this up here so that it can be shared // across methods. static ImageTool imTool = new ImageTool (); public static void main (String[] argv) { // Read in an image and display it. Image image = imTool.readImageFile ("bugs.jpg"); imTool.showImage (image, "original"); // All the work of shrinking is done in the shrink() method. // We'll shrink to 20% of the current size. Image shrunkImage = shrink (image, 0.2); // Now display the result. imTool.showImage (shrunkImage, "shrunk"); } static Image shrink (Image image, double factor) { // Extract the pixels. int[][][] pixels = imTool.imageToPixels (image); // The array dimensions. int numRows = pixels.length; int numCols = pixels[0].length; // The resulting shrunk image will have fewer rows and columns. int numTargetRows = (int) (factor * numRows); int numTargetCols = (int) (factor * numCols); // What we'll do is skip pixels in the original. This is the // skip amount: int multiplier = numRows / numTargetRows; // Make a pixel array to hold the shrunk image. int[][][] shrunkPixels = new int [numTargetRows][numTargetCols][4]; // Now fill the pixel values in the new image. for (int i=0; i < numTargetRows; i++) { for (int j=0; j < numTargetCols; j++) { // For each pixel in the new image, we'll get // one of the original pixels as determined by the "skip". int originalRow = i * multiplier; int originalCol = j * multiplier; // Pay attention to this pointer copy: shrunkPixels[i][j] = pixels[originalRow][originalCol]; } } // Now convert pixels into an instance of Image and return that. Image shrunkImage = imTool.pixelsToImage (shrunkPixels); return shrunkImage; } }Note:
shrunkPixels[i][j] = pixels[originalRow][originalCol];
shrunkPixels[i][j][k] = something ...
shrunkPixels[i][j][0] = pixels[originalRow][originalCol][0]; shrunkPixels[i][j][1] = pixels[originalRow][originalCol][1]; shrunkPixels[i][j][2] = pixels[originalRow][originalCol][2]; shrunkPixels[i][j][3] = pixels[originalRow][originalCol][3];
for (int k=0; k < 4; k++) { shrunkPixels[i][j][k] = pixels[originalRow][originalCol][k]; }
In general, pointer copies are efficient but require care. They should not be used if the original or copy will be modified, as this example shows: (source file)
public class PointerCopyProblem { public static void main (String[] argv) { // Make a 3 x 2 array. int[][] A = { {1, 2}, {3, 4}, {5, 6} }; // Now make a new array B that is to be a copy of A. // To B, we assign space for 3 pointers: int[][] B = new int [3][]; for (int i=0; i < A.length; i++) { B[i] = A[i]; } print (A, B); // Make a change in B. B[0][0] = 9; print (A, B); } static void print (int[][] A, int[][] B) { System.out.println ("\nArray A: "); for (int i=0; i < A.length; i++) { for (int j=0; j < A[i].length; j++) { System.out.print (" " + A[i][j]); } System.out.println (); } System.out.println ("Array B: "); for (int i=0; i < B.length; i++) { for (int j=0; j < B[i].length; j++) { System.out.print (" " + B[i][j]); } System.out.println (); } } }The output is:
Array A: 1 2 3 4 5 6 Array B: 1 2 3 4 5 6 Array A: 9 2 3 4 5 6 Array B: 9 2 3 4 5 6Thus, array A is modified.
Let's continue image transformations with enlargement: take an image and make it larger: (source file)
import java.awt.*; import java.awt.image.*; public class ImageEnlarger { static ImageTool imTool = new ImageTool (); public static void main (String[] argv) { Image image = imTool.readImageFile ("bugsThumb.jpg"); imTool.showImage (image, "original"); Image enlargedImage = enlarge (image, 5); imTool.showImage (enlargedImage, "enlarged"); } static Image enlarge (Image image, double factor) { // Extract the pixels and array dimensions. int[][][] pixels = imTool.imageToPixels (image); int numRows = pixels.length; int numCols = pixels[0].length; // The resulting shrunk image will have more rows and columns. int numTargetRows = (int) (factor * numRows); int numTargetCols = (int) (factor * numCols); int multiplier = numTargetRows / numRows; // Make the pixel array for the target. int[][][] enlargedPixels = new int [numTargetRows][numTargetCols][4]; for (int i=0; i < numRows; i++) { for (int j=0; j < numCols; j++) { // Copy a single pixel from the smaller into a region of the larger. int startRow = i * multiplier; int startCol = j * multiplier; for (int m=0; m < multiplier; m++) { for (int n=0; n < multiplier; n++) { // This time, we'll do the copy correctly. for (int k=0; k < 3; k++) { enlargedPixels[startRow+m][startCol+n][k] = pixels[i][j][k]; } } } // End-copy-region } } Image enlargedImage = imTool.pixelsToImage (enlargedPixels); return enlargedImage; } }Note:
Next, let's consider a linear map:
import java.awt.*; import java.awt.image.*; public class LinearMap { static ImageTool imTool = new ImageTool (); public static void main (String[] argv) { // Read in an image and display. Image image = imTool.readImageFile ("prof.jpg"); imTool.showImage (image, "original"); // Convert to grey scale and display. Image mappedImage = linearMap (image, 2.0, 1.0); imTool.showImage (mappedImage, "mapped image"); } static Image linearMap (Image image, double a, double b) { // Extract pixels and size. int[][][] pixels = imTool.imageToPixels (image); int numRows = pixels.length; int numCols = pixels[0].length; // Make the new pixels. int[][][] mappedPixels = new int [numRows][numCols][4]; // Almost like a copy. for (int i=0; i < numRows; i++) { for (int j=0; j < numCols; j++) { mappedPixels[i][j][0] = pixels[i][j][0]; // Multiply by "a" and add "b". mappedPixels[i][j][1] = enforcePixelBounds (a * pixels[i][j][1] + b); mappedPixels[i][j][2] = enforcePixelBounds (a * pixels[i][j][2] + b); mappedPixels[i][j][3] = enforcePixelBounds (a * pixels[i][j][3] + b); } } // Returned transformed image. Image mappedImage = imTool.pixelsToImage (mappedPixels); return mappedImage; } // Each pixel value needs to be between 0 and 255. static int enforcePixelBounds (double pixelValue) { int value = (int) pixelValue; if (value < 0) { return 0; } if (value > 255) { return 255; } return value; } }
Recall that Java only supports pass-by-value parameters, as this example shows: (source file)
public class Parameters { public static void main (String[] argv) { int a = 1; int b = 2; System.out.println ("BEFORE: a=" + a + " b=" + b); swap (a, b); System.out.println ("AFTER: a=" + a + " b=" + b); } static void swap (int x, int y) { System.out.println (" BEFORE: x=" + x + " y=" + y); int temp = x; x = y; y = temp; System.out.println (" AFTER: x=" + x + " y=" + y); } }Note:
BEFORE: a=1 b=2 BEFORE: x=1 y=2 AFTER: x=2 y=1 AFTER: a=1 b=2
Now let's consider the same question for arrays, with this example: (source file)
import java.util.*; public class Parameters2 { public static void main (String[] argv) { int[] A = {1, 2}; int[] B = {3, 4}; System.out.println ("BEFORE: A=" + Arrays.toString(A) + " B=" + Arrays.toString(B)); swap (A, B); System.out.println ("AFTER: A=" + Arrays.toString(A) + " B=" + Arrays.toString(B)); } static void swap (int[] X, int[] Y) { System.out.println (" BEFORE: X=" + Arrays.toString(X) + " Y=" + Arrays.toString(Y)); int[] temp = X; X = Y; Y = temp; System.out.println (" AFTER: X=" + Arrays.toString(X) + " Y=" + Arrays.toString(Y)); } }
Next, consider this variation: (source file)
public class Parameters3 { public static void main (String[] argv) { int[] A = {1, 2}; int[] B = {3, 4}; System.out.println ("BEFORE: A=" + Arrays.toString(A) + " B=" + Arrays.toString(B)); swap (A, B); System.out.println ("AFTER: A=" + Arrays.toString(A) + " B=" + Arrays.toString(B)); } static void swap (int[] X, int[] Y) { System.out.println (" BEFORE: X=" + Arrays.toString(X) + " Y=" + Arrays.toString(Y)); for (int i=0; i < X.length; i++) { int temp = X[i]; X[i] = Y[i]; Y[i] = temp; } System.out.println (" AFTER: X=" + Arrays.toString(X) + " Y=" + Arrays.toString(Y)); } }
The wrong way to copy an array: (source file)
import java.util.*; public class ArrayAssignment { public static void maiyn (String[] argv) { int[] A = {1, 2, 3}; System.out.println ("A: " + Arrays.toString(A)); // Declare a new array variable. int[] B; // Pointer copy! B = A; // A modification of B affects A B[1] = 5; System.out.println ("A: " + Arrays.toString(A)); } }
The right way: (source file)
import java.util.*; public class ArrayAssignment2 { public static void main (String[] argv) { int[] A = {1, 2, 3}; System.out.println ("A: " + Arrays.toString(A)); int[] B; // First make the space - getting the length from A. B = new int [A.length]; // Copy each entry. for (int i=0; i < A.length; i++) { B[i] = A[i]; } // A modification of B does not affect A. B[1] = 5; System.out.println ("A: " + Arrays.toString(A)); } }