You can't directly create an Image object that contains an image because the class is abstract. While a reference of type Image is used to refer to an object that encapsulates the data for an image, the Image class does not provide you with the means of obtaining the image data. There has to be some other mechanism for creating an Image object corresponding to a particular image.
The Applet class defines two overloaded versions of a method, getImage(), that return a reference to an Image object. One version accepts an object of type URL as an argument that specifies the source of the image data. The other accepts two arguments, a URL object plus a String object, where the String object defines the specification of the source of the image data in the context of the URL object. In the previous example, we could have created an Image object directly with the following statement:
Image image = getImage(new URL(getCodeBase(),"Images/wrox_logo.gif"));
We still need to put this in a try block because of the possibility of a MalformedURLException (or others) being thrown by the URL class constructor. There is a significant difference between this statement obtaining a reference to an Image object for an image from a given source, and the statement in the example that created an ImageIcon object. When the ImageIcon class constructor returned to the init() method, the object was fully constructed and the image was available. In the statement above we get a reference to an Image object returned that may at some time in the future contain the data for the image. This gets over the problem implicit in using an ImageIcon object - it hangs up your applet until all the image data for the icon has been retrieved from the source. When you call getImage(), your applet code can continue to execute and get on with other things while the image data is being downloaded. This still leaves the question of determining when the image is actually here which is where the ImageObserver interface comes in.
The Component class implements the ImageObserver interface so all components will inherit this implementation. The ImageObserver interface declares one method, imageUpdate(), that is called automatically when information about an image that was previously requested becomes available. The imageUpdate() method will draw the image on the component. So how do you request information about an image?
One way is to call the drawImage() method for a Graphics object. By calling this method you implicitly request all the information necessary to draw the image in the graphics context - the width, the height, the pixel data and so on. As you saw, the last argument to the drawImage() method was a reference to an ImageObserver object that in our case was the panel on which the image was to be drawn. If any of the image data was not available at the time of the call, the imageUpdate() method for the ImageObserver object would be called as more information became available.
Another way you can request information about an image is to call methods for the Image object itself. The Image class defines methods getWidth() and getHeight() to access the width and height of the image. Both of these methods require a reference of type ImageObserver to be passed - normally a reference to the component on which the image is to be drawn - and that object's imageUpdate() method is called when the information becomes available.
The last part we need to understand in all this is exactly what the imageUpdate() method does when it is called. It simply draws the image incrementally with the information available. This has the effect on a slow Internet link or with a large image of making the image appear piecemeal, as and when the data becomes available. This usually gives the user a visual cue that, even though the entire image has not been retrieved yet, something is still happening.
Most of the time, the default implementation of imageUpdate() is sufficient - it just repaints the component. If we use the getImage() method in the previous example to load the image, we have two objects that are interested in the image data, the applet object and the ImagePanel object. For the ImagePanel object, the default imageUpdate() method is fine. The applet object is different. It wants to know the height and the width of the image so it can resize itself to fit the image. Calling getWidth() and getHeight() for the Image object provide these values if they are available, but these may not be available during the execution of the init() method even when the image file is local, in which case these methods will return -1. In this case therefore, we can usefully implement our own version of the imageUpdate() method to do what we want.
There are six parameters to the imageUpdate() method and it returns a boolean value so its outline implementation is like this:
public boolean imageUpdate(Image img, // Reference to the image
int flags, // Flags identifying what
int x, // x coordinate
int y, // y coordinate
int width, // Image width
int height) // Image height
// Code to respond to data that is available..
The imageUpdate() method will be called when any new item of image data becomes available, so the data will typically be incomplete. You might have the width of the image available for instance but not the height. A return value of false indicates that you have received all the image data that you need, otherwise the method should return true so it will be called again later when more information is available.
The reference passed as the first parameter identifies the image that the call relates to, so if your class object is observing more than one image, you can use this to tell to which Image object the call of the method relates. The second parameter provides the key to determining what image data is available. The flags value is a combination of one or more of the following single bit flags: