IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Web architecture | Java technology
developerWorks
Creating Java2D composites for rollover effects
code114KBe-mail it!
Contents:
Introduction
Background
RolloverIcon
RolloverComposite
Completing the code
The finished result
Conclusion
Resources
About the authors
Rate this article
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
How to define variables for rollover and selection events within Java applications

Level: Intermediate

Joe Winchester (mailto:winchest@uk.ibm.com?cc=&subject=Creating Java2D composites for rollover effects), Senior software engineer, IBM
Renee Schwartz (mailto:rsch@us.ibm.com?cc=&subject=Creating Java2D composites for rollover effects), Software visual design, IBM

1 September 2002

When creating or using Java applications, you may have experienced some interesting rollover and/or selection effects when using default Swing mechanisms. A more consistent, more common result can be reached using the Java2D API which allows you to methodically define composite values for rollover and selection events. In this article, discover how images are constructed within Java language and how they can be manipulated using AWT composites.

Introduction
Many applications have buttons containing icons where the icons are changed as the button is selected or the mouse moved over the button. An example of this is with toolbar buttons in a Web browser, such as Microsoft Internet Explorer, where the buttons use gray colors for the graphic that becomes colored as the mouse moves over the button. To achieve this effect, two sets of graphics can be created; one for the normal state and one for the active state when the mouse is over the button. Creating two sets of graphics, however, is time consuming for designers and means that dual maintenance must be done on both sets if the graphic needs to change. A more optimal approach would be to have a single icon and the graphic effect done programmatically to avoid the overhead of creating and maintaining a separate icon. The problem described in this article is for a set of buttons in a wizard page shown in Figure 1, where as the user hovers over each button, the graphic changes to show them it is active.

Figure 1. The Web Module button is active with the mouse over it and the graphic has been changed.
The Web Module button is active with the mouse over it and the graphic has been changed.

In this article we show how we achieved this effect by using the Java2D API to create a class that is able to take an image and create the desired effect. This involves understanding how images are constructed and can be manipulated using AWT composites.

Background
The class javax.swing.JButton has a boolean property rolloverEnabled and a property rolloverIcon that is typed to javax.swing.Icon. If rolloverEnabled is true, then the value of the rolloverIcon is used as the button's graphic when the mouse is over it. We decided that a good solution would be to have a new class called RolloverImageIcon that was given an image in its constructor and would then manipulate the icon before drawing it.

The code to create a rolloverButton would then be as follows:


 JButton button = new JButton(regularIcon);
 button.setRolloverEnabled(true);
 button.setRolloverIcon(new RolloverIcon(regularIcon));

The next step is to create the RolloverIcon class that is able to wrap an original icon and paint it with the desired graphics effect shown in Figure 1.

RolloverIcon
The RolloverIcon class will implement the interface javax.swing.Icon that allows it to be a valid value for a button's rolloverIcon property and store the original icon in an instance variable called fIcon:

