Java Programming   Java Programming
 |Sofia Home | Content Gallery |
Home
Syllabus
Schedule
Lessons
Assignments
Discussion
Resources

Lesson 11.3 Images

Painting Images

Now that you know how to paint geometric pictures and you've made the acquaintence of the Canvas and Component classes, it's time to move out of the cubist age and into the world of photorealism. Or surrealism if you prefer.

It's time to learn to paint images.

Using images in your Java program is a little different than using images in an HTML Web page. Like HTML, Java has built-in support for GIF or JPEG images, including animated GIFs. As with HTML, if you want to display TIFFs or BMPs, you'll have to translate them first.

With HTML, you can display an image in your Web page simply by using the image's URL in an img tag. Things are a little more complex in Java, but not much more.

To draw an image on your applet you must:

  • First load the image into memory.
  • Then draw the image using a Graphics object, just as you've drawn geometric shapes.
The only real complication is that an applet can only load an image from the same Web server that the applet was loaded from. Applets are not allowed to make socket connections to host machines other than the one that served the applet. This is designed as a security measure, so that an applet cannot "hijack" your session.

Java applications, as opposed to applets, have no such restriction. A Java application can display both local images [from files located on the machine running the application] and remote images, from any valid URL. You'll learn about building graphical Java applications in the next lesson.

Back to Top

Loading Images

To load an image in an applet, you first create an Image variable like this: 

Image img = null;

Then you use the Applet getImage() method to retrieve the image from your Web server and load it into memory, like this: 

String imgName = "bullet.gif";
img = getImage(getCodeBase(), imgName);

Notice that you don't use the new operator to construct an Image object.

The getImage() Method

The getImage() method takes two arguments. The first is a URL object. You can construct a URL manually--you'll learn how in Lesson 13, "Files and Networking"--but it's easier to use the two Applet methods getCodeBase() and getDocumentBase().

Both methods return a "base" URL, that is, a URL that refers to a directory, not a file. The difference between the two methods is that the getCodeBase() method returns the directory containing your applet while getDocumentBase() returns the directory containing your HTML page. They are often the same, of course.

The second argument to getImage() is a String containing the name of the file containing the image you want to retreive. This String can contain relative path information if you like. Both of these are valid calls to getImage()

im1 = getImage(getCodeBase(), 
               "../myfile.gif");

im2 = getImage(getCodeBase(), 
               "myfile.gif");

The first of these looks for myfile.gif in an images directory "on the same level" as the directory containing your applet. If your applet--named myapplet.class--was in a directory called applets, then myfile.gif would be found in a directory that looks like this: 

/applets/myapplet.class
/myfile.gif

The second example looks for an image in a directory "below" the directory containing your applet. Given the same circumstances as the previous example, myapplet.class and myfile.gif would be contained in a directory structure like this:

/applets/myapplet.class
/applets/myfile.gif

Asynchronous Loading

When you ask Java to load an image via getImage(), it may take some time for it to carry out your request. If the image is very large, and your Internet connection very slow, it could take, literally, minutes until the entire image arrives.

Because of this, Java loads its images asynchronously. Rather than stopping the program and waiting for the image to arrive, getImage() works in the background to load your image while the rest of your program continues just as if getImage() were already finished.

There are several ways you can deal with this asynchronous image loading. We'll look at the simplest method in the next section.

Back to Top

Drawing Images

To draw an image, you use the Graphics drawImage() method. There are six different versions of drawImage(), but the simplest takes four arguments like this: 

g.drawImage(img, x, y, this);

The first argument is an Image object, previously acquired by getImage(). The x, y arguments represent the upper-left corner where the image is to be drawn. The last argument represents an ImageObserver object. For now we'll pass this as an argument. As you'll see later, passing this allows you to monitor your Image as it loads.

The drawImage() method returns a boolean. If the image has completely arrived and could be painted successfully, then it returns true. Otherwise, drawImage() return false. If you put drawImage() in an if statement, you can tell it to repaint() until your image has completely arrived.

A Quick Example

You now have enough information to load and display an image. Let's try it out. We'll just draw on our applet, to create the simplest image-drawing program possible.

Here are the steps to follow:

  1. Obtain a GIF or JPG image which you want to display.
  2. Create an applet named Image1.java.
  3. Add an Image field to your applet and set it to null.
  4. In your init() method, load the image by calling getImage().
  5. Override the paint() method and call drawImage(), passing your Image object.
  6. If drawImage() returns false, call repaint(), passing a suitable delay factor.

Here's my copy of the Image1 applet:

Image1.java
import java.awt.*;
import java.applet.*;

public class Image1 extends Applet
{
  Image img = null;

  public void init()
  {
    img = getImage(getCodeBase(), 
                   "spam.jpg");
  }

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

Back to Top

Other drawImage() Methods

You might find a second version of drawImage() useful when displaying transparent GIF images that you don't want to be transparent. This version allows you to specify which color should be used to represent the transparent pixels in an image. Normally, the transparent pixels are transparent.

Method 1: Coloring
Here's an example. The code: 

g.drawImage(img, 0, 0, this);
g.drawImage(img, getSize().width/ 2, 0,
            Color.green, this);

produces the images shown here:

Method 2: Resizing
A third version of drawImage() lets you supply a width and height for the rendered image, as well as a position. The code:
int w  = getSize().width;
int h = getSize().height;

if (! g.drawImage(img, 0, 0, w, h, this))
  repaint(100);

produces the image you see here:

Method 3: Transformation
A final version of drawImage() takes ten arguments like this: 
g.drawImage(img,                // Image
            dx1, dy1, dx2, dy2, // Destination
            sx1, sy1, sx2, sy2, // Source
            this)

The first argument is an Image as before and the last argument is an ImageObserver [or this] as before.

The first four integer arguments, dx1, dy1, dx2, dy2 represent four points of the destination image. [This is unlike the previous version of drawImage() where the second two arguments are width and height, not a coordinate pair.] The second set of four integer arguments, sx1, sy1, sx2, sy2, represent the source image coordinates. The image rectangle represented by these coordinates is mapped to the destination image coordinates when the image is drawn. The code shown here:

g.drawImage(img, 
             0,  0, 145, 145,
            60, 58,   0,   0,
            this);

displays the image shown below. Notice that the point 68,58 in the image is drawn at position 0, 0 in on your screen while the pixel at 0,0 in the image is drawn at 145,145 on your screen.

Back to Top

Image Size

You already know how to find out how big your Component is; you use getSize().width and getSize().height. The Image class has a pair of methods for retrieving similar information from your images:

img.getWidth(null);
img.getHeight(null);

The argument to getWidth() and getHeight() is an ImageObserver, just like with drawImage(). As with drawImage(), if you pass this, instead of null, you'll be notified as your image loads. Until the image is completely loaded, getWidth() and getHeight() return -1.

Image Copy

If you need to paint the same image on the screen multiple times, then you can use drawImage(), but the Graphics copyArea() method is usually faster.

To use copyArea() you first define an area on the screen by specifying x, y, width, and height, and then another point to copy the image to. Here's an example:

public void paint(Graphics g)
{
  if (!g.drawImage(img, 0, 0, 25, 25, this))
    repaint(100);

  int h = (getSize().width  / 25) + 1;
  int v = (getSize().height / 25) + 1;

  for (int row = 0; row < v; row++)
    for(int col = 0; col < h; col++)
      g.copyArea(0, 0, 25, 25, 
                 col * 25, row * 25);
}

Here's the result. [The Buttons don't do anything. They're just there to add "color"]. This seems to look OK in Navigator, but has some problems on IE where it doesn't finish repainting unless you reload the page.

Back to Top

Waiting for Images

Painting images a piece at a time, as we've been doing, can result in some noticable flicker. A better solution is to wait until the image has fully arrived before trying to repaint it.

There are two ways to do this:

