In a previous life, I worked in the 3D graphics arena writing applications, APIs, and drivers for realtime 3D graphics visualization. Fun stuff. At that time (this is 5+ years ago), there was an idea kicking around the 3D graphics world of "Image Based Rendering". This approach would play tricks with images in order to simulate real-time 3D viewing. So, for example, instead of rendering the model associated with a building on every frame as the viewer walked around that world, you might render that model once from a single viewpoint, cache that result in an image, and warp that image thereafter. So as the viewer moved around, these images would be warped to "appear" as though they were being re-rendered on every frame, but it was actually just neat image rendering tricks to make it look "good enough". This was indeed a neat trick; you could get great performance because you avoided complex model re-rendering and could do simple image operations instead. Of course, you would need to re-render models occasionally when the errors got bad enough (you can't walk around to the other side of a building model and not expect to re-render that building a few times in the process; for one thing, the original rendering of that model didn't include any details on the side or back of that building, so looking at it from the other side would result in some pretty messed-up artifacts). Some example uses of this approach to rendering include Apple's Quicktime VR (there are several 3D views of a scene and you can smoothly animate around that scene by interpolating between the various pre-rendered images), various interesting academic papers at graphics conferences, and even proposals for hardware that would use this approach (notably, Microsoft's Talisman proposal). Sadly, graphics hardware moved on, 3D rendering got orders of magnitude faster, and the need for using image-based tricks instead of re-rendering 3D geometry went away (check out the comment by Id Software's Brian Hook on the demise of Talisman). After all, with current graphics hardware able to render tens of millions of multitextured triangles per second, why should anyone have to play tricks and deal with associated rendering artifacts? I love neat performance tricks, so part of me is sad to see this go away (although the rest of me is overjoyed because it means the whole system has gotten so much faster in the meantime. I don't think we would have such humanitarian and educational projects as Doom 3 if we were still trying to figure out how to warp images to get around polygon-count limitations...). Fortunately, Image Based Rendering still has a place in the world, at least in my current world of 2D graphics. While not as complex or fascinating as the 3D Image Based Rendering techniques, the technique I'll discuss here does have similar performance advantages in the 2D realm. I call this approach "Intermediate Images". The Big Idea
The main idea here is that it is a lot faster to copy an image than to
perform a complex rendering operation. In general, a simple image copy
( Okay, so that's the "why" of the equation; simple image copies are simply faster. What about the "how"?
Example: Pre-Transformed Images
The basic approach with intermediate images is to create an image of the type and size that you need, perform your expensive operations to that intermediate image, and thereafter copy from that image instead of performing your rendering operations directly.
For example, let's say you have an image of some size and you
want it to be scaled to size
This will cause Java2D to scale the image every time through this function (assuming your original image is not actually at size scaleW x scaleH). You could, instead, use the Intermediate Image approach, where you create an image of the appropriate size, scale the original image to that new temporary image, and then do a simple copy from that temporary image:
Note that, in this approach, This approach is not limited to scaling transforms; you can use the same approach for arbitrary transformations. Note, however, that some transforms of an image may produce non-rectangular results, thus you will need an image with a transparent background so that the intermediate image background does not show up during the copy operation. See some of the code below for this transparent- background image approach. But Wait, There's More!
We've shown that you can do this for pre-transforming images, saving on the cost of scaling, rotating, or whatever you want to do with an image. But Intermediate Images are not limited to image-based operations; you can use the same technique for arbitrary rendering operations, such as complex shapes or even text.
The idea here is to create an image of the appropriate size with a transparent
background, render your graphics operations into it once (or whenever they change),
and thereafter just call
Complex Shapes
Suppose you want to have a "Happy!" icon represented by some insipid smiley face, such as
this:
You could render that smiley with the following graphics operations:
Rendering this once isn't a problem. But suppose you had some application where you needed to render this same graphic several times every time you painted the component (maybe it's a graphical chat room application with lots of happy people in it). At some point, it just doesn't make sense to keep re-doing all of the same rendering over and over; you may as well cache the rendering results in an intermediate image and do simple image copies instead. Note that in this case, the graphics you are rendering are non-rectangular. So when you cache the graphics as an image, you need to make the background of the image transparent so that copying that image will only result in copying the colors from the graphics, not the colors from the background of the image. You can make this work by creating a BITMASK transparent image, filling it with a transparent color and then rendering your graphics. Here is code that will accomplish the same thing as above, only using an transparent- background intermediate image instead of drawing the graphics directly to the component:
(Note that we are only rendering the graphics in one size at a single position; this is a simplifying assumption just for making the sample code more readable).
Text
Along the same lines as the arbitrary shape above, you could also use intermediate images to draw static text. This is obviously not suitable for very text-heavy applications such as word-processing; there is simply too much text and the text is too dynamic to make this a workable solution. But many applications use text for such things as static labels; they might benefit from intermediate images. For example, a game that wanted the best performance possible might want to draw its "Score:" label with an image, to avoid the slower path of text rasterization.
The main work to do in caching text as images is in getting the image size correct (based
on the size of the text in a given font) and positioning that text image correctly. Note that an image
is positioned through its upper-left coordinate, whereas a string is drawn starting from the
lower left (actually, this lower-left point is the point of the base line; descending characters
in the font (such as lower case "y" and "g") may go below this line). Note too that the approach
below for sizing and positioning the string image correctly is too simplistic for the general case;
those interested in more correct (and more involved) text manipulation should check out the
javadocs for Here is some sample code that draws some extremely interesting text in the upper-left corner of the component:
Anti-Aliased Text
You can take the text example one step further and use images to render anti-aliased text as well. With a slight tweak to the above code, we can create the right kind of image, render anti-aliased text to it, and get image copies that will respect the smooth properties of the text rendering that you asked for:
The changes made to the prior text code are in bold above. Basically, we need to create
a translucent image instead of the prior transparent-background image.
Anti-aliased rendering works by rendering the text with alpha values in the
colors at the edges of the text; this makes those edge colors blend with the
background colors. If we create a
And so on...
The examples above are merely meant to illustrate the concept; I'll leave it as extra credit for you to figure out how to apply the technique in your particular situation. For example, you could take the anti-aliased text example above a step or two further and cache rendering of any anti-aliased or translucent operations. Notes
There are some important things to note about this technique:
That's all there is to it. The technique is not very difficult to understand or to program (as the simple examples above hopefully demonstrate). The only tricks here are getting the image type correct (to account for transparent background and translucent situations) and getting the cached image positioned correctly (to match the positioning of the original graphics). My main point here is that you can apply this technique across all of your graphics operations; anything that you draw repeatedly in the same way (static text, images scaled to the same size, complex shapes, whatever) is a candidate for Intermediate Image rendering. Okay, so it's not quite as geeky-cool as the 3D Image Based Rendering algorithms, but I figure anything that gives better graphics performance is pretty worthwhile anyway. For More Information
About the Author
Chet Haase is a member of the Java 2D team at Sun Microsystems. He has
been involved with graphics software technologies for the past 15 years
and currently spends his time worrying about performance and hardware
acceleration issues for the Java graphics libraries.
| |||||||||||||||||||
|
| ||||||||||||