In This Issue Welcome to the Core Java Technologies Tech Tips for
September 2006. Core Java Technology Tech Tips provide hints and
tips to help you get the most out of core Java technologies and
APIs, such as those in the Java 2 Platform, Standard Edition (J2SE). Note: You can use Scott Violet's Interactive Graphics Editor (IGE) to run the code samples in this month's tech tips. Cut and paste the code into the Interactive Graphics Editor application to see how your changes affect rendering. If you're familiar with Java 2D, you probably already know that you can use any arbitrary shape to clip your rendering. For example, you can give the illusion of someone shining a flashlight on your application by setting a circular clip and then rendering your scene normally. You can read the Java Tutorial for refresher material about Java 2D clipping. When you clip using a complex shape, you will typically see "jaggies" around the edges of the clipped region. Jaggies are ugly, ragged edges on your rendered images. To illustrate an effect I call "hard clipping," try the following example:
// Clear the background to black
g.setColor(Color.BLACK);
g.fillRect(0, 0, width, height);
// Set the clip shape
g.clip(new java.awt.geom.Ellipse2D.Float(width/4, height/4, width/2, height/2));
// Fill the area with a gradient; this will be clipped by our ellipse, but
// note the ugly jaggies
g.setPaint(new GradientPaint(0, 0, Color.RED, 0, height, Color.YELLOW));
g.fillRect(0, 0, width, height);
Here's the resulting image:
Wouldn't it be nice if you could antialias those hard edges to remove the jaggies caused by clipping? Unfortunately Sun's current implementation of Java 2D does not support "soft clipping." I was surprised to find that when I tried the above code on my Mac, there were no jaggies! What's going on here? It turns out that Apple's Java 2D implementation uses Quartz under the hood, which appears to do soft clipping by default. Apple is planning to use Sun's software renderer instead of their Quartz renderer by default in Java SE 6, so these tips should be relevant for Macs as well. You might expect a Fortunately, we've found a fairly simple way to achieve a soft
clipping effect using an intermediate image (see Chet Haases' article
on that subject) and a little known
import java.awt.image.*;
// Clear the background to black
g.setColor(Color.BLACK);
g.fillRect(0, 0, width, height);
// Create a translucent intermediate image in which we can perform
// the soft clipping
GraphicsConfiguration gc = g.getDeviceConfiguration();
BufferedImage img = gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
Graphics2D g2 = img.createGraphics();
// Clear the image so all pixels have zero alpha
g2.setComposite(AlphaComposite.Clear);
g2.fillRect(0, 0, width, height);
// Render our clip shape into the image. Note that we enable
// antialiasing to achieve the soft clipping effect. Try
// commenting out the line that enables antialiasing, and
// you will see that you end up with the usual hard clipping.
g2.setComposite(AlphaComposite.Src);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.WHITE);
g2.fillOval(width/4, height/4, width/2, height/2);
// Here's the trick... We use SrcAtop, which effectively uses the
// alpha value as a coverage value for each pixel stored in the
// destination. For the areas outside our clip shape, the destination
// alpha will be zero, so nothing is rendered in those areas. For
// the areas inside our clip shape, the destination alpha will be fully
// opaque, so the full color is rendered. At the edges, the original
// antialiasing is carried over to give us the desired soft clipping
// effect.
g2.setComposite(AlphaComposite.SrcAtop);
g2.setPaint(new GradientPaint(0, 0, Color.RED, 0, height, Color.YELLOW));
g2.fillRect(0, 0, width, height);
g2.dispose();
// Copy our intermediate image to the screen
g.drawImage(img, 0, 0, null);
Compare the resulting image in Figure 2 to the jaggy one above:
Looks better, right? I'll admit that this example is a bit contrived, and it might be hard to see the real world applicability. However, my next next tip will show you how to apply this technique when creating a lighting effect for arbitrary shapes. In the first tip I demonstrated a technique for achieving a soft clipping effect. Now let's put it to good use. In this tip I'll show how to add a lighting effect to give your otherwise flat shapes a 3D appearance. If you like your literature illustrated, perhaps a picture will help. I'll show you how to go from the boring, flat shape on the left to the slightly less boring, glossy shape on the right:
With the right colors, you can use this technique to simulate a
colored light shining across your shape, producing a subtle glow. How
do we achieve this effect? Check out the code below; the comments
above the
import java.awt.geom.*;
import java.awt.image.*;
private static final Color clrHi = new Color(255, 229, 63);
private static final Color clrLo = new Color(255, 105, 0);
private static final Color clrGlowInnerHi = new Color(253, 239, 175, 148);
private static final Color clrGlowInnerLo = new Color(255, 209, 0);
private static final Color clrGlowOuterHi = new Color(253, 239, 175, 124);
private static final Color clrGlowOuterLo = new Color(255, 179, 0);
private Shape createClipShape() {
float border = 20.0f;
float x1 = border;
float y1 = border;
float x2 = width - border;
float y2 = height - border;
float adj = 3.0f; // helps round out the sharp corners
float arc = 8.0f;
float dcx = 0.18f * width;
float cx1 = x1-dcx;
float cy1 = 0.40f * height;
float cx2 = x1+dcx;
float cy2 = 0.50f * height;
GeneralPath gp = new GeneralPath();
gp.moveTo(x1-adj, y1+adj);
gp.quadTo(x1, y1, x1+adj, y1);
gp.lineTo(x2-arc, y1);
gp.quadTo(x2, y1, x2, y1+arc);
gp.lineTo(x2, y2-arc);
gp.quadTo(x2, y2, x2-arc, y2);
gp.lineTo(x1+adj, y2);
gp.quadTo(x1, y2, x1, y2-adj);
gp.curveTo(cx2, cy2, cx1, cy1, x1-adj, y1+adj);
gp.closePath();
return gp;
}
private BufferedImage createClipImage(Shape s) {
// Create a translucent intermediate image in which we can perform
// the soft clipping
GraphicsConfiguration gc = g.getDeviceConfiguration();
BufferedImage img = gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
Graphics2D g2 = img.createGraphics();
// Clear the image so all pixels have zero alpha
g2.setComposite(AlphaComposite.Clear);
g2.fillRect(0, 0, width, height);
// Render our clip shape into the image. Note that we enable
// antialiasing to achieve the soft clipping effect. Try
// commenting out the line that enables antialiasing, and
// you will see that you end up with the usual hard clipping.
g2.setComposite(AlphaComposite.Src);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.WHITE);
g2.fill(s);
g2.dispose();
return img;
}
private static Color getMixedColor(Color c1, float pct1, Color c2, float pct2) {
float[] clr1 = c1.getComponents(null);
float[] clr2 = c2.getComponents(null);
for (int i = 0; i < clr1.length; i++) {
clr1[i] = (clr1[i] * pct1) + (clr2[i] * pct2);
}
return new Color(clr1[0], clr1[1], clr1[2], clr1[3]);
}
// Here's the trick... To render the glow, we start with a thick pen
// of the "inner" color and stroke the desired shape. Then we repeat
// with increasingly thinner pens, moving closer to the "outer" color
// and increasing the opacity of the color so that it appears to
// fade towards the interior of the shape. We rely on the "clip shape"
// having been rendered into our destination image already so that
// the SRC_ATOP rule will take care of clipping out the part of the
// stroke that lies outside our shape.
private void paintBorderGlow(Graphics2D g2, int glowWidth) {
int gw = glowWidth*2;
for (int i=gw; i >= 2; i-=2) {
float pct = (float)(gw - i) / (gw - 1);
Color mixHi = getMixedColor(clrGlowInnerHi, pct,
clrGlowOuterHi, 1.0f - pct);
Color mixLo = getMixedColor(clrGlowInnerLo, pct,
clrGlowOuterLo, 1.0f - pct);
g2.setPaint(new GradientPaint(0.0f, height*0.25f, mixHi,
0.0f, height, mixLo));
//g2.setColor(Color.WHITE);
// See my "Java 2D Trickery: Soft Clipping" entry for more
// on why we use SRC_ATOP here
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, pct));
g2.setStroke(new BasicStroke(i));
g2.draw(clipShape);
}
}
Shape clipShape = createClipShape();
//Shape clipShape = new Ellipse2D.Float(width/4, height/4, width/2, height/2);
// Clear the background to white
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
// Set the clip shape
BufferedImage clipImage = createClipImage(clipShape);
Graphics2D g2 = clipImage.createGraphics();
// Fill the shape with a gradient
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setComposite(AlphaComposite.SrcAtop);
g2.setPaint(new GradientPaint(0, 0, clrHi, 0, height, clrLo));
g2.fill(clipShape);
// Apply the border glow effect
paintBorderGlow(g2, 8);
g2.dispose();
g.drawImage(clipImage, 0, 0, null);
Note that I've left a few "alternate" lines of code commented out in the example above. Feel free to try uncommenting those lines to see how they affect the rendering. Bonus: Astute readers may notice that the same technique used in
the Here's some more code you can insert into the above example to add a shadow border to that same shape:
private void paintBorderShadow(Graphics2D g2, int shadowWidth) {
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int sw = shadowWidth*2;
for (int i=sw; i >= 2; i-=2) {
float pct = (float)(sw - i) / (sw - 1);
g2.setColor(getMixedColor(Color.LIGHT_GRAY, pct,
Color.WHITE, 1.0f-pct));
g2.setStroke(new BasicStroke(i));
g2.draw(clipShape);
}
}
// Apply the border shadow before we paint the rest of the shape
paintBorderShadow(g, 6);
And here's the resulting image:
Note that this is just a quick attempt at adding a drop shadow for
demonstration purposes. If I weren't so lazy, I'd probably use a
lighter gray color and a non-linear ramp to achieve a more realistic
effect. Also note that this is just one of many ways to add a drop
shadow using Java 2D. Romain Guy has discussed different drop shadow
implementations these two blog entries: Non
Rectangular Shadows and Fast
or Good Drop Shadows. The SwingLabs
folks have a For More Information You can get more information about Java 2D and the Java Platform from the following sources:
| ||||||
|
| ||||||||||||