  • By overriding the imageUpdate() method in the class that loads your images.
  • By using a MediaTracker object.

ImageObserver

Every member of the Component class has the ability to monitor image loading, because the Component class implements the ImageObserver interface. To monitor image loading, you override the imageUpdate() method.

The documentation for imageUpdate() is a little inscrutable, to say the least. Fortunately, if all you want to do is wait for an image to load, you can just add the imageUpdate() method to your code, exactly as it appears here:
ImageUpdate.java
import java.awt.*;
import java.applet.*;

public class ImageUpdate extends Applet
{
  Image img = null;   public void init()
  {
    img = getImage(getCodeBase(), "tux.gif");
  }

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

  public boolean imageUpdate(Image im, 
         int flags, int x, 
         int y, int w, int h)
  {
    if ((flags & ALLBITS) == 0)
      return true;

    repaint();
    return false;
  }
}

When you add an imageUpdate() method to your program, make sure you remove the if statement in front of drawImage(), along with the repaint(), that you used previously to wait for your image to load. Just call drawImage() unconditionally in paint(), as shown above. As every chunk of image arrives at your computer, the updateImage() method is called. Inside the method you check if the entire image has arrived by testing the expression:

(flags & ALLBITS) == 0

What's That Mean?
This expression uses a bitwise AND operator to test whether two bits are both 1. We haven't studied these--and you won't be asked to use them--so don't be concerned if you don't recognize or understand the expression.

If this expression is true, then the image has not yet loaded, and you immediately return true, asking the image producer to notify you when the next "chunk" of image has arrived. When this expression is false--meaning the entire image has arrived and is loaded in memory--you invoke the repaint() method and return false, telling the image producer that you no longer wish to be notified. By placing a System.out.println() inside your imageUpdate() method, you can watch this loading process as it proceeds. Here's what happens when you do that with the code shown above. Note that the image is loaded starting at the top, which you can watch by monitoring the increase in y. 

Watching lines of an image arrive in the DOS console.

Back to Top

MediaTracker

A second way to wait for your images to load is to use a MediaTracker object. The MediaTracker class was designed to monitor the loading of all types of multimedia resources over the network. Only image-loading was ever implemented, however.

Using a MediaTracker is a three-step process:

  1. Create a MediaTracker object. To do this you call the single-argument MediaTracker constructor, passing this as the argument. Tell the MediaTracker to track your image loading, using the addImage(img, ID) method. The first argument is the Image object you want to monitor. The second is an arbitrary integer ID which you can use to identify the image you are tracking. If you want to wait for all of your images to load, then give them all the same ID. If you want to monitor each individually, give them separate ID numbers.
  2. Use the MediaTracker monitoring methods inside a try-catch block, which catches InterruptedException. The methods you'll use are waitForAll() or waitForID(ID).

Here's another short example based on the Image1 applet:

Image5.java
import java.awt.*;
import java.applet.*;

public class Image5 extends Applet
{
  Image img = null;   public void init()
  {
    img = getImage(getCodeBase(), "tux.gif");

    MediaTracker mt = new MediaTracker(this);
    mt.addImage(img, 0);
    try
    {
      mt.waitForID(0);
    }
    catch (InterruptedException ie) { }
  }

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

Something to Talk About

What changes do you need to make to modify Image1.java so that it prints the same image four times on your applet? (Assume that each image should be 1/2 the width and 1/2 the height of your applet, and should appear in each of the four corners.)

Please continue to the next section of this lesson.

 

Back to Top

 

Content Developed by Stephen Gilbert, Licensed under a Creative Commons License
Published by the Sofia Open Content Initiative
© 2004 Foothill-De Anza Community College District &The William and Flora Hewlett Foundation