public class RolloverIcon implements Icon { protected Icon fIcon; {

An alternative solution could be to subclass JButton and encapsulate the rollover effect in the subclass, but by placing the logic in the RolloverIcon class, the graphic effect can be used in other scenarios such as for checkboxes or menu items. Should a custom subclass be desired, it is straightforward to use and delegate the logic to create the graphic effect to an instance of RolloverIcon.

The javax.swing.Icon interface has three methods: getIconWidth(), getIconHeight() and paintIcon(Component,Graphics,x,y). The first of the two methods can be delegated to the icon that the RolloverIcon is creating the graphic effect for, as the rollover image is going to be the same size as the original.


public int getIconHeight() {
  return fIcon.getIconHeight();
}
public int getIconWidth() {
  return fIcon.getIconWidth();
}

The paint method is the one that is actually going to draw onto the graphics context. The Component argument is the control for which the icon is being drawn such as the instance of the javax.swing.JButton. This allows us to access details such as the control's font, insets, etc. and other properties we might want to take into account when we paint the icon. The x and y arguments are the position that the graphic is being asked to be rendered on the drawing surface. These positions are absolute to the drawing surface, unlike the values returned from getLocation() on the component which will be the position of the button relative to its parent container. The drawing APIs need to use absolute values so having them passed into the paintIcon method avoids having to compute them by traversing all the parents of the component.

The graphics argument is the object that represents the drawing surface. Although it is typed to be java.awt.Graphics it will be an instance of java.awt.Graphics2D. The abstract Graphics2D class is part of the Java2D API that was introduced as part of the Java2 platform and for backward compatibility, the argument of the paint method was not retyped to be Graphics2D, although as long as the JRE being used is Java2 or higher then it can be guaranteed to be an instance of java.awt.Graphics2D.

The full signature of the paintIcon method is as follows:

public void paintIcon(Component c, Graphics g, int x, int y);

The RolloverIcon instance is wrapping the original icon we want to paint with the graphics effect shown in Figure 1. To do this we can make use of the composite property on the Graphics2D object. The composite property is typed to the interface java.awt.Composite and all primitive drawing on the graphics surface done by the Graphics2D object is directed through its composite. A number of pre-existing composite classes exist such as one for creating an XOR effect. The XORComposite is the class sun.java2d.loops.XORComposite and its constructor takes an argument of the color for the drawing being rendered to be XOR'd against. Each color is made up of a red, green, and blue value, and if color is XOR'd against black ( which is r,g,b of 0,0,0 ) then it is effectively inverted. To see this, the paint method could be written as follows:


public void paintIcon(Component c, Graphics g, int x, int y) {
  Graphics2D g2D = (Graphics2D)g;
  Composite oldComposite = g2D.getComposite();
  g2D.setComposite(new sun.java2d.loops.XORComposite(Color.black));
  fIcon.paintIcon(c,g,x,y);
  g2D.setComposite(oldComposite);
}

To render the original icon, which is held in the instance variable fIcon, we just defer to its paintIcon(Component,Graphics,int,int) method having previously set the graphic's composite to be the XORComposite object. It is good practice when manipulating properties of the graphics object to restore them to the original values when finished. This is shown in the method above where the original composite is stored before changing it and then restored afterwards. If you do not do this, then the XORComposite will be left in the graphics object and it will affect all subsequent drawing.

Figure 2 shows the effect of the XORComposite. The top row of buttons have the original buttons with the RolloverIcon set as their rolloverIcon property. The bottom row of buttons are permanently set to show the result of the RolloverIcon which is set as their icon property. The XORComposite has taken the icon and inverted it by XORing each of the pixel values against 0.

Figure 2. An XORComposite can be used to control how the icon is rendered.
The Web Module button is active with the mouse over it and the graphic has been changed.

The XOR effect is not the result shown in Figure 1, but it does show how the composite is responsible for rendering the icon onto the drawing surface. If we create our own composite class that gives us access to the precise rendering of the icon, we should be able to achieve our desired rollover effect. Our class will be called RolloverComposite and the paintIcon method can set this into the graphics object before rendering the original icon.

Before implementing the RolloverComposite class, we need to understand more about how colors are represented in the Java language and how they are painted onto a drawing surface.

RolloverComposite
The RolloverComposite must implement the interface java.awt.Composite. This has a single method Entry Helpers that returns an instance of java.awt.CompositeContext. Rather than the composite being responsible for doing the actual primitive manipulation of the pixels on the graphics surface, it delegates this to the composite context object that is able to maintain state and work in a multithreaded environment as several composite context objects can exist for a single composite.


public CompositeContext createContext(ColorModel srcColorModel, ColorModel 
dstColorModel, RenderingHints hints) ; 

The abstract class ColorModel has the API to translate between color components and primitive red, green, blue, and alpha components. The RenderingHints are the rendering hints from the Graphics2D that can be used by the composite if required, such as whether antialias should be on for text rendering or what kind of join style for lines should be used. The CompositeContext is the thread safe object that actually manipulates the raw pixels and we will create an anonymous inner class for this purpose, as follows:


return new CompositeContext(){
  public void dispose(){
  }
  public void compose(Raster src, Raster dstIn, WritableRaster dstOut){

The CompositeContext interface has two methods, dispose() and compose(Raster,Raster,WritableRaster). The dispose method lets us clean up any resources that may have been allocated and the compose method is the one that is responsible for the primitive drawing. We do not have any objects that need to be cleaned up so we can have a no-op implementation of dispose(), and the compose(...) method is where we will do the pixel manipulation.

The three arguments of the compose(...) method are the Raster for the source image, the Raster for the destination image, and the WritableRaster that represents the output that will be rendered on the graphics surface. To access the raw pixel for a given location the method getPixel(int x, int y, pixel int[]) can be used. The int[] argument is a four argument array that represents the red, green, blue, and alpha value for the pixel. The src argument contains the raster for the source argument and the dstOut argument is a WritableRaster that has the method setPixel(int x, int y, pixel int[]). By iterating over each pixel element from the source, we can create a new pixel with the manipulated values and set it into the dstOut that will be drawn on the graphics surface. The code for the compose method is as follows:


      
            // Get the source pixels 
            int[] srcPixels = new int[4]; 
            src.getPixel(x,y,srcPixels); 
            // Ignore transparent pixels 
            if (srcPixels[3] != 0){ 
                // Lighten each color by 1/2, and increasing the blue 
                srcPixels[0] = srcPixels[0] / 2; 
                srcPixels[1] = srcPixels[1] / 2; 
                srcPixels[2] = srcPixels[2] / 2 + 68; 
                dstOut.setPixel(x,y,srcPixels); 
            } 
        } 
    }; 
} 

Note that the fourth pixel (srcPixels[3]) is checked to see whether or not it is transparent or not. If it is transparent on the source then we don't want to write anything to the dstOut as we want the existing pixel of the drawing surface to remain.

Completing the code
Having created the RolloverComposite class that returns a CompositeContext that sets the desired pixel values into the raster that is drawn on the graphics surface, we need to complete the code in the RolloverIcon class. Earlier when we were testing the XOR composite we created a new instance before setting it into the graphic's composite in the paintIcon(...) method. Creating objects is expensive in Java language, as memory must be allocated and the resulting object then garbage collected when it is no longer required, so we will instead have a singleton instance of the RolloverComposite class that can be reused as required. We do not have to worry about multiple icons or graphics objects having access to the same object, as all of the state is held by the CompositeContext that is re-created as required and provides thread-safe access.

The RolloverComposite will contain its single instance in a public static field called DEFAULT. To enforce people to use this singleton rather than creating a new object each time the constructor is made private so that only the RolloverComposite class is able to create instances of itself.


public class RolloverComposite implements Composite { 
  public static RolloverComposite DEFAULT = new RolloverComposite(); 
  private RolloverComposite(){ } 

The paintIcon method in RolloverIcon needs to set the composite of the graphics argument to be the singleton RolloverComposite before painting the original icon that it wrappers. The original composite needs to be restored afterwards to ensure that any subsequent drawing is not unnecessarily affected by leaving the custom composite instance there for longer than is required.


public void paintIcon(Component c, Graphics g, int x, int y) {
  Graphics2D g2D = (Graphics2D)g;
  Composite oldComposite = g2D.getComposite(); 
  g2D.setComposite(RolloverComposite.DEFAULT); 
  fIcon.paintIcon(c,g,x,y); 
  g2D.setComposite(oldComposite); 
} 

The finished result
Having coded the RolloverIcon class, we now need to test it. For this, a test harness was written that created two rows of four buttons, the top of which had the rollover effect and the bottom of which had the button's icon permanently set to be the rollover icon. You can download the code for the test harness, as well as all of the code for the RolloverIcon and the RolloverComposite class from the Resources section below.

Figure 3. The test harness shows the effect of the RolloverIcon.
The test harness shows the effect of the RolloverIcon

The figure above shows finished result of the RolloverIcon. The original graphics in the top row are darkened by 50% and the intensity of the blue has been increased slightly to give the graphic effect we originally desired.

Conclusion
The RolloverIcon is an example of how extensible the Java2D API is, so that we can access very low level components in the graphics subsystem to create our own extensions. The one we used to good affect for our graphics effect was to control the composite used by the graphics object, but there are many other custom effects that can be achieved by understanding more about how Java2D works. More information on the complete API can be found in one of the many good books on Java2D (see Resources).

Resources

  • Read Java 2D API Graphics by Vincent J. Hardy, Prentice Hall PTR, ISBN 0130142662. This is an excellent book to learn about the Java 2D. It is well written and has many good examples.

  • Read Pure Jfc 2d Graphics and Imaging by Satyaraj, Ph.D Pantham, Sams, ISBN 0672316692. This book is for the more experienced developer and it contains code samples for some advanced graphics effects.

  • See this excellent on-line Java2D API tutorial that shows different effects possible with the Java2D API and includes sample code.

  • Download some examples with accompanying code that show the different effects possible with the Java2D API. These samples can be run in a browser plugin.

  • You can download the code for the test harness described earlier in this article.

About the authors
Joe Winchester is a lead developer for WebSphere Application Developer (previously WebSphere Studio). He works in the Hursley, UK Labs as a senior software engineer. You can contact Joe at winchest@uk.ibm.com.


Renee Schwartz works as a visual designer for IBM at the Research Triangle Park Lab in Raleigh, N.C. Primarily focused on WebSphere products, she applies her traditional visual design skills and knowledge to Web and Java application development. You can contact Renee at rsch@us.ibm.com.



code114KBe-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Comments?



developerWorks > Web architecture | Java technology
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact