Teaching Two-Dimensional Array Concepts in Java

Teaching Two-Dimensional Array Concepts in Java With Image Processing Examples

Kevin R. Burger Dept. of Mathematics, Computer Science, and Physics

Rockhurst University, Kansas City, MO 64110, USA kevin.burger@rockhurst.edu

Abstract

Two-dimensional arrays (2d-arrays) are fundamental data structures in many software programs and must be mastered by beginning programming students. Teachers of introductory programming are challenged to devise new and interesting exercises for teaching 2d-array concepts. We believe image processing (IP) examples can provide stimulating, challenging, and fun exercises for students. We describe briefly the new Java2 Image I/O API and show how it can be used to read and write images in GIF, JPEG, and PNG formats. We also provide Java code for an Image class that hides the complexity of this API from the student. We conclude with several sample student exercises that can be used to teach 2d-array programming.

Categories and Subject Descriptors

D.3.2 [Language Classifications]: Java, E.1 [Data Structures]: Arrays, I.4 [Image Processing and Computer Vision]: Miscellaneous, K.3 [Computers and Education]: Computer and Information Science Education--Computer Science Education.

General Terms

Algorithms, Languages.

Keywords

CS1, CS2, Image Processing, Java, Java Image I/O API, Pedagogy, Two-Dimensional Arrays, Teaching Exercises.

1 Introduction

Two-dimensional arrays (2d-arrays) are fundamental data structures in many software programs and are directly supported by most programming languages, including the C, C++, and Java languages. Consequently, students learning to write computer programs must master both the conceptual framework of 2darrays and the syntactic and semantic details of their implementation in a particular programming language. For the teacher of beginning programming courses, this means he or she must develop examples to teach both the concepts and their use in the selected programming language.

_______________

Permission to make digital or hard copies of all or part of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and the full citation on the first page. To copy otherwise, or republish, to post on servers or to redistribute to lists, requires prior specific permission and/or a fee. SIGCSE'03, February 19-23, 2003, Reno, Nevada, USA. Copyright 2003 ACM 1-58113-648-X/03/0002...$5.00.

In the days of C++ and text-based programs, one of our favorite 2d-array examples was the mathematical matrix encountered in linear algebra. We would have students write a class named Matrix that would implement the matrix as a 2d-array using composition and provide methods for adding, subtracting, and multiplying matrices (sometimes overloading the +, -, and * operators). The Matrix class could then be used to write programs that manipulated matrices--such as using Gaussian elimination to solve systems of equations. Another favorite example was to use the Matrix class to represent an undirected graph in adjacencymatrix form, and then write programs to manipulate the graph using the Matrix class. In both of these examples, all input and output was text-based because graphical programming in C++ was complicated by the lack of standard GUI libraries.

Our institution, like many others, has recently made the switch to Java in the introductory programming course, and although the tried-and-true Matrix class could still be used to teach 2d-arrays, we were interested in finding new examples that would still be relatively simple to teach and learn but would allow us to introduce more graphical concepts into the course.

An obvious example of a graphical 2d-array is a two-dimensional digital image, which in its simplest form, is a 2d-array of integer (or byte) values representing the pixels of the image. The image has a width, which is the number of columns in the 2d-array, and a height, which is the number of rows. For a monochrome image, the value of each pixel in the 2d-array is a number representing a gray level measured on a gray scale from [Gmin = black to Gmax = white]. Chromatic images are slightly more complex and will not be discussed in this paper. Image processing (IP) is an important research area in computer science and engineering with application to numerous real-world problems in areas such as medicine, geology, archaeology, physics, astronomy, biology, and others [4, p.3]. Therefore, we believe IP is a good vehicle for teaching 2d-arrays because:

? many of the IP concepts are quite easy to understand, ? IP naturally promote graphical programming, ? students can observe visually what their program has done

after processing an image, and ? (we believe) it is fun.

Other instructors have reported on their use of image processing assignments in [1,2,6]. Unfortunately, for the would-be programmer of an IP application, there are a plethora of image file formats including the better-known BMP, EPS, GIF, JPEG, PCX, PNG, and TIFF formats as well as many lesser-known formats. Writing image codecs (encoders-decoders) to handle all of these formats would be near impossible.

However, many of the images that we might wish to use in a programming course will either be preexisting images that have been downloaded from the Internet or photographs digitized by a scanner. In these cases, the most likely image file formats to be encountered will be GIF, JPEG, and possibly PNG. Fortunately for us, the newest version of the Java2 Platform Standard Edition 1.4 Release [5] supports reading and writing of images in these formats quite easily using the Java Image I/O Application Programming Interface (Java IIO API). Our goals in this paper, then, are to introduce the reader to the new Java IIO API, provide a generic and reusable Image class that can be used to read and write images using this library, and provide some interesting 2darray IP exercises for students.

