|
How to define variables for rollover and selection events
within Java applications
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.
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 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 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.
|
|
|