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. |