Tuesday 28 March 2017

Image Fundamentals: Creating, Loading, and Displaying & ImageObserver - Java Tutorials

Image Fundamentals: Creating, Loading, and Displaying

There are three common operations that occur when you work with images: creating an image, loading an image, and displaying an image. In Java, the Image class is used to refer to images in memory and to images that must be loaded from external sources. Thus, Java provides ways for you to create a new image object and ways to load one. It also provides a means by which an image can be displayed. Let’s look at each.


Creating an Image Object

You might expect that you create a memory image using something like the following:

  Image test = new Image(200, 100); // Error -- won't work

Not so. Because images must eventually be painted on a window to be seen, the Image class doesn’t have enough information about its environment to create the proper data format for the screen. Therefore, the Component class in java.awt has a factory method called createImage( ) that is used to create Image objects. (Remember that all of the AWT components are subclasses of Component, so all support this method.) The createImage( ) method has the following two forms:

      Image createImage(ImageProducer imgProd)
      Image createImage(int width, int height)

The first form returns an image produced by imgProd, which is an object of a class that implements the ImageProducer interface. (We will look at image producers later.) The second form returns a blank (that is, empty) image that has the specified width and height. Here is an example:

  Canvas c = new Canvas();
  Image test = c.createImage(200, 100);

This creates an instance of Canvas and then calls the createImage( ) method to actually make an Image object. At this point, the image is blank. Later you will see how to write data to it.


Loading an Image

The other way to obtain an image is to load one. To do this, use the getImage( ) method defined by the Applet class. It has the following forms:

      Image getImage(URL url)
      Image getImage(URL url, String imageName)

The first version returns an Image object that encapsulates the image found at the location specified by url. The second version returns an Image object that encapsulates the image found at the location specified by url and having the name specified by imageName.


Displaying an Image

Once you have an image, you can display it by using drawImage( ), which is a member of the Graphics class. It has several forms. The one we will be using is shown here:

      boolean drawImage(Image imgObj, int left, int top, ImageObserver imgOb)

This displays the image passed in imgObj with its upper-left corner specified by left and top. imgOb is a reference to a class that implements the ImageObserver interface. This interface is implemented by all AWT components. An image observer is an object that can monitor an image while it loads. ImageObserver is described in the next section.

With getImage( ) and drawImage( ), it is actually quite easy to load and display an image. Here is a sample applet that loads and displays a single image. The file seattle.jpg is loaded, but you can substitute any GIF or JPG file you like (just make sure it is available in the same directory with the HTML file that contains the applet).

  /*
   * <applet code="SimpleImageLoad" width=248 height=146>
   * <param name="img" value="seattle.jpg">
   * </applet>
   */
  import java.awt.*;
  import java.applet.*;

  public class SimpleImageLoad extends Applet
  {
    Image img;

    public void init() {
      img = getImage(getDocumentBase(), getParameter("img"));
    }

    public void paint(Graphics g) {
      g.drawImage(img, 0, 0, this);
    }
  }

In the init( ) method, the img variable is assigned to the image returned by getImage( ). The getImage( ) method uses the string returned by getParameter(“img”) as the filename for the image. This image is loaded from a URL that is relative to the result of getDocumentBase( ), which is the URL of the HTML page this applet tag was in. The filename returned by getParameter(“img”) comes from the applet tag <paramname=“img” value=“seattle.jpg”>. This is the equivalent, if a little slower, of using the HTML tag <img src=“seattle.jpg” width=248 height=146>. Figure 23-1 shows what it looks like when you run the program.

When this applet runs, it starts loading img in the init( ) method. Onscreen you can see the image as it loads from the network, because Applet’s implementation of the ImageObserver interface calls paint( ) every time more image data arrives.

Seeing the image load is somewhat informative, but it might be better if you use the time it takes to load the image to do other things in parallel. That way, the fully formed image can simply appear on the screen in an instant, once it is fully loaded. You can use ImageObserver, described next, to monitor loading an image while you paint the screen with other information.




ImageObserver

ImageObserver is an interface used to receive notification as an image is being generated. ImageObserver defines only one method: imageUpdate( ). Using an image observer allows you to perform other actions, such as show a progress indicator or an attract screen, as you are informed of the progress of the download. This kind of notification is very useful when an image is being loaded over the network, where the content designer rarely appreciates that people are often trying to load applets over a slow modem.

The imageUpdate( ) method has this general form:

      boolean imageUpdate(Image imgObj, int flags, int left, int top, int width, int height)

Here, imgObj is the image being loaded, and flags is an integer that communicates the status of the update report. The four integers left, top, width, and height represent a rectangle that contains different values depending on the values passed in flags. imageUpdate( ) should return false if it has completed loading, and true if there is more image to process.

The flags parameter contains one or more bit flags defined as static variables inside the ImageObserver interface. These flags and the information they provide are listed below.


Bit Flags of the imageUpdate( ) flags Parameter

WIDTH:  The width parameter is valid and contains the width of the image.

