One area of Java where you can get into trouble is trying to balance memory
management with performance. Take, for example, double buffering. Usually
when you are drawing on a buffer, you aren't exactly sure how large a
buffer you will need. In this example, we are using a
600-pixels-by-600-pixels applet window. The image we want to buffer is a
grid of squares, which is 2000-pixels-by-2000 pixels. Here are some rough
numbers on how much memory the buffer will use.
Base App 4.392M
Sliding Buffer (600x600) 1,220k
Regular Buffer (2kx2k) 8,348k
If we design it with a buffer big enough for our image, our simple applet
exceeds 12MB of memory. And if your applet has a large memory profile, the
constant paging will defeat any performance gains of double buffering. What
you really need is a solution that combines the best of both worlds: a
sliding double buffer. (A sliding double buffer is one in which the image
is larger than your buffer, and you can slide the image across the buffer.)
The problem is to manage this move efficiently, because it requires you to
redraw the buffer. First, create an applet with a buffering image:
public class BufferTest extends java.applet.Applet
{
Image offscreenImage=null;
Graphics offscreenGraphics=null;
private void setupBuffer ()
{
try
{
//set up image for double buffering
offscreenImage = createImage (
this.miBuffersize, this.miBuffersize);
offscreenGraphics =
offscreenImage.getGraphics ();
}//end try
catch (Exception e)
{
e.printStackTrace ();
}//end catch
}//end setupBuffer()
To slide the image across the buffer, add some sliding parameters to the
drawImage method. Here we use the global parameters miBufferShiftx (move
image x pixels right on the buffer) and miBufferShifty (move image y pixels
down on the buffer):
private void drawGrid ()
{
int iXpos =0;
int iYpos =0;
offscreenGraphics.setFont(new Font("TimesRoman",
Font.PLAIN,8));
for (int x=0; x <= 2000; x+= 150)
for (int y=0; y<=2000; y+= 150)
{
iXpos = x + miBufferShiftX +25 ;
iYpos = y + miBufferShiftY +25;
offscreenGraphics.drawRect(iXpos, iYpos, 50, 50);
offscreenGraphics.drawString(x+ "," +y, iXpos +5,
iYpos +20);
}
}//end drawGrid ()
Now that we have the ability to slide our image on the buffer, we need to
maintain the position of our view of this buffer, in other words, the area
the user sees.
public void paint (Graphics g)
{
g.drawImage (offscreenImage, miXpan, miYpan, this);
}//end paint
Here the two variables--miXpan and miYpan-- representing the position of the
"View Window" relative to the buffer. In this program, we want the user to
be able to drag the image with the mouse, so we want to make sure that when
the user releases his mouse button, we slide the image in the background.
The visual effect of this is to make part of the image (which might not
have been visible) suddenly appear. We use mfDragInit to remember if we
have saved the starting mouse down position already:
public boolean mouseUp(Event evt, int x, int y)
{
SlideBuffer ();
return true;
}//emd mouseUp(.,.,.)
public boolean mouseDown(Event evt, int x, int y)
{
mfDragInit = false;
return true;
}
Now we want to add code that ensures that when a user drags his mouse, the
image appears to slide across the screen. We do this by taking the change
in mouse position since the MouseDown event, and setting our panning
variables to this:
public boolean mouseDrag (Event evt, int x, int y)
{
if (mfDragInit)
{
miXpan = ( x - miOldx);
miYpan = (y - miOldy);
repaint();
}//end if
else
{
miOldy = y;
miOldx = x;
mfDragInit = true;
}//end else
return true;
} //end mouseDrag
This solution works great, except that the user may notice that part of the
image seems to disappear until they MouseUp(). If you can redraw your
image in under 100 milliseconds, you can call your sliding routine after certain intervals
to get rid of this side-effect. However, if you are drawing a very complex
image, doing a redraw in the middle of a mouseDrag method will make dragging the
mouse appear jerky. Try this:
public boolean mouseDrag (Event evt, int x, int y)
{
if (mfDragInit)
{
miXpan = ( x - miOldx);
miYpan = (y - miOldy);
repaint();
}//end if
else
{
miOldy = y;
miOldx = x;
mfDragInit = true;
}//end else
//Add 100 pixel mouse drag interval
//to do a buffer slide
int miMouseDragInterval = 100;
if ((miXpan >= miMouseInterval)
||(miXpan <= - miMouseInterval)
||(miYpan >= miDragInterval)
||(miYpan <=- miDragInterval) )
SlideBuffer ();
return true;
} //end mouseDrag
Even if you have an image that takes several seconds to draw, you may be
able to optimize it enough to use this technique. One way to do this is to
only draw the part of your image equal to your view size plus your mouse
drag interval. You know you don't need to draw something that is a
thousand pixels away from your view window.
This technique is useful anywhere you need to double buffer, but I usually
reserve it for cases where I have really large images to scroll, or where
the size of the image is hard to predict. (It isn't really applicable to an
image where you don't control the drawing, such as a .GIF image.) Still,
it's a very helpful technique for which you'll doubtless find lots of uses.