HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Java is a programming language. Nothing complicated so far. And just like all other computer languages, it will not make a bad programmer into a good programmer. All the same techniques picked up while coding in, say, C or C++, generally can be applied to the same program in Java. But what happens if you're not coming from an experienced coding background. You don't have this wealth of knowledge. Fortunately or unfortunately, depending on your side of the fence, when it comes to coding multimedia applets many experienced developers are back at square one with the novices. The problem is that in pre-Web days, the majority of programmers never needed to worry about image manipulation procedures. But now, it would be rare for a Java programmer not to have coded some sort of image processing applet. Whether it is displaying a logo or animating a series of images, the problems that can occur are the same. Granted, unlike many other languages, Java makes displaying images very easy, however, without careful consideration, the resulting applet / application may look far from professional.

This article looks at bridging the gap between a poorly executed flickery animation and a smooth, professional, flicker-free animation. The techniques presented here are not tied to just the animation field, but can be employed in applications where graphics are used in general.

Loading Images
For the purpose of this article, a simple, flicker book style animation will be implemented. This animation will consist of thirty small, 20x20, images in either GIF or JPG form depending on the mode of the artist on the day. This equates to thirty files, thirty file requests and thirty file transfers. Very inefficient.

It is much better to transfer one big file as opposed to lots of small ones, as the throughput of the connection will increase - more time transferring data and less time transferring file headers. For this reason, we will take all thirty images, place them one under another and store them in the same file. We now have one long strip of images that can be loaded into memory as one file.

The next step is to split this image into thirty smaller images. This is performed in a two stage process (see Listing 1).

The first step is to load the image referenced by imgStip into an array of integers. This is performed using the java.awt.image.PixelGrabber class, a very powerful class,that allows, either, the complete image or a subset of the image to be grabbed and stored in an array. There are two different constructors for this class, but the one employed here has the following parameters:

public PixelGrabber( Image img, //- Image that holds the pixels
int x, //- The x-corner of the image where the scan will //- begin
int y, //- The y-corner of the image
int w, //- The width of the subset image
int h, //- The height of the subset image
int pix[], //- The integer array the sub-image will be placed
int off, //- The offset into the array where the image will
//- start
int scansize) //- The length of each row to be scanned

Since we are pulling the complete image into memory, the majority of the parameters are very straightforward to set up. Having created a new instance of this class, we now initiate the pixel capture using the grabPixel() method. When this method returns, the pixels are in the array. Since, a lot of things can go wrong, it is placed within an exception handler to catch such errors.

Pixel Representation
Each pixel is represented as a composite of four components: Red, Green, Blue and an Alpha channel. This 32-bit value is arranged as shown in Figure 1, with each component occupying eight bits.

Figure 1
Figure 1:

An AA value of 0x00 indicates the pixel is transparent. Before going to the next step in creating the new images, a simple image filter can be applied to the integer array. For example, one can either lighten or darken the image by simply increasing or decreasing the value of the RGB values. The code presented in Listing 2 increases the darkness or brightness by a given percentage.

Since the pixels are simply an array of integers, many filters can be applied before creating the final image. For example, a simple filter could be written to swap the RED components with the GREEN components, or to eliminate all the RED components from the image. You could even randomize this to create a completely new image, based on the original every time.

Image Creation
After preparing the array of integers, it is now your job to create the actual image. This is achieved in two steps. The first step is to use the java.awt.image MemoryImage Source class, which is used as an input parameter to the method createImage() in the class java.awt.Component. The MemoryImageSource class implements the java.awt.image.ImageProducer interface that allows the creation of images in memory. As with the previous PixelGrabber class, this class has many constructors.

For this example, the constructor in Table 1 is used.

Table 1

The next step in creating a series of images is not a big one. In order to flick through the images in a sequence, the images are best stored in an array.

The code in Listing 4 first creates a placeholder for thirty images, and then runs through a simple loop to create the individual images from the array. Notice how the offset parameter is calculated so it returns the starting index of each image. Each image is 30 x 30 pixels, or 600 pixels in total. The starting index of each image is therefore 0, 600, 1200, 1800, ... etc. into the array.

Flicker Elimination
Having now created the thirty images from a single image, they must now be displayed in sequence while minimizing flicker. This is a very simple technique that can be applied to almost all scenarios relating to graphics and Java.

The majority of developers simply override the java.awt.Component.paint(Graphics G) method by placing all of their graphic operations within. This method is called every time the component needs refreshing, whether it originates from a manual update or via a system call, for example when the window is maximized. However, by just using the paint() method, the component may suffer from flicker. This is due to the processing of the component class that ensures the area is restored to its original background colour and graphic context before any painting is performed. A very handy feature, you may think - clean canvas every time. However, if the original color is white and the proceeding paint results in a dark image, the flicker of the update will be very apparent. What you need to do is to stop the component from restoring the area to its original glory, a thank you, but no thank you approach.

