The Source for Java Technology Collaboration
User: Password:



   

Pixel Pushing Pixel Pushing

by Jonathan Simon
06/07/2005

Contents
Our Victim
Analysis
The Icon Interface
Dropper
Wrap-Up
Props
Resources

Building a graphical interface is hyper-detailed, laborious work. A server-side or non-graphical component can usually get by fixing an interface that needs a little reworking "in the next release." Graphical interfaces simply don't have that option. This is because any detail left unattended is displayed in plain sight to the users of our applications. Adjacent borders, buttons and text have to line up to the pixel--otherwise, the interface simply looks sloppy. And we, as graphical interface developers, need to strive to make interfaces that don't look sloppy.

With that in mind, we are going to focus on the nitty-gritty, pixel-level details of a graphical interface. In this article, we'll copy a native operating system icon in Java code and explore some tools and process that can help you get used to working in the pixel-pushing world.

Our Victim

The icon on the bottom right-hand corner is the universal Windows signal for "you can resize me." Well, almost universal; there are other commonly used icons to indicate resizability. But these are not used for Windows Explorer, and standards (or lack thereof) would make for a whole separate article. Anyway, this resize icon is a good example of something graphical that is not built into Java, but that you will need in every Java Windows application. Sorry, Mac and Linux folks--you'll still learn a lot, so come along for the ride!

The Windows Explorer status bar
Figure 1. The Windows Explorer status bar. Notice the right corner icon.

In order to mimic this icon effectively, we'll follow the standard software process of analysis, development, and testing. Analysis and development are the same with graphical as with non-graphical development. The big process change is with testing. Normally, we are focused on code testing, whereas here we need to be not only concerned with code testing, but also graphical testing. We'll get started going through this process on the Windows resize icon and the graphical zoom tools that can help you check your work.

As a quick note, the windows icon implies not only resizing from the corners, but also resizing while hovering over the icon. Since this article is focused on painting, layout, and other visual issues, and not functionality, we'll skip over the code to make the resizing work from hovering over the icon. You'll need to add a custom mouse listener for the icon to do window resizing.

Analysis

The first step in developing any component is analysis, whether you're developing the component from a picture provided by an interaction designer, or by copying a native component. I start my analysis by taking a closer look. And when I say a closer look, I mean a closer look. I like to use a free zoom utility called ZoomIn. Figure 2 shows a screen shot of ZoomIn zooming in on the corner icon:

The ZoomIn utility zooming in on a status bar
Figure 2. The ZoomIn utility zooming in on a status bar

Next to my IDE, ZoomIn is probably the most important interface development tool in my arsenal. I don't just use it to inspect images for recreating them, but also to inspect the components I build. How many pixels are there between the text and the border? Does using a nested layout break my alignment, even by a pixel? Do my custom text components place text equally horizontal and vertical? These are the types of questions interface developers need to be able to answer--and you can't do it by sight alone. You need a quick zoom utility to help you clearly see what's going on at the pixel level. Think of it as a debugger for your graphics.

Mac-based developers aren't left out--you can also zoom by turning on zooming in Mac OS X's accessibility features. Go to System Preferences -> Universal Access -> Seeing. You should see Zoom as the middle panel. You can use the respective key combinations to zoom in or out once this is turned on.

Turning on zoom on the mac
Figure 3. Turning on zoom for the Mac

The Icon Interface

There are a few different ways we could achieve this effect: we could create an image (.jpg, .gif, etc.), or we could extend a component and override paint(). But there is another very convenient yet often unthought-of third option--we can make our own implementation of the Icon interface. Although the most common uses of javax.swing.Icon are its concrete implementations like ImageIcon, which you instantiate with an image file (like a .gif or a .jpg), we can also implement the Icon interface directly.

For control icons like this one, I find it easier to implement the icon directly in code--this saves me from having to work between different programs as well as being able to ignore graphics-specific details like opacity in the image file. If you are not comfortable working with graphics programs, this can be a reasonable alternative. Furthermore, this approach gives you control over the colors at runtime. Let's say you want the foreground color to match the border color: this can easily be done in code, but is extremely difficult to do with a static image files, as you can't easily change them on the fly.

