EnglishSeparatorDeutsch
Thinking Out of the Box. Centigrade Blog.
Simon Albers

Frosted Glass on the Java™ Desktop

March 16th, 2010 by Simon Albers

More and more operating systems use a border resembling frosted glass for their windows, like, e.g., the Aero Glass® decoration known from Windows Vista® and Windows 7®. Providing this ’special effect’ on the Java™ platform is still not easy to realize. Most Look and Feels use opaque borders, which do not visually match the surrounding designs of these operating systems.
This article describes a pragmatic approach to solve to this problem.

The desired result of a Java implementation is illustrated in the following figure:

Illustration of the desired frosted glass border

Illustration of the desired frosted glass border

The whole procedure of rendering such a border can be divided into three steps:

  1. Blurring the background that is covered by the border area
  2. Overlaying the ‘milky’ surface with respect to the rounded corners
  3. Painting outline decoration and drop shadow

How to blur the covered background

Blurring a region of an image is quite complicated in Java 2D since it is not a simple composite operation – instead, it is a more complicated convolution operation. Additionally, the fact that a JComponent and its UI only receive a reference to a Graphics instance on which painting occurs makes it even more complicated.
One option to actually access the pixel data is to override JDesktopPane’s paint(Graphics) method and invoke super.paint(Graphics) with a Graphics instance created from an offscreen image, which finally contains the necessary data and can be painted on the original Graphics. To ensure that JDesktopPane’s paint method is in charge of painting the child components ‘optimized drawing’ has to be disabled.

public class FrostedGlassDesktopPane extends JDesktopPane {

  public boolean isOptimizedDrawingEnabled() {
    // we want to be in charge of painting the children
    return false;
  }

  public void paint(Graphics g) {

    // we only have to care about
    // what's inside clipping bounds
    Rectangle clipBounds = g.getClipBounds();
    // request a (reused) offscreen buffer
    BufferedImage bufferImage =
      getBufferImage(clipBounds.width, clipBounds.height);
    Graphics bufferGraphics = bufferImage.createGraphics();
    // translate and clip according to our given graphics g
    bufferGraphics.translate(-clipBounds.x, -clipBounds.y);
    bufferGraphics.setClip(clipBounds);

    try {
      // let children paint
      super.paint(this.bufferGraphics);
      // blit offscreen buffer to given graphics g
      g.drawImage(bufferImage, clipBounds.x, clipBounds.y,
        null);
    } catch (RuntimeException ex) {
      throw ex;
    } finally {
      bufferGraphics.dispose();
    }
  }
}
Desktop pane painting child components into an offscreen buffer



Since a BufferedImage is used for offscreen rendering it would be possible to use a ConvolveOp for the blur effect. Unfortunately, even with a small sized kernel of 5 by 5 pixels, the operation seems to be too slow for a real-world situation where the CPU is also busy with drawing additional GUI elements and executing application logic.
A good alternative seems to be using a one dimensional box blur (also called motion blur), which can be computed very fast using a moving average algorithm. Applying it horizontally leads to the following result:

Example of a one dimensional box blur

Example of a one dimensional box blur

Doing it in a first pass horizontally and in a second one vertically results in a two dimensional box blur:

Example of a two dimensional box blur

Example of a two dimensional box blur

Since execution is really fast but visually imperfect, this filter operation can be done multiple times to get increasingly better results with every iteration. Just doing it twice already looks a lot smoother and less artificial:

Example of a two dimensional box blur with two iterations

Example of a two dimensional box blur with two iterations

This solution delivers acceptable results regarding visual quality and performance. Benchmarking a small demo application with four and a second one with eight internal frames shows good frame rates compared to the aforementioned ConvolveOp version:

Comparison of home-grown box blur and ConvolveOp with 4 internal frames

Comparison of home-grown box blur and ConvolveOp with 4 internal frames

Comparison of home-grown box blur and ConvolveOp with 8 internal frames

Comparison of home-grown box blur and ConvolveOp with 8 internal frames

‘Milky’ overlay and rounded corners

To get a naturally looking frosted glass effect, a ‘milky’ overlay is applied after doing the blur. This can be done in a simple fashion by filling an area with a translucent color. However the rounded corners are more difficult to achieve.
The first choice for realizing this non-rectangular outline would be a clipping shape. But because the blur is done directly in a pixel raster by computing RGB values in contrast to operating on a Graphics instance, a clipping shape is not applicable. Another drawback of using a clipping shape is missing anti-aliasing resulting in an unaesthetic staircase effect. Although these artifacts can be avoided by overlaying an image with a thin anti-aliased outline (like we did for the leather texture in Cezanne’s showcase application Icon Book), there are more efficient options available.
A reasonable compromise between effort, performance and visual quality is

  1. saving the corner area to a buffer (cornerBuffer) before painting the border
  2. painting the border with rectangular shape
  3. cutting out the piece of the corner buffer that should not be obscured by rounded corners
  4. copying that piece back into the Graphics

For step 3, a masking image (of which only the alpha channel is necessary) can be used together with an AlphaComposite of type DST_OUT:

protected void restoreCorner(Graphics g, int x, int y) { 

  Graphics2D g2d = cornerBuffer.createGraphics();
  g2d.setComposite(AlphaComposite.getInstance(
    AlphaComposite.DST_OUT));
  g2d.drawImage(maskingImage, 0, 0, null);
  g2d.dispose();
  g.drawImage(cornerBuffer, x, y, null);
}
Restoring the corner area ‘below’ the rounded border

The following images illustrate the process step by step:

Background with indication of corner area

Background with indication of corner area

Rectangularly painted border

Rectangularly painted border

Corner buffer combining with corner mask

Corner buffer combining with corner mask

Background restored from corner buffer

Background restored from corner buffer

Painting outline decoration and drop shadow

The last and certainly most easy step is painting the border outline and the drop shadow. This can be done by assembling picture elements using the 9-Slice-Scaling method.

The result

Result after applying all visual effects

Result after applying all visual effects

A sample application demonstrating this new frosted glass desktop can be started via Web Start:

Start sample application via Web Start

Interestingly the signed version of the demo application is noticeably faster (about 40% on some machines):

Start signed sample application via Web Start

Conclusion

Although creating a frosted glass border decoration in Java is not an easy task, it is possible to achieve great results with a few tricks.
The implementation I did for this article is not a full-featured replacement for JDesktopPane and JInternalFrame’s UI. It lacks some essential aspects of the original Java counterparts, like, e.g., the system menu, proper decoration of full size frames and iconified frames and correct location of drag-resize area. Nevertheless, it serves as a good example on how the visual appearance can be achieved.

Feel free to download the Jar and give it a try in your application (as long as it is non-commercial):

Download frosted glass desktop Jar (about 60 KBytes)



Using it is pretty simple: You can either assign the JInternalFrame UI on a per instance basis …

import de.centigrade.frostedglass.FrostedGlassDesktopFactory;
...
JDesktopPane desktopPane = FrostedGlassDesktopFactory
  .getInstance().createDesktopPane();
JInternalFrame internalFrame = new JInternalFrame(null, true,
  true, true, false);
internalFrame.setUI(FrostedGlassDesktopFactory.getInstance()
  .createInternalFrameUI());
internalFrame.setBounds(100, 100, 200, 150);
internalFrame.setVisible(true);
desktopPane.add(internalFrame);

… or set it as global UI for all JInternalFrames

import de.centigrade.frostedglass.FrostedGlassDesktopFactory;
...
UIManager.put("InternalFrameUI",
  FrostedGlassDesktopFactory.class.getName());
JDesktopPane desktopPane = FrostedGlassDesktopFactory
  .getInstance().createDesktopPane();
JInternalFrame internalFrame = new JInternalFrame(null, true,
  true, true, false);
internalFrame.setBounds(100, 100, 200, 150);
internalFrame.setVisible(true);
desktopPane.add(internalFrame);

You can vary the frost effect by using different color overlays. These can be created with static factory methods in class de.centigrade.frostedglass.FrostEffect. Either you can use one of the predefined colors or you choose createColored(Color) which takes an arbitrary color (but has slightly lower performance).

import de.centigrade.frostedglass.FrostEffect;
import de.centigrade.frostedglass.FrostedGlassDesktopFactory;
...
JInternalFrame internalFrame = new JInternalFrame(null, true,
  true, true, false);
internalFrame.setUI(FrostedGlassDesktopFactory.getInstance()
  .createInternalFrameUI());
FrostEffect darkFrostEffect = FrostEffect.createDark();
darkFrostEffect.apply(internalFrame);

Sun, Sun Microsystems, the Sun Logo, Java and Web Start are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.
Microsoft, Windows Vista, Windows 7, Aero and Aero Glass are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries.
Copyright ©2010 Centigrade GmbH. All rights reserved.