| Home > Core Java FAQ
> Drawing FAQ |
| Drawing |
| Drawing
AWT Components (10) * Loading
and Drawing Images (17) * Images
(11) |
| |
|
Q .
How do I load an image from the net into my applet?
|
Ans :
You first
invoke getImage to establish a
pointer to the image's data source, and then invoke another
method, such as drawImage, to
actually start pulling the image data across the network.
The
primary function of the AWT Image class is to bridge
the gap between a source of image data and a specific output
format for rendering that data:
| image data
source => Image instance => rendering (pixels) in
specific format |
Correspondingly,
loading an image from a network data source happens in two stages.
You first create a connection to a source of image data via a URL.
Then you fetch and convert the image data to match your rendering
target.
For the simple case of displaying an
image on screen in your applet, the two loading stages translate
to the following method calls:
- Invoke
getImage
(from the Applet class) to register a URL as the
data source for the image.
- Invoke
drawImage
on a Graphics instance to render the image at a
given location, and optionally, scaled to a given size.
Step
1, invoking getImage, merely specifies a data source.
The drawImage method in step 2 initiates the real
work. It starts a combined process of transferring, converting,
and rendering the image data. Because this process can take some
time, the AWT handles it in a separate thread.
The following sample applet loads
and displays an image file:
import java.awt.*;
public class LoadImageExample extends java.applet.Applet {
Image myImage;
public void init() {
myImage = getImage(getDocumentBase(), "beach.gif");
}
public void paint(Graphics g) {
g.drawImage(myImage, 0, 0, this);
}
}
|
|
Q . How do I load an image from a file in a stand-alone Java application, rather than in an applet?
|
Ans :
Use one of
the getImage
methods in the AWT Toolkit class.
Applet
is the only Component subclass that provides its own getImage
method. To load an image outside of an applet, you need the Toolkit
class. Toolkit is an all-purpose utility class for
AWT functionality not belonging to any Component in
particular. Toolkit provides two getImage
methods, one taking a file name as an argument, and the other
taking a URL instance:
getImage(String)
getImage(URL)
If
the image file is on your local file system, you can invoke getImage
with a string that represents the file name. For example:
myImage = getToolkit().getImage("beach.gif");
The getToolkit
method returns a Toolkit instance, on which you can
then invoke getImage to create an Image
instance. That Image instance is connected to the
specified file as its source of data.
To access a remote file, you need to
invoke getImage with a URL argument. This requires
the extra step of creating the URL instance:
String urlString = "http://somemachine.com/somedir/somefile.gif";
try {
URL imageUrl = new URL(urlString);
myImage = getToolkit().getImage(imageUrl);
} catch (MalformedURLException e) {
// ... recover from bad URL string
}
|
|
Q . When is an image actually loaded--why not immediately?
|
Ans :
The AWT
seeks smarter and more efficient data transfer by waiting as long
as possible before starting to load images.
As
mentioned in, the two main steps for image loading are: (1) to
associate an Image instance with its data source
(using getImage) and (2) to fetch and convert the
image data.
The AWT defers the second step until
you invoke some method that specifically triggers the loading.
These methods are listed in Table. The central idea is to wait to
fetch the image data until the data is really needed. At this
point, there is often enough information, such as target size, to
make the process more efficient. The following scenarios
illustrate the savings in time and space.
Table
: Methods that Trigger Image Loading
| Class |
Methods |
Component |
prepareImage(Image,
ImageObserver), prepareImage(Image, int, int, ImageObserver) |
| Graphics |
drawImage
[family of four methods] |
| Image |
getWidth(),
getHeight(), getProperty() |
PixelGrabber |
grabPixels(),
grabPixels(long) |
MediaTracker |
wait,
check, and status family of methods |
Scenario
1—loading a large image for use at a smaller scale.
Suppose your source image is a GIF file that expands to 1 million
pixels (1000´1000), each requiring 4 bytes of storage. If you
automatically prefetch image data, your program must hold the full
4 megabytes in memory before deciding what to do with it. If all
you want is a 200´200 version of the image, you can call drawImage
and pass in the desired width and height as arguments:
// ... set x and y as desired
g.drawImage(myImage, x, y, 200, 200, this);
This triggers the
actual image loading. Your Image instance will
rescale the image as it fetches it, and the image data takes only
160,000 bytes of final storage (1/25th of the full-scale image).
Scenario 2—loading/filtering
a small portion of a large image. From the same-size image source
as Scenario 1 (1 million pixels), you might want to display only a
portion. The CropImageFilter class can extract a
subarea from a larger source image, and it does so without
requiring the full image data ever to be stored in memory.
Scenario 3—faster
web-page loading. Since an applet is usually just one among many
occupants on a web page, it is a good strategy to invoke only getImage
in your applet's init method. This allows your applet
to initialize quickly and not hinder the rest of the page from
loading. Your applet should wait until its start method to
initiate potentially lengthy image loads. By this point, the rest
of the web page will be available to the reader, and waiting for
images will be less irksome.
Delayed image loading provides
substantial benefits, but also has its share of surprising twists
that must be learned carefully. The drawImage method,
for instance, returns immediately without waiting for the image to
be completely loaded. Similarly, invoking getWidth or
getHeight methods on an image before it is fully
loaded will return the impossible value of -1. The AWT MediaTracker
class provides a key tool for overseeing the loading process
and for ensuring that valid data is available from an image.
|
|
Q . How can I make sure that my images are completely loaded before I check for their data or parameters?
|
Ans :
Use the MediaTracker
class.
The
AWT MediaTracker class lets you monitor how your
program loads images. (As the class's name suggests, MediaTracker
may handle other media objects in the future, such as sound.) MediaTracker
supports four kinds of operations for tracking images:
- specifying
groups of images to track
- waiting for
images to finish loading
- checking for
errors in the image-loading process
- checking the
current status of images being loaded
A
single MediaTracker instance can track any number of
images, either all together or in separate groups. After creating
a MediaTracker instance and as many Image
instances as you need, you can add each image to a tracking group
by invoking addImage on your MediaTracker
instance. For example:
/* code in an Applet subclass: */
MediaTracker imageTracker = new MediaTracker(this);
Image smallImage = getImage(getDocumentBase(), "small.gif");
Image bigImage1 = getImage(getDocumentBase(), "big1.gif");
Image bigImage2 = getImage(getDocumentBase(), "big2.gif");
imageTracker.addImage(smallImage, 4); // 1 image in group 4
imageTracker.addImage(bigImage1, 8); // 2 images in a group 8,
imageTracker.addImage(bigImage2, 8); // e.g., for animation
This code fragment
starts with a MediaTracker constructor, which
requires that you specify a component that will be the ultimate
consumer of the image data. The next three lines create Image
instances, and the final three lines specify that one image
belongs to group 4 and the other two belong to group 8. You can
choose whatever numbers you like for your image groups—MediaTracker
merely uses the number as a tag for the group as a whole. Note
that the JDK 1.0.2 provides methods only for adding images to
tracking groups; the JDK 1.1 provides a matching set of methods
for removing images from tracking groups.
The grouping of images allows other MediaTracker
methods to apply to either a specific image group or to all images
that the MediaTracker instance knows about. This
distinction pervades the rest of the MediaTracker
methods, as shown in Table.
Table
: MediaTracker
Methods
| Method
family |
Apply
to images in specified group |
Apply
to all images |
| wait
for loading to complete |
waitForID(int) |
waitForAll() |
waitForID(int,
long) |
waitForAll(long) |
| check
for loading errors |
isErrorID(int) |
isErrorAny() |
getErrorsID(int) |
getErrorsAny() |
| check
loading status |
checkID(int) |
checkAll() |
checkID(int,
boolean) |
checkAll(boolean) |
statusID(int,
boolean) |
statusAll(boolean) |
Methods
in the wait... family do not return until all images
in the specified set of images have either finished loading or
encountered an error. (The optional long parameter specifies a
maximum number of milliseconds to wait, rather than waiting
indefinitely.) These methods also trigger loading of any images
that haven't already started loading. Continuing the example just
started, you could use the following code to wait for all images
in group 8 to complete loading:
try {
imageTracker.waitForID(8);
} catch (InterruptedException e) {
// ... handle exception
}
Note that waitForID,
like several other methods that potentially suspend a thread's
execution for a period of time, can throw an InterruptedException.
The isError... and getErrors...
methods check whether any errors have yet occurred while loading
the specified set of images. The isError... methods
return a boolean value indicating whether an error
occurred. The getErrors... methods return an Object
array indicating specifically which images had errors (or null
if none of the specified images had an error).
Finally, the loading status family
of methods, check... and status...,
report on the loading progress of the specified set of images. The
check... methods return a boolean value
indicating whether the images have finished loading. The status...
methods report more specifically what states the images are in:
not started, LOADING, COMPLETE, ERRORED,
or ABORTED. The optional boolean
parameter in the loading status methods specifies whether the
method should also trigger loading for any images in the set that
haven't yet started loading.
|
|
Q . Why does my call to Graphics's drawImage method fail to show the image?
|
Ans :
The Graphic's
drawImage method returns
immediately, whether or not the image data is available; you need
to check the image status by other means.
The
drawImage method in the Graphics class
has two main tasks: it triggers the loading of image data across
the network if the image data isn't already present, and it issues
a request to start the drawing of the image. The method itself,
however, is asynchronous—it issues requests to start this
activity, but it returns immediately without waiting to see that
everything has properly finished. If either the loading or drawing
process goes astray, some other part of your program needs to
detect and handle the situation.
Two different objects can track the
progress of image loading and rendering. First, all versions of drawImage
require an ImageObserver object, that is, an object
that implements the ImageObserver interface. The AWT
invokes the imageupdate method on the ImageObserver
object whenever the image's status changes—typically either
additional data to render or trouble in loading.
Instances of any component subclass,
including an applet, can serve as an ImageObserver,
by using their imageupdate method to monitor images being drawn
onto them. However, the default imageupdate method in component
does not check for image errors; it merely repaints
the component periodically to reflect any newly available image
data. If you want to catch errors with an ImageObserver
object, you need to override imageupdate to check explicitly for
error status in the method's flags argument.
Another way to catch image failures
is to use a MediaTracker instance. After registering
your images with a MediaTracker object, you can wait
for the images to finish loading and then check whether any images
were stopped with an error. For example:
Object[] imageErrorList;
public void start() {
// ...
try {
imageTracker.waitForAll();
imageErrorList = imageTracker.getErrorsAny();
} catch (InterruptedException e) {
// ... handle exception;
}
public void paint(Graphics g) {
g.drawImage(backImage, 0, 0, this);
g.drawImage(foreImage, 130, 115, this);
g.drawImage(bogusImage, 10, 10, this);
if (imageErrorList != null) {
String errors = imageErrorList.length
+ " image(s) could not be loaded.";
g.setColor(Color.red);
g.drawString(errors, 10, 255);
}
}
|
|
Q . Can I force Applet's getImage method to make a new connection for each image rather than reusing a cached version of the image?
|
Ans :
Yes; use
the flush method in the Image
class.
Although
the default behavior for getImage is to try first to
use a cached image copy, you can cancel this by invoking flush on
the Image instance:
// ... myImage instance already created
myImage.flush();
// ... any use of myImage will now trigger a fresh download
This clears the Image
instance so that it must be recreated from scratch or refetched
across the net.
|
|
Q . How do I draw text over a background image?
|
Ans :
Use the Graphics
class's drawString method and draw
the text directly on top of the image.
The
drawString method in the Graphics class
draws only the pixels that make up the letter shapes. All other
space amidst the text is transparent and lets the underlying image
show through.
You might be tempted to place a Label
component on top of your image, but the effect will be quite
unsatisfactory. Label instances always have a
background color; the Label provides the text but
also covers the image with an opaque background rectangle.
|
|
Q .
How do I load and display a transparent GIF image over a background image?
|
Ans
:
Use the
same steps as for any other image.
Loading
and displaying an image with transparency involves the same
programming steps as a fully opaque image. The difference is just
in how the image is rendered. The AWT doesn't paint anything for
the transparent pixels of an image. Displaying a transparent image
over a background image requires only that you draw the foreground
image on top of the background image:
public void paint(Graphics g) {
// ... set x and y to position foregroundImage
g.drawImage(backgroundImage, 0, 0, this);
g.drawImage(foregroundImage, x, y, this);
}
|
|
Q . How can I create an image from a buffer of raw image data (red, green, and blue values for each pixel)?
|
Ans
:
Use the MemoryImageSource
class in the java.awt.image package.
The MemoryImageSource
class uses an array of ints to produce pixel values
for an image. In the default color model, shown in Table 8.3, int
values carry four channels of information:
Table
8.3: Default Color Model—32-bit int
Value
| top
8 bits (0xFF << 24) |
alpha
(opacity) value |
| 2nd
8 bits (0xFF << 16) |
red
value |
| 3rd
8 bits (0xFF << 8) |
green
value |
| bottom
8 bits (0xFF) |
blue
value |
Each 8 bits is
interpreted as an unsigned value ranging from 0 to 255, with 255
indicating full presence of the color or full opacity.
The array of int
elements provides one-dimensional storage for a conceptually
two-dimensional grid of values. The array index progresses from
left to right in a row and then back to the start of the next row
down. For example, a grid with four rows by six columns would
yield the following arrangement of indices:
0 1 2 3 4 5
6 7 8 9 10 11
12 13 14 15 16 17
18 19 20 21 22 23
Once
you've created your int array, build a MemoryImageSource
object from that, and then create an Image instance from the MemoryImageSource
object:
int width, height;
int[] pixels;
// ... set values for width, height, and pixels as desired
Image myImage = createImage(new MemoryImageSource(width, height,
pixels, 0,
width));
Running
animation from a MemoryImageSource object is clumsy
in JDK 1.0.2, but significantly easier in JDK 1.1
|
|
Q . What is double buffering--how can I create and draw to an offscreen image?
|
Ans
:
Double
buffering is a drawing optimization in which you draw off screen
first to create a complete image and then copy that image onto the
screen all at once.
Using
an offscreen image for drawing is only a little more complicated
than drawing directly to onscreen components. The two extra steps
are: (1) to create an offscreen Image instance and
(2) to get a reference to its Graphics instance. For
example:
int offscreenWidth = ...;
int offscreenHeight = ...;
Image offscreenImage = createImage(offscreenWidth, offscreenHeight);
Graphics offscreenGraphics = offscreenImage.getGraphics();
At this point you can
draw or place images using this new Graphics instance
the same way as with any other Graphics instance. The
following code continues from the previous example and draws a
series of green squares onto a black background within the
graphics context of the offscreen image:
public void init() {
// ...
int offScreenSize = Math.min(offscreenWidth, offscreenHeight);
offscreenGraphics.setColor(Color.black);
offscreenGraphics.fillRect(0, 0,
offscreenSize, offscreenSize);
offscreenGraphics.setColor(Color.green);
for (int i = 1; i < offscreenSize; i += 6) {
int x = (offscreenSize - i) / 3;
int y = (offscreenSize - i) / 3;
offscreenGraphics.drawRect(x, y, i, i);
}
}
Finally, you can
render the offscreen image onto the screen:
public void paint(Graphics g) {
// ... set x and y to position the top-left corner
// of the image
g.drawImage(offscreenImage, x, y, this);
}
Using
offscreen images has the main advantage that a complex image can
be created once out of view of the user and then rendered on
screen as many times as needed. This technique usually results in
smoother animation, less flicker, and greater efficiency.
|
|
Q .
How can I get at the raw data of an image, such as the pixel value at a given coordinate?
|
Ans
:
The PixelGrabber
class (in the java.awt.image
package) lets you extract pixel values from all or part of an
image.
Creating
an AWT Image instance does not immediately create an
array of pixel values for the image; it specifies a connection to
a data source for the image but defers loading the image data
until some specific operation requires it. What an Image
instance really represents, thus, is a conduit for obtaining image
data rather than a stocked data repository. PixelGrabber
is a convenience class that obtains pixel data from an Image
instance and makes that data available for your inspection at the
pixel level.
To use PixelGrabber,
you specify all the important information up front in the PixelGrabber
constructor. After that, you invoke grabPixels on the
PixelGrabber instance (and provide for the InterruptedException
that the method might throw). For example:
// ... set width and height as desired
Image myImage = ...
int[] pixels = new int[width * height];
PixelGrabber pg = new PixelGrabber(myImage,
x, y, width, height,
pixels,
0, width);
try {
pg.grabPixels();
} catch (InterruptedException e) {
// ... handle exception
}
In
this example, the x, y, width,
and height arguments in the PixelGrabber
constructor determine a rectangular region in the image: the
region's top-left point (x, y) and its size (width,
height). The grabPixels method pulls pixel
values from this region and stores them as int values in the
pixels array.
The int values in the
pixel array encode colors following the AWT default color model,
which allots 1 byte each for opacity (alpha), red, green, and blue
values . To obtain Color instances corresponding to
the extracted pixel values, use the Color(int)
constructor:
// ... set index as desired
Color pixelColor = new Color(pixels[index]);
Unlike
many other image-related methods, grabPixels is
synchronous, so you can't be sure how long it takes to return. For
robust applets, you should avoid calling grabPixels
in any of the methods called within a system thread, such as init,
start, and paint
|
|
Q . If you have an InputStream (rather than a file) that contains an Image, how can you display it?
|
Ans
:
Use this method, and some adroit shuffling.
Toolkit.getImage(URL url)
Create a thread that pretends to be an http server. Make it listen to some port (8765 for example) for incoming requests. When the thread
gets a request, it should simply whisk up the appropriate http headers
and follow it by the InputStream. Thus the component that has the input
stream and wants to do the getImage(url) can now invoke:
Toolkit.getImage("localhost:8765/")
The thread will act as a stream-to-url adapter, and send back the data It saves you from having to read 200K of JPEG data before you can begin
drawing anything.
|
|
Q . I loaded an Image file from a JPEG/GIF file using the Toolkit/Applet.createImage(URL/String) method, and (the height and width are -1 / it will not draw to the screen). What is wrong?
|
Ans
:
The behaviour of the AWT on creating images in this way is to do
nothing at all.
When the image is first drawn using Component.drawImage(), or its size is requested, the image begins to load in a different Thread.
As the image loads, the ImageObserver specified in the drawImage()/getHeight() call is periodically notified of the loading
status of the image, by calls to its imageUpdate() method. In the case of Component.drawImage() call, the default behavior of
Component.imageUpdate() is to schedule *another* repaint() call when the image has fully loaded. This means that, in particular the
following code will not work:
class MyComponent extends Component {
...
public void paint(Graphics g) {
ImageFilter cropper=new CropImageFilter(0,0,16,16);
Image cropped_image=createImage(new
FilteredImageSource(image.getSource(),cropper));
g.drawImage(image,10,400,this); // this line works
// this line doesn't -
g.drawImage(cropped_image,400,15,this);
}
}
The cropped_image will not be created in time to be painted, and whenit is finally created, another call will be scheduled to paint, which
will try to draw another one, etc. (Note also that creating objects like this in paint() methods isgenerally a very poor idea in Java, since they are called very
frequently, and you will strongly offend the garbage collector. In order to get round this problem, you may i) add all such Images to a
MediaTracker, and call the waitForAll() method. ii) implement your own
ImageObserver interface, and wait for the imageUpdate() method to be
called with the ALLBITS/FRAMEBITS value. i) is easier, but ii) is recommended, since there are reports of MediaTracker not working in
some environments.
|
|
Q . Does Java support PNG?
|
Ans
:
Yes. PNG - Portable Network Graphics
- provides a patent-free replacement for GIF and TIFF. If you save a
GIF, don't forget to pay the royalty to Unisys - see Unisys's web page
at . That patent is why GIFs are a poor choice for internet images.
The PNG format is specified in RFCs 1950, 1951, and 2083, and is unencumbered by licenses or patents. See also the PNG-1.1 specification
at ftp://swrinde.nde.swri.edu/pub/png/documents.
The PNG format is supported by the Java Advanced Imaging API which is part of the 1.2 media APIs.
|
|
Q . Why didn't my text display in my GUI? Is the Inset wrong?
|
Ans
:
The most common Inset problem is not an Inset problem at all, but
rather that people just assume the x,y location of a
Graphics.drawString() actually refers to the top left part of the string image. In fact it refers to the baseline. So you'll need to take
the font metrics into account:
g.drawString("Hello World",0,getFontMetrics(getFont()).getAscent());
|
|
Q . Why did my polygon come out the wrong shape?
|
Ans
: This question and answer comes directly off
comp.lang.java.programmer, and deserves to be immortalized for posterity.
When I use fillPolygon with the following points I get two inverted triangles instead of a rectangle. Why?
int xPoints[] = {71, 78, 71, 78};
int yPoints[] = {147, 147, 130, 130};
g.fillPolygon(xPoints, ypoints, xPoints.length);
Developer Felix Pahl supplied the answer in limerick form:
o A developer (for details bored her) didn't follow the polygon's border
so instead of right angles
she got two triangles 'cause the endpoints were in the wrong order!
You must put the points in the order you would encounter them in if you went round the polygon's border. The filling algorithm is doing the
right thing! Try drawing the points on paper to see:
71,130 78,130
O--------O
| |
| |
| |
O--------O
71,147 78,147
Under JDK1.1, the two endpoints are connected automatically and you would order the array elements as:
int xPoints[] = { 71, 78, 78, 71};
int yPoints[] = {130, 130, 147, 147};
Under JDK1.0.2, you have to explicitly connect the two endpoints, and you would write the array elements as:
int xPoints[] = { 71, 78, 78, 71, 71};
int yPoints[] = {130, 130, 147, 147, 130};
|
|
Q . Do DrawRect and FillRect work on rectangles of the same size?
|
Ans
:
No. java.awt.Graphics.drawRect draws a rectangle that's 1 pixel
wider and 1 pixel taller than a rectangle drawn by fillRect.
|
|
|
|