Back Contents Next

Creating Image Objects

The Image class is at the heart of all images in Java. An image will always be an object of a class that has Image as a base. 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.

Image Observers

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.

Implementing imageUpdate()

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

                                                      //    is available 

                           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:

 

Parameter

Description

WIDTH

The width of the image is available.

HEIGHT

The height of the image is available.

SOMEBITS

Some more pixels required for a scaled version of the image are available, and the bounding rectangle for these is given by the x, y, width and height values.

ALLBITS

This flag indicates that all the bits of an image that has been drawn previously are now available. This enables the updateImage() method to decide when to return false when an image is being drawn incrementally.

FRAMEBITS

This indicates that another complete frame of a multi-frame image that has been previously drawn is now available to be drawn again. This can be used to determine when to call the repaint() method to display more of an image.

PROPERTIES

This flag indicates that the properties of the image are now available. These are obtained by calling the getProperties() method for the Image object.

ABORT

This flag indicates that the process of retrieving the image was aborted and no further image data will be available.

ERROR

This flag indicates that an error has occurred retrieving the image and no further image data will be available.

 

We aren’t going to go into the detail of how all these flags are used, but the complete set is here for reference. We are only interested in the height and the width of the image in our applet, so we can implement the imageUpdate() method as:

 

public boolean imageUpdate(Image img,         // Reference to the image

                           int flags,         // Flags identifying what is available

                           int x,             // x coordinate

                           int y,             // y coordinate

                           int width,         // Image width

                           int height)        // Image height

{

  if((flags & WIDTH) > 0 && (flags & HEIGHT) > 0)

  {

    resize(width,height);                     // Set applet size to fit the image

    repaint();                                // Repaint the applet in its new size

    return false;

  }

  else

    return true;                             // More info required

}

 

We only want to resize the applet when we have both the width and height of the image, so we test for each of these flags by bitwise ANDing the flags value with the WIDTH and HEIGHT values that are defined in the ImageObserver interface.  If the results of both operations are positive, both flags were set so we can resize the applet and repaint it. While either or both of the WIDTH and HEIGHT flags are not set, we return true so the method will be called again. Of course, the HTML rendering in a browser will work much better if the correct size for the applet is specified at the outset.

Try It Out – Implementing the imageUpdate() Method

Let's try out the whole applet in its revised guise:

 

import java.awt.*;

import javax.swing.*;

import java.net.*;

 

public class DisplayImage extends JApplet

{

  public void init()

  {

    Image image = null;

    try

    {

      // Image from a file specified by a URL

      image = getImage(new URL(getCodeBase(),"Images/wrox_logo.gif"));

    }

    catch (MalformedURLException e)

    {

      System.out.println("Failed to create URL:\n" + e);

      return;

    }

    int imageWidth = image.getWidth(this);                     // Get its width

    int imageHeight = image.getHeight(this);                   // and its height

    if(imageWidth != -1 && imageHeight != -1)                  // If they are available

      resize(imageWidth,imageHeight);                          // set applet size to fit

 

    ImagePanel imagePanel = new ImagePanel(image);  // Create panel showing the image

    getContentPane().add(imagePanel);               // Add the panel to the content pane

  }

 

  public boolean imageUpdate(Image img,         // Reference to the image

                             int flags,         // Flags identifying what is available

                             int x,             // x coordinate

                             int y,             // y coordinate

                             int width,         // Image width

                             int height)        // Image height

  {

    if((flags & WIDTH) > 0 && (flags & HEIGHT) > 0)

    {

      resize(width,height);                        // Set applet size to fit the image

      repaint();                                   // Repaint the applet in its new size

      return false;

    }

    else

      return true;                                // More info required

  }

 

   // Class representing a panel displaying an image

   class ImagePanel extends JPanel

   {

     public ImagePanel(Image image)

     {

      this.image = image;

     }

 

     public void paint(Graphics g)

     {

       g.drawImage(image, 0, 0, this);                      // Display the image

     }

 

     Image image;                                             // The image

   }

}

How It Works

The effect of the applet will appear much the same as before because the image file is local so everything will happen quite quickly. You can verify that the image data is not available in the init() method for the applet by commenting out the if that checks for a value of –1 for the width or the height before calling resize(). Without this check, the resize() method will be called with either or both arguments as –1 so the size of the applet will briefly become miniscule and the applet viewer window along with it.

 

A further experiment will show that our imageUpdate() method in the DisplayImage class is only called if we have previously requested image data that is not available. Modify the code in the init()method that calls the methods to obtain the image size like this:

 

int imageWidth = 30, imageHeight = 30;               // Added...

 

//     imageWidth = image.getWidth(this);            // Get its width

//     imageHeight = image.getHeight(this);          // and its height

//    if(imageWidth != -1 && imageHeight != -1)

//      resize(imageWidth,imageHeight);              // Set applet size to fit the image

 

Now the applet will not be resized at all and the image will not be displayed. If you uncomment either or both of the statements calling imageWidth() and imageHeight() and recompile, everything will work as it should. To see when imageUpdate() is called, you could add a call to System.out.println() at the beginning of the method.

 

The default imageUpdate() method is called automatically for the ImagePanel object because the paint() method calls drawImage() and identifies the object as the image observer. The default version calls repaint() as new data becomes available, so over a slow link our image will be drawn incrementally.

 


Back Contents Next
©1999 Wrox Press Limited, US and UK.