| Home > Core Java FAQ
> Drawing FAQ |
| Drawing |
| Drawing
AWT Components (10) * Loading
and Drawing Images (17) * Images
(11) |
| |
|
Q .
Using the JDK 1.0.2, can I clear or reset a clipping rectangle that either I or the system has created?
|
Ans :
In the JDK
1.0.2 you can't; for a given Graphics
instance, the drawable region represented by the clipping
rectangle can only get smaller as new clipping rectangles are
specified (this restriction is no longer true in the JDK 1.1).
A Graphics instance
provides a drawing space to your program in two parts:
- a coordinate
space to locate the pixels affected by your graphics
operations
- a clipping
rectangle outside of which your graphics operations have no
effect
You can change the
coordinate space with the Graphics translate method,
and you can change the clipping rectangle with the Graphics
clipRect method.
The clipRect method,
however, does not simply set a new clipping rectangle. Instead,
the rectangle you specify as the argument to clipRect
is intersected with the existing rectangle to yield the new
drawable region. Each new clipping rectangle is contained wholly
within the previous one. This ensures, for example, that
components cannot draw outside the bounds of their containers, all
the way up the containment chain. Similarly, no part of an applet
can ever draw outside its bounds onto the rest of the web page.
If you need more freedom in using
multiple clipping rectangles within your program, you can create
separate "throwaway" graphics objects as children of
your main Graphics instance. Below is an example of
this cumbersome workaround:
/* using JDK 1.0.2: */
public void paint(Graphics g) {
Graphics ng;
int x, y, width, height;
// ... set x, y, width, and height as needed
ng = g.create(x, y, width, height);
ng.drawSomething();
ng.dispose();
// ... set new values for x, y, width, and height
ng = g.create(x, y, width, height);
ng.drawSomethingElse();
ng.dispose();
// ... and so on
}
The Graphics
create(x, y, w, h) method creates a new Graphics
instance with the clipping rectangle (x, y, w, h)
based on the old Graphics instance. The origin (0,
0) in the new Graphics instance corresponds to
(x, y) in the parent Graphics context.
Also, any clipping rectangle in the child will still be
constrained to lie completely within the parent's clipping
rectangle.
The JDK 1.1 provides direct support
for resettable clipping rectangles, which removes the need for the
above workaround
|
|
Q . Using the JDK 1.0.2, can I copy a subarea of one image into another image?
|
Ans :
Yes, but
it's not as easy as it should be (and this is fixed in the JDK
1.1); the trick in the JDK 1.0.2 is to use the clipping rectangle
of the receiving image to delimit the subarea of the source image.
Like
all drawing methods in the Graphics class, drawImage
honors the current clipping rectangle of the Graphicsinstance.
In other words, the only part of the image that shows up is the
part within the Graphics clipping rectangle. The
following code fragment illustrates how to take advantage of this
clipping behavior—it loads one image from a file, creates a
second image that is one-half as long on each edge, and then draws
a portion of the main image into the smaller image.
String srcImageName = ...;
int size = ...;
Image srcImage = getImage(getDocumentBase(), srcImageName);
Image dstImage = createImage(size/2, size/2);
Graphics dstImageGraphics = dstImage.getGraphics();
// ... select x, y to indicate the upper-left corner
// of the subarea (e.g., size/4, size/4)
dstImageGraphics.drawImage(srcImage, -x, -y, this);
Understanding the last
line of this code fragment is the key. That line tells the dstImageGraphics
instance to render a copy of srcImage such that point (0, 0)
of srcImage aligns with point (-x, -y)
in the dstImage's coordinate space.
Now, which part of srcImage gets
copied into dstImage? Only the part that falls within
dstImageGraphics's clipping rectangle: from (0,
0) in the top-left corner to (size/2, size/2)
in the bottom-right corner. Table 8.4 shows the key
correspondences that map from dstImage's clipping
rectangle into srcImage's coordinates:
Table
8.4: Coordinate Correspondences
dstImage |
srcImage |
(-x,
-y) |
(0,
0) |
(0,
0) |
(x,
y) |
(size/2,
size/2) |
(x
+ size/2, y + size/2) |
Thus, in srcImage's
coordinates, the subarea that gets copied ranges from (x, y)
in the top-left corner to (x + size/2, y + size/2) in
the bottom-right corner.
If you want to get fancier and draw
the image subarea to a specific point in the second image, you
need to restrict the clipping rectangle on the target rectangle.
The following code, for example, copies srcImage's
subarea (srcX, srcY, w, h) to point (dstX, dstY)
in dstImage:
dstImageGraphics.clipRect(dstX, dstY, w, h);
dstImageGraphics.drawImage(srcImage, dstX - srcX, dstY - srcY,
this);
provides further
information on setting clipping rectangles in conjunction with
temporary Graphics objects. Note:
The JDK 1.1 provides a much simpler way to handle subarea copying
|
|
Q . How do I control animation with MemoryImageSource?
|
Ans :
Use the new
MemoryImageSource
methods in the JDK 1.1: setAnimated,
setFullBufferupdates, and newPixels.
The MemoryImageSource
class converts arrays of pixel values into a data source for Image
instances . In the JDK 1.0.2, MemoryImageSource was
designed for one-time image creation. Such images displayed
quickly, but they did not automatically consult the original array
data for each display. To update the image after the
underlying array had changed required flushing the old image data
(using Image's flush method) and essentially creating an entirely
new image. This model did not work well for animation.
The JDK 1.1 enhances MemoryImageSource
with a family of methods designed for animation, as outlined in
Table 8.5.
Table
8.5: Animation Methods in MemoryImageSource—JDK1.1
setAnimated(boolean) |
specifies
whether the image can receive dynamic updates for
changes in the data |
setFullBufferupdates(boolean) |
specifies
whether the image updates should always send a full
buffer of pixel data |
newPixels()
newPixels(int, int, int, int)... |
signals
to any consumers of the image data that a new bufferful
(or region) of pixel data is ready |
The
setAnimated and setFullBufferupdates
methods are used to prepare a MemoryImageSource
instance for animation. The right place to invoke them is before
you create an Image instance from the MemoryImageSource
instance. For example:
int width = ...;
int height = ...;
int[] pixels = new int[width * height];
Image myImage;
MemoryImageSource imageSource;
Image buildAnimatedImage() {
for (int i = 0; i < width * height; ++i) {
pixels[i] = ...;
}
imageSource = new MemoryImageSource(width, height, pixels,
0, width);
/* Enable animation and subregion updates.*/
imageSource.setAnimated(true);
imageSource.setFullBufferUpdates(false);
return createImage(imageSource);
}
These
steps prepare the image for animation, but you must still invoke
one of the newPixels methods to trigger an image update.
The following code fragment continues from the above example:
Thread animation = ...;
public void run() {
while (Thread.currentThread() == animation) {
int x = ...; // 0 <= x <= width
int y = ...; // 0 <= y <= height
int index = y * width + x;
pixels[index] = previousValue; // change just one pixel
imageSource.newPixels(x, y, 1, 1);
// ... wait or sleep for a bit
}
}
And, finally, the
image itself must be displayed:
public void paint(Graphics g) {
g.drawImage(myImage, 0, 0, 240, 240, this);
}
|
|
Q . Does Java support animated GIFs?
|
Ans :
Java 1.0.2 and earlier releases use GIF and JPEG formats, and do
not use the GIF89 animated GIF format. (An animated GIF is one that
contains successive frames of an image, so when they are displayed in
quick sequence the image appears to contain movement). When you display
an animated GIF in Java 1.0.2, you will just get the first frame. There
doesn't appear to be any easy way to get other frames from the image.
The advantage of an animated GIF file is that there is only one file to
download, and it is simple to do simple animations. The advantage of
programmatic control over individual frames is that you control the
rate and order of displaying them.
Here's a surprise: JDK 1.1 supports the animated display of animated GIFs. For simple animations animated GIFs are a lot easier and
lighter-weight than coding an animation explicitly.
|
|
Q . How do I create animated GIFs?
|
Ans :
Use
GIFanimator from ULead (said to
be the best) , or GIF Construction Set from Alchemy Mindworks
|
|
Q . How do I prevent animated GIFs from flashing while displaying?
|
Ans :
The problem is most likely that in your paint method you have
g.drawImage(img, ix, iy, this);
You should change this to
g.drawImage(img, ix, iy, getBackground(), this);
This will change all the transparent regions of the image to the background color before painting to the screen. If you paint
transparent images directly to the screen they flicker. If that does not solve it then check that imageUpdate is
public boolean imageUpdate(Image img, int flags, int x, int y,
int width, int height) {
if ((flags & (FRAMEBITS|ALLBITS))!= 0) repaint();
return (flags & (ALLBITS|ABORT)) == 0;
}
update is
public void update(Graphics g) { paint(g); }
If you have a background Image behind the partly transparent animated GIF you will have to double buffer. You can crop the backgound image so
you won't have to double buffer the full app and waste too much memory.
|
|
Q . Does Java support transparent GIFs?
|
Ans :
GIF89a images with a transparent background show up as transparent
without further filtering. This has been supported from 1.0 on. Java
correctly displays both animated GIFs and transparent GIFs.
Even better, you can fill the transparent pixels with a color (so they appear non-transparent in Java). Just pass the fill color explicitly:
drawImage(img, x, y, w, h, fillcolor, this);
Further, you can filter the pixels of an Image to turn any bits you wish transparent. However, the most you can do is reveal what is
underneath the image. You cannot reveal what is underneath the applet
(i.e. on the browser itself). By default applets have a plain grey background.
|
|
Q . How can I save an Image file to disk in JPG or GIF format?
|
Ans
:
o If you have an Image and you want a JPG in a file, download James
Weeks's code from http://www.obrador.com/essentialjpeg/
o Sean Breslin also wrote a program that compresses a Java Image into a JPEG file. http://www.afu.com/jpeg.txt
o If you just want to convert some file, a non-Java solution is to use the standard IJG 'cjpeg' utility. It supports GIF, PPM,
BMP, PNG and Targa input files.
o If you have an Image and you want a GIF or PPM in a file, download Jef Poskanzer's abstract ImageEncoder class at
http://www.acme.com/java/software/
Also try the Java Image Management Interface (JIMI), which is free for
non-commercial use. JIMI is a toolkit that lets your Java programs read
and write many graphics file formats (PNG, JPG, BMP, GIF etc). JIMI is
written in 100% Java, and best of all, it's a breeze to get started
with. See http://www.activated.com/jimi.html
|
|
Q . How can I convert between GIF and JPEG formats?
|
Ans
:
In a word: don't.
There's hardly any overlap between the set of images that JPEG works well on and the set that GIF works well on. Sometimes, with enough
care, you can get an acceptable conversion...but most of the time GIF<->JPEG conversion will just turn your image to mush. It's better to
pick the right format in the first place.
Other sites:
o If you're determined to convert formats anyway, try the GBM (Generalized Bitmap Module). The package is GNU licensed, in C and
is very good. Find it at http://www.interalpha.net/customer/nyangau/
GBM does a good job converting to JPEG, and 'lossiness' is adjustable to 0%. It also converts to/from about 20 other formats,
does cropping, sizing, color mapping, gamma correction, halftoning, everything you could want. GBM source doesn't support
JPEG directly, but utilizes JPEG source from IJG called jpeg-6a and found at
ftp://sun2.urz.uni-heidelberg.de/pub/simtel/graphics/jpegsr6a.zip
o For more info see the JPEG FAQ at http://www.faqs.org/faqs/jpeg-faq/
|
|
Q . Using the JDK 1.1, how do I reset a clipping rectangle?
|
Ans
:
Invoke one
of the setClip
methods; setClip establishes a new
clipping region that is independent of previous calls to setClip.
The
JDK 1.1 distinguishes between two kinds of clipping regions to
limit your program's drawing space:
- a
system-supplied clipping region set by the AWT as part of the
Graphics
argument in invocations of paint and update
- a user-defined
clipping region, lying within the system-supplied region,
determined by the most recent invocation of
setClip
on the current Graphics object
The overall drawable
space is strictly bounded by the AWT-supplied clipping region, but
you are free to set and reset a local clipping region within that
space as much as you like. These temporary clipping regions are
very useful for controlling individual rendering operations.
The new JDK 1.1 methods for
controlling the clipping region are shown in Table 8.6.
Table
8.6: Methods for Getting and Setting the Clip Region—JDK
1.1
Shape
getClip() |
returns
the current clipping region as a Shape
object |
Shape
getClipBounds() |
returns
the smallest rectangle that completely encloses the
current clipping region |
void
setClip(Shape) |
sets
the current clipping region to the specified Shape
object (e.g., a Rectangle instance) |
void
setClip(int, int, int, int) |
sets
the current clipping region to a rectangle with the
specified x, y, width,
and height values |
The
JDK 1.1 supports only rectangular clipping regions, but, in
anticipation of future implementations, the above methods are
designed around the Shape interface (which has just
one method, getBounds).
The following code fragment recasts
the sample code of in terms of the JDK 1.1 methods:
/* using JDK 1.1: */
public void paint(Graphics g) {
int x, y, width, height;
// ... set x, y, width, and height as needed
g.setClip(x, y, width, height);
g.drawSomething();
// ... set new values for x, y, width, and height
g.setClip(x, y, width, height);
g.drawSomethingElse();
// ... and so on
}
You can also use the getClip
method to save a clipping region so that you can restore it later.
For example:
Shape originalClip = g.getClip();
g.setClip(x, y, width, height);
// ... draw using new clipping region
g.setClip(originalClip);
// ... draw using original clipping region
|
|
Q .
What's the best way in JDK 1.1 to draw just a subarea of an image?
|
Ans
:
Use the new
drawImage
methods (starting with JDK 1.1), which let you specify a
rectangular subarea of the image to draw.
The
JDK 1.0.2 provides drawImage methods that let you
specify how a full image will be rendered into a target graphics
context, but you have to work indirectly to achieve the effect of
drawing just a portion of the image. The JDK 1.1 adds two new drawImage
methods that let you specify a mapping from a rectangle in the
image to a rectangle in the target graphics context. This mapping
provides two capabilities not present in the JDK 1.0.2:
- you can specify
a subarea of the source image to render
- you can flip the
image horizontally, vertically, or both
Here's how it works.
The two new drawImage methods specify rectangles
differently from the rest of the AWT: they specify a logical
top-left point and a logical bottom-right point for each
rectangle,
To draw a subarea
of an image, provide the subimage rectangle in the two-point
format just described. For example:
/* using JDK 1.1: */
Image myImage = ...;
public void paint(Graphics g) {
g.drawImage(myImage,
0, 0, 200, 100, // points (0,0) and (200,100)
// of destination rectangle
50, 50, 150, 100, // points (50,50) and (150,100)
// of source rectangle
Color.black, // draw image on black background
this); // use this instance as the image observer
// ...
}
This code takes an
image subarea 100 units wide by 50 high, at location (50, 50), and
draws it into the graphics area 200 units wide by 100 high,
located at (0, 0). The following diagram shows the two rectangles,
and how the two points defining the image source rectangle map to
the two points defining the target graphics context rectangle:
The drawImage
methods also let you flip images vertically, horizontally, or
both. The following drawImage invocation modifies the
previous example to flip the image vertically:
g.drawImage(myImage,
0, 0, 200, 100, // points (0,0) and (200,100)
// of destination rectangle
50, 100, 150, 50, // points (50,100) and (150,50)
// of source rectangle
Color.black, // draw image on black background
this); // use this instance as the image observer
|
|
|
|