 |
Who put a rhino in my NetBeans?
Posted by kirillcool on October 17, 2005 at 03:36 AM | Comments (11)
What would you say if you opened the Options dialog in your application and it looked like this (click to see larger version):

I've blogged about adding watermark image before, but pay attention to the buttons. They are shaped like little rhinos. Don't like rhinos (or Beyonce)? No biggie, just change a few VM flags and here you go (click for larger view):

And now to the reason why would you want to do such a thing. It didn't appear to me until after i have started to think about pluggable button shapers that i realized a whole market that Java is missing - non-male non-geek non-blind-Dvorak-typing one. What about applications that are mainly targetting female / child audience. Wouldn't it be nice to make an application that has dolphin-shaped buttons when the application is a marine encyclopedia?
Swing already has support for different button shapes - you need to implement your own ButtonUI delegate and override a couple of functions (notably the one that paints the background, the one that paints the border and the one that tests the mouse hit). So, the button shaper plugin for Substance look-and-feel provides exactly that - a collection of button shapers. The currently available shapers are:

The implementation is quite simple - each button shaper has an associated contour (implemented by GeneralPath). When a button needs to be drawn, the contour is stretched to accomodate the text. Note that the contour maintains the original proportion (unless the application explicitly sets the dimension, like in NetBeans first screenshot). In addition, each button has a shine spot that follows the outline of the button itself (more on this later):

