May 27

Note: Internet Explorer doesn’t seem to render this post very well. I suggest using Firefox (or Safari) to read it.

I’ve been reading Romain’s blog with interest for the past few months. I’ve built my share of Swing (and SWT) based applications, and I’ve developed my share of custom controls for Swing over the past 6-7 years, but I’ve never built anything as “pretty” as what he’s been making.

Last night, I thought I would see how long it would take to create something “pretty”. I decided on trying to see what it would take to create (just draw for now) “mac-like” Aqua buttons. Here is the result of my 30 minute experiment:

Red Aqua Button     Red Aqua Button

I’m sure they could probably done a little bit better, or a little more authentic, but I don’t think it’s bad for a 30 minute exercise. :-) So, how can you create something like this? Here’s how:

Step 1: Create a buffered image to use as our canvas to draw on

Later in the process, we will want to create apply effects (blurring) to what we have created. Instead of drawing directly with the Graphics2D instance that we are handed by Swing, we will create a buffered image that we can use to “draw on”:

BufferedImage vBuffer = new BufferedImage(vWidth, vHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D bg = vBuffer.createGraphics();
bg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

Step 2: Paint the background color

For right now, we’ll just paint the background white:

bg.setColor(Color.WHITE);
bg.fillRect(0, 0, vWidth, vHeight);

Step 3: Draw a rounded rectangle with a gradient fill

We want to draw a rounded rectangle with a gradient that gets lighter from top to bottom. So the first thing we need to do is figure out the start color and the end color of the gradient and use that to create the gradient paint:

Color vGradientStartColor =  buttonColor.darker().darker().darker();
Color vGradientEndColor = buttonColor.brighter().brighter().brighter();
Paint vPaint = new GradientPaint(0, inset, vGradientStartColor, 0, vButtonHeight, vGradientEndColor, false);
bg.setPaint(vPaint);

Now that we have our “paint” created, we need to draw the rounded rectangle:

bg.fillRoundRect(inset, inset, vButtonWidth, vButtonHeight, vArcSize, vArcSize);

If we would run our application now, it would give us this:

Aqua Button after Step 3

Step 4: Paint the highlight (lighting effect)

Next, we need to paint a highlight on top of the rounded rectangle to make it look like a lighting effect. To do this, we are going to paint another rounded rectangle (inset 1 pixel) that fades from white to a lighter shade of the base color.

Like the last rounded rectange we painted, we first need to create the gradient paint:

// Create the paint for the second layer of the button
vGradientStartColor = Color.WHITE;
vGradientEndColor = buttonColor.brighter();
vPaint = new GradientPaint(0,inset+vHighlightInset,vGradientStartColor,0,inset+vHighlightInset+(vButtonHighlightHeight/2), buttonColor.brighter(), false);
bg.setPaint(vPaint);

Next, we need to paint the rounded rectangle:

bg.fillRoundRect(inset+vHighlightInset,inset+vHighlightInset,vButtonHighlightWidth,vButtonHighlightHeight,vHighlightArcSize,vHighlightArcSize);

If we would run our application now, it would give us this:

Aqua Button after Step 4

Step 5: Add clipping and transparency

The last step brings us pretty close to what we want, however we can make it a little better by clipping the second rounded rectangle we painted, as well as adding a little transparency to it. In order to do this, we’ll add a few more calls before we paint the rounded rectangle:

bg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,.8f));
bg.setPaint(vPaint);
bg.setClip(new RoundRectangle2D.Float(inset+vHighlightInset,inset+vHighlightInset,vButtonHighlightWidth,vButtonHighlightHeight / 2,vButtonHighlightHeight / 3,vButtonHighlightHeight /3));
bg.fillRoundRect(inset+vHighlightInset,inset+vHighlightInset,vButtonHighlightWidth,vButtonHighlightHeight,vHighlightArcSize,vHighlightArcSize);

If we would run our application now, it would give us this:

Aqua Button after Step 5

Step 6: Blur the image

In order to make everything “softer”, we will apply a blur to the image to give it that last finishing touch:

