Swing Glint

It's been almost a week since the last trick and it's high time I presented you something new. Our beloved 2D user interfaces are great but make it difficult, sometimes, to given an impression of depth. Obviously it would be much easier to use a 3D API like JOGL or Java3D but Java2D can do just fine with a bit of work. Consider the following screenshot:

That crude dialog is just a test bed and will be later animated. That is why I do not provide the source code nor an executable demo. Finally, the bottom panel with the Start button is only here for debugging purpose. But let's take a look at how the depth is simulated.

First, a bi-directionnal gradient is used in the background. It is created using two gradient starting from the middle of the window, one going up and the other one going down:

Color gradientStart = new Color(204, 249, 124);
Color gradientEnd = new Color(174, 222, 94);

GradientPaint painter = new GradientPaint(0, 0, gradientEnd,
                                          0, height / 2, gradientStart);
Rectangle2D rect = new Rectangle2D.Double(0, 0, width, height / 2.0);

painter = new GradientPaint(0, height / 2, gradientStart,
                            0, height, gradientEnd);
rect = new Rectangle2D.Double(0, (height / 2.0) - 1.0, width, height);

The next step is to reduce the size of the elements, simple pictures in our case, as they are (supposed to be) farther on the depth axis. To stress the effect, I also decreased the opacity of the elements according to the distance. The nearest one is fully opaque (100%), the two closest neighbours are 80% opaque and the last ones are 60% opaque. Finally, and I guess you alreadt noticed, a reflection is drawn for each element.

To draw a reflection, I call the following method, giving it the element as a BufferedImage:

private BufferedImage createReflectedPicture(BufferedImage avatar) {
    int avatarWidth = avatar.getWidth();
    int avatarHeight = avatar.getHeight();

    BufferedImage gradient = createGradientMask(avatarWidth, avatarHeight);
    BufferedImage buffer = createReflection(avatar,
                                            avatarWidth, avatarHeight);

    applyAlphaMask(gradient, buffer, avatarWidth, avatarHeight);

    return buffer;

Three steps are required here. The first one is to create the gradient mask which will tell how to change the opacity on the reflected picture. Next step is to create a picture twice as high as the original one containing the element and its reflected counterpart. In last step, we apply the alpha mask, a linear gradient here, to the reflected picture. Here are the different pictures used or generated along the process (put on a black background to better see the opacity changes):

Creating the gradient mask is the easiest part of the process, thanks to Java2D GradientPaint:

private BufferedImage createGradientMask(int avatarWidth, int avatarHeight) {
    BufferedImage gradient = new BufferedImage(avatarWidth, avatarHeight,
    Graphics2D g = gradient.createGraphics();
    GradientPaint painter = new GradientPaint(0.0f, 0.0f,
                                              new Color(1.0f, 1.0f, 1.0f, 0.5f),
                                              0.0f, avatarHeight / 2.0f,
                                              new Color(1.0f, 1.0f, 1.0f, 0.0f));
    g.fill(new Rectangle2D.Double(0, 0, avatarWidth, avatarHeight));

    return gradient;

To create the reflected picture, we can simply use an AffineTransform with a negative scale. And the last operation is to apply the gradient mask to the reflected picture's opacity:

private void applyAlphaMask(BufferedImage gradient,
                            BufferedImage buffer,
                            int avatarWidth, int avatarHeight) {

    Graphics2D g2 = buffer.createGraphics();
    g2.drawImage(gradient, null, 0, avatarHeight);

Thanks to S├ębastien who came up with a much better solution for applyAlphaMask() that the one I had. He was able to use the composite DstOut instead of a complicated loop full of bitwise operations. I'm glad to see that I was dumb and the JDK wasn't missing this useful operation after all (even though I don't understand how I missed it since I tried all the available AlphaComposites ^^ I guess I missed this one :)

Anyway, you can just call createReflectedPicture() to get the final result and use it right away on any drawable surface. I did not try to use it to reflect Swing components but it could be funny. This reflection technique can be useful in splash screens for instance, to create effects similar to this one:

Happy Swinging!

2 Responses to “Swing Glint”

  1. Robert says:

    Instead of using a GradientPaint as a alpha mask can we use a TexterPaint set to use a iamge with a gradient alpha values?