The button contour is created, edited and saved in the shape editor (click here to launch Web Start version). Working with editor (on new shape) involves the following steps:
- Create an optional image (to simplify the process of creating the contour):
- Load the image in the editor:
- Edit major points (each segment will be represented with quadTo of GeneralPath:
- You can unselect Show image check box to view major points only:
- Edit minor points (each segment will be represented with quadTo of GeneralPath:
- You can unselect Show image check box to view minor points only:
- Click Save contour and choose the location to save to
The contour is saved as binary file (all the coordinates + the ratio of the original image) and loaded at runtime.
The most challenging part of creating arbitrarily-shaped buttons was creating a (fake) 3D shine-spot on the top half of the image. Here is how it can be accomplished using a few tricks of Java2D:
The easy part is the main background and the contour itself. Just use Graphics2D.setClip() and Graphics2D.draw(Shape) (GeneralPath is a Shape):

Now, we create a temporary image and draw the top half of the contour unto it in thick black stroke. Note that the image has margins on all sides so the the contour will fit in (this will be needed in the next step):
int shineHeight = (int) (height / 1.8);
int kernelSize = (int) Math.min(12, Math.pow(Math
.min(width, height), 0.8) / 4);
if (kernelSize == 0)
kernelSize = 1;
BufferedImage ghostContour = getBlankImage(width + 2 * kernelSize,
height + 2 * kernelSize);
Graphics2D ghostGraphics = (Graphics2D) ghostContour.getGraphics()
.create();
ghostGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
ghostGraphics.setStroke(new BasicStroke(2 * kernelSize));
ghostGraphics.setColor(Color.black);
ghostGraphics.translate(kernelSize, kernelSize);
ghostGraphics.draw(contour);
The result is:

Now, we convolve the contour image with a simple filter, creating a blurred version (this is way we needed the extra margins in the previous step, otherwise the blurred contour becomes too transparent):
int kernelMatrixSize = (2 * kernelSize + 1) * (2 * kernelSize + 1);
float[] kernelData = new float[kernelMatrixSize];
for (int i = 0; i < kernelMatrixSize; i++)
kernelData[i] = 1.0f / kernelMatrixSize;
Kernel kernel = new Kernel(2 * kernelSize, 2 * kernelSize,
kernelData);
ConvolveOp convolve = new ConvolveOp(kernel);
BufferedImage blurredGhostContour = getBlankImage(width + 2
* kernelSize, height + 2 * kernelSize);
convolve.filter(ghostContour, blurredGhostContour);
Graphics2D blurredGraphics = (Graphics2D) blurredGhostContour
.getGraphics();
The result is:

Now, we use the AlphaComposite.DstOut to fill the reverse of the blurred contour. The fill is opaque on top and translucent on bottom:
BufferedImage reverseGhostContour = getBlankImage(width + 2
* kernelSize, height + 2 * kernelSize);
Graphics2D reverseGraphics = (Graphics2D) reverseGhostContour
.getGraphics();
Color bottomShineColorTransp = new Color(bottomShineColor.getRed(),
bottomShineColor.getGreen(), bottomShineColor.getBlue(), 32);
GradientPaint gradientShine = new GradientPaint(0, kernelSize,
topShineColor, 0, kernelSize + shineHeight,
bottomShineColorTransp);
reverseGraphics.setPaint(gradientShine);
reverseGraphics.fillRect(0, kernelSize, width + 2 * kernelSize,
kernelSize + shineHeight);
reverseGraphics.setComposite(AlphaComposite.DstOut);
reverseGraphics.drawImage(blurredGhostContour, 0, 0, null);
graphics.drawImage(reverseGhostContour, 0, 0, width - 1,
shineHeight, kernelSize, kernelSize,
kernelSize + width - 1, kernelSize + shineHeight, null);
The result is:

This is almost what we wanted. However, the shine spot is a little bit too blurry, making the button look fuzzy. We need to draw the blurred contour once again in the main gradient of the button background, using AlphaComposite.DstIn:
BufferedImage overGhostContour = getBlankImage(width + 2
* kernelSize, height + 2 * kernelSize);
Graphics2D overGraphics = (Graphics2D) overGhostContour
.getGraphics();
overGraphics.setPaint(new GradientPaint(0, kernelSize,
topFillColor, 0, kernelSize + height / 2, midFillColor));
overGraphics.fillRect(kernelSize, kernelSize, kernelSize + width,
kernelSize + shineHeight);
overGraphics.setComposite(AlphaComposite.DstIn);
overGraphics.drawImage(blurredGhostContour, 0, 0, null);
graphics.drawImage(overGhostContour, 0, 0, width - 1, shineHeight,
kernelSize, kernelSize, kernelSize + width - 1, kernelSize
+ shineHeight, null);
The result is:

Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Hi
im not able to load any image in Shape Editor. Anyway it looks great - Keep up the Good Work
regards,
Jens
Posted by: mac_systems on October 17, 2005 at 05:16 AM
-
Do you cache the result of the code you are showing here? When reading this I'm concerned by the use of a ConvolveOp to blur the gradient. My experience with it tells me it's really slow and expansive.
Posted by: gfx on October 17, 2005 at 05:30 AM
-
romain,
The backgrounds are cached with the following key: height / width / cycle (for pulsating buttons) / theme / shaper. However, i've found that the computation is not very expensive - for butterfly path (40 quad segments), it takes 18-25 ms to compute 105*73 image and 33-42 ms to compute 134*94 image.
mac_systems,
The WebStart app is not signed, so it can't access your local disk. You are welcome to DL the jars from Documents & Files section and run the test.ShapeEditor class. I don't think that i will provide a signed version of shape editor.
Posted by: kirillcool on October 17, 2005 at 07:56 AM
-
Who put a rhino in my NetBeans?
Nice article. But judging by the title, I had that dejavu feeling that you would be complaining that rhino.jar is now bundled on Netbeans :-)
-- Felipe
Posted by: felipeal on October 17, 2005 at 10:32 AM
-
Well, between the wallpaper and the buttons, my first reaction would be "What 13 year old boy designed this, and did he add any backdoor security holes?"
The technique is cool, and I can imagine many applications where it would be quite useful to have shape based buttons. An icon could actually be the button.
I think you need to read some useability literature though. Women, more than men, view computers as a way to do something useful rather than as a toy. This is exactly opposite your idea that cutesy buttons would help target an app at women.
Posted by: k_mccarthy on October 17, 2005 at 11:14 AM
-
>However, i've found that the computation is not very expensive - for
>butterfly path (40 quad segments), it takes 18-25 ms to compute
>105*73 image and 33-42 ms to compute 134*94 image.
Those numbers are good indeed but what kind of computer do you use? Until recently I had a mobile P4 1.6 Ghz (not Centrino) and I'm pretty sure I'd get worse numbers :(( In my demos I had to use a clever algorithm Sebastien Petrucci wrote to get better perfs with blur.
Posted by: gfx on October 17, 2005 at 11:47 AM
-
romain,
It's P4 2.8GHz with 512MB RAM. Not top-line when it was bought, and not top-line now. In any case, the results are cached as i mentioned (not in SoftReference cache, i'll add it some time soon), so it's a one-time (per button size) hit. About the convolving - i restrict the maximum kernel size to 12 as you can see in the code. I had my own convolution algorithm that made kernel wider on bottom part of shine spot (to make it more blurry there), but the performance on large buttons was horrible. The new one (using Java2D functions) works twice as fast on 200*300 buttons and 6 times as fast on 640*310 buttons.
That said, i would need to benchmark various parts of the code above, and if the convolution takes more than 30-40% of the total computation time, i'd be happy to use that magic code - in case it's BSD. Does that code make any assumptions on the image it's convolving, because the button contour can be arbitrary (view butterfly)? Do you have a link at hand?
Posted by: kirillcool on October 17, 2005 at 11:57 AM
-
k_mccarthy,
The "female and child" audience was a sort of generalization. I was mainly referring to applications that target a very specific market. Let's say Revlon wishes to make an application that allows to scan your picture and try different sorts of makeups. Wouldn't it be nice to have a lipstick-shaped buttons (designed by a professional designer and not by me of course) as "Apply" or "Revert"? The same goes to educational applications targeted at children. It's not that the targeted recipient wouldn't be able to click on rectangular button. It's just the "look" of the application.
That said, you are correct about the "13-year old" remark. I didn't invest a lot of thought putting together the theme, the watermark and the button shaper. Just showing what can be done with Swing GUI with no changes to the code itself. The usability literature would be left to the (hopefully) potential users of the look-and-feel. Next time i promise to use a wallpaper of Wentworth Miller :)
Last thing about using an icon as button - what about the text (that can be long and / or multiline)? And the rollover effects? The listeners?
Posted by: kirillcool on October 17, 2005 at 12:02 PM
-
I didn't mean use icons as buttons, I meant using your technique, something that might have been an icon on a button could be turned into the button itself. An interactive map comes to mind here.
Generalizations get you into trouble when they are inaccurate, and then color your thinking. Shaped buttons are something I see frequently in media players (Winamp, etc.). They are used to be "cool", and the market is certainly not "women and children". They target a market of "people who want cool looking" and that is not easily turned into a gender category, or even an age category.
Posted by: k_mccarthy on October 18, 2005 at 06:22 AM
-
A simpler approach seems to me a regular JButton with an icon, no text and no borders. That would show up with the shape of the icon.
OK, this is unsatisfactory on some levels. An icon won't have edges like a button, and without a 3D effect if might be difficult to see it as such. But the corresponding icon can be designed to look like a button with sharp edges and a drop shadow. And different versions (button pressed, released) of the icon can be prepared.
The implementation is way easier and the icon design work is pure icon design - no programming needed. There's much more involved with your way of coming up with buttons for new shapes.
Posted by: sumitkishore on October 26, 2005 at 07:26 PM
-
sumitkishore,
Not only will you have to provide different versions of the icon for state (button pressed, button released), but also you will have to provide different versions of the icon for each string length / height. This is the major issue of pixel-precision vs. vector-based graphics. Vector based drawing is more complicated but much more flexible. All rhinos in the above images were created using the same vector-based contour that allowed creating arbitrarily-sized button. How will you be able to address this aspect with pixel-based icons?
Posted by: kirillcool on October 27, 2005 at 08:43 AM
|