Chapter 12 Images Images are objects of the image class which is part of the java.awt.package. Images are manipulated using the classes found in the java.awt.image package. The image processing parts of Java are buried within the java.awt.image package. The package consists of three interfaces and eleven classes, two of which are abstract. Some of them are specifed below: The ImageObserver interface provides the single method necessary to support the asynchronous loading of images. The interface implementers watch the production of an image and can react when certain conditions arise. The ImageConsumer and ImageProducer interfaces provide the means for low level image creation. The ImageProducer provides the source of the pixel data that is used by the ImageConsumer to create an Image. The PixelGrabber and ImageFilter classes, along with the CropImageFilter and RGBImageFilter subclasses, provide the tools for working with images. PixelGrabber consumes pixels from an Image into an array. The ImageFilter classes modify an existing image to produce another Image instance. CropImageFilter makes smaller images; RGBImageFilter alters pixel colors. MemoryImageSource and FilteredImageSource produce new images. MemoryImageSource takes an array and creates an image from it. FilteredImageSource uses an ImageFilter to read and modify data from another image and produces the new image based on the original. Both MemoryImageSource and FilteredImageSource implement ImageProducer because they produce new pixel data. The classes in the java.awt.image package let you create Image objects at run-time. These classes can be used to rotate images, make images transparent, create image viewers for unsupported graphics formats, and more. 12.1 Simple Image Loader In java, the image class is used to refer to images in memory and to images that must be loaded from external sources. Hence there are three common operations that occur when you work with images. Creating an image Loading an image Dispalying an image. Creating an image object The component class in java.awt has a factory method called createImage( ) that is used to create image objects. The two different form of method is shown below. 1. public Image createImage (int width, int height) The createImage() method creates an empty Image of size width x height. The returned Image is an in-memory image that can be drawn on for double buffering to manipulate an image in the background. If an image of size width x height cannot be created, the call returns null. 2. public Image createImage (ImageProducer producer) This createImage() method allows you to take an existing image and modify it in some way to produce a new Image. This can be done through ImageFilter and FilteredImageSource or a MemoryImageSource, which accepts an array of pixel information. Loading an image To load the image, we use the getImage( ) method defined by the Applet class. It has the following forms. 1. public Image getImage(URL url) This method returns an image object that encapsulates the image found at the location specified by url. 2. public Image getImage(URL url, String imageName) This returns an Image object that encapsulates the image found at the location specified by URL and having the specified by imageName. Displaying an Image Finally, To display the image we use drawImage( ) which is a member of the Graphics class. The four different forms of drawImage methods are shown below: 1. public abstract boolean drawImage(Image img, int 2. public abstract boolean drawImage(Image img, int ImageObserver observer); 3. public abstract boolean drawImage(Image img, int ImageObserver observer); 4. public abstract boolean drawImage(Image img, int height,Color bgcolor,ImageObserver observer); x, int y, ImageObserver observer); x, int y, int width, int height, x, int y,Color bgcolor, x, int y, int width, int In all these methods it passes the image itself and the coordinates of its top-left corner is specified and an ImageObserver is provided. ImageObserver is an interface that is implemented by the Component class.. ImageObservers are useful because they can be sent information about the image as it is being loaded. Because Applet implements the ImageObserver interface, you can pass this as the ImageObserver parameter. The second drawImage method is passed parameters for width and height. This causes the image to be scaled so that it appears in the specified rectangle. The final two drawImage methods are the same as the first two with the addition of being able to specify a background color for the image. Example : public class MyClass extends Applet { public void init() { myImage = getImage(getDocumentBase(), "savannah.jpg"); } public void paint(Graphics g) { g.drawImage(myImage, 100, 100, this); } } 12.2 ImageObserver ImageObserver is an interface used to receive notification as an image is being generated. The ImageObserver interface includes a single method. This method, imageUpdate, is called whenever additional information about an image becomes available. For example, it might take time to retrieve a large image across the Internet. The ImageObserver interface can monitor the progress of an image retrieval. An applet could then possibly display a progress message, an estimated time to complete, or take any other useful action. Therefore, when you call drawImage(), you can pass this as the final argument; the component on which you are drawing serves as the ImageObserver for the drawing process. The communication between the image observer and the image consumer happens behind the scenes; you never have to worry about it, unless you want to write your own imageUpdate() method that does something special as the image is being loaded. If you call drawImage() to display an image created in local memory (either from double buffering or from a MemoryImageSource), you can set the ImageObserver parameter of drawImage() to null because no asynchrony is involved; the entire image is available immediately, so an ImageObserver isn't needed. The signature of imageUpdate is as follows: public abstract boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height); The first parameter represents the image being updated. The second parameter represents a combination of various flags that give information about the image. These flags are described in Table 14.2. The remaining parameters usually represent a rectangle indicating the portion of the image that has been retrieved so far. Depending on the values in the infoflags parameter, some of these parameters might be invalid. The imageUpdate method should return true if you want to continue receiving updates, or false otherwise. Flag Table 14.2. Flags used in the imageUpdate method. Description ABORT Retrieval of the image was aborted. ALLBITS All bits of the image have been retrieved. ERROR An error occurred while retrieving the image. FRAMEBITS A frame that is part of a multi-frame image has been completely retrieved. HEIGHT The height parameter now represents the final height of the image. PROPERTIES The properties of the image have been retrieved. SOMEBITS More bits have been retrieved. WIDTH The width parameter now represents the final width of the image. As an example of how imageUpdate can be used, consider EX14I, as shown in Listing 14.9. In this example, a text area is created that will be used to display status messages. The imageUpdate method compares the value in the infoflags parameter against ImageObserver.ERROR and ImageObserver.ALLBITS. When one of these flags is set, a message is appended to the text area. Listing 14.9. EX14I.java illustrates the use of the ImageObserver interface. import java.applet.*; import java.awt.*; import java.awt.image.*; public class EX14I extends Applet { private Image myImage; TextArea status; public void init() { resize(520, 470); // create the image myImage = getImage(getDocumentBase(), "savannah.jpg"); // create a text area for displaying status information status = new TextArea(5, 20); add(status); } public void paint(Graphics g) { // paint the image in the specified rectangle g.drawImage(myImage, 0, 110, 350, 350, this); } public synchronized boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { // if an error occurs, display the message if ((infoflags & ImageObserver.ERROR) != 0) status.appendText("Error\r\n"); // once all the bits have been received display // a message and repaint the applet if ((infoflags & ImageObserver.ALLBITS) != 0) { status.appendText("Allbits\r\n"); repaint(); } return true; } } 12.3 Graphical feedback Double Buffering Double buffering means drawing to an offscreen graphics context and then displaying this graphics context to the screen in a single operation. By using the double buffering technique, you can take your time drawing to another graphics context that isn't displayed. When you are ready, you tell the system to display the completely new image at once. Doing so eliminates the possibility of seeing partial screen updates and flickering. The first thing you need to do is create an image as your drawing canvas. To get an image object, call the createImage() method. createImage() method is as follow: Image im = createImage (300, 300); // width and height Once you have an Image object, to draw associated Image we need to get a Graphics context from the Image. To do so, call the getGraphics() method of the Image class, and use that Graphics context for your drawing: Graphics buf = im.getGraphics(); Now you can do all your drawings with buf. To display the drawing, the paint() method only needs to call drawImage(im, top, left, width, heigth). Another feature of buffering is that you do not have redraw the entire image with each call to paint(). The buffered image you're working on remains in memory, and you can add to it at will. If you are drawing directly to the screen, you would have to recreate the entire drawing each time paint() is called; remember, paint() always hands you a completely new Graphics object Example 2.3 puts it all together for you. It plays a game, with one move drawn to the screen each cycle. We still do the drawing within paint(), but we draw into an offscreen buffer; that buffer is copied onto the screen by g.drawImage(im, 0, 0, this). If we were doing a lot of drawing, it would be a good idea to move the drawing operations into a different thread, but that would be overkill for this simple applet. Example 2.3: Double Buffering--Who Won? import java.awt.*; import java.applet.*; import java.awt.image.*; public class buffering extends Applet { Image im; Graphics buf; int pass=0; public void init () { // Create buffer im = createImage (size().width, size().height); // Get its graphics context buf = im.getGraphics(); // Draw Board Once buf.setColor (Color.red); buf.drawLine ( 0, 50, 150, 50); buf.drawLine ( 0, 100, 150, 100); buf.drawLine ( 50, 0, 50, 150); buf.drawLine (100, 0, 100, 150); buf.setColor (Color.black); } public void paint (Graphics g) { // Draw image - changes are written onto buf g.drawImage (im, 0, 0, this); // Make a move switch (pass) { case 0: buf.drawLine buf.drawLine break; case 1: buf.drawOval break; case 2: buf.drawLine buf.drawLine break; case 3: buf.drawOval break; case 4: buf.drawLine buf.drawLine break; case 5: buf.drawOval break; case 6: buf.drawLine buf.drawLine break; case 7: buf.drawOval break; case 8: buf.drawLine buf.drawLine break; } pass++; if (pass <= 9) repaint (500); (50, 50, 100, 100); (50, 100, 100, 50); (0, 0, 50, 50); (100, 0, 150, 50); (150, 0, 100, 50); (0, 100, 50, 50); (0, 50, 50, 100); (0, 100, 50, 50); (100, 50, 50, 50); (50, 0, 100, 50); (50, 50, 100, 0); (50, 100, 50, 50); (100, 100, 150, 150); (150, 100, 100, 150); } } 12.4 MediaTracker When multiple images are to be loaded synchronously imageUpdate( ) does not work well. Hence MediaTracker class is used belonging to java.awt package A MediaTracker is an object that will check the status of an arbitrary number of images in parallel. First an object of MediaTracker has to be created. Then to use MediaTracker, we need to create a object and addImage( ) method to track the loading status of an image. Adding images The addImage() methods add objects for the MediaTracker to track. When placing an object under MediaTracker's control, you must provide an identifier for grouping purposes. When multiple images are grouped together, you can perform operations on the entire group with a single request. addImage( ) has the following general forms. 1. public synchronized void addImage (Image image, int id, int width, int height) a The addImage() method tells the MediaTracker object that it needs to track the loading of image. The id is used as a grouping. Width and height specify the dimensions of the object when it is displayed. 2. public void addImage (Image image, int id) The addImage() method tells the MediaTracker object that it needs to track the loading of image. The id is used as a grouping. Note: ID number do not need to be unique. You can use the same number with several images as a means of identifying them as part of a group. Once the image is registered we can check the status of an image using checkID( ) and checkAll() methods. Boolean checkID(int imgid) :Here imgid specfies the ID of the image you want to check. If all images having a particular id have been loaded, it returns true otherwise false. Boolean checkAll( ) is used to know if all the imageTracker have been loaded while the other images are being download. Removing images The removeImage() methods, allow you to remove objects from the MediaTracker. Getting rid of loaded images results in better performance because the tracker has fewer objects to check. public void removeImage (Image image) The removeImage() method tells the MediaTracker to remove all objects of image from its tracking list. public void removeImage (Image image, int id) The removeImage() method tells the MediaTracker to remove all objects of image from group id of its tracking list. public void removeImage (Image image, int id, int width, int height) This removeImage() method tells the MediaTracker to remove all objects of image from group id and scale width x height of its tracking list. 12.3 ImageProducer The ImageProducer is an interface for object that want to produce data for image. It is used for creating an image given a byte or integer array. Image producers serve as sources for pixel data; they may compute the data themselves or interpret data from some external source, like a GIF file. No matter how it generates the data, an image producer's job is to hand that data to an image consumer, which usually renders the data on the screen. An image producer works as follows: It waits for image consumers to register their interest in an image. As image consumers register, it stores them in a Hashtable, Vector, or some other collection mechanism. As image data becomes available, it loops through all the registered consumers and calls their methods to transfer the data ImageProducer Interface Methods public void addConsumer (ImageConsumer ic) The addConsumer() method registers ic as an ImageConsumer interested in the Image information. Once an ImageConsumer is registered, the ImageProducer can deliver Image pixels immediately or wait until startProduction() has been called. public boolean isConsumer (ImageConsumer ic) The isConsumer() method checks to see if ic is a registered ImageConsumer for this ImageProducer. If ic is registered, true is returned. If ic is not registered, false is returned. public void removeConsumer (ImageConsumer ic) The removeConsumer() method removes ic as a registered ImageConsumer for this ImageProducer. If ic was not a registered ImageConsumer, nothing should happen. This is not an error that should throw an exception. Once ic has been removed from the registry, the ImageProducer should no longer send data to it. public void startProduction (ImageConsumer ic) The startProduction() method registers ic as an ImageConsumer interested in the Image information and tells the ImageProducer to start sending the Image data immediately. The ImageProducer sends the image data to ic and all other registered ImageConsumer objects, through addConsumer(). The method crateImage( ) method takes an ImageProducer object as its argument to produce a new image. There are 2 image producer in the image package. Namely MemoryImageSource. FilterImageSource FilteredImageSource The FilteredImageSource class combines an ImageProducer and an ImageFilter to create a new Image. The image producer generates pixel data for an original image. The FilteredImageSource takes this data and uses an ImageFilter to produce a modified version: the image may be scaled, clipped, or rotated, or the colors shifted, etc. The FilteredImageSource is the image producer for the new image. The ImageFilter object transforms the original image's data to yield the new image; it implements the ImageConsumer interface public FilteredImageSource (ImageProducer original, ImageFilter filter) The FilteredImageSource constructor creates an image producer that combines an image, original, and a filter, filter, to create a new image. The ImageProducer of the original image is the constructor's first parameter; given an Image, you can acquire its ImageProducer by using the getSource() method. The following code shows how to create a new image from an original. Image image = getImage (new URL ("http://www.ora.com/graphics/headers/homepage.gif")); Image newOne = createImage (new FilteredImageSource (image.getSource(), new SomeImageFilter())); MemoryImageSource MemoryImageSource is a class that creates a new image from an array of data. MemoryImageSource is constructed out of an array of integer specified by pixel, in the default RGB color model to produce data for an image object. The MemoryImageSource class allows you to create images completely in memory; you generate pixel data, place it in an array, and hand that array and a ColorModel to the MemoryImageSource constructor. The MemoryImageSource is an image producer that can be used with a consumer to display the image on the screen. You could also use a MemoryImageSource to modify a pre-existing image. It defines several constructors. One of them is MemoryImageSource(int width, int height, int pixel[],int offset,int scanLineWidth); Example: import java.awt.*; import java.applet.*; import java.awt.image.*; public class mig extends Applet { Image im; public void init( ) { Dimension d=getSize( ); int w = d.width; int h=d.height; int p[]=new int[w*h]; int i=0; for(int y=0; y<h;y++) { for (int x=0;x<w;x++) { int r=(x^y)&0xff; int g=(x*2^y*2)&0xff; int b=(x*4^y)&0xff; p[i++]=(255<<24) | (r<<16) |(g<<16) |(g<<8) |b; } } im=createImage(new MemoryImageSource(w,h,p,0,w)); } public void paint(Graphics g) { g.drawImage(im,0,0,this); } } Image Consumer It is an interface for objects that want to take pixel data from images and supply it as another kind of data. It creates int or byte array that represents pixels from the image object. An object that implements the ImageConsmuer is PixelGrabber Class which is defined within java.lang.image. PixelGrabber:This class is the inverse of the Memory ImageSource Class. It takes an image and grabs the pixels array from it. First create an array to hold pixel data and create an object of PixelGrabber Class and finally use the method grabPixels( ). 12.5 ImageFilter Image filters provide another way to modify images. An ImageFilter is used in conjunction with a FilteredImageSource object. The ImageFilter, which implements ImageConsumer (and Cloneable), receives data from an ImageProducer and modifies it; the FilteredImageSource, which implements ImageProducer, sends the modified data to the new consumer. The ImageFilter class implements a "null" filter that does nothing to the image. To modify an image, we must use any two subclass of ImageFilter. They are CropImageFilter. RGBImageFilter; it is useful for filtering an image on the basis of a pixel's color. To use an ImageFilter, you pass it to the FilteredImageSource constructor, which serves as an ImageProducer to pass the new pixels to their consumer. The following code runs the image logo.jpg through an image filter, SomeImageFilter, to produce a new image. The constructor for SomeImageFilter is called within the constructor for FilteredImageSource, which in turn is the only argument to createImage(). Image image = getImage (new URL ( "http://www.ora.com/images/logo.jpg")); Image newOne = createImage (new FilteredImageSource (image.getSource(), new SomeImageFilter())); ImageFilter Methods Variables protected ImageConsumer consumer; The actual ImageConsumer for the image. It is initialized automatically for you by the getFilterInstance() method. Constructor public ImageFilter () The only constructor for ImageFilter is the default one, which takes no arguments. Subclasses can provide their own constructors if they need additional information. RGBImageFilter RGBImageFilter is an abstract subclass of ImageFilter that provides a shortcut for building the most common kind of image filters: filters that independently modify the pixels of an existing image, based only on the pixel's position and color. Because RGBImageFilter is an abstract class, you must subclass it before you can do anything. The only method your subclass must provide is filterRGB(), which produces a new pixel value based on the original pixel and its location. A handful of additional methods are in this class;. Creating your own RGBImageFilter is fairly easy. One of the more common applications for an RGBImageFilter is to make images transparent by setting the alpha component of each pixel. To do so, we extend the abstract RGBImageFilter class. The filter in Example 12.9 makes the entire image translucent, based on a percentage passed to the class constructor. Filtering is independent of position, so the constructor can set the canFilterIndexColorModel variable. A constructor with no arguments uses a default alpha value of 0.75. Example 12.9: TransparentImageFilter Source import java.awt.image.*; class TransparentImageFilter extends RGBImageFilter { float alphaPercent; public TransparentImageFilter () { this (0.75f); } public TransparentImageFilter (float aPercent) throws IllegalArgumentException { if ((aPercent < 0.0) || (aPercent > 1.0)) throw new IllegalArgumentException(); alphaPercent = aPercent; canFilterIndexColorModel = true; } public int filterRGB (int x, int y, int rgb) { int a = (rgb >> 24) & 0xff; a *= alphaPercent; return ((rgb & 0x00ffffff) | (a << 24)); } } CropImageFilter The CropImageFilter is an ImageFilter that crops an image to a rectangular region. When used with FilteredImageSource, it produces a new image that consists of a portion of the original image. The cropped region must be completely within the original image. It is never necessary to subclass this c If you crop an image and then send the result through a second ImageFilter, the pixel array received by the filter will be the size of the original Image, with the offset and scansize set accordingly. The width and height are set to the cropped values; the result is a smaller Image with the same amount of data. CropImageFilter keeps the full pixel array around, partially empty Example 12.10 uses a CropImageFilter to extract the center third of a larger image. No subclassing is needed; the CropImageFilter is complete in itself. The output is displayed in Figure 12.7. Example 12.10: Crop Applet Source (1) import java.applet.*; import java.awt.*; import java.awt.image.*; public class Crop extends Applet { Image i, j; public void init () { MediaTracker mt = new MediaTracker (this); i = getImage (getDocumentBase(), "rosey.jpg"); mt.addImage (i, 0); try { mt.waitForAll(); int width = i.getWidth(this); int height = i. getHeight(this); j = createImage (new FilteredImageSource (i.getSource(), new CropImageFilter (width/3, height/3, width/3, height/3))); } catch (InterruptedException e) { e.printStackTrace(); } } public void paint (Graphics g) { g.drawImage (i, 10, 10, this); // regular if (j != null) { g.drawImage (j, 10, 90, this); // cropped } } } Listing 14.7. EX14G.java.(2) import java.applet.*; import java.awt.*; import java.awt.image.*; public class EX14G extends Applet { private Image myImage; private Image croppedImage; public void init() { resize(630, 470); myImage = getImage(getDocumentBase(), "savannah.jpg"); // create a filter that will crop the image to the // area starting at point (195, 0) with a width of 140 // and a height of 150 CropImageFilter myCropFilter = new CropImageFilter(195,0, 140, 150); // create a new image source based on the original image // and using the newly created filter FilteredImageSource imageSource = new FilteredImageSource(myImage.getSource(), myCropFilter); // create the cropped image croppedImage = createImage(imageSource); } public void paint(Graphics g) { // paint the cropped image g.drawImage(croppedImage, 0, 0, this); // paint the original image to the right of the cropped one g.drawImage(myImage, 160, 0, this); } } Figure 14.11 : The CropImageFilter can be used to crop images. Figure 14.12 : The RedactFilter can be used to conceal the identity of a Mafia informant. Listing 14.8. EX14H.java illustrates the creation of a custom filter. import java.applet.*; import java.awt.*; import java.awt.image.*; class RedactFilter extends RGBImageFilter { int startX, startY, endX, endY; // the constructor is passed the coordinates of the area // to redact and stores these values public RedactFilter(int x, int y, int width, int height) { startX = x; startY = y; endX = startX + width; endY = startY + height; } public { // // // if int filterRGB(int x, int y, int rgb) if the (x,y) position is in the redacted area return red, otherwise return the same color that was passed in (x >= startX && x <= endX && y >= startY && y <= endY) return 0xff0000ff; else return rgb; } } public class EX14H extends Applet { private Image myImage; private Image redactedImage; public void init() { resize(570, 470); // get the original image myImage = getImage(getDocumentBase(), "savannah.jpg"); // create a filter and specify the range to be redacted ImageFilter filter = new RedactFilter(220, 80, 80, 15); // create a new image source based on the original // image and the new filter FilteredImageSource imageSource=new FilteredImageSource( myImage.getSource(), filter); // create the redacted image from the image source redactedImage = createImage(imageSource); } public void paint(Graphics g) { // paint the redacted image g.drawImage(redactedImage, 0, 0, this); } }