Skip to main content

Pixel Pushing

June 7, 2005

{cs.r.title}









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!

<br "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 "http://www.csc.calpoly.edu/~bfriesen/software/zoomin.shtml">ZoomIn.
Figure 2 shows a screen shot of ZoomIn zooming in on the corner
icon:

<br "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.

<br "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.

<br "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.

<br " 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.

<br "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 "http://www.csc.calpoly.edu/~bfriesen/software/dropper.shtml">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.

<br "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

width="1" height="1" border="0" alt=" " />
Jonathan Simon is a developer and author specializing in user interaction.
Related Topics >> GUI   |