Complete listings of the programs discussed in this lesson are provided in Listing 20 and 21.
/*File ImgMod23.java Illustrates contrast and brightness control. This program is designed to be driven by the program named ImgMod02. Enter the following at the command line to run this program. java ImgMod02 ImgMod23 imageFileName The purpose of this program is to illustrate how to modify the contrast and the brightness of an image by modifying the distribution of color values. The contrast of an image is determined by the width of the distribution of the color values belonging to the image. If all color values are grouped together in a very tight distribution, the details in the image will tend to be washed out and the overall appearance of the image will tend toward some shade of gray. The shade will depend on the location of the grouping between the extremes of 0 and 255. At the extremes, if the color values are closely grouped near zero, the colors will range from black to dark gray. If the color values are grouped near 255, the colors will range from very light gray to white. The contrast of the image can be increased by increasing the width of the distribution of the color values. The contrast can be decreased by decreasing the width of the distribution. The overall brightness of an image is determined by the location of the grouping of color values. If the grouping tends to be near the upper limit of 255, the image will tend to be very bright. If the grouping tends to be near the lower limit of 0, the image will tend to be very dark. The brightness of an image can be increased by moving the grouping toward 255, and can be decreased by moving the grouping toward 0. A straightforward way to change the width of the distribution with, or without changing the general location of the distribution consists of the following steps: * Calculate the mean or average value of all the color values. * Subtract the mean from every color value, causing the mean value to be shifted to zero. * Multiply every color value by the same scale factor. If the scale factor is greater than 1.0, the width of the distribution will be increased. If the scale factor is less than 1.0, the width of the distribution will be decreased. * Add the original mean value, or a new mean value to every color value to restore the distribution to its original location or to move it to a new location. The above steps will change the contrast of the image, and may, or may not change its brightness depending on the mean value that is added in the final step. A straightforward way to change the location of the distribution is to add (or subtract) the same constant to every color value. This will change the brightness without changing the contrast. After performing these operations, it is important to make certain that all color values fall within the range of an unsigned byte. This requires eliminating all color values that are greater than 255 and all color values that are less than 0. A simple way to do this is to simply clip the color values at those two limits if they fall outside the limits. Remember, however, that clipping can change the width, the shape, and the mean value of the distribution if the location of the distribution is near the lower or upper limit. Clipping values to the limits will tend to narrow the distribution and to create a spike in the distribution at the value of the limit. There are several ways to measure the width of a distribution. One way is to measure the distance between the minimum value and the maximum value. This is not a very good way because a couple of outliers can lead to erroneous conclusions regarding the width of the distribution. A better way is to compute the root mean square (rms) value of all the color values in the distribution. This approach is less sensitive to outliers and produces results that are more representative of the bulk of the distribution. For distributions of known shapes, it is possible to say what percentage of the color values fall within a range bounded by plus and minus the rms value. For example, if the distribution is uniform (which it probably isn't), approximately 60-percent of all the color values will fall within the range bounded by the rms value on either side of the mean. If the shape of the distribution is not known, we can only say that for an image containing a large number of different color values, a large percentage of those color values will lie within the range bounded by plus and minus the rms value. The rms value is computed and displayed in this program solely to provide information to the user. The rms value is not used in the computations that control the contrast and brightness of the image. For most images, the user should be able to see a direct correspondence between the rms value and the contrast. For small rms values, the contrast in the image should appear to be washed out and the overall color of the image should tend towards gray. The program provides a GUI with two text fields and two histograms. The text fields make it possible for the user to modify the contrast and brightness of the processed image by entering new values for Contrast and Brightness and pressing the Replot button on the main display. The values entered into the text fields are multiplicative factors. The initial value in each text field is 1.0. For these values, the processed image should be identical to the new image. To increase the contrast or the brightness, type a value greater than 1.0 into the corresponding text field and press the Replot button. To decrease the contrast or the brightness, type a value less than 1.0 into the text field and press the Replot button. It isn't necessary to press the Enter key to type the value into the text field, but doing so won't cause any harm. Entering a value that cannot be converted to a value of type double will cause the program to throw an exception. The top histogram shows the distribution of color values for the original image. The bottom histogram shows the distribution of color values for the modified image. The histogram values are normalized to a peak value of 100, exclusive of the values at 0 and 255. These two values can, and often will exceed 100 and go out of plotting range on the histograms. Tested using SDK 1.4.2 and WinXP ************************************************/ import java.awt.*; import javax.swing.Box; import javax.swing.BoxLayout; class ImgMod23 extends Frame implements ImgIntfc02{ TextField contrastField; TextField brightField; Panel input; OrigHistogramPanel origHistPanel; NewHistogramPanel newHistPanel; int[] origHistogram = new int[256]; int[] newHistogram = new int[256]; //Constructor must take no parameters ImgMod23(){ //Create a Box container with a vertical // layout and place it in the center of // the Frame. Box aBox = new Box(BoxLayout.Y_AXIS); this.add(aBox,BorderLayout.CENTER); //Create and place the user input panel at // the top of the vertical stack. Make it // yellow input = new Panel(); //Create panels to group the labels with the // text fields and add them to the input // panel under FlowLayout. Panel contrastPanel = new Panel(); contrastPanel.add(new Label("Contrast")); contrastField = new TextField("1.0",5); contrastPanel.add(contrastField); input.add(contrastPanel); Panel brightnessPanel = new Panel(); brightnessPanel.add(new Label("Brightness")); brightField = new TextField("1.0",5); brightnessPanel.add(brightField); input.add(brightnessPanel); input.add(new Label( "Press Replot to change")); input.setBackground(Color.YELLOW); aBox.add(input); //Create and place the panel for the // original histogram in the middle of the // stack. Make it green. origHistPanel = new OrigHistogramPanel(); origHistPanel.setBackground(Color.GREEN); aBox.add(origHistPanel); //Create and place the panel for the new // histogram at bottom of the stack. Make // it orange. newHistPanel = new NewHistogramPanel(); newHistPanel.setBackground(Color.ORANGE); aBox.add(newHistPanel); //Set miscellaneous properties. setTitle("Copyright 2004, Baldwin"); setBounds(400,0,275,400); setVisible(true); }//end constructor //-------------------------------------------// //An inner class for the original histogram // panel. This is necessary to make it // possible to override the paint method. class OrigHistogramPanel extends Panel{ public void paint(Graphics g){ //Following constant corrects for positive // direction on the y-axis. final int flip = 110; //Following constant is used to shift the // histogram plot 5 pixels to the right. final int shift = 5; //Draw the horizontal axis g.drawLine(0 + shift,flip, 255 + shift,flip); //Draw the histogram for(int cnt = 0;cnt < origHistogram.length; cnt++){ g.drawLine(cnt + shift,flip - 0, cnt + shift, flip - origHistogram[cnt]); }//end for loop }//end paint }//end class OrigHistogramPanel //-------------------------------------------// //An inner class for the new histogram // panel. This is necessary to make it // possible to override the paint method. class NewHistogramPanel extends Panel{ public void paint(Graphics g){ //Following constant corrects for positive // direction on the y-axis. final int flip = 110; //Following constant is used to shift the // histogram plot 5 pixels to the right. final int shift = 5; //Draw the horizontal axis g.drawLine(0 + shift,flip, 255 + shift,flip); //Draw the histogram for(int cnt = 0;cnt < newHistogram.length; cnt++){ g.drawLine(cnt + shift,flip - 0, cnt + shift, flip - newHistogram[cnt]); }//end for loop }//end paint }//end class NewHistogramPanel //-------------------------------------------// //This method is required by ImgIntfc02. public int[][][] processImg( int[][][] threeDPix, int imgRows, int imgCols){ System.out.println("Width = " + imgCols); System.out.println("Height = " + imgRows); //Get user input values for contrast and // brightness. These values will be used as // multipliers to change the contrast and // the brightness. double contrast = Double.parseDouble( contrastField.getText()); double brightness = Double.parseDouble( brightField.getText()); //Make a working copy of the 3D array to // avoid making permanent changes to the // original image data. int[][][] output3D = new int[imgRows][imgCols][4]; for(int row = 0;row < imgRows;row++){ for(int col = 0;col < imgCols;col++){ output3D[row][col][0] = threeDPix[row][col][0]; output3D[row][col][1] = threeDPix[row][col][1]; output3D[row][col][2] = threeDPix[row][col][2]; output3D[row][col][3] = threeDPix[row][col][3]; }//end inner loop }//end outer loop //Get, save, display, and remove the mean. int mean = getMean(output3D,imgRows,imgCols); System.out.println("Original mean: " + mean); removeMean(output3D,imgRows,imgCols,mean); //Get and display the rms value. The rms // value is for user information only. It // is not actually used by the program. int rms = getRms(output3D,imgRows,imgCols); System.out.println("Original rms: " + rms); //Scale each color value by the contrast // multiplier. This will either expand or // compress the distribution. scale(output3D,imgRows,imgCols,contrast); System.out.println("New rms" + getRms(output3D,imgRows,imgCols)); //Restore the mean to a non-zero value by // adding the same value to each color value. // The value added is the product of the // new mean and the brightness multiplier. shiftMean(output3D,imgRows,imgCols, (int)(brightness*mean)); System.out.println("New mean : " + getMean(output3D,imgRows,imgCols)); //Clip all color values at 0 and 255 to make // certain that no color value is out of the // range of an unsigned byte. clip(output3D,imgRows,imgCols); System.out.println(); //Create and draw the two histograms origHistogram = getHistogram(threeDPix, imgRows,imgCols); origHistPanel.repaint(); newHistogram = getHistogram(output3D, imgRows,imgCols); newHistPanel.repaint(); //Return the modified 3D array of pixel data. return output3D; }//end processImg //-------------------------------------------// //Method to create a histogram and return it in // an array of type int. The histogram is // normalized to a peak value of 100 exclusive // of the values at 0 and 255. Those values // can, and often do exceed 100. int[] getHistogram(int[][][] data3D, int imgRows,int imgCols){ int[] hist = new int[256]; for(int row = 0;row < imgRows;row++){ for(int col = 0;col < imgCols;col++){ hist[data3D[row][col][1]]++; hist[data3D[row][col][2]]++; hist[data3D[row][col][3]]++; }//end inner for loop }//end outer for loop //Get the maximum value, exclusive of the // values at 0 and 255 int max = 0; for(int cnt = 1;cnt < hist.length - 1;cnt++){ if(hist[cnt] > max){ max = hist[cnt]; }//end if }//end for loop //Normalize histogram to a peak value of 100 // based on the max value, exclusive of the // values at 0 and 255 for(int cnt = 0;cnt < hist.length;cnt++){ hist[cnt] = 100 * hist[cnt]/max; }//end for loop return hist; }//end getHistogram //-------------------------------------------// //Method to calculate and return the mean // of all the color values int getMean(int[][][] data3D,int imgRows, int imgCols){ int pixelCntr = 0; long accum = 0; for(int row = 0;row < imgRows;row++){ for(int col = 0;col < imgCols;col++){ accum += data3D[row][col][1]; accum += data3D[row][col][2]; accum += data3D[row][col][3]; pixelCntr += 3; }//end inner for loop }//end outer for loop return (int)(accum/pixelCntr); }//end getMean //-------------------------------------------// //Method to remove the mean causing the new // mean value to be 0 void removeMean(int[][][] data3D,int imgRows, int imgCols,int mean){ for(int row = 0;row < imgRows;row++){ for(int col = 0;col < imgCols;col++){ data3D[row][col][1] -= mean; data3D[row][col][2] -= mean; data3D[row][col][3] -= mean; }//end inner for loop }//end outer for loop }//end removeMean //-------------------------------------------// //Method to calculate the root mean square // value of all the color values. int getRms(int[][][] data3D,int imgRows, int imgCols){ int pixelCntr = 0; long accum = 0; for(int row = 0;row < imgRows;row++){ for(int col = 0;col < imgCols;col++){ accum += data3D[row][col][1] * data3D[row][col][1]; accum += data3D[row][col][2] * data3D[row][col][2]; accum += data3D[row][col][3] * data3D[row][col][3]; pixelCntr += 3; }//end inner for loop }//end outer for loop int meanSquare = (int)(accum/pixelCntr); int rms = (int)(Math.sqrt(meanSquare)); return rms; }//end getRms //-------------------------------------------// //Method to scale the data and expand or // compress the distribution void scale(int[][][] data3D,int imgRows, int imgCols,double scale){ for(int row = 0;row < imgRows;row++){ for(int col = 0;col < imgCols;col++){ data3D[row][col][1] *= scale; data3D[row][col][2] *= scale; data3D[row][col][3] *= scale; }//end inner for loop }//end outer for loop }//end scale //-------------------------------------------// //Method to shift the mean to a new value. void shiftMean(int[][][] data3D,int imgRows, int imgCols,int newMean){ for(int row = 0;row < imgRows;row++){ for(int col = 0;col < imgCols;col++){ data3D[row][col][1] += newMean; data3D[row][col][2] += newMean; data3D[row][col][3] += newMean; }//end inner for loop }//end outer for loop }//end shiftMean //-------------------------------------------// //Method to clip the color data at 0 and 255 void clip(int[][][] data3D,int imgRows, int imgCols){ for(int row = 0;row < imgRows;row++){ for(int col = 0;col < imgCols;col++){ if(data3D[row][col][1] < 0) data3D[row][col][1] = 0; if(data3D[row][col][1] > 255) data3D[row][col][1] = 255; if(data3D[row][col][2] < 0) data3D[row][col][2] = 0; if(data3D[row][col][2] > 255) data3D[row][col][2] = 255; if(data3D[row][col][3] < 0) data3D[row][col][3] = 0; if(data3D[row][col][3] > 255) data3D[row][col][3] = 255; }//end inner for loop }//end outer for loop }//end clip //-------------------------------------------// }//end class ImgMod23 Listing 20
/*File ImgMod02a.java This is an update of the program named ImgMod02. This update is designed to use BufferedImage.getRaster in place of PixelGrabber. The purpose of this program is to make it easy to experiment with the modification of pixel data in an image and to display the modified version of the image along with the original version of the image. The program extracts the pixel data from an image file into a 3D array of type: int[row][column][depth]. The first two dimensions of the array correspond to the rows and columns of pixels in the image. The third dimension always has a value of 4 and contains the following values by index value: 0 alpha 1 red 2 green 3 blue Note that these values are stored as type int rather than type unsigned byte which is the format of pixel data in the original image. This type conversion eliminates many problems involving the requirement to perform unsigned arithmetic on unsigned byte data. The program supports gif and jpg files and possibly some other file types as well. Operation: This program provides a framework that is designed to invoke another program to process the pixels extracted from an image. In other words, this program extracts the pixels and puts them in a format that is relatively easy to work with. A second program is invoked to actually process the pixels. Typical usage is as follows: java ImgMod02a ProcessProgramName ImageFileName For test purposes, the source code includes a class definition for an image processing program named ProgramTest. If the ImageFileName is not specified on the command line, the program will search for an image file in the current directory named junk.gif and will process it using the processing program specified by the second command-line argument. If both command-line arguments are omitted, the program will search for an image file in the current directory named junk.gif and will process it using the built-in processing program named ProgramTest. The image file must be provided by the user in all cases. However, it doesn't have to be in the curent directory if a path to the file is specified on the command line. When the program is started, the original image and the processed image are displayed in a frame with the original image above the processed image. A Replot button appears at the bottom of the frame. If the user clicks the Replot button, the image processing method is rerun, the image is reprocessed and the new version of the processed image replaces the old version. The processing program may provide a GUI for data input making it possible for the user to modify the behavior of the image processing method each time it is run. This capability is illustrated in the built-in processing program named ProgramTest. The image processing programming must implement the interface named ImgIntfc02. That interface declares a single method with the following signature: int[][][] processImg(int[][][] threeDPix, int imgRows, int imgCols); The first parameter is a reference to the 3D array of pixel data stored as type int. The last two parameters specify the number of rows of pixels and the number of columns of pixels in the image. The image processing program cannot have a parameterized constructor. This is because an object of the class is instantiated by invoking the newInstance method of the class named Class on the name of the image processing program provided as a String on the command line. This approach to object instantiation does not support parameterized constructors. If the image processing program has a main method, it will be ignored. The processImg method receives a 3D array containing pixel data. It should make a copy of the incoming array and modify the copy rather than modifying the original. Then the program should return a reference to the modified copy of the 3D pixel array. The program also receives the width and the height of the image represented by the pixels in the 3D array. The processImg method is free to modify the values of the pixels in the array in any manner before returning the modified array. Note however that native pixel data consists of four unsigned bytes. If the modification of the pixel data produces negative values or positive value greater than 255, this should be dealt with before returning the modified pixel data. Otherwise, the returned values will simply be masked to eight bits before display, and the result of displaying those masked bits may not be as expected. There are at least two ways to deal with this situation. One way is to simply clip all negative values at zero and to clip all values greater than 255 at 255. The other way is to perform a further modification so as to map the range from -x to +y into the range from 0 to 255. There is no one correct way for all situations. When the processImg method returns, this program causes the original image and the modified image to be displayed in a frame on the screen with the original image above the modified image. If the user doesn't specify an image processing program, this program will instantiate and use an object of the class named ProgramTest and an image file named junk.gif. The class definition for the ProgramTest class is included in this source code file. The image file named junk.gif must be provided by the user in the current directory. Just about any gif file of an appropriate size will do. Make certain that it is small enough so that two copies will fit on the screen when stacked one above the other. The processing program named ProgramTest draws a diagonal white line across the image starting at the top left corner. The program provides a dialog box that allows the user to specify the slope of the line. To change the slope, type a new slope into the text field and press the Replot button on the main graphic frame. It isn't necessary to press the Enter key after typing the new slope value into the text field, but doing so won't cause any harm. (Note that only positive slope values can be used. Entry of a negative slope value will cause an exception to be thrown.) Other than to add the white line, the image processing program named ProgramTest does not modify the image. It does draw a visible white line across transparent areas, making the pixels underneath the line non-transparent. However, it may be difficult to see the white line against the default yellow background in the frame. If the program is unable to load the image file within ten seconds, it will abort with an error message. Some operational details follow. This program reads an image file from the disk and saves it in memory under the name rawImg. Then it declares a one-dimensional array of type int of sufficient size to contain one int value for every pixel in the image. Each int value will be populated with one alpha byte and three color bytes. The name of the array is oneDPix. Then the program instantiates an object of type PixelGrabber, which associates the rawImg with the one-dimensional array of type int. Following this, the program invokes the grabPixels method on the object of type PixelGrabber to cause the pixels in the rawImg to be extracted into int values and stored in the array named oneDPix. Then the program copies the pixel values from the oneDPix array into the threeDPix array, converting them to type int in the process. The threeDPix array is passed to an image processing program. The image processing program returns a modified version of the 3D array of pixel data. This program then creates a new version of the oneDPix array containing the modified pixel data. It uses the createImage method of the Component class along with the constructor for the MemoryImageSource class to create a new image from the modified pixel data. The name of the new image is modImg. Finally, the program overrides the paint method where it uses the drawImage method to display both the raw image and the modified image on the same Frame object. The raw image is displayed above the modified image. Tested using SDK 1.4.2 under WinXP. ************************************************/ import java.awt.*; import java.awt.event.*; import java.awt.image.*; import com.sun.image.codec.jpeg.*; import java.io.*; class ImgMod02a extends Frame{ Image rawImg; BufferedImage buffImage; int imgCols;//Number of horizontal pixels int imgRows;//Number of rows of pixels Image modImg;//Reference to modified image //Inset values for the Frame int inTop; int inLeft; //Default image processing program. This // program will be executed to process the // image if the name of another program is not // entered on the command line. Note that the // class file for this program is included in // this source code file. static String theProcessingClass = "ProgramTest"; //Default image file name. This image file // will be processed if another file name is // not entered on the command line. You must // provide this file in the current directory. static String theImgFile = "junk.gif"; MediaTracker tracker; Display display = new Display();//A Canvas Button replotButton = new Button("Replot"); //References to arrays that store pixel data. int[][][] threeDPix; int[][][] threeDPixMod; int[] oneDPix; //Reference to the image processing object. ImgIntfc02 imageProcessingObject; //-------------------------------------------// public static void main(String[] args){ //Get names for the image processing program // and the image file to be processed. // Program supports gif files and jpg files // and possibly some other file types as // well. if(args.length == 0){ //Use default processing class and default // image file. No code required here. // Class and file names were specified // above. This case is provided for // informtion purposes only. }else if(args.length == 1){ theProcessingClass = args[0]; //Use default image file }else if(args.length == 2){ theProcessingClass = args[0]; theImgFile = args[1]; }else{ System.out.println("Invalid args"); System.exit(1); }//end else //Display name of processing program and // image file. System.out.println("Processing program: " + theProcessingClass); System.out.println("Image file: " + theImgFile); //Instantiate an object of this class ImgMod02a obj = new ImgMod02a(); }//end main //-------------------------------------------// public ImgMod02a(){//constructor //Get an image from the specified file. Can // be in a different directory if the path // was entered with the file name on the // command line. rawImg = Toolkit.getDefaultToolkit(). getImage(theImgFile); //Use a MediaTracker object to block until // the image is loaded or ten seconds has // elapsed. tracker = new MediaTracker(this); tracker.addImage(rawImg,1); try{ if(!tracker.waitForID(1,10000)){ System.out.println("Load error."); System.exit(1); }//end if }catch(InterruptedException e){ e.printStackTrace(); System.exit(1); }//end catch //Make certain that the file was successfully // loaded. if((tracker.statusAll(false) & MediaTracker.ERRORED & MediaTracker.ABORTED) != 0){ System.out.println( "Load errored or aborted"); System.exit(1); }//end if //Raw image has been loaded. Get width and // height of the raw image. imgCols = rawImg.getWidth(this); imgRows = rawImg.getHeight(this); this.setTitle("Copyright 2004, Baldwin"); this.setBackground(Color.YELLOW); this.add(display); this.add(replotButton,BorderLayout.SOUTH); //Make it possible to get insets and the // height of the button. setVisible(true); //Get and store inset data for the Frame and // the height of the button. inTop = this.getInsets().top; inLeft = this.getInsets().left; int buttonHeight = replotButton.getSize().height; //Size the frame so that a small amount of // yellow background will show on the right // and on the bottom when both images are // displayed, one above the other. Also, the // placement of the images on the Canvas // causes a small amount of background to // show between the images. this.setSize(2*inLeft+imgCols + 1,inTop + buttonHeight + 2*imgRows + 7); //=========================================// //Anonymous inner class listener for replot // button. This actionPerformed method is // invoked when the user clicks the Replot // button. It is also invoked at startup // when this program posts an ActionEvent to // the system event queue attributing the // event to the Replot button. replotButton.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ //Pass a 3D array of pixel data to the // processing object and get a modified // 3D array of pixel data back. The // creation of the 3D array of pixel // data is explained later. threeDPixMod = imageProcessingObject.processImg( threeDPix,imgRows,imgCols); //Convert the modified pixel data to a // 1D array of pixel data. The 1D // array is explained later. oneDPix = convertToOneDim( threeDPixMod,imgCols,imgRows); //Use the createImage() method to // create a new image from the 1D array // of pixel data. modImg = createImage( new MemoryImageSource( imgCols,imgRows,oneDPix,0,imgCols)); //Repaint the image display frame with // the original image at the top and // the modified pixel data at the // bottom. display.repaint(); }//end actionPerformed }//end ActionListener );//end addActionListener //End anonymous inner class. //=========================================// //Create a 1D array object to receive the // pixel representation of the image oneDPix = new int[imgCols * imgRows]; //Create an empty BufferedImage object buffImage = new BufferedImage( imgCols, imgRows, BufferedImage.TYPE_INT_ARGB); // Draw Image into BufferedImage Graphics g = buffImage.getGraphics(); g.drawImage(rawImg, 0, 0, null); //Convert the BufferedImage to numeric pixel // representation. DataBufferInt dataBufferInt = (DataBufferInt)buffImage.getRaster(). getDataBuffer(); oneDPix = dataBufferInt.getData(); //Convert the pixel byte data in the 1D // array to int data in a 3D array to // make it easier to work with the pixel // data later. Recall that pixel data is // unsigned byte data and Java does not // support unsigned arithmetic. // Performing unsigned arithmetic on byte // data is particularly cumbersome. threeDPix = convertToThreeDim( oneDPix,imgCols,imgRows); //Instantiate a new object of the image // processing class. Note that this // object is instantiated using the // newInstance method of the class named // Class. This approach does not support // the use of a parameterized // constructor. try{ imageProcessingObject = ( ImgIntfc02)Class.forName( theProcessingClass).newInstance(); //Post a counterfit ActionEvent to the // system event queue and attribute it // to the Replot button. (See the // anonymous ActionListener class // defined above that registers an // ActionListener object on the RePlot // button.) Posting this event causes // the image processing method to be // invoked, passing the 3D array of // pixel data to the method, and // receiving a 3D array of modified // pixel data back from the method. Toolkit.getDefaultToolkit(). getSystemEventQueue().postEvent( new ActionEvent( replotButton, ActionEvent.ACTION_PERFORMED, "Replot")); //At this point, the image has been // processed and both the original // image and the the modified image // have been displayed. From this // point forward, each time the user // clicks the Replot button, the image // will be processed again and the new // modified image will be displayed // along with the original image. }catch(Exception e){ System.out.println(e); }//end catch //Cause the composite of the frame, the // canvas, and the button to become visible. this.setVisible(true); //=========================================// //Anonymous inner class listener to terminate // program. this.addWindowListener( new WindowAdapter(){ public void windowClosing(WindowEvent e){ System.exit(0);//terminate the program }//end windowClosing() }//end WindowAdapter );//end addWindowListener //=========================================// }//end constructor //===========================================// //Inner class for canvas object on which to // display the two images. class Display extends Canvas{ //Override the paint method to display both // the rawImg and the modImg on the same // Canvas object, separated by one row of // pixels in the background color. public void paint(Graphics g){ //First confirm that the image has been // completely loaded and neither image // reference is null. if (tracker.statusID(1, false) == MediaTracker.COMPLETE){ if((rawImg != null) && (modImg != null)){ g.drawImage(rawImg,0,0,this); g.drawImage(modImg,0,imgRows + 1,this); }//end if }//end if }//end paint() }//end class myCanvas //=============================================// //Save pixel values as type int to make // arithmetic easier later. //The purpose of this method is to convert the // data in the int oneDPix array into a 3D // array of ints. //The dimensions of the 3D array are row, // col, and color in that order. //Row and col correspond to the rows and // columns of the image. Color corresponds to // transparency and color information at the // following index levels in the third // dimension: // 0 alpha // 1 red // 2 green // 3 blue // The structure of this code is determined by // the way that the pixel data is formatted // into the 1D array of ints produced by the // grabPixels method of the PixelGrabber // object. int[][][] convertToThreeDim( int[] oneDPix,int imgCols,int imgRows){ //Create the new 3D array to be populated // with color data. int[][][] data = new int[imgRows][imgCols][4]; for(int row = 0;row < imgRows;row++){ //Extract a row of pixel data into a // temporary array of ints int[] aRow = new int[imgCols]; for(int col = 0; col < imgCols;col++){ int element = row * imgCols + col; aRow[col] = oneDPix[element]; }//end for loop on col //Move the data into the 3D array. Note // the use of bitwise AND and bitwise right // shift operations to mask all but the // correct set of eight bits. for(int col = 0;col < imgCols;col++){ //Alpha data data[row][col][0] = (aRow[col] >> 24) & 0xFF; //Red data data[row][col][1] = (aRow[col] >> 16) & 0xFF; //Green data data[row][col][2] = (aRow[col] >> 8) & 0xFF; //Blue data data[row][col][3] = (aRow[col]) & 0xFF; }//end for loop on col }//end for loop on row return data; }//end convertToThreeDim //-------------------------------------------// //The purpose of this method is to convert the // data in the 3D array of ints back into the // 1d array of type int. This is the reverse // of the method named convertToThreeDim. int[] convertToOneDim( int[][][] data,int imgCols,int imgRows){ //Create the 1D array of type int to be // populated with pixel data, one int value // per pixel, with four color and alpha bytes // per int value. int[] oneDPix = new int[ imgCols * imgRows * 4]; //Move the data into the 1D array. Note the // use of the bitwise OR operator and the // bitwise left-shift operators to put the // four 8-bit bytes into each int. for(int row = 0,cnt = 0;row < imgRows;row++){ for(int col = 0;col < imgCols;col++){ oneDPix[cnt] = ((data[row][col][0] << 24) & 0xFF000000) | ((data[row][col][1] << 16) & 0x00FF0000) | ((data[row][col][2] << 8) & 0x0000FF00) | ((data[row][col][3]) & 0x000000FF); cnt++; }//end for loop on col }//end for loop on row return oneDPix; }//end convertToOneDim }//end ImgMod02a.java class //=============================================// //The ProgramTest class //The purpose of this class is to provide a // simple example of an image processing class // that is compatible with the program named // ImgMod02a. //The constructor for the class displays a small // frame on the screen with a single textfield. // The purpose of the text field is to allow the // user to enter a value that represents the // slope of a line. In operation, the user // types a value into the text field and then // clicks the Replot button on the main image // display frame. The user is not required to // press the Enter key after typing the new // value, but it doesn't do any harm to do so. //The method named processImage receives a 3D // array containing alpha, red, green, and blue // values for an image. The values are received // as type int (not type byte). // The threeDPix array that is received is // modified to cause a white diagonal line to be // drawn down and to the right from the upper // left-most corner of the image. The slope of // the line is controlled by the value that is // typed into the text field. Initially, this // value is 1.0. The image is not modified in // any other way. //To cause a new line to be drawn, type a slope // value into the text field and click the Replot // button at the bottom of the image display // frame. //This class extends Frame. However, a // compatible class is not required to extend the // Frame class. This example extends Frame // because it provides a GUI for user data input. //A compatible class is required to implement the // interface named ImgIntfc02. class ProgramTest extends Frame implements ImgIntfc02{ double slope;//Controls the slope of the line String inputData;//Obtained via the TextField TextField inputField;//Reference to TextField //Constructor must take no parameters ProgramTest(){ //Create and display the user-input GUI. setLayout(new FlowLayout()); Label instructions = new Label( "Type a slope value and Replot."); add(instructions); inputField = new TextField("1.0",5); add(inputField); setTitle("Copyright 2004, Baldwin"); setBounds(400,0,200,100); setVisible(true); }//end constructor //The following method must be defined to // implement the ImgIntfc02 interface. public int[][][] processImg( int[][][] threeDPix, int imgRows, int imgCols){ //Display some interesting information System.out.println("Program test"); System.out.println("Width = " + imgCols); System.out.println("Height = " + imgRows); //Make a working copy of the 3D array to // avoid making permanent changes to the // image data. int[][][] temp3D = new int[imgRows][imgCols][4]; for(int row = 0;row < imgRows;row++){ for(int col = 0;col < imgCols;col++){ temp3D[row][col][0] = threeDPix[row][col][0]; temp3D[row][col][1] = threeDPix[row][col][1]; temp3D[row][col][2] = threeDPix[row][col][2]; temp3D[row][col][3] = threeDPix[row][col][3]; }//end inner loop }//end outer loop //Get slope value from the TextField slope = Double.parseDouble( inputField.getText()); //Draw a white diagonal line on the image for(int col = 0;col < imgCols;col++){ int row = (int)(slope*col); if(row > imgRows -1)break; //Set values for alpha, red, green, and // blue colors. temp3D[row][col][0] = (byte)0xff; temp3D[row][col][1] = (byte)0xff; temp3D[row][col][2] = (byte)0xff; temp3D[row][col][3] = (byte)0xff; }//end for loop //Return the modified array of image data. return temp3D; }//end processImg }//end class ProgramTest Listing 21