The job of restoration is given to the java.awt.Component.update(Graphics G) method, and by overriding this method, with a call to paint() will stop the area from being reset (see Listing 5).

Double Buffering
If a component consists of just one image, and no other surrounding graphics, then the technique in Listing 5 is sufficient to eliminate flicker. However, if the area is made up of several images and graphics operations, an additional step is required to completely eliminate flicker. This technique is referred to as double buffering'. This translates to creating an image of the area in memory and then transferring it to the active area in one operation. The majority of graphics hardware supports the BitBlt(...) function (a very fast way of transferring images to the hardware), and assuming the author of the Java Runtime Libraries executing the code has taken advantage of this, the efficiency of your paint() method increases.

Adding double buffering to an applet does not require many additional lines and should be employed in most instances. The first step (Listing 6) is to create an off-screen image and retrieve a graphics handle to it, to allow painting to it.

The variables, imgScreen and offScreenGraphics, should remain in scope throughout the life of the class, as it is more efficient to create them once and re-use them, as opposed to re-creating every time a paint is required. Notice the use of the createImage() method again. However, this variation of the method allows the creation of a blank image of a given width and height. The next step is to get a handle to the graphics context that will enable painting directly to the image.

Now, every time the paint() method is called, instead of painting into the graphics context passed in, all operations go to the newly created memory image, with the final statement transferring this image to the component (see Listing 7).

Conclusion
With Java used primarily for the Web, it is not surprising that applets increasingly are being used for more and more graphically intensive applications. Stunning graphics can make a site a very pleasant place to visit, but the overall pleasure can be detracted from by a poorly implemented applet that flickers badly. As this article demonstrated, the steps required to eliminate this rather annoying side effect are not rocket science, nor does it take huge amounts of code. If they are implemented every time, you can give a professional edge to any applet.

About the Author
Alan Williamson is currently co-authoring a book with his colleague Ceri Moran on Java and databases, while running his Java-based company in the UK. He can be reached at www.n-ary.com or [email protected]

	

Listing 1

int[] src_pixels = new int[ 600 * 30 ];
PixelGrabber  pg = new PixelGrabber( imgStrip, 0,0, 30, 600,src_pixels, 0, 30 );

	try {
		pg.grabPixels();
	}
	catch( Exception E ){
		//- Do something
	}

Listing 2

public int[] Highlight( int[] array, int arraysize, boolean bBrighter, int Percentage )
	{
	int rgb,r, g, b;

	for ( int x=0; x < arraysize; x++ )
		{
	rgb	= 0x00FFFFFF & array[x];
	r	= (rgb >> 16) & 0xFF;
	g	= (rgb >> 8) & 0xFF;
	b	= rgb &	0xFF;

			if ( bBrighter )
			{
	r = (255 - ((255 - r) * (100 - Percentage) / 100 ));
	g = (255 - ((255 - g) * (100 - Percentage) / 100 ));
	b = (255 - ((255 - b) * (100 - Percentage) / 100 ));
			}
			else
			{
	r = (r * (100 - Percentage) / 100 );
	g = (g * (100 - Percentage) / 100 );
	b = (b * (100 - Percentage) / 100 );
			}

			if ( r < 0 )
				r = 0;

			if ( r > 255 )
				r = 255;

			if ( g < 0 )
				g = 0;

			if ( b > 255 )
				b = 255;

			if ( b < 0 )
				b = 0;

			if ( b > 255 )
				b = 255;

array[x] = (rgb & 0xff000000 ) | (r<<16) | (g<<8) | (b<<0);
		}

		return array;
   }

Listing 3

	Image imgX = createImage( new MemoryImageSource
	( 30,30,src_pixels,30,30) );

Listing 4

		imgPics = new Image[30];
		for ( int x=0; x < 30 ; x++ )
		imgPics[x] = createImage( new MemoryImageSource
		( 30,30,src_pixels,x*30*30,30) );

Listing 5

	public void update( Graphics _G ) {
		paint( _G );
	}

Listing 6

Image imgScreen;
Graphics offScreenGraphics;
:
imgScreen	= createImage( 50, 50 );
offScreenGraphics	= imgScreen.getGraphics();

Listing 7

	public void update( Graphics _G ) {
		paint( _G );
	}

	public void paint( Graphics _G ) {
		offScreenGraphics.drawString( "Hello!", 10, 10 );
		_G.drawImage( imgScreen, 0, 0, this );
	}


 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.