Loading Images


A  Java applet can load images in GIF or JPG format using the 'getImage' method. In fact, that method is pretty slick: it returns immediately, and - if the URL did refer to a valid image - the rest of the program can go on without interruption. For example, if the method
public void init() 
{ 
   image = getImage(myURL);
   setup(); 
}
executes, it does not wait until the image is completely retrieved from the URL. Instead, it simply starts loading the image and returns immediately, so that the next method 'setup()' can execute right away. That allows your applet to load the rest of its pieces, if necessary, and to start executing without having to wait for a long file coming from a potentially slow Internet connection.

Here is a simple example of an applet that will load an image and display it on the screen (we will later expand it to a simple SlideShow applet.
import java.applet.Applet;
import java.awt.*;

public class ImageTest extends Applet
{
   private Image img;

   public void init()
   {
      img = null;
   }
   public void loadImage()
   {
      try
      { 
         img = getImage(getDocumentBase(), "image1.gif");
      }
      catch(Exception e) { }
   }
   public void paint(Graphics g)
   {
      if (img == null)
         loadImage();
      g.drawImage(img, 0, 0, this);
   }
}
So, the steps are simple: a field img of type Image is needed to store an image. Initially, it is set to null. When the applet initializes and (automatically) calls the paint method, that method in turn will call loadImage (because initially the img is set to null). The loadImage method calls, in a try-catch block, the getImage method. The image name is set to "image1.gif" and the location is the same location as the HTML file containing the applet (achived by the getDocumentBase() method).

We have previously seen how to use the PARAM tags inside an applet to pass parameters from a web page into an applet. Let's use that approach to modify the above applet so that you can pass the name of the image as a parameter into the applet.
import java.applet.Applet;
import java.awt.*;

public class ImageTest extends Applet
{
   private Image img;
   private String imgName;

   public void init()
   {
      img = null;
      imgName = getParameter("imageName");
   }
   public void loadImage()
   {
      try
      {
          img = getImage(getDocumentBase(), imgName);
      }
      catch(Exception e) { }
   }
   public void paint(Graphics g)
   {
      if (img == null)
         loadImage();
      g.drawImage(img, 0, 0, this);
   }
}
Of course, you now must adjust the HTML code to something similar to the following:
<HTML>
<APPLET CODE="ImageTest.class" WIDTH=350 HEIGHT=350>
   <PARAM NAME="imageName" VALUE="image1.gif">
</APPLET>
Now our applet is more useful, because it can handle any image (in the same directory as the applet and the HTML file): just use the appropriate name in the PARAM tag, and the applet will load that image.

We have said that the getImage() method will return immediately. But, when you execute the paint method to draw the image, it may only be able to draw an unfinished picture, since it may not be loaded completely. Moreover, what if the paint method is called before the image has completed loading, but then is not called for a while even though the image is now complete ? To avoid that problem, the 'drawImage' method will inform the "ImageObserver" that the image is now loaded a little more, and the "ImageObserver" will then redraw the image until it is completed. Recall the Java API description for the drawImage method:
public abstract boolean drawImage(Image img, int x, int y, ImageObserver observer)
Draws the specified image with its top left corner at  in this graphics context's coordinate space. Transparent pixels in the image do not affect whatever pixels are already there.

If the image has not yet been completely loaded, the image observer's imageUpdate method is notified as more of the image becomes available.

public abstract boolean drawImage(Image img, int x, int y, 
                                  int width, int height, ImageObserver observer)
Draws the specified image inside the specified rectangle of this graphics context's coordinate space. The image is  scaled if necessary. Transparent pixels in the image do not affect whatever pixels are already there.

If the image has not yet been completely loaded, the image observer's imageUpdate method is notified as more of the image becomes available.

Thus, a typical call to the 'drawImage' method would be:
public void paint(Graphics g) 
{ 
   g.drawImage(myImage,0,0,this); 
}
so that the 'current object' is notified when the image has loaded completely.

Now, let's try to expand on the above applet to draw not one but several images, and to provide buttons for displaying the "next" and "previous" picture in a slideshow fashion.
we need two buttons as fields for next and previous image
we need to modify the img field to now be an array of images
we need an array of Strings to store the image names
we need an action method to act when a button is clicked
the action method will use "handleNext" and "handlePrev" methods
we need to modify the loadImage method to always load the "current" image
we need to modify the paint method to always draw the "current" image
we therefore need an extra field of type int for the "current" image
Here is the sample code:
import java.applet.*;
import java.awt.*;

public class SlideShow extends Applet
{
   private Button prev = new Button("Previous");
   private Button next = new Button("Next");
   private Image img[] =  new Image[3];
   private String imgNames[] = {"image1.gif", "image2.gif", "image3.gif"};
   private int currentImage = 0;

   public void init()
   {
      Panel buttons = new Panel();
      buttons.add(prev);
      buttons.add(next);
      setLayout(new BorderLayout());
      add("North", buttons);
      for (int i = 0; i < imgNames.length; i++)
         img[i] = null;
   }
   public boolean action(Event e, Object o)
   {
      if (e.target == prev)
         handlePrev();
      else if (e.target == next)
         handleNext();
      return true;
   }
   public void paint(Graphics g)
   {
      if (img[currentImage] == null)
         loadImage();
      g.drawImage(img[currentImage], 0, 0, this);
   }
   public void loadImage()
   {
      try
      {
         img[currentImage] = getImage(getDocumentBase(), imgNames[currentImage]);
      }
      catch(Exception e)
      {
         System.err.println("Error loading image");
      }
      repaint();
   }
   public void handlePrev()
   {
      if (currentImage > 0)
         currentImage--;
      else
         currentImage = imgNames.length-1;
      repaint();
   }
   public void handleNext()
   {
      if (currentImage < imgNames.length-1)
         currentImage++;
      else
         currentImage = 0;
      repaint();
   }
}
Of course, you need three images for this to work, and their names have to match the "hard-coded" names above. When you do run the above applet, you will find that the top of the images is acutally cut off. That is because the paint method will use the entire applet area to paint. The top of the applet is, however, already occupied by the row of buttons which will chop of the top of the images. As an easy solution for this problem, modify the g.drawImage method to use an appropriate integer different from zero for drawing the images at some y location. The other, and proper, solution would be to use a canvas for the actual drawing, and place the canvas via a layout manager into the applet.

We currently designed or SlideShow applet so that it will load an image only when it is needed. That means that a user does not have to wait until all images are loaded: the first image will display as soon as it is loaded without waiting for the remaining images to load. However, sometimes you may want wait until one or more images are entirely loaded before continuing with the rest of the program. For example, when doing animation, you do want to make sure all images are entirely loaded first before starting the animation. Otherwise, the animation may look very strange with partically loaded images. For that purpose, Java provides a 'MediaTracker' class. Here's part of what the Java API says:
public class MediaTracker extends Object
{   // Constructors
    public MediaTracker(Component  comp); 
    // Selected Methods
    public void addImage(Image  image, int  id);
    public void waitForAll();
    public void waitForID(int  id);
}
To use a MediaTracker, first instatiate one and tie it to the current component using the 'this' keyword. Then add all images that need to be loaded to the MediaTracker. The images still need to be loaded as usual, using 'getImage', and they will start loading 'in the background' immediately. But now the MediaTracker can inform you (or whatever component it was tied to) about the status of the loading process. Using the 'waitForAll' or 'waitForID(int id)' methods, for example,  will wait until either all images, or an image with a particular id, have completed loading. This is what's called a 'blocking' method, meaning that once calls it blocks execution of the rest of the program until, in this case, the indicated image(s) have finished loading.  Here is the above SlideShow applet, but now it will first load all images, then continue to execute.
import java.applet.*;
import java.awt.*;

public class SlideShow extends Applet
{
   private Button prev = new Button("Previous");
   private Button next = new Button("Next");
   private MediaTracker tracker = null;
   private Image img[] =  new Image[3];
   private String imgNames[] = {"image1.gif", "image2.gif", "image3.gif"};
   private int currentImage = 0;

   public void init()
   {
      Panel buttons = new Panel();
      buttons.add(prev);
      buttons.add(next);
      setLayout(new BorderLayout());
      add("North", buttons);
      loadImages();
   }
   public boolean action(Event e, Object o)
   {
      if (e.target == prev)
         handlePrev();
      else if (e.target == next)
         handleNext();
      return true;
   }
   public void paint(Graphics g)
   {
      g.drawImage(img[currentImage], 0, 40, this);
   }
   public void loadImages()
   {
      tracker = new MediaTracker(this);
      try
      {
         for (int i = 0; i < imgNames.length; i++)
         {
            img[i] = getImage(getDocumentBase(), imgNames[i]);
            tracker.addImage(img[i], i);
         }
      }
      catch(Exception e)
      {
         System.err.println("Error loading image");
      }

      try
      {
         tracker.waitForAll();
      }
      catch(Exception e)
      {
         System.err.println("Unknown error while loading images");
      }
   }
   public void handlePrev()
   {
      if (currentImage > 0)
         currentImage--;
      else
         currentImage = imgNames.length-1;
      repaint();
   }
   public void handleNext()
   {
      if (currentImage < imgNames.length-1)
         currentImage++;
      else
         currentImage = 0;
      repaint();
   }
}
We have removed the call to loadImage in the paint method. Instead, we have added a call to loadImages to the init method. That means that as the applet instantiates, it will load all images. The loadImages method uses a MediaTracker to make sure all images are completely loaded before continuing. The paint method can therefore assume that all images are available, and does therefore not need a call to loadImage any longer.

This time there will be a delay (during which you should really use the showStatus message to keep the user informed), but then the images will appear all at once, not incrementally as before. Note that the MediaTracker's addImage and waitForID methods must been placed inside the try-catch block. Aside from the fact that that is really where they should go logically, the waitForID method also throws an Exception that must be caught.

As improvements to the SlideShow for your portfolio, you might want to consider the following modifications:
Make the SlideShow "user configuarable" via "PARAM" tags in the applet. Your code, for example, might work with an HTML file containing an applet tag as follows:
<APPLET CODE="SlideShow.class" WIDTH=350 HEIGHT=350>
   <PARAM NAME="numberImages" VALUE=4>
   <PARAM NAME="image1" VALUE="fahy.gif">
   <PARAM NAME="image1" VALUE="mooney.gif">
   <PARAM NAME="image1" VALUE="stillman.gif">
   <PARAM NAME="image1" VALUE="corrigan.gif">
</APPLET>
Add a thread together with the standard start and stop methods, and the appropriate run method so that the slide show will automatically advance the slides, and you can still press the buttons as well. Use a Canvas to correctly place the images via a layout manager.