This same effect can be achieved with overriding paint() on a component. But by implementing Icon, you can add images to buttons, labels, and other components that deal with icons, without having to implement custom layouts and worry about opacity.

The Icon interface has three methods:

  • void paintIcon(Component c, Graphics g, int x, int y);
  • int getIconWidth();
  • int getIconHeight();

Here is the beginning of our implementation. We'll call it WindowsBottomRightCornerIcon.


public class WindowsBottomRightCornerIcon implements Icon {
       ...
}

We need to get a closer look at the icon before we can write the rest of the code. This is a good time to bring out ZoomIn again and see what the icon looks like up close.

The corner Icon from Windows Explorer
Figure 4. The corner Icon from Windows Explorer

The icon is a series of six gray squares with a subtle white overlap 3D effect. We may not be totally sure of the height and width of the squares or of the dimensions of the overlap, but we have a good idea of the overall structure. We can get a better feeling for the drawing at the pixel level with a grid that marks the boundaries of a pixel when zoomed in. ZoomIn, like most graphics applications, has one of these grids. Here is the corner icon with the grid turned on.

The corner icon, zoomed in with grid
Figure 5. The corner icon, zoomed in with grid

Now it's clear that the squares are two pixels wide, two pixels high, and spaced two pixels apart. We can also see that the white squares are the same size as the gray squares and offset by one horizontal and one vertical pixel.

And now we have enough information to calculate the dimensions and write the getWidth and getHeight methods. Here is the width calculation:

(2 pixel box width + 1 pixel white 3D effect + 1 pixel space) * 3 boxes = 12 pixels

The height is calculated the same way:

(2 pixel box height + 1 pixel white 3D effect + 1 pixel space) * 3 boxes = 12 pixels

Here are the getWidth and getHeight methods. We'll declare width and height constants and return them from the appropriate getter:


//Dimensions
private static final int WIDTH = 12;
private static final int HEIGHT = 12;

...

public int getIconHeight() {
  return HEIGHT;
}

public int getIconWidth() {
  return WIDTH;
}

With the appropriate dimensions in place, we can move on to the paint() method. We'll start by looking at the colors of the squares and the 3D effect, since we need them to draw the icon. ZoomIn has a color introspection tool that is useful for this. When you hover the cursor over a zoomed-in pixel, the RGB value is displayed on the ZoomIn status bar. Figure 6 shows ZoomIn with the cursor over one of the squares in the icon, letting us know the RGB values are R:184, G:180, and B:161.

 ZoomIn showing RGB color values in its status bar
Figure 6. ZoomIn showing RGB color values in its status bar

But close inspection reveals that the gray squares themselves are not a solid color. In fact, the left side is one color, the top right is a second, and the bottom right is a third! This is a perfect example of the subtle gradations used all over new operating systems, and definitely something to look out for. It also is a great example of using a zoom tool to get a better look and make sure the colors are what we think they are. So now we'll define constants for these colors:


//RGB values discovered using ZoomIn
private static final Color THREE_D_EFFECT_COLOR
    = new Color(255, 255, 255);
private static final Color SQUARE_COLOR_LEFT
    = new Color(184, 180, 163);
private static final Color SQUARE_COLOR_TOP_RIGHT
    = new Color(184, 180, 161);
private static final Color SQUARE_COLOR_BOTTOM_RIGHT
    = new Color(184, 181, 161);

With these colors defined, we can begin drawing. We could paint the gray squares followed by drawing a series of "L"s to make the 3D effect. But there is an easier approach. We could simply paint the white squares and repeat the same paint algorithm offset by one pixel horizontally and vertically. The end result will be the same, since the gray squares will paint over one pixel of the white squares--but the code will be much easier to write and understand.

In order to simplify the painting algorithm further, here is a helper method to paint the white squares. Painting algorithms are notorious for spaghetti code, so make use of helper methods!