HEIGHT:  The height parameter is valid and contains the height of the image.

PROPERTIES:  The properties associated with the image can now be obtained using imgObj.getProperty( ).

SOMEBITS:  More pixels needed to draw the image have been received. The parameters left, top, width, and height define the rectangle containing the new pixels.

FRAMEBITS:  A complete frame that is part of a multiframe image, which was previously drawn, has been received. This frame can be displayed. The left, top, width, and height parameters are not used.

ALLBITS:  The image is now complete. The left, top, width, and height parameters are not used.

ERROR:  An error has occurred to an image that was being tracked asynchronously. The image is incomplete and cannot be displayed. No further image information will be received. The ABORT flag will also be set to indicate that the image production was aborted.

ABORT:  An image that was being tracked asynchronously was aborted before it was complete. However, if an error has not occurred, accessing any part of the image’s data will restart the production of the image.


The Applet class has an implementation of the imageUpdate( ) method for the ImageObserver interface that is used to repaint images as they are loaded. You can override this method in your class to change that behavior.

Here is a simple example of an imageUpdate( ) method:

  public boolean imageUpdate(Image img, int flags,
                             int x, int y, int w, int h) {
    if ((flags & ALLBITS) == 0) {
      System.out.println("Still processing the image.");
      return true;
    } else {
      System.out.println("Done processing the image.");
      return false;
    }
  }


ImageObserver Example

Now let’s look at a practical example that overrides imageUpdate( ) to make a version of the SimpleImageLoad applet that doesn’t flicker as much. The default implementation of imageUpdate( ) in Applet has several problems. First, it repaints the entire image each time any new data arrives. This causes flashing between the background color and the image. Second, it uses a feature of Applet.repaint( ) to cause the system to only repaint the image every tenth of a second or so. This causes a jerky, uneven feel as the image is painting. Finally, the default implementation knows nothing about images that may fail to load properly. Many beginning Java programmers are frustrated by the fact that getImage( ) always succeeds even when the image specified does not exist. You don’t find out about missing images until imageUpdate( ) occurs. If you use the default implementation of imageUpdate( ), then you’ll never know what happened. Your paint( ) method will simply do nothing when you call g.drawImage( ).

The example that follows fixes all three of these problems in ten lines of code. First, it eliminates the flickering with two small changes. It overrides update( ) so that it calls paint( ) without painting the background color first. The background is set via setBackground( ) in init( ), so the initial color is painted just once. Also, it uses a version of repaint( ) that specifies the rectangle in which to paint. The system will set the clipping area such that nothing outside of this rectangle is painted. This reduces repaint flicker and improves performance.

Second, it eliminates the jerky, uneven display of the incoming image by painting every time it receives an update. These updates occur on a scan line-by-scan line basis, so an image that is 100 pixels tall will be “repainted” 100 times as it loads. Note that this is not the fastest way to display an image, just the smoothest.

Finally, it handles the error caused by the desired file not being found by examining the flags parameter for the ABORT bit. If it is set, the instance variable error is set to true and then repaint( ) is called. The paint( ) method is modified to print an error message over a bright red background if error is true.

Here is the code.

  /*
   * <applet code="ObservedImageLoad" width=248 height=146>
   * <param name="img" value="seattle.jpg">
   * </applet>
   */
  import java.awt.*;
  import java.applet.*;

  public class ObservedImageLoad extends Applet {
    Image img;
    boolean error = false;
    String imgname;

    public void init() {
      setBackground(Color.blue);
      imgname = getParameter("img");
      img = getImage(getDocumentBase(), imgname);
    }

    public void paint(Graphics g) {
      if (error) {
        Dimension d = getSize();
        g.setColor(Color.red);
        g.fillRect(0, 0, d.width, d.height);
        g.setColor(Color.black);
        g.drawString("Image not found: " + imgname, 10, d.height/2);
      } else {
        g.drawImage(img, 0, 0, this);
      }
    }

    public void update(Graphics g) {
      paint(g);
    }

    public boolean imageUpdate(Image img, int flags,
                               int x, int y, int w, int h) {
      if ((flags & SOMEBITS) != 0) { // new partial data
        repaint(x, y, w, h); // paint new pixels
      } else if ((flags & ABORT) != 0) {
        error = true; // file not found
        repaint(); // paint whole applet
      }
      return (flags & (ALLBITS|ABORT)) == 0;
    }
  }

The top screen shows the image half loaded, and the bottom screen displays a filename that has been mistyped in the applet tag. Here is an interesting variation of imageUpdate( ) you might want to try. It waits until the image is completely loaded before snapping it onto the screen in a single repaint.

  public boolean imageUpdate(Image img, int flags,
                             int x, int y, int w, int h) {
    if ((flags & ALLBITS) != 0) {
      repaint();
    } else if ((flags & (ABORT|ERROR)) != 0) {
      error = true; // file not found
      repaint();
    }
    return (flags & (ALLBITS|ABORT|ERROR)) == 0;
  }

No comments:

Post a Comment