|
|
|||||||||||
by Jeff Friesen | |||||||||||
| |||||
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?

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.
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 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.
EmbossedIcon classTo 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:
ImageIcon ii = (embossed)
? new EmbossedIcon ("logo.gif")
: new ImageIcon ("logo.gif");
JLabel lbl = new JLabel (ii);
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:
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 EmbossedIcon
copies an Image's pixels into a
BufferedImage. The following EmbossedIcon
constructor 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 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.
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.
Jeff Friesen is a freelance software developer and educator specializing in Java technology. Check out his site at javajeff.mb.ca.
Read more Java Tech columns.
View all java.net Articles.
Showing messages 1 through 3 of 3.
private BufferedImage emboss (BufferedImage src)
{
Kernel kernel = new Kernel (3, 3, new float [] { -2, 0, 0, 0, 1, 0, 0, 0, 2 });
BufferedImageOp op = new ConvolveOp (kernel);
BufferedImage tmp = op.filter (src, null);
ColorConvertOp grayOp = new ColorConvertOp (ColorSpace.getInstance (ColorSpace.CS_GRAY), null);
return grayOp.filter (tmp, null);
}
class EmbossOp implements BufferedImageOp
{
public final BufferedImage filter (BufferedImage src, BufferedImage dst)
{
if (dst == null)
dst = createCompatibleDestImage (src, null);
int width = src.getWidth ();
int height = src.getHeight ();
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;
}
public BufferedImage createCompatibleDestImage (BufferedImage src,
ColorModel dstCM)
{
BufferedImage image;
if (dstCM == null)
dstCM = src.getColorModel ();
int width = src.getWidth ();
int height = src.getHeight ();
image = new BufferedImage (dstCM,
dstCM.createCompatibleWritableRaster (width, height),
dstCM.isAlphaPremultiplied (), null);
return image;
}
public final Rectangle2D getBounds2D (BufferedImage src)
{
return src.getRaster ().getBounds ();
}
public final Point2D getPoint2D (Point2D srcPt, Point2D dstPt)
{
if (dstPt == null)
dstPt = new Point2D.Float ();
dstPt.setLocation (srcPt.getX (), srcPt.getY ());
return dstPt;
}
public final RenderingHints getRenderingHints ()
{
return null;
}
}
</pre>
Using EmbossOp in EmbossIcon's emboss() method is extremely simple, as the following emboss() method reveals:
private BufferedImage emboss (BufferedImage src)
{
EmbossOp op = new EmbossOp ();
return op.filter (src, null);
}
I recommend enhancing EmbossedOp to emboss from different directions.
Jeff
|
|