Skip to main content

Java Tech: Image Embossing

December 8, 2005

{cs.r.title}









Contents
An Algorithm for Embossment
The EmbossedIcon class
Conclusion
Resources

What do books written in Braille, documents sealed with hot wax
to guarantee their authenticity, and coins have in common? These
objects illustrate embossment--the act of raising in relief
from a surface. Embossment helps blind individuals read via the
Braille system of raised dots, imprints seals onto documents for
the purpose of authentication, and imprints seals onto coins (in
the past, these seals guaranteed the weights and values of precious
metals used in the coins).

In addition to its uses in the real world, embossment provides a
three-dimensional chiseled look for computer images. This look
tends to result in images that have a more professional appearance.
For example, suppose you are responsible for creating professional
investment software. Given a choice between the dialogs shown in
Figure 1, which dialog do you think would be more appropriate?

<br "A comparison of dialogs with non-embossed and embossed images" />
Figure 1. A comparison of dialogs with non-embossed and embossed
images

From time to time, you might want to emboss your own images.
Although you can use existing image-enhancement software to
accomplish that task, you may find it more convenient to let your
Java programs do the work for you.

This article presents an algorithm for embossing images. The
article then implements it as EmbossedIcon, a class
that displays embossed images. Because EmbossedIcon
subclasses javax.swing.ImageIcon, you can easily
convert source code that depends on ImageIcon to
source code based on EmbossedIcon.

An Algorithm for Embossment

Think of an image as mountainous terrain. Each pixel represents
an elevation: brighter pixels represent higher elevations. When an
imaginary light source shines down on this terrain, the uphills
that face the light source are lit, whereas the downhills that face
away from the light source are shaded. An embossment algorithm
captures this information.

The algorithm scans an image in the direction that a light ray
is moving. For example, if the light source is located to the
image's left, its light rays move from left to right, so the scan
proceeds from left to right. During the scan, adjacent pixels (in
the scan direction) are compared. The difference in intensities is
represented by a specific level of gray (from black, fully shaded,
to white, fully lit) in the destination image.

There are eight possible scanning directions: left to right,
right to left, top to bottom, bottom to top, and four diagonal
directions. To simplify the code, the algorithm usually scans left
to right and top to bottom, and chooses its neighbors as
appropriate. For example, if the light source is above and to the
left of the image, the algorithm would compare the pixel above and
to the left of the current pixel with the current pixel during the
left-to-right and top-to-bottom scan. The comparison is
demonstrated in the following embossment algorithm pseudocode:

[prettify]FOR row = 0 TO height-1
    FOR column = 0 TO width-1
        SET current TO src.rgb [row][column]

        SET upperLeft TO 0
        IF row &gt; 0 AND column &gt; 0
           SET upperLeft TO
               src.rgb [row-1][column-1]

        SET redIntensityDiff TO
            red (current)-red (upperLeft)
        SET greenIntensityDiff TO
            green (current)-green (upperLeft)
        SET blueIntensityDiff TO
            blue (current)-blue (upperLeft)

        SET diff TO redIntensityDiff
        IF ABS (greenIntensitydiff) &gt; ABS (diff)
           SET diff TO greenIntensityDiff
        IF ABS (blueIntensityDiff) &gt; ABS (diff)
           SET diff TO blueIntensityDiff

        SET grayLevel TO
            MAX (MIN (128 + diff, 255), 0)

        SET dst.rgb [row][column] TO grayLevel
    NEXT column
NEXT row
[/prettify]

The pseudocode identifies the image to be embossed as
src and the image to hold the embossment as
dst. These images are assumed to be rectangular
buffers of RGB pixels.

Because pixels in the topmost row don't have neighbors above
them, and because pixels in the leftmost column don't have
neighbors to their left, the pseudocode assigns a mid-gray value to
each pixel in the top row and in the left column. Figure 1 reveals
these highlights along the top and left edges of the embossed
image.

After obtaining the red, green, and blue intensities for
the current pixel and its upper-left neighbor, the pseudocode
calculates the difference in each intensity. The upper-left
neighbor's intensities are subtracted from the current pixel's
intensities because the light ray is moving in an upper-left to
lower-right direction.

The pseudocode identifies the greatest difference (which might
be negative) between the current pixel and its upper-left neighbor's
three intensity differences. That's done to obtain the
best-possible chiseled look. The difference is then converted to a
level of gray between 0 and 255, and that gray level is stored in
the destination image at the same location as the current pixel in
the source image.

The EmbossedIcon class

To make it convenient for your Java programs to emboss their own
images, I have created an EmbossedIcon class that
subclasses ImageIcon. The code fragment below (see the
Resources section for this article's sample code)
shows how easy it is to choose between an ImageIcon
and EmbossedIcon:

[prettify]ImageIcon ii = (embossed)
               ? new EmbossedIcon ("logo.gif")
               : new ImageIcon ("logo.gif");
JLabel lbl = new JLabel (ii);
[/prettify]

EmbossedIcon overrides ImageIcon's
public ImageIcon(String filename) constructor and
public void setImage(Image image) method to load an
image into a java.awt.Image object, and then cache an
embossed version of the image in a
java.awt.image.BufferedImage object. The
public
void paintIcon(Component c, Graphics g, int x, int y)
method
is also overridden to paint the embossed image.

EmbossedIcon's constructor and overridden
setImage() method depend on the private
BufferedImage emboss(BufferedImage src) method to
perform the embossing. That method assumes its src
image has the same TYPE_INT_RGB type as the
destination image. The emboss() method appears
below:

[prettify]private BufferedImage emboss (BufferedImage src)
{
   int width = src.getWidth ();
   int height = src.getHeight ();

   BufferedImage dst =
     new BufferedImage (width, height,
                        BufferedImage.TYPE_INT_RGB);

   for (int i = 0; i &lt; height; i++)
        for (int j = 0; j &lt; width; j++)
        {
             int current = src.getRGB (j, i);

             int upperLeft = 0;
             if (i &gt; 0 &amp;&amp; j &gt; 0)
                 upperLeft = src.getRGB (j - 1,
                                         i - 1);

             int rDiff = ((current &gt;&gt; 16) &amp; 255) -
                         ((upperLeft &gt;&gt; 16) &amp; 255);
             int gDiff = ((current &gt;&gt; 8) &amp; 255) -
                         ((upperLeft &gt;&gt; 8) &amp; 255);
             int bDiff = (current &amp; 255) -
                         (upperLeft &amp; 255);

             int diff = rDiff;
             if (Math.abs (gDiff) &gt; Math.abs (diff))
                 diff = gDiff;
             if (Math.abs (bDiff) &gt; Math.abs (diff))
                 diff = bDiff;

             int grayLevel =
               Math.max (Math.min (128 + diff, 255),
                         0);
             dst.setRGB (j, i,
                         (grayLevel &lt;&lt; 16) +
                         (grayLevel &lt;&lt; 8) +
                         grayLevel);
        }

   return dst;
}
[/prettify]

Because emboss()'s source code closely approximates
the embossment algorithm's pseudocode, and because you have already
discovered how that pseudocode performs embossment, you should have
no difficulty in figuring out how emboss() works.
Instead, you might want to learn how EmbossedIcon
copies an Image's pixels into a
BufferedImage. The following EmbossedIcon
constructor reveals all:

[prettify]public EmbossedIcon (String filename)
{
   super (filename);

   bi = new BufferedImage (getIconWidth (),
                           getIconHeight (),
                           BufferedImage.TYPE_INT_RGB);
   Graphics2D bg = bi.createGraphics ();
   bg.drawImage (getImage (), null, null);
   bi = emboss (bi);
}
[/prettify]

After invoking the ImageIcon superclass
constructor, which loads the image from the file identified by
filename and saves it into an Image, a
BufferedImage is created. Its width and height are
determined by invoking getIconWidth() and
getIconHeight(). The Image's pixels are
then copied into the BufferedImage by first obtaining
a Graphics2D from the new BufferedImage
and then invoking one of its drawImage() methods to
draw the original image into the Graphics2D. Finally,
emboss() is called to emboss the contents of the
BufferedImage.

Conclusion

Embossing your Java program's images can add a degree of
professionalism to the program. An embossment algorithm simulates a
light ray moving across an image in a specific direction, and
computes intensity differences between neighboring pixels in this
direction. The greatest of these differences determines a gray
level in the destination image. The EmbossedIcon class
implements embossing, via its emboss() method. You can
conveniently use EmbossedIcon wherever you would use
its ImageIcon superclass.

Now that you have explored the embossment algorithm and the
EmbossedIcon class, you might want to enhance
EmbossedIcon, by overriding other
ImageIcon constructors and

public Image
getImage()
to return the BufferedImage instead
of the Image. Finally, you might want to change the
embossment algorithm, to create different chiseled looks.

Resources

width="1" height="1" border="0" alt=" " />
Jeff Friesen is a freelance software developer and educator specializing in Java technology. Check out his site at javajeff.mb.ca.
Related Topics >> GUI   |   Programming   |   Swing   |