float[] BLUR = {0.10f, 0.10f, 0.10f, 0.10f, 0.30f, 0.10f, 0.10f, 0.10f, 0.10f};
ConvolveOp vBlurOp = new ConvolveOp(new Kernel(3, 3, BLUR));
BufferedImage vBlurredBase = vBlurOp.filter(vBuffer, null);

If we would run our application now, it would give us this:

Aqua Button after Step 6

Step 7: Adding some text

Any “real” button includes text (or an image), so the next logical step would be to add some text to our Aqua buttons:

g2.setColor(foregroundColor);
Font vFont = g2.getFont().deriveFont((float)(((float)vButtonHeight) * .6));
g2.setFont(vFont);
FontMetrics vMetrics = g2.getFontMetrics();
Rectangle2D vStringBounds = vMetrics.getStringBounds(text,g2);
float x = (float)((vWidth / 2) - (vStringBounds.getWidth() / 2));
float y = (float)((vHeight / 2) + (vStringBounds.getHeight() / 2)) - vMetrics.getDescent();
g2.drawString(text,x,y);

If we would run our application now, it would give us this:

Aqua Button after Step 7

Conclusion

I think it’s a pretty good demonstration of the power of Java2D that I can create something like that in less than 30 minutes. I guess that logical next step would be to use technicque this to create a real implimentation of a button (or UI delegate for JButton) so people can add Aqua like buttons to any swing application.

If you want to play around with this yourself, you can download the source code here.

Have fun!

8 Responses to “How to create an Aqua buttons with Java2D”

  1. Kirill Says:

    You are welcome to take a look at the substance look and feel currently being developed on java.net. The code is a little bit more messy than above, but is much more general, being used for all types of buttons (regular, toggle, combobox, check boxes, radio buttons, and so on).

  2. Jose Sandoval Says:

    Note: Internet Explorer doesn’t seem to render this post very well. I suggest using Firefox (or Safari) to read it.

    Why not fix it?

  3. Romain Guy Says:

    Nice trick! There is a similar hack in the book Swing Hacks :) Some OSX buttons use the “bezel” style. They show a bézier curve in the background as in this picture: http://www.progx.org/users/Gfx/newsfire1.png

    I did a quick ripoff of them in Swing: http://www.progx.org/users/Gfx/netvideo2.png

    I, for once, did implement this as UI if you are interested (http://www.jroller.com/page/gfx/20050328#packed_with_effects, if the source code is not linked from there, just ask).

    I’m looking forward to see other cool stuff like this from you :)

  4. Jon Lipsky Says:

    I was thinking about implementing those buttons with the “bezel” style as well. I may have to take your code and enhance it a little bit to allow groups of buttons (similar to the backwards and forwards links in Safari).

    I’ve also got a few other custom swing controls and examples I’ve done over the years that I want to release (as well as document for others how to create similar things).

  5. atog » Blog Archive » Aqua button. Says:

    [...] « Solo is Simple, part two. Aqua button. How to create an Aqua buttons with Java2D. This entry was posted on Monday, May 30 [...]

  6. Joris Kluivers Says:

    Note: Internet Explorer doesn’t seem to render this post very well. I suggest using Firefox (or Safari) to read it.

    Jose Sandoval Says: Why not fix it?

    Let me remind you it isn’t Jon’s responsibility to fix IE. Page display’s fine in Safari btw.

    Drawing buttons like this is very neat, and I like the idea of being able to change the colors programatically.

  7. Ricardo Says:

    Hi,

    Great Stuff …. and Don’t Stop…

    by the way, i saw that you mentionned that you have been using Swing and SWT for 6-7 years…. Do you have some examples and tutorials for SWT….? That would be Great since i like your style….

  8. Jon Lipsky Says:

    Thanks!

    Yes, I have some custom controls that I have developed for SWT that I could use as a basis for a tutorial. :-) It’s just a matter of how many hours I have free in a day… I’ve got to pay the bills with my day job.

Leave a Reply