private void draw3dSquare(Graphics g, int x, int y){
    Color oldColor = g.getColor(); //cache the old color
    g.setColor(THREE_D_EFFECT_COLOR); //set the white color
    g.fillRect(x,y,2,2); //draw the square
    g.setColor(oldColor); //reset the old color
}

And now we'll do the same for the gray squares--only this is going to be a little more complicated, since we are dealing with three different colors.


private void drawSquare(Graphics g, int x, int y){
    Color oldColor = g.getColor();
    g.setColor(SQUARE_COLOR_LEFT);
    g.drawLine(x,y, x,y+1);
    g.setColor(SQUARE_COLOR_TOP_RIGHT);
    g.drawLine(x+1,y, x+1,y);
    g.setColor(SQUARE_COLOR_BOTTOM_RIGHT);
    g.drawLine(x+1,y+1, x+1,y+1);
    g.setColor(oldColor);
}

Now for the paintIcon() code, where we'll lay out our virtual grid: it starts by defining anchor row and column coordinates, followed by row and column spacing, and finally, the coordinates for each of the three rows and columns:



//column and row anchors
int firstRow = 0;
int firstColumn = 0;

//spacing
int rowDiff = 4;
int columnDiff = 4;

//second and third columns and rows
int secondRow = firstRow + rowDiff;
int secondColumn = firstColumn + columnDiff;
int thirdRow = secondRow + rowDiff;
int thirdColumn = secondColumn + columnDiff;

We can tie it all together and paint the squares in the triangle pattern. Here is the remainder of the paintIcon() code.


//Draw the white squares first, so the gray squares
//will overlap.  notice the 1 pixel offsets

draw3dSquare(g, firstColumn+1, thirdRow+1);

draw3dSquare(g, secondColumn+1, secondRow+1);
draw3dSquare(g, secondColumn+1, thirdRow+1);

draw3dSquare(g, thirdColumn+1, firstRow+1);
draw3dSquare(g, thirdColumn+1, secondRow+1);
draw3dSquare(g, thirdColumn+1, thirdRow+1);

//draw the gray squares overlapping the white
//background squares

drawSquare(g, firstColumn, thirdRow);

drawSquare(g, secondColumn, secondRow);
drawSquare(g, secondColumn, thirdRow);

drawSquare(g, thirdColumn, firstRow);
drawSquare(g, thirdColumn, secondRow);
drawSquare(g, thirdColumn, thirdRow);

For comparison, Figure 6 shows our corner icon on the right with the Windows Explorer icon on the left--the icons are identical.

Native Windows icon (left) and Java-imaged icon (right), zoomed in
Figure 7. Native Windows icon (left) and Java-imaged icon (right), zoomed in

The only differences between the corners are outside the icon--and therefore the scope of the article. But just to get used to looking for these differences, let's take a look at them. Notice the faint blue line above the bottom and the horizontal gradations toward the top of the status bar. These are all problems that we would want to address before the final complete status bar.

Dropper

Another tool I use from the makers of ZoomIn is Dropper. It has a draggable eyedropper that tells you the color of any location on the screen. ZoomIn can tell you what the color of a zoomed-in pixel is, as well, but Dropper is very useful when you need to find a color but don't need to zoom in--the color of a solid background, for example. Dropper also shows you colors in numeric RGB, RGB hex, COLORREF, and HTML. Figure 8 shows a screen capture of Dropper.

The Dropper utility
Figure 8. The Dropper utility

Wrap-Up

This process is universal for graphic interface development. Whether you're copying a native component, working from a design image, or simply checking your custom components and screens, the process is the same. Analyze the task at hand, write the code, check the result at high magnification to test the results. Rinse and repeat. In this article, we also learned a bit about the Icon interface, noting its use for implementing runtime-tweakable icons and not resorting to graphics applications.

Props

A big thanks to Brett Kotch for first showing me ZoomIn.

Resources

Jonathan Simon is a developer and author specializing in user interaction.

View all java.net Articles.

 Feed java.net RSS Feeds