Java Tech: Image Embossing Blog

Version 2



    An Algorithm for Embossment
    The EmbossedIconclass

    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?

    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 EmbossedIconsubclasses 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:

    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 > 0 AND column > 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) > ABS (diff) SET diff TO greenIntensityDiff IF ABS (blueIntensityDiff) > 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

    The pseudocode identifies the image to be embossed assrc and the image to hold the embossment asdst. 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.

    TheEmbossedIcon 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 theResources section for this article's sample code) shows how easy it is to choose between anImageIcon and EmbossedIcon:

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

    EmbossedIcon overrides ImageIcon'spublic ImageIcon(String filename) constructor andpublic 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 ajava.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 overriddensetImage() method depend on the privateBufferedImage emboss(BufferedImage src) method to perform the embossing. That method assumes its srcimage has the same TYPE_INT_RGB type as the destination image. The emboss() method appears below:

    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 < height; i++) for (int j = 0; j < width; j++) { int current = src.getRGB (j, i); int upperLeft = 0; if (i > 0 && j > 0) upperLeft = src.getRGB (j - 1, i - 1); int rDiff = ((current >> 16) & 255) - ((upperLeft >> 16) & 255); int gDiff = ((current >> 8) & 255) - ((upperLeft >> 8) & 255); int bDiff = (current & 255) - (upperLeft & 255); int diff = rDiff; if (Math.abs (gDiff) > Math.abs (diff)) diff = gDiff; if (Math.abs (bDiff) > Math.abs (diff)) diff = bDiff; int grayLevel = Math.max (Math.min (128 + diff, 255), 0); dst.setRGB (j, i, (grayLevel << 16) + (grayLevel << 8) + grayLevel); } return dst; }

    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 EmbossedIconcopies an Image's pixels into aBufferedImage. The following EmbossedIconconstructor reveals all:

    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); }

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


    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 theEmbossedIcon class, you might want to enhanceEmbossedIcon, by overriding otherImageIcon 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.