2 The Java Image I/O API The Java Image I/O API is a new addition to the J2SE 1.4 release of the Java programming language [5]. This API provides several classes for reading, writing, and manipulating images. Specifically, the current release of the API supports reading image files in GIF [3], JPEG [7,8], and PNG [9] formats and writing image files in JPEG and PNG formats; further releases may add support for other image file formats. Knowing the underlying details of the image file formats is not necessary to use the API to read and write images. The API is written completely in Java and does not use native code, so it is portable across computing platforms. Sun provides implementations for Solaris, Linux, and Windows platforms. The API consists of six packages:

javax.imageio ? The main package. Contains classes that can be used to read and write images.

javax.imageio.event ? Classes dealing with synchronous notification of events during reading and writing.

javax.imageio.metadata ? Supports reading and writing of image file metadata.

javax.imageio.plugins.jpeg ? Classes supporting the native JPEG image plug-in.

javax.imageio.spi ? Classes for authors of third-party codec plugins for other image file formats.

javax.imageio.stream ? Classes dealing with low-level stream input/output.

Since the API is designed to support third-party plug-ins for other extant and future image file formats, many of the classes in the packages are intended to be used by authors of image codec plugins. If a programmer simply wishes to read and write image files in the default formats, then only a small subset of the classes in these packages is important. Specifically, those classes are in the javax.imageio and javax.imageio.stream packages.

2.1 The javax.imageio Package

This is the main package in the Java IIO API, and the main classes for reading and writing images are ImageIO, ImageReader, and ImageWriter.

2.1.1 ImageIO This class contains the following public static convenience methods for locating ImageReaders and ImageWriters.

static Iterator getImageReadersByFormatName (String formatName)

Returns a java.util.Iterator containing all currently registered ImageReaders which claim to be able to decode the named format.

static Iterator getImageReadersBySuffix (String fileSuffix) Returns a java.util.Iterator containing all currently registered ImageReaders which claim to be able to decode image files with the specified file name suffix.

static Iterator getImageWritersByFormatName (String formatName) Returns a java.util.Iterator containing all currently registered ImageWriters which claim to be able to encode the named format.

static Iterator getImageWritersBySuffix (String fileSuffix) Returns a java.util.Iterator containing all currently registered ImageWriters which claim to be able to encode image files with the specified file name suffix.

2.1.2 ImageReader ImageReader is an abstract superclass for decoding and reading image files. The Java IIO API provides default ImageReader subclasses for GIF, JPEG, and PNG image files. Relevant public methods in this class include the following:

int getHeight(int imageIndex) Returns the height in pixels of the specified image within the input source. The first image in an image file containing multiple images is at index 0.

int getWidth (int imageIndex) Returns the width in pixels of the specified image within the input source.

BufferedImage read(int imageIndex) Reads the image indexed by imageIndex and returns it as a java.awt.image.BufferedImage.

void setInput (javax.imageio.stream.FileImageInputStream stream) Sets the input source for the image to the specified input stream. This method must be called before any of the preceding methods are used.

2.1.3 ImageWriter ImageWriter is an abstract superclass for encoding and writing image files. The Java IIO API provides default ImageWriter subclasses for JPEG and PNG image files (a GIF writer is not provided, perhaps because of patent restrictions enforced by Unisys [10]). Relevant public methods in this class include the following:

void write(java.awt.RenderedImage image) Writes the image data to the output stream.

void setOutput (javax.imageio.stream.FileImageOutputStream s) Sets the output source for the image to the specified output stream. This method must be called before write().

2.2 The javax.imageio.stream Package The javax.imageio.stream package contains classes and interfaces for dealing with low-level file and stream I/O. For reading and

writing images from and to files, the relevant classes are FileImageInputStream and FileImage OutputStream.

2.2.1 FileImageInputStream This is a class for reading image data from a file.

FileImageInputStream (File f) Constructs an object that will read image data from the specified java.io.File.

2.2.2 FileImageOutputStream This is a class for writing image data to a file.

FileImageOutputStream (File f) Constructs an object that will write image data to the specified java.io.File.

3 IP Exercises Using the Java Image I/O API The Image class shown below can be used to read, display, and write monochrome images using the Java IIO API. This class hides the details of the API and the java.awt.BufferedImage class from the student by storing the pixel values in a 2d-array named samples, which can be manipulated to perform IP operations. The resulting monochrome image can be written to a file using the write() method which uses the underlying ImageWriter class.

import java.awt.*; import java.awt.image.*; import java.io.*; import java.util.*; import javax.imageio.*; import javax.imageio.stream.*;

class Image { protected int width,height; // 'samples' stores the image pixel values. protected int[][] samples; // Constructor: Reads the image from the // specified file name. public Image(String filename) throws Exception { read(filename); } // Returns the pixel width of the image. public int getWidth() { return width; } // Returns the pixel height of the image. public int getHeight() { return height; } // Reads the image from the specified file // name into the 'samples' array. Throws an // exception if the image is stored in an // unsupported file format (currently only // .GIF, .JPG, and .PNG are supported by Sun). public void read(String filename) throws Exception { // Extract the file name suffix. String ext = filename.substring (filename.indexOf('.')+1); // Create a file object for the file name. File fileImage = new File(filename); // Get a list of ImageReaders that claim // to be able to decode this image file // based on the file name suffix. Iterator imageReaders = ImageIO. getImageReadersBySuffix(ext); ImageReader imageReader; // Grab the first ImageReader in the list. if (imageReaders.hasNext()) imageReader = (ImageReader) imageReaders.next(); // If we get here we cannot decode the image.

else throw new IIOException ("Unsupported image format");

// Create a file input stream object to // read the image date. FileImageInputStream imageInputStream =

new FileImageInputStream(fileImage); // Tell the ImageReader object to read data // from our file input stream object. imageReader.setInput(imageInputStream); // Get the width and height of the image. width = imageReader.getWidth(0); height = imageReader.getHeight(0); // Read the image from the file input stream, // and close the input stream when done. BufferedImage bufImage =

imageReader.read(0); imageInputStream.close(); // Get a raster object so we can extract the // pixel data from the BufferedImage. WritableRaster wRaster =

bufImage.getRaster(); // Create our 'samples' 2d-array. samples = new int[height][width]; // Extract the image data into our 'samples' // array. for (int row = 0; row < height; row++)

for (int col = 0; col < width; col++) samples[row][col] = wRaster.getSample(col,row,0);

} // Write the image stored in the 'samples' // array to the specified file. The file name // suffix should be a supported image file // format (currently either .JPG or .PNG). public void write(String filename)

throws Exception { // Extract the file name suffix. String ext = filename.substring

(filename.indexOf('.')+1); // Create a file object for the file name. File fileImage = new File(filename); // Get a list of ImageWriters that claim to // be able to encode images in the specified // image file format based on the file name // suffix. Iterator imageWriters = ImageIO.

getImageWritersBySuffix(ext); ImageWriter imageWriter; // Grab the first ImageWriter in the list. if (imageWriters.hasNext())

imageWriter = (ImageWriter) imageWriters.next();

// If we get here we cannot encode the image. else throw new IIOException

("Unsupported image format"); // Create a file output stream object to // write the image data. FileImageOutputStream imageOutputStream

= new FileImageOutputStream (fileImage); // Tell the ImageWriter to use our file // output stream object. imageWriter.setOutput (imageOutputStream); // The ImageWriter.write() method expects a // BufferedImage. Convert our 'samples' array // into a BufferedImage. BufferedImage bufImage = createBufferedImage(); // Encode the image to the output file. imageWriter.write(bufImage); imageOutputStream.close(); }

// Draws the image stored in the 'samples' // array on the specified graphics context. public void draw(Graphics gc,int x,int y){

BufferedImage bufImage = createBufferedImage();

gc.drawImage(bufImage,x,y,null); } // Converts the 'samples' array into a // BufferedImage object. Students do not have // to understand how this works. private BufferedImage createBufferedImage() {

// Create a monochrome BufferedImage object. BufferedImage bufImage = new

BufferedImage(width,height, BufferedImage.TYPE_BYTE_GRAY); // Create a WriteableRaster object so we can // put sample data into the BufferedImage // object's raster. WritableRaster wRaster = bufImage.getRaster(); // Copy the 'samples' data into the // BufferedImage object's raster. for (int row = 0; row < height; row++) for (int col = 0; col < width; col++)

wRaster.setSample (col,row,0,samples[row][col]); // Return the newly created BufferedImage. return bufImage; } } // End of Class Image

The TestImage class shown below illustrates how to use the Image class to read, display, and write an image stored in a file named Horse.jpg.

import java.awt.*; import javax.imageio.*; class TestImage {

public static void main(String args[]) throws Exception { // Create a frame to display the image. Frame frame = new Frame("Test Image"); frame.setSize(1024,768); frame.setVisible(true); Graphics gc = frame.getGraphics(); try { // Read the image from the file. Image img = new Image("Horse.jpg"); // Display the image. img.draw(gc,10,40); // Flip the image upside down img.flipX(); // Display the flipped image. img.draw(gc,20+img.getWidth(),40); // Write the new image to a file img.write("HorseNew.jpg"); } catch (Exception e) { System.out.println ("Exception in main() "+e.toString()); }

} }

Figure 1: Monochrome Image Operations

Again, the student does not need to understand any of the Image class details--he or she is simply expected to write IP methods which manipulate the samples array. For example, some reasonable IP exercises would include writing the code for the methods

described below. Figure 1 shows the result of applying these operations to an image named Horse.jpg. Space precludes us from listing the Java code for all of these methods here; however, the

complete Image.java file can be found on the author's web site at . The code for the chgInt(), flipX(), and histogram() methods is shown below. More information on these and other IP operators can be found in [4].

chgInt()

Changes the intensity of the image pixels by a specified percentage (e.g., 1.5 is 150%).

flipX()

flipY() rotate90() invert()

toBW()

histogram() blur()

gauss()

sharpen()

prewitt()

Flips the image about the x-axis (i.e., upside down).

Flips the image about the y-axis (i.e., left-to-right).

Rotates the image 90 degrees clockwise.

Creates a "negative" of the image by inverting each pixel.

Creates a black-and-white image from the original image

Creates a histogram of the intensity data.

Blurs the image using a low-pass 3x3 uniform spatial filter.

Blurs the image using a low-pass 3x3 Gaussian filter.

Sharpens the image using a high-pass 3x3 uniform spatial filter.

Performs edge enhancement using the Prewitt derivative operators.

public void chgInt(double pct) { for (int row = 0; row < height; row++) for (int col = 0; col < width; col++) { int s = (int)(samples[row][col] * pct); if (s < 0) s=0; else if (s > 255) s=255; samples[row][col] = s; }

} public void flipX() {

for (int row = 0; row < height/2; row++) for (int col = 0; col < width; col++) { int s = samples[row][col]; samples[row][col] = samples[height-row-1][col]; samples[height-row-1][col] = s; }

} public BufferedImage histogram() {

int maxCount = 0, levelCount[] = new int[256]; for (int row = 0; row < height; row++)

for (int col = 0; col < width; col++) if (++levelCount[samples[row][col]] > maxCount) maxCount = levelCount[samples[row][col]];

BufferedImage bufImage = new BufferedImage(256,128, BufferedImage.TYPE_BYTE_GRAY);

Graphics gc = bufImage.getGraphics(); gc.setColor(Color.white); gc.fillRect(0,0,256,128); gc.setColor(Color.black); for (int i = 0; i < 256; i++)

gc.drawLine(i,127,i,128(int)((double)levelCount[i]/maxCount*128)); return bufImage; }

4 Summary We have briefly described the Java Image I/O API and presented an Image class that can be used to read, display, and write images using this API. We have also presented IP exercises using the Image class that instructors might wish to assign to their students to help them learn 2d-array processing. We believe these exercises would be stimulating, challenging, and fun for the

students, and we hope that instructors will find them useful as well. Of course, there are many other exercises that could be assigned that we have not described. These might include the following: (1) scale the image up or down by a specified factor; (2) crop the image; (3) rotate the image by angles other than 90 degrees; (4) contrast enhancement; (5) histogram equalization; (6) and other low- and high-pass filters.

5 Acknowledgements

I would like to thank Prof. Keith Brandt for his assistance in proofreading this paper.

References

[1] Astrachan, O., and Rodger, S. H., Animation, Visualization, and Interaction in CS1 Assignments, Proceedings of the 29th Technical Symposium on Computer Science Education, (1998), Association for Computing Machinery, 317?321.

[2] Fell, H. J., and Proulx, V. K., Exploring Martian Planetary Images: C++ Exercises for CS1, Proceedings of the 28th Technical Symposium on Computer Science Education, (1997), Association for Computing Machinery, 30?34.

[3] GIF89a Specification, 1989. Online. Internet. [13 May 2002]. CompuServe, Inc., Columbus, Ohio. Available WWW: .

[4] Gonzalez, R., and Woods, R., (1992). Digital Image Processing, Addison-Wesley, Reading, Mass.

[5] Java2 Platform Standard Edition 1.4 Release, 2002. Online. Internet. [13 May 2002]. Sun Microsystems. Available WWW: .

[6] Jim?nez-Peris, R., Khuri, S., and Pati?o-Mart?nez, M., Adding Breadth to CS1 and CS2 Courses Through Visual and Interactive Programming Projects, Proceedings of the 30th Technical Symposium on Computer Science Education, (1999), Association for Computing Machinery, 252?256.

[7] JPEG File Interchange Format, 1992. Online. Internet. [5 June 2002]. World Wide Web Consortium. Available WWW: .

[8] JPEG - JBIG. Online. Internet. [5 June 2002]. Elysium, Ltd. Available WWW: .

[9] Roelofs, G., PNG (Portable Network Graphics) Home Site, 2002. Online. Internet. [6 June 2002]. Available WWW: .

[10] Sarrazin, P., The Unisys/Compuserve GIF Controversy. Online. Internet. [16 Aug 2002]. Available WWW: .

................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download