Skip navigation
1 2 3 Previous Next


31 posts

Leaving Sun Blog

Posted by campbell-3072198 Jan 7, 2010

I’ve decided to leave Sun Microsystems, after spending 8+ years there (well, 10+ years if you count my two internships) working on graphics engines for the Java platform. Tomorrow, January 8th, 2010 will be my last day on the job.

For more on this change, check out this longer write-up that I posted to my new blog and website. Note that this will be my last blog entry on, so if you’re accustomed to reading my (increasingly infrequent) entries here, please be sure to update your bookmarks.

Picking up where I left off six (cough, cough) months ago, in my series on the effects framework in JavaFX...

In JavaFX, you can chain effects together, contrary to a couple blog entries I've come across recently. This feature may not be entirely obvious from the API documentation, so let's look at chaining in a bit more detail here.

Just like Nodes in the JavaFX scene graph,Effects can be linked together in a tree-like structure. Most Effect subclasses expose one or more "input" variables that allow you to chain Effectinstances together. By default, the input variable is set to null, indicating that the rendered contents of the Node to which the Effect is attached will be used as the input to the effect. For example, in the following code snippet, since we do not set theBoxBlur.input variable, the blur effect will be applied to the Text node content:

    Text {
        effect: BoxBlur {}
        content: "Hello!"


If we instead explicitly set the BoxBlur.inputvariable to, say, a SepiaTone instance, the sepia effect will first be applied to the node content, and the result of that operation will then be blurred:

    Text {
        effect: BoxBlur {
            input: SepiaTone {}
        content: "Hello!"


The following is a simple applet that demonstrates what aText node looks like when one or more effects are chained to it:


(The source bundle for this demo is available here.)

Note that in some situations, the order of the chained effects can be quite significant. One such scenario is in "cover flow" style components, where each node has a Reflectioneffect and is positioned in (faux) 3D using aPerspectiveTransform. In this case, it is important to apply the reflection effect first, and then transform the result last. If you try it the other way around, the result will be goofily [goofily, really?] non-realistic. The applet above demonstrates the difference in behavior of the two orderings.

Unfortunately, due to a minor technical limitation, we have not yet exposed an input variable on theDropShadow and InnerShadow classes as of the 1.2 release. This will certainly be resolved in an upcoming release. In the meantime, you can achieve the same result (albeit with a bit more code) by using nested Groups. For example, we can apply a Lighting effect to aText, and then wrap that in a Group that has a DropShadow applied to it:

    Group {
        effect: DropShadow {
            radius: 14
            offsetX: 4
            offsetY: 4
        content: Text {
            effect: Lighting {
                light: DistantLight { azimuth: -135 }
                surfaceScale: 5


In the next few installments, I'll look at individual effects such as Lighting and Reflection in more detail. (Let's hope this time I get to it sooner than six months from now!)

In my ears: Charles Mingus, The Black Saint And The Sinner Lady
In my eyes: Ricky Gervais and Karl Pilkington, The World of Karl Pilkington

Still Alive 

In early 2008, I wrote my only blog entry of the year, on the subject of the filter effects framework that serves as part of the foundation of JavaFX. It has been a fun project to work on, and I'm fairly proud of the result. There was so much more I wanted to write about on the subject, but I wanted to wait until we first had a stable API and delivered an actual product.

It was a long road to JavaFX 1.0, but now that it has shipped, I can finally talk more about using filter effects in JavaFX and what's going on behind the scenes. Since it's a big subject, I'll try to break things up into smaller, more focused blog entries instead of my usual monolithic tomes. (At minimum, it will provide the illusion that I'm more prolific in 2009 than I was in 2008.)

Like any good graphics API, the filter effects framework in JavaFX is easy to use, provides high-quality visual results, and offers top-notch performance. In this entry, I'll just focus on the ease-of-use angle.

Bunny Jump, Bunny Jump 

The best place to start is to look at the javafx.scene.effectpackage API. All of the core filter effect classes extend the abstract javafx.scene.effect.Effect base class. AnyEffect instance can be applied to a scene graphNode by setting the Node.effect variable. Each Effect subclass exposes a small number of variables that control the visual appearance of theNode. In addition, most Effect subclasses provide one or more input variables that can be used to "chain" effects (more on that in a future blog entry).

To take a specific example, the DropShadow class provides 5 variables, analogous to knobs on a control panel:color, offsetX, offsetY,radius, and spread. Each "knob" is generally documented with default, minimum, maximum, and identity values, which makes it easy to see the acceptable range of values. Here's a really simple example that shows how to apply aDropShadow effect to a rounded Rectangleand control its appearance through the magic of thebind operator.


And here's the relevant code (or download the complete sourceshere):

            Rectangle {
                effect: DropShadow {
                    radius: bind radius
                x: 50 y: 30 width: 150 height: 100
                arcWidth: 40 arcHeight: 40
                fill: Color.RED

That's it! Pretty easy, isn't it? You, the developer, only need to concern yourself with turning these simple knobs. The effects framework does all the heavy lifting for you, such as painting theNode into an offscreen image at the appropriate resolution, manipulating the pixels in one of the CPU and/or GPU accelerated backends, and automatically repainting the affected portion of the scene.

For more interactive examples of the built-inEffect subclasses, check out the EffectsPlaygroundapplet featured in the JavaFX Samples Gallery. Stay tuned for entries on quality, performance, and more...

In my ears: No Age, Nouns
In my eyes: Woody Allen, Mere Anarchy

For the past few months, I've been involved with various aspects of Project Scene Graph and the JavaFX Script runtime libraries. Hans has already introduced the core elements of the scenegraph package, and Chet has donned his Groucho mask to explain the animation package in great detail (three chairs for that!).

Today I'm finally going to talk about the third major component of the overall Scene Graph project, and that is the Effects Framework. If you've been wondering why I've been quiet lately, this is the reason.

Our team has already had plenty of experience in delivering GPU-accelerated imaging operations. Now we've taken it to the next level, by offering a long list of effects commonly used by designers, packaging them up into an easy-to-use API, and providing a sophisticated implementation that can take advantage of GPU acceleration (and later, SIMD instructions on the CPU) for blazingly fast performance.

All of this integrates seamlessly with the scenegraph and animation packages, and soon will be exposed just as pleasantly through the JavaFX Script runtime APIs. You can even take advantage of the Effects Framework in existing Swing and Java 2D applications.

Anyway, enough yapping; let's move on to the introductory demo... This application demonstrates just a few of the effects available in the new API. I'll let it speak for itself. (RequiresJava SE 6 or above.)


      webstart.small2.gif    With default options -- will take advantage of the Direct3D-based backend (if possible*) on Windows...

      webstart.small2.gif    With OpenGL backend enabled -- for Windows, Linux, and Solaris (if possible*); Mac OS X coming soon...

      webstart.small2.gif    With all GPU-acceleration disabled -- try this if you have problems with one of the above.

(*) GPU acceleration requires the latest build of JDK 6uN, a compatible graphics card, and up-to-date graphics drivers. If GPU acceleration cannot be enabled for any reason, the framework will automatically fall back on the software-based implementation. Yes, the software-based backend is much slower than it should be; we're working on that.

These projects are still in the early stages, but over the next few weeks, I'll have much more to say about (and more demos to share from) the Effects Framework and the Scene Graph project at large, so stay tuned. In the meantime, take a gander at the JavaDocs and let us know what you think. Hans should have news shortly of an 0.5 release that includes all of this and more.

In my ears: Enon, "High Society"
In my eyes: Found Magazine #3

We've been doing a lot of work over the past couple years to accelerate many complex Java 2D operations on the GPU via OpenGL fragment shaders. (Fragment shaders are little programs that operate on each pixel being rendered on the screen; they're infinitely more flexible than the fixed functionality that's historically been available in OpenGL.) The sky's the limit when it comes to the kind of effects one can achieve by writing shader programs.

The first GPUs with fully programmable shader support were made available from Nvidia and ATI in 2002. It took a couple years for the new hardware to penetrate into the consumer market, but now shader-capable GPUs are extremely prevalent (thanks in part to the hefty graphical requirements of Mac OS X and Windows Vista), so much so that we can reliably take advantage of shaders to accelerate complex Java 2D operations. Even those first-generation boards are capable of providing huge performance gains, and each new generation of hardware seems to give an order of magnitude improvement over the last. This should be quite evident from the charts that follow. While CPU speeds seem to be nearing an asymptote, GPU performance continues to rocket, and now Java 2D is able to benefit from that power. Not only does this mean improved performance for your Swing or Java 2D application, but also reduced CPU consumption, thus freeing up your CPU to crunch on application logic rather than getting bogged down with rendering tasks.

[This is one of those blog entries that could be novel length, but no one would actually read the words, because there are too many pretty bar charts to distract the reader. Blah blah blah, words words words. See? No one's reading. So let's skip the prose and get on with it... Oh, but first, I have to tell you how to read these charts. I generated these numbers on a couple different machines, using J2DBench on Windows XP with the latest graphics drivers (ATI Catalyst 7.3 and Nvidia 93.71). Since the machines vary slightly in processor performance and bus speed (the GeForce 7800 is a PCI-E board, the rest are AGP), I decided to use our software pipeline as a baseline, and then compare the OGL pipeline numbers to that baseline. For example, if you see a result that lines up with the number 2000, it means that test is 2000% of baseline, or in other words, it is about 20 times faster on the GPU than on the CPU. Your mileage may vary, but the big takeaway is that most operations are many times faster when executed on the GPU...]

Text Rendering (Bug 6274813: Available in JDK 6)

LCD-Optimized Text
I already discussed this a bit in a blog entry from about a year ago. Since then, we've enabled this by default in JDK 7 (and soon in a JDK 6 update) when the OGL pipeline is enabled. It's cool to see how software performance improves little over time, but each new generation of GPUs brings big performance improvements.


BufferedImageOps (Bug 6514990: Available in JDK 7 b08)

ConvolveOp is commonly used for modern UI effects such as blurs and drop shadows. Due to limitations in first-generation shader-level hardware, we are currently only acceleratingConvolveOp for 3x3 and 5x5 sized Kernels. These are fairly common kernel sizes, but most drop shadow and glow effects require larger kernels, so we are working to loosen these restrictions and accelerate a wider range of kernel sizes.



LookupOp is often used to perform simple brightness and contrast adjustments on images. To simplify our code, we are currently only accelerating LookupOp forByteLookupTables and ShortLookupTableswith a maximum length of 256 elements, and for 1-, 3-, and 4-band sRGB images only.



RescaleOp is basically a degenerate case ofLookupOp that can be accelerated very efficiently in shaders; after all, it's just a multiply and an add. We are currently accelerating RescaleOp for 1-, 3-, and 4-band sRGB images only.


Multi-stop Gradient Paints (Bug 6521533: Available in JDK 7 b10)

For 2-stop linear gradients (NO_CYCLE orREFLECT), we can delegate to our existingGradientPaint codepath, which is already ridiculously fast via OpenGL's fixed functionality. For all other linear gradients (for all CycleMethods andColorSpaceTypes, up to a maximum of 12 color stops), we can accelerate the operation using shaders, for both antialiased and non-antialiased rendering.



The same restrictions for linear gradients also apply to radial gradients (maximum of 12 color stops, etc). For gradients with more than 12 stops, we simply fall back on our existing software routines.


Extended Composite Modes (Bugs 6531647,5094232: On the way)

Antialiased Painting (with Non-SrcOver AlphaComposite)
Historically the OGL pipeline has been able to accelerate the compositing step of an antialiased painting operation only whenAlphaComposite.SrcOver (orAlphaComposite.Src, if the paint is opaque) due to the math involved. (This is described in the quasi-official guide to the OpenGL-based Java 2D pipeline, but that document could use a refresh for JDK 6 and beyond.) But now with shaders we're able to accelerate antialiased rendering for any arbitrary AlphaComposite mode.

Coming Soon: PhotoComposite
It's not officially approved (or integrated) yet, but I've been working on adding more blending modes to Java 2D, in addition to those already provided by AlphaComposite. Many of these modes come from traditional photography techniques, thus the name "PhotoComposite". Some modes are simple (Add, Multiply) and can be accelerated easily using OpenGL's built-in blending rules, others are more involved (ColorBurn, SoftLight) and benefit greatly from the use of shaders for efficient rendering.

What's Next? 

There are plenty more optimizations that can be made to common Java 2D operations by leveraging shader technology; we'll keep working on this. Also, there have been some discussion on the interest list recently about the use of shaders in Java 2D. Some folks would like to be able to write arbitrary shaders and have them work on Java 2D content. I think it would be hard to come up with a general solution (in the public API) to make this work everywhere, and would shift an unreasonable burden to Java 2D (which is designed with WORA in mind).

However, I do recognize that it would be great if it were easy for developers to make use of shaders in their applications (as Romain has demonstrated) without getting bogged down in the OpenGL/GLSL learning curve. To that extent, I've been working on a few utility classes for JOGL in the vein of TextureIO and related classes. This is another way to make the transition easier for existing Swing developers to leverage JOGL in their applications. More on that in a future blog entry.

Finally, it's worth mentioning that all the shader-based optimizations I've described above are currently only available for the OpenGL-based Java 2D pipeline, but that will soon change. For JDK 7 (maybe sooner?), we have a newly redesigned Direct3D-9-based pipeline in the works that will share much of the architecture (and code) of the OpenGL-based pipeline. We fully expect that all of these shader-based optimizations will be available for most Windows users in the near future. Stay tuned.

In my ears: Panda Bear, "Person Pitch"
In my eyes: JPG, Issue 9

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.

It's been a while since I posted a "Trickery" blog (previous installments in this series include "Soft Clipping" and "Light and Shadow"). This week at the Desktop Matters conference (which was great, by the way) someone asked if there was a way to antialias the edges of an image when rotating it in Java 2D. The kind gentleman noted that on Mac OS X with Apple's JDK, they do see antialiased edges if they enableRenderingHints.VALUE_ANTIALIAS_ON, but they don't see the same behavior on Sun's JDK. The reason for that is Quartz: as I mentioned in the "Soft Clipping" entry, Apple's Java 2D implementation uses Quartz under the hood, which does antialias image edges in this particular case. (But as I mentioned there as well, Apple is moving away from their Quartz renderer by default in their JDK 6 implementation, so it would be nice if there was a solution for all platforms.)

It is true that Sun's Java 2D implementation does not automatically antialias image edges when rendering withGraphics.drawImage(). However, there is a simple workaround: use TexturePaint and render a transformed/antialiased fillRect(). The following code demonstrates this technique:

import java.awt.geom.*;
import java.awt.image.*;

// Clear the background to black
g.fillRect(0, 0, width, height);

// Create the source image
BufferedImage srcImage =
    new BufferedImage(50, 50, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = srcImage.createGraphics();
g2.fillRect(0, 0, 50, 50);
g2.fillOval(5, 5, 40, 40);
g2.fillOval(15, 15, 5, 5);
g2.fillOval(30, 15, 5, 5);
g2.drawOval(20, 30, 10, 7);

// Render the image untransformed
g.drawImage(srcImage, 15, 7, null);
g.drawString("Untransformed", 80, 25);

// Render the image rotated (with NEAREST_NEIGHBOR)
AffineTransform xform = new AffineTransform();
xform.translate(15, 70);
xform.rotate(Math.PI/8, 25, 25);
g.drawImage(srcImage, xform, null);
g.drawString("Nearest Neighbor", 80, 85);

// Render the image rotated (with BILINEAR)
xform.translate(15, 130);
xform.rotate(Math.PI/8, 25, 25);
g.drawImage(srcImage, xform, null);
g.drawString("Bilinear", 80, 145);

// Render the image rotated (with BILINEAR and antialiased edges)
g.drawString("Bilinear, Antialiased", 80, 205);
g.setPaint(new TexturePaint(srcImage,
    new Rectangle2D.Float(0, 0, srcImage.getWidth(), srcImage.getHeight())));
xform.translate(15, 190);
xform.rotate(Math.PI/8, srcImage.getWidth()/2, srcImage.getHeight()/2);
g.fillRect(0, 0, srcImage.getWidth(), srcImage.getHeight());

Here's the resulting screenshot showing the different options:



Note that there will likely be some performance hit from transforming the image in this manner, but if quality is of utmost importance (as it was in this developer's case), then the cost is probably acceptable. Hope this helps!

In my ears: The Fall, "Perverted By Language"
In my eyes: Yoshio Tsuchiya, "The Fine Art Of Japanese Food Arrangement"

[If it's not readily apparent, I wrote this blog entry months ago because I was waiting around for the bug fixes described here to make it into JDK 7. So just as a brief diversion from your day, pretend for a few moments that you've traveled back to the distant past, specifically the trail end of the year 2006...]

How I Spent My Summer
The big problem with my job at Sun is that I often simultaneously have ten different toes dipped into ten different puddles. This past spring I made one final splash in my primary puddle, the OpenGL-based Java 2D pipeline, to fix the remaining big ticket items and make it as solid as possible for the JDK 6 release. By and large those goals were met (for more info, go crazy, go crazier, or um, go more crazier), but along the way I experienced a bit of burnout. After all, I'd been hacking on the OGL pipeline since late 2001 (and pondering it as early as 1999 as evidenced in this posting by some clever, dare I say dashing, intern) so it's surprising that the burnout didn't happen sooner.

Anyway, with JDK 6 in a frozen state and summer rapidly approaching, I figured that it would be a good time for a change, but (oh!), what to work on? I started with a blank slate, but quickly found my toes overdipped. Here's a partial list of my non-OGL dabblings over the last six or so months:

  • Finished implementing the new Java Plugin startup animation (check it out soon in JDK 6u1 and JDK 7, with more tweaks to follow)
  • Kicked off my "Trickery" blog series (here and here) with some techniques used in the aforementioned animation work
  • Toyed around with the idea of reimplementing our native rendering loops in pure Java code (yes, believe it or not there are advantages; definitely worth revisiting)
  • Played with the SwingX Painters API and Timing Framework (partial success documented in my PhotoCube blog)
  • Started investigating new icon-related APIs in Swing (for L&F provided icons and multi-res icons)
  • Installed Ubuntu and quickly found that, yes indeed, Swing's GTK L&F could use some work (despite the big improvements already seen in JDK 6 in that area)


This list serves dual purposes. First, it's a pathetic attempt to prove to my manager that I did accomplish something this summer besides, um, building an Adirondack chair and paddling around Shook's Pond. More relevantly, it demonstrates that I have issues with focusing on one concrete project. However, the one item that showed the most potential (and had well-defined constraints) was the set of GTK L&F deficiencies. Ubuntu wasn't a tier-one testing platform for us in JDK 6 (something I hope will change for JDK 7), and it turns out that Ubuntu's 6.06 release introduced a new GTK engine (ubuntulooks) and theme (Human). The ubuntulooks engine is different enough from the clearlooks engine (from which it was derived) to warrant a close look, and it would require a number of special fixes to make Swing's GTK L&F look great on that distro.

Another reason to focus on a concrete project like the GTK L&F is that there's been an effort underway for some time by the NetBeans team to enable the GTK L&F by default on Linux and Solaris, just as Mac OS X and Windows use the platform L&F by default. After all, if Sun doesn't set an example by enabling the platform L&F in all of our desktop-focused software, how can we expect developers to do the same in their apps? Again, despite all the major improvements to Swing's GTK L&F in JDK 6, there were a few issues that slipped through the cracks, and we wanted to make sure those are addressed soon (ideally in the upcoming JDK 6 update releases). Expect to see NetBeans make this switch in their 6.0 release (or thereabouts) in Spring 2007.

[Update: The NetBeans folks just this week flipped the switch in NetBeans 6.0 M7 so that the GTK L&F is now enabled by default on Solaris and Linux (on JDK 6u1 and above). Check out the"new and noteworthy" page for a few sample screenshots and more information. To get things looking good in time for the NetBeans 6.0 release, a lot of energy was invested by the NetBeans, Solaris, and Swing teams. Thanks guys!]

Before and After Science
All right, all right, enough words already. As the saying (rarely) goes, a screenshot's worth a thousand words, so it's time for some before-and-after shots (click to enlarge):

Fig. 1 -- Native "twf" demo application.

Fig. 2 -- Java/Swing mockup of "twf", courtesy of Elliott Hughes, running on JDK 6.

Fig. 3 -- Same as above, but running on JDK 7-b08; can you spot the 6 (or 25) differences?

These particular screenshots were taken on an Ubuntu 6.06 system with the default Human theme, but similar improvements can be seen on many other GTK themes such as Nimbus (the new default on Solaris), Clearlooks (the most popular on various Linux distros), and so on. In total, over 30 GTK-specific bugs have been fixed so far in JDK 7, and there are plenty more fixes on the way. For more details on these bug fixes, refer to this list. As you can probably see from Figure 3, there's still a bit more work to be done (e.g. table headers, non-editable comboboxes, tabs), but we're getting closer.

Help Wanted
Here's the thing about the GTK L&F... It's not rocket science. Granted, some of the issues are tricky to fix, which is why it's taken quite a while to get it to where it is today. It's not for lack of smarts, but rather a lack of time. It can take a number of hours just to track down exactly why something is broken in the GTK L&F, and it can take up to a couple days to devise a robust and well-understood fix.

I hope these statements don't come across as whining. The fact of the matter is that at Sun we have anywhere from 1.33 to 2.117 persons available to work on the GTK L&F at any given time. Since time is the limiting factor, there's only so many fixes we can get in within a given year, maybe 50 tops. But what if we took a page from the book of CMT and increased the number of threads crunching on these issues in parallel? I think we could see some quick progress.

There are a number of people (you know who you are, Elliott Hughes; nudge nudge, Brian Harry; wink wink Roman Kennke) who are actively interested in the success of the GTK L&F. That's great, but I'd like to see more activity in this area, perhaps in addition to the aforementioned/illustrious names. And not just in the form of an independent project. We'd be happy to work closely with 1 or 2 (or 3) developers out there in the OpenJDK community, collaborating on fixes, and so on. Eventually it would be nice to see a couple folks in the community with the same level of expertise as we have in Kirill K. and Peter Z. (our resident GTK experts on the Swing team at Sun).

At the very least it would be a fun experiment as we move towards completely opening up the JDK library source code in the next few months. As has been stated elsewhere, we still have a lot of work to do w.r.t. opening up our internal processes (bug evaluations, code reviews, etc) to the outside world, but I think it would help the transition if we had a few external folks get more involved with this process now. Send me email if you'd like to get involved. I can't promise you fame or fortune, but I can promise you a beer or two (*, **, ***, ****, *****) in return for your hard work.

* Valid only in the Bay Area, California, unless you want to fly me to your location. (I'd gladly oblige, especially if you're located outside the U.S.)
** Under 21 receives R.C. Cola or equivalent.
*** Pabst Blue Ribbon or equivalent only.
**** Must be happy hour or half-off equivalent.
***** You must buy me a beer in return, because that's the way the system works.

In my ears: Deerhoof, "Reveille" [digging through old favorites...]
In my eyes: Woody Allen, "Without Feathers"

[Concision is often a difficult thing for me. Everytime I write a blog entry I tell myself, "This one's a-gonna be a short 'un." But it just never seems to work out that way. Perhaps I'll succeed today.]

For the dozen or so folks that attended the User Experience BOF at JavaOne 2006, this is probably not news. But if you're one of my (14 million) readers who did not attend that BOF, you may be interested to learn that we've revamped the quick little animation that users see when the Java Plugin is being initialized in the browser. In the years prior to JDK 5, users saw little more than a gray box and a tiny coffee cup logo. Besides being dull, there was no indication of applet startup progress; not exactly a brilliant user experience!

Fast forward to 2004 when a certain high-level-exec-turned-CEO decided it was time to improve that experience. A short startup animation (with progress indicator) was proposed, and the general design was (somewhat unfortunately) handed down from above. Our multi-talented visual designer, Coleen Baik *, and I were given only a couple weeks to churn out the final design and implementation (written purely in Java 2D, of course) in time for the impending JDK 5 release. Thus was born the original "gray box animation," called as such despite the fact that it was now much more white than gray:



About a year later we were again asked to update the look of the animation, this time for the JDK 6 release. Our initial reaction was one or more of:

  • "Are they bloody crazy?"
  • "Didn't we just get finished with the first one?"
  • "Has anyone even noticed the first one yet?"
On the other hand, the old animation was already beginning to look a bit dated, so I think we were happy to give the design (and implementation) a much needed makeover. Coleen's new designs were much more sleek and modern looking than the old gray box. And this time they were predominantly bright orange. (Hooray for Sun branding!) The design we settled on looked fantastic, but it was also a bit of a fun challenge for me trying to convert her Photoshop blueprints into Java 2D-isms. Fortunately around that time I had pushed to get LinearGradientPaint (and RadialGradientPaint) included in the JDK, which turned out to be a lifesaver. I also had to figure out how to achieve cheap, efficient drop shadows, glow effects, and soft clipping in Java 2D. (The first two entries in my "Trickery" series on "Soft Clipping" and "Light and Shadow" might make a bit more sense now that you're clued into their origins.) The sequencing of the progress bar and the subsequent fade-out implementation were also thankfully improved by Rita Fisher, resident Java Plugin expert.


[Concision, man, concision! Enough yapping! Show them some pictures, already!]

So without further ado, I present the new "orange box" Java Plugin startup animation...



The screenshot doesn't really do it justice, so I'd recommend trying the live demo (sandboxed; requires JDK 6) to see the fancy page flips, fades, and so on. You can resize the window to see how the animation behaves at different sizes. Note that the demo is derived from one of the final prototypes (before it was integrated into the JDK), so the behavior might differ slightly in the actual deployment scenario. And before anyone asks, as always you can override this animation (instructions here) with a static image of your choosing, such as a company logo, if desired. Also, despite the fact that this animation looks significantly more complex than the old one, I'm happy to say that the new animation requires less CPU cycles than the old one; in both cases, we promise we're not slowing the applet startup process by displaying this animation (applet startup is usually I/O bound, and the animation requires very few compute cycles).

The new animation unfortunately missed the original JDK 6 release due to time constraints, but I'm glad to report that it's now available in early builds of JDK 6u1 and JDK 7. If you have either of those installed, try loading a large applet (the animation is usually only visible for larger applets that take a while to download) and let us know your thoughts. Hope you enjoy the new look!

* ... who unfortunately is leaving Sun as of today... Why, Coleen, why?

In my ears: King Geedorah, "Take Me To Your Leader"
In my eyes: Woody Allen, "Side Effects" [a nice diversion from my ongoing battle with Faulkner...]

The Hemingway Version

In my last blog entry, Easy 2D/3D Mixing in Swing, my goal was to show Swing developers how easy it can be to include 3D content in a desktop application via JOGL and OpenGL. I think we've done a decent job of making things easier for that class of developers, but what if we look at the problem from the other end of the barrel? There are lots of JOGL/OpenGL developers that want to include Java 2D elements in their applications. Historically it has been possible to include Java 2D content in a JOGL application, but there haven't been any good standard APIs to make this sort of integration convenient for developers. Until now.

Let's get the demo out of the way before getting into any of the details. Here's a screenshot (click to enlarge), for the truly impatient reader:



Better yet, run the demo yourself (the app itself is sandboxed and only requires JDK 5; here's the source code, with NetBeans project included):

The Faulkner Version

Ken Russell (the mastermind behind JOGL) promises me that he'll soon have a blog of his own, which means I'll have to stop stealing all of his thunder. In the meantime, let's look at some of the new convenience classes that he's cooked up in the past few weeks (with some prodding from James Gosling, and inspired by some of the reader comments from my last blog).

Texture and TextureIO
These aren't new (they were added to Sun's JOGL implementation over 18 months ago), but it helps to mention them in the context of the newer convenience classes. The Texture class is a really nice abstraction layer around an OpenGL texture object. It makes it very easy for developers to use non-power-of-two sized images in OpenGL (historically this has been tricky for OpenGL developers) and provides simple methods for enabling and binding texture state. The TextureIO class offers an extremely convenient API for loading image data from disk, the network, or any other source into OpenGL texture memory. (If you're into SAT-style analogies: TextureIO is toTexture as ImageIO is toBufferedImage.) It even supports special compressed texture formats, mipmapping, and other complex things that are usually a nightmare for OpenGL developers.

The first of the three new convenience APIs found in thecom.sun.opengl.util.j2d package. This class builds on top of the Texture class described above and provides a convenient way for developers to dynamically render into an OpenGL texture using Java 2D. From the following code snippet, it should be clear that you can use a TextureRendererinstance just as you would a BufferedImage:

    // Create the TextureRenderer and render into it using Java 2D
    TextureRenderer renderer = TextureRenderer(w, h, true);
    Graphics2D g2 = renderer.createGraphics();
    // and so on...
    renderer.sync(0, 0, w, h);

    // Now use it as you would any other OpenGL texture
    Texture tex = renderer.getTexture();


Note that currently the TextureRenderer class uses a BufferedImage behind the scenes as backing store (but that's just an implementation detail). This means that when you're rendering into a TextureRenderer, it currently all goes through software routines. However, Ken and I have some tricks up our sleeves: if all goes well in the next few weeks, we may have an alternate codepath that leverages the JOGL/Java 2D bridge, which will mean that all rendering into theTextureRenderer will be accelerated in hardware via the OpenGL-based Java 2D pipeline, with no costlyBufferedImage-Texture copy steps required. This could be an exciting development, so stay tuned.

Since the beginning of time, rendering text in OpenGL applications has been a huge hassle for developers. There were some really hokey GLUT routines that used ugly bitmap fonts with no support for antialiasing or non-Latin text. In the Java world, crafty developers using JOGL and LWJGL have figured out custom ways to leverage Java 2D's high-quality font rendering in their applications. But until this new TextRenderer class came along, (as far as I know) there was no easy/standard way to get access to the high quality, antialiased text offered by Java 2D. It includes full Unicode support, a really smart string/glyph caching algorithm, and clean APIs that allow for both 2D and 3D rendering of text in a JOGL application (as you saw in my demo above). Under the hood, it takes advantage of the aforementionedTextureRenderer and Texture classes. Ken has been bold enough to declare it "the last word in text rendering" for JOGL apps, and I think he's right. I can even imagine existing C-based OpenGL apps being ported to Java and JOGL for this feature alone...

I didn't actually use this one in the above demo, but it's a really useful and simple way to add a "heads-up display" to your JOGL application. Imagine a flight simulator application: you could render the terrain and all of your 3D content using JOGL, then you could use the new Overlay class as a way to render the airplane's control panel using the high-quality Java 2D APIs. It's really just an even more convenient wrapper around the already convenientTextureRenderer APIs.

To help explain how these new APIs fit together and how they layer on top of OpenGL, I put together a pancake diagram showing how it all comes together. (I don't have my charting software handy, so the following, might I add beautiful, ASCII art diagram will have to suffice.)

       | TextRenderer  |   Overlay  |
       |      TextureRenderer   - - | - - ->  BufferedImage, Graphics2D
       |          Texture       - - | - - ->  TextureIO, ImageIO
       |      OpenGL textures       |


That's all for now. In my next entry I'll have some exciting performance numbers to share in the context of the OpenGL-based Java 2D pipeline (hint: hardware accelerated BufferedImageOps).

In my ears: Sloan, "Never Hear The End Of It" *
In my eyes: William Faulkner, "Absalom, Absalom!"**

* It's a bit long and wordy, but I'm still just as much of a slobbering fan of theirs as I was in high school...
**... But sometimes "long and wordy" can be a virtue.

Well, Richard Bair was kind enough to tag me, but I'm not sure if it was out of pity, or simply out of convenience (we work about 3.5 feet from each other). I've been shocked by how prevalent this whole tag thing has been recently; I mean, doesn't it remind you of those lame chain emails circa 1996? I told myself, "Self, if this tagging thing ever reaches you, you'd better not play along! Those other bloggers are being so vain! Remember your principles!"

But it's a new year (a time for resolutions), and I should probably resolve to stop resisting social phenomena just for the sake of resistance. Then again, I usually resist making new year's resolutions as well, for all the same reasons. (Late last year, Chet and I summed up this philosophy as: "I'm sticking it to the man, but the man ain't listening.")

Anyway, that's it. I give in. Here are five things you may not know about me:

  • My two favorite things in the whole wide world: sheep that wear coats, and inverted umbrellas on windy/rainy day in Manhattan.

  • I'm the sole member (a "non-musician" of sorts [but to compare myself to Eno would be ludicrous]) of a number of bands, some real (La Bonne Soupe), some less real (Shimmy, Burt Rido and the Texas Nachos). The second La Bonne Soupe album, "Literary Gentlemen," was released in November 2006 to worldwide accla... fanfa... umm, something. You can listen to it in full here(not gapless, unfortunately) on the not-so-great-but-at-least-it-aint-myspace

  • When I was 5, I wrote my first BASIC program on our Commodore 64. It was something like a New York State information guide (yes, that was my patriotic year), with lots of menus and very little content. When I was 14, I led a group of random folks in building a virtual reality amusement park (anyone remember REND386?). Yes, that was one of many dorky years, but as you can see graphics programming is in my blood. More on "dzzyland" here; it still runs, if you can track down all the pieces.

  • I'm afflicted by some really silly, non-debilitating medical conditions; for example:

    • Reynaud's syndrome -- my left middle finger goes white when the temperature drops below 55 F; my right ring finger will follow suit below 50 F
    • Sleep paralysis -- it comes and goes, but is most common when my sleep schedule is confused; this condition has been an endless source of amusement for my close friends

  • If I could have lunch with anyone, it would probably be Woody Allen. If I could choose our waiter: Ricky Gervais.


Okay, that's that. I still feel dirty about keeping this phenomenon alive, but I hope these colleagues of mine have more willpower than I do: Shannon, Alex, Artem, and Jasper.

In my ears: Cluster, "Cluster '71"
In my eyes: William Faulkner, "Absalom, Absalom"

When You're A Jet...

A couple weeks back, I had the privilege of speaking to the Java Programming Club at Monta Vista High School in Cupertino, CA. I'm still amazed at the turnout: about 25 students showed up, all with varying levels of experience with Java, and all apparently willing to endure my so-so demos on their lunch hour. The thing that grabs me though is, if they have a Java club, does that imply that they have other similarly specialized programming clubs too? Do the Ruby, Haskell, Java, and Lisp clubs all confront each other just outside school grounds when they have a score to settle,

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.

In the first installment of my "Trickery" series, I demonstrated a technique for achieving a soft clipping effect. Now let's put it to good use. In this installment I'll show how to add a lighting effect to give your otherwise flat shapes a 3D-ish 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:

Flat Shape Glowing Shape

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 drawBorderGlow() method explain the core approach in a bit more detail:

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);
    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.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.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

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

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

Shape clipShape = createClipShape();
//Shape clipShape = new Ellipse2D.Float(width/4, height/4, width/2, height/2);

// Clear the background to 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.setPaint(new GradientPaint(0, 0, clrHi, 0, height, clrLo));

// Apply the border glow effect
paintBorderGlow(g2, 8);


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 in IGE and see how they affect the rendering.

Bonus: Astute readers may notice that the same technique used in the paintBorderGlow() method above could be used to add a drop shadow around the shape. Care to guess how this would work? Okay, time's up. Instead of rendering the border on top of our shape (remember that the clip ensures that the stroke only touches the inside of the shape), we can render a varying gray border around our shape beforehand. This means the shadow stroke will appear outside of our shape; the inner part of the shadow stroke will be effectively rendered over by our shape.

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

// 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 wasn'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 has discussed different drop shadow implementations in his blog hereand here. The SwingLabs folks have aDropShadowBorder in the SwingX project, andDropShadowPanel is currently in the Incubator.

In the next installment of my "Trickery" series, I'll move away from low-level Java 2D effects and show you how to add animations to your applications using the SwingX Painters API and Chet's Timing Framework [tm]. In the meantime, let's see if Josh and Richard pick up the slack and turn some of the above code into stock painters (nudge nudge).

In my ears: Wire, "Chairs Missing" [for about the 3 billionth time in my life]
In my eyes: "Idiot's Guide To Buying A Home" [I really hate growing up...]

I'm sure you're all sick of reading my blogs at this point, because each time I state definitively that the OpenGL-based Java 2D pipeline is "now better than ever," only to follow it up a couple months later with a blog that says, "no really, this time it's even better," and so on. It reminds me of a nearby store called "Cheaper Then Cheaper Cigarettes" [sic]; can you really improve on "cheaper" like that?

Anyway, at the risk of sounding like the boy who cried wolf, as of Mustang b92, the OGL pipeline is now "better than better", one might even say it's superfantastic. In the spirit of my recent "Five Easy Pieces" entry, here are five more reasons why the OGL pipeline is better than ever...

OGL: scrolling is extremely slow on Nvidia boards
(Bug 6298243)

This bug frustrated me for a number of months, but I was confident that the Nvidia driver engineers would be able to come up with a solution as they've done with so many other issues. Unfortunately, the problem is specific to the way data is laid out in texture memory on certain Nvidia hardware, so a simple driver fix wasn't possible. However, they were kind enough to offer a (somewhat odd) workaround of using the GL_ARB_texture_rectangle extension, since rectangular textures are laid out more efficiently on said hardware and therefore do not exhibit the performance issues reported in this bug.

It took quite a bit of work, but the good news is that we were able to get support for the GL_ARB_texture_rectangle extension implemented for b92, so scrolling/dragging performance is back to normal on Nvidia hardware, for both pbuffers and framebuffer objects (FBOs). As an added bonus, the use of that extension allows us to use less texture memory since we no longer have to pad up to power-of-two sized textures on older hardware. (This is what Michael Scott would call a "win win win" situation.)

OGL: enable sun.java2d.opengl.fbobject by default
(Bug 6439320)

Another big benefit in fixing the previous scrolling performance bug is that it removed the last big barrier that prevented us from enabling the FBO codepath by default. (I mentioned the FBO codepath in a blog entry from last autumn. Note that since then, Ken Russell and I managed to get JOGL's GLJPanel working with the FBO codepath, so that is no longer a barrier.) So as of b92, we've enabled the FBO codepath by default, which has many benefits when compared to the existing pbuffer-based codepath:

  • Reduces costly context-switching overhead. This has helped to improve SwingMark (an internal Swing benchmark) scores by 15-20% on both ATI and Nvidia hardware, when compared tofbobject=false.
  • Greatly improves performance when rendering VolatileImages (especially when applying an AffineTransform).
  • Decreases the amount of VRAM consumed by VolatileImages, due to the fact that we have more control over which buffers are/aren't created.
  • Improves maintainability, since the extension is cross-platform and not tied to any windowing system-isms.
  • Works around a number of pbuffer-specific bugs in ATI's drivers, most notably the one that causes a HotSpot crash when exiting an application.


If you've been been bitten by the OGL pipeline in the past (especially those folks with ATI hardware), I'd highly recommend installing b92 for this change alone.

OGL: use new AffineTransform methods in enableGradientPaint()
(Bug 6434670)

This one's not terribly exciting... Just a three-line change, but I wanted to mention it in case folks are not aware of some new convenience methods added to the AffineTransform class in Mustang. The first is AffineTransform.invert(), which is useful in those cases where you want to invert the transform matrix in place (hence no need to create a new AffineTransform object). The second is AffineTransform.rotate(vecx, vecy), which is helpful when you want to rotate about a vector (instead of using a relative angle value).

In this particular case, the old code was not a major bottleneck in the gradient/texture acceleration path, so there wasn't a huge performance benefit. However, if you have some code that exercises AffineTransform heavily, you might want to check to see if these new methods give you a boost.

OGL: accelerate Gradient/TexturePaint when antialiasing is enabled
(Bug 6436942)

This one is terribly exciting, to me at least. We've had some nifty acceleration for GradientPaint and TexturePaint since the OGL pipeline was introduced in JDK 5, but it always came with the caveat that only non-antialiased primitives were accelerated. But as of Mustang b92, we've figured out a way to use multitexturing to combine the gradient/texture acceleration code with our existing antialiased rendering code, which means GradientPaint and TexturePaint are now fully accelerated by the OGL pipeline, regardless of whether antialiasing is enabled. The bug report has more details, but I'll include the executive summary here, because the numbers speak for themselves:

  • Up to 25x improvement for antialiased TexturePaint operations
  • At least 2-7x improvement for antialiased GradientPaint operations
  • No significant impact on existing accelerated operations

OGL: overlapping LCD glyphs not rendered properly
(Bug 6439274)

This was a visible problem caused by an obscure bug, but fortunately the fix was easy. It was the last known problem with the LCD text acceleration code mentioned in my last blog on the subject, and the driver bugs mentioned there have since been fixed as well.

So if you're a fan of LCD text (meaning everyone but Scott, wink wink) and you have shader level hardware (Nvidia GeForce FX/6/7 series, ATI Radeon 9500 and up, etc), please try-Dsun.java2d.opengl.lcdshader=true and let us know if you see any problems. I'm fairly confident that things are working well, but it hasn't received quite enough testing yet, so we're not planning to enable OpenGL acceleration of LCD text by default in Mustang. Assuming all looks good in the next few months, we can consider turning it on by default in a Mustang update release.

Bonus: The Driver Report

As I've mentioned in past blogs, we've been working with driver teams from Nvidia, ATI, and Sun to ensure that any remaining driver issues are resolved by the time Mustang ships. We have updated our basic acceptance test (BAT) suite with over 50 tests that said driver teams can use to prevent the introduction of new regressions and to test that their existing drivers work well with our OpenGL-based pipeline.

For the past couple years, ATI's drivers have been in a sad state of affairs w.r.t. our OGL pipeline (lots of crashes and rendering artifacts). However, I'm now happy to report that ATI has fixed the remaining Linux-specific OpenGL driver bugs that affect our OGL pipeline, so our BAT suite now runs without failure on their forthcoming 8.27 series release. If you're a Linux user with ATI hardware, I'd suggest upgrading to 8.27 when it becomes available in the next couple weeks.

As for ATI's OpenGL drivers for Windows, some bugs remain and we are continuing to push those with ATI. But as I mentioned above, the change in b92 that makes the FBO codepath the default means that most of those bugs are no longer reproducible for most newer boards.

On the Nvidia and Sun fronts, things are looking really good. There were a couple driver bugs found recently on certain Nvidia laptop GPUs that have since been fixed. If your laptop has an Nvidia GPU and you've been seeing weird artifacts when enabling the OGL pipeline, please install Nvidia's 95.xx series driver when it becomes available in the near future.

In my ears: Viktor Vaughn, "Vaudeville Villain"
In my eyes: Edward P. Jones, "The Known World"

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.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 articleon that subject) and a little known AlphaCompositerule 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.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.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.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
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.setPaint(new GradientPaint(0, 0, Color.RED, 0, height, Color.YELLOW));
g2.fillRect(0, 0, width, height);

// 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"