Up to
this point, we’ve confined ourselves to working with the
high-level drawing commands of the Graphics2D
class, using images in a hands-off mode. In this section, we’ll
clear up some of the mystery surrounding images and see how they are
created and used. The classes in the
java.awt.image
package handle images and their
insides; Figure 18.1 shows the important
classes in this package.[47]
First, we’ll return to our discussion of image observers and
see how we can get more control over image data as it’s
processed asynchronously by GUI components. Then we’ll open the
hood and have a look at the inside of a
BufferedImage
. If you’re interested in
creating sophisticated graphics, such as rendered images or video
streams, this will teach you about the foundations of image
construction in Java.
The architects of Java realized that
images might take some time to load over a slow network. Image observers implement the
ImageObserver
interface. They are effectively nosy
neighbors of images that watch as the image data arrives.
An image is simply a rectangle of pixels. A pixel has both a color and a transparency; the transparency specifies how pixels underneath the image show through. For a static image, such as a GIF or JPEG data file, the observer is notified when the entire image is complete, and production is finished. For a video source or animation, the image observer would be notified repeatedly (at the end of each frame) as a continuous stream of pixel data was received.
The observer is notified as new portions of the image and new
attributes are ready. Its job is to track this information and let
another part of the application know its status. The image observer
is essentially a callback that is notified asynchronously as the
image is built. The default Component
class image
observer that we used in our previous examples called
repaint( )
for us each time a new section of the
image was available, so that the screen was updated more or less
continuously as the data arrived. A different kind of image observer
might wait for the entire image before telling the application to
display it; yet another observer might update a loading meter showing
how far the image loading had progressed.
To be an image observer, you have to implement the single method,
image-Update( )
, defined by the
java.awt.image.ImageObserver
interface:
public boolean imageUpdate(Image image, int flags, int x, int y, int width, int height)
imageUpdate( )
is called by the graphics system,
as needed, to pass the observer information about the construction of
its view of the image. Essentially, any time the image changes, the
observer is notified so it can perform any necessary actions, like
repainting. image
holds a reference to the
Image
object in question. flags
is an integer whose bits specify what information about the image is
now available. The values of the flags are defined as
static
variables in the
ImageObserver
interface, as shown in Table 18.1.
Table 18-1. ImageObserver Information Flags
Flag |
Description |
---|---|
|
The height of the image is ready. |
|
The width of the image is ready. |
|
A frame is complete. |
|
Some new pixels have arrived. |
|
The image is complete. |
|
The image loading has been aborted. |
|
An error occurred during image processing; attempts to display the image will fail. |
The flags determine which of the other parameters,
x
, y
, width
,
and height
, hold valid data and what that data
means. To test whether a particular flag in the
flags
integer is set, we have to resort to some
binary shenanigans. The following class,
MyObserver
, implements the
ImageObserver
interface; it reports on the
information it receives:
//file: MyObserver.java import java.awt.*; import java.awt.image.*; class MyObserver implements ImageObserver { public boolean imageUpdate( Image image, int flags, int x, int y, int width, int height) { if ( (flags & HEIGHT) !=0 ) System.out.println("Image height = " + height ); if ( (flags & WIDTH ) !=0 ) System.out.println("Image width = " + width ); if ( (flags & FRAMEBITS) != 0 ) System.out.println("Another frame finished."); if ( (flags & SOMEBITS) != 0 ) System.out.println("Image section :" + new Rectangle( x, y, width, height ) ); if ( (flags & ALLBITS) != 0 ) { System.out.println("Image finished!"); return false; } if ( (flags & ABORT) != 0 ) { System.out.println("Image load aborted..."); return false; } return true; } }
The imageUpdate( )
method of
MyObserver
is called by the consumer periodically,
and prints simple status messages about the construction of the
image. Notice that width
and
height
play a dual role. If
SOMEBITS
is set, they represent the size of the
chunk of the image that has just been delivered. If
HEIGHT
or WIDTH
is set,
however, they represent the overall image dimensions. Just for
amusement, we have used the java.awt.Rectangle
class to help us print the bounds of a rectangular region. (You may
not want to create a new object each time you just need to report
some coordinates.)
imageUpdate( )
returns a
boolean
value indicating whether or not it’s
interested in future updates. If the image is finished or aborted,
imageUpdate( )
returns false
to
indicate it isn’t interested in further updates. Otherwise, it
returns true
.
The following example uses a MyObserver
object to
generate information about an image as it is loaded. (To see the
messages, enable your browser’s Java console.)
//file: ObserveImage.java import java.awt.*; public class ObserveImage extends java.applet.Applet { Image img; public void init( ) { img = getImage( getClass( ).getResource(getParameter("img")) ); MyObserver mo = new MyObserver( ); img.getWidth( mo ); img.getHeight( mo ); prepareImage( img, mo ); } public void paint(Graphics g) { g.drawImage(img, 0, 0, null); } }
After requesting the Image
object with
getImage( )
, we perform three operations on it to
kick-start the loading process. getWidth( )
and
getHeight( )
ask for the image’s width and
height. If the image hasn’t been loaded yet, or its size
can’t be determined until loading is finished, our observer
will be called when the data is ready. prepareImage( )
asks that
the image be readied for display on the component. It’s a
general mechanism for starting the process of loading, converting,
and possibly scaling the image. If the image hasn’t been
otherwise prepared or displayed, this happens asynchronously, and our
image observer will be notified as the data is constructed.
You should be able to see how we could implement all sorts of
sophisticated image loading and tracking schemes. The two most
obvious strategies, however, are to draw an image progressively, as
it’s constructed, or to wait until it’s complete and draw
it in its entirety. We have already seen that the
Component
class implements the first scheme.
Another class, java.awt.MediaTracker
, is a general
utility that tracks the loading of a number of images or other media
types for us. We’ll look at it next.
[47] Before Java 2, creating and modifying images was the domain of image producers and consumers. We won’t be covering these topics in this chapter; instead, we’ll stick to the “new stuff,” which is more capable and easier to use in some cases.
Get Learning Java now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.