 |
Java 2D Trickery: Soft Clipping
Posted by campbell on July 19, 2006 at 10:59 PM | Comments (10)
Note: The code snippets in this blog entry are intended to be used with Scott Violet's nifty Interactive Graphics Editor. Just cut and paste the code into that application, et voila: instant gratification. This allows you to tinker with the code and immediately see how your changes affect the rendering.
If you're familiar with Java 2D, you probably already know that you can clip out a portion of your rendering with any arbitrary shape. (If not, go check out the Java Tutorial.) 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.
When you clip using a complex shape, you will typically see jaggies around the edges of the region being clipped, which can uglify your app. To illustrate the effect that I'll 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? Well, unfortunately Java 2D (or at least Sun's current implementation) does not support "soft clipping."
Sidebar: I add the caveat about Sun's implementation because I was surprised to find that when I tried the above code on my Mac, there were no jaggies! What's going on here? Well, it turns out that Apple's Java 2D implementation uses Quartz under the hood, which appears to do soft clipping by default. In Mustang, Apple is planning to use Sun's software renderer instead of their Quartz renderer by default, so the tips in this blog entry should be relevant for Macs as well.
You'd think there would be a RenderingHint to control this behavior, but sorry, no such luck. A few developers have asked for soft clipping in the past, but it doesn't seem to be common enough to warrant adding support for it in our implementation. (I was going to say that it's too much work, and it probably would be, but then my esteemed readers would probably say "well if Apple could implement it in Quartz, why can't you?" Damn those show-offs at Apple. But I digress...)
Fortunately, we've found a fairly simple way to achieve a soft clipping effect using an intermediate image (see Chet's article on that subject) and a little known AlphaComposite rule known as SrcAtop. (Note that SrcIn would work equally well in this example, but SrcAtop has the added benefit that it blends the source with the destination, as opposed to simply replacing it, which will come in handy in the next installment.) Try out the following code snippet:
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 to the jaggy one above:
Looks better, no? I'll admit that this example is a bit contrived, and it might be hard to see the real world applicability. In the next installment of my "Trickery" series, I'll show you how to apply this technique when creating a lighting effect for arbitrary shapes.
In my ears: Asobi Seksu, "Citrus" [just got back from their show at the Bowery Ballroom no less]
In my eyes: Edward P. Jones, "The Known World"
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Wow. Thats brilliant - we have a rounded border that would benefit from more "crispness". Ill give it a go.
Posted by: profiler on July 20, 2006 at 01:15 AM
-
It sounds like Rich has been bugging someone about rendering features. :)
Posted by: joshy on July 20, 2006 at 09:06 AM
-
I didn't have anything to do with inspiring it, but it sure is a great trick! Very timely too Chris, Josh and I were just discussing anti-aliased clips (and how to do them). Great timing!
Posted by: rbair on July 20, 2006 at 10:22 AM
-
At last! Chris and Jim used to bully me about this SrcAtop composite a few months ago (like a month before JavaOne.) Chris, I'm glad you finally wrote that blog about it, I was growing impatient ;-)
Posted by: gfx on July 20, 2006 at 11:02 AM
-
Nice effect, thanks! I've been trying to get that effect by setting all kinds of Graphics hints because I didn't know it was impossible.
Its interesting the way the white-coloured circle outline produces an anti-alias pattern suitable for an orange/yellow circle. That must be because the AA-ed pixels come out grey - a mix of white & the black background.
Posted by: commanderkeith on July 20, 2006 at 07:58 PM
-
Another method is to use the PaintContext API to convert the Paint into a BufferedImage, create a TexturePaint (or something similar) out of it and then just fill the clip shape. Might be faster since custom composites aren't very optimized AFAIK.
Posted by: herbyderby on July 21, 2006 at 05:43 AM
-
@herbyderby: SrcAtop isn't treated as a custom (i.e. user-defined) composite, and therefore performance should be about the same as any other AlphaComposite such as SrcOver (this goes for our software loops as well as our hardware pipelines, such as the OGL pipeline).
Posted by: campbell on July 21, 2006 at 07:20 AM
-
Scenic library also does antialiased clipping (with HW-acceleration) without needing any special tricks. See https://scenic.dev.java.net/
Posted by: jtulkki on July 23, 2006 at 07:06 AM
-
Chris, you mention that Apple is planning to use Sun's software renderer instead of Quartz now. I find this really interesting. What motivated this change? Compatibility reasons? Or does Sun's renderer perform better? To me, this sounds like a huge testament to the quality of Suns implementation. Anyway, I've never had the need for soft clipping in the past... but, now I know Apple has it and we dont, I want it =) Just so I can keep on telling people that Java2D truly is the best vector graphics API around! (and I really believe that)
Posted by: benloud on October 11, 2006 at 11:00 PM
-
@benloud: Yep, it's mainly for compatibility reasons. I think they get complaints about the Quartz renderer not behaving exactly like Sun's renderer, so this way they can get closer pixel-for-pixel. Also, there are some operations that are significantly slower with the Quartz renderer, like non-aa lines (although the same goes vice versa). Note that the Quartz renderer will still be available, it just won't be the default anymore.
Thanks, Chris.
Posted by: campbell on October 12, 2006 at 12:12 AM
|