Advertisement: Support JavaWorld, click here!
August 1998
HOME FEATURED TUTORIALS COLUMNS NEWS & REVIEWS FORUM JW RESOURCES ABOUT JW

Media Programming

Antialiasing, images, and alpha compositing in Java 2D

Java 2D adds support for antialiased rendering, image transforms, and alpha compositing

Summary
In this second installment on Java 2D, you'll learn how to enable high-quality graphics using antialiased rendering. Bill also discusses using text strings as clipping shapes, and the new image features in Java 2D -- including support for compositing multiple images. (3,800 words)
By Bill Day


Last month, I introduced some basic notions of Java 2D: that all 2D objects can be manipulated using AffineTransforms, that arbitrary paths can be constructed and shapes filled using GeneralPaths, and that text strings are drawn and operated on just like any other shape in Java 2D. This month, I'll continue the discussion by presenting the solution to last month's aliasing problem. I'll also illustrate how to use one shape to clip another, and delve into the new image-manipulation capabilities provided by Java.

What is aliasing, and how do you avoid it?
Aliasing occurs when a signal (in this case, a 2D graphics signal) is sampled and quantized from a continuous space into a discretized space. Sampling is the process of reading a value from a continuously varying signal. Quantization is the process by which these continuous sampled values are assigned a discrete value in the finite space represented by digital (binary-based) systems.

Aliasing is a by-product of this quantization. Humans perceive this by-product visually as abrupt changes in color from pixel to pixel. Graphics professionals often refer to these jagged edges as jaggies.

In general, aliasing is a bad thing. It leads to lower-quality signals of all kinds. In fact, if you look closely at the examples in last month's column, especially those with slanted and curved edges, you can see aliasing effects all the way back to Example02. (Example01's lines are drawn parallel and perpendicular to the scan-line direction of the computer screen, so there are no quantization errors.)

If you are not familiar with aliasing effects, you can refer to any decent graphics or signal processing textbook for much more in-depth information. Charles Poynton's A Technical Introduction to Digital Video (John Wiley & Sons; ISBN: 047112253X) gives a particularly good description of aliasing as it pertains to video signals. If you are familiar with aliasing, but would like a quick refresher, see the Resources section for a link to "The Truth about Antialiasing" by Jonathan Knudsen.

So, how do you handle aliasing? Java 2D lets you set one of several rendering hints to indicate that you would like for your 2D graphics to be drawn using antialiasing algorithms -- which smoothes the edges. Note that I said hints: Whichever Java 2D implementation you're using, it is allowed to decide whether or not to follow the hint and carry out the antialiasing as requested.

Let's compare the aliased output from last month's Example04 to some antialiased output, generated from Example05.


001   /**
002    * In previous examples, we saw some jagged edges due to aliasing.
003    * Example05 illustrates how to use rendering hints to request
004    * an anti-aliased render from Graphics2D.
005    **/
006   public void paint(Graphics g) {
007     Graphics2D g2d = (Graphics2D) g;
008
009     //This time, we want to use anti-aliasing if possible
010     //to avoid the jagged edges that were so prominent in
011     //our last example.  With ask the Java 2D rendering
012     //engine (Graphics2D) to do this using a "rendering hint".
013     g2d.setRenderingHints(Graphics2D.ANTIALIASING,
014        Graphics2D.ANTIALIAS_ON);
015
016     //We reuse our GeneralPath filled shape.  We translate
017     //and rotate this shape as we did before.
018     GeneralPath path = new GeneralPath(GeneralPath.EVEN_ODD);
019     path.moveTo(0.0f,0.0f);
020     path.lineTo(0.0f,125.0f);
021     path.quadTo(100.0f,100.0f,225.0f,125.0f);
022     path.curveTo(260.0f,100.0f,130.0f,50.0f,225.0f,0.0f);
023     path.closePath();
024
025     AffineTransform at = new AffineTransform();
026     at.setToRotation(-Math.PI/8.0);
027     g2d.transform(at);
028     at.setToTranslation(0.0f,150.0f);
029     g2d.transform(at);
030
031     g2d.setColor(Color.green);
032     g2d.fill(path);
033
034     Font exFont = new Font("TimesRoman",Font.PLAIN,40);
035     g2d.setFont(exFont);
036     g2d.setColor(Color.black);
037     g2d.drawString("JavaWorld",0.0f,0.0f);
038   }

The effects of the antialiasing are shown below.


Antialiasing renders graphics with smoother edges.

You request antialiased rendering by calling Graphics2D.setRenderingHints(Graphics2D.ANTIALIASING,Graphics2D.ANTIALIAS_ON) (lines 013 and 014). Antialiasing and the other rendering hints are discussed in more detail in the Java 2D API javadoc documentation.

So if aliasing is always bad, and antialiasing algorithms are available (assuming your Java runtime environment supports the rendering hint), then why not just perform antialiasing calculations all the time? The simple answer is performance. Antialiasing algorithms take longer to compute than their less-complicated aliasing brethren, slowing down your rendering times. Example05 is noticeably slower than Example04 on most current Pentium PC-class machines, though it still renders in a few hundred milliseconds.

In applications that render something only once, or rather infrequently, and where rendering speed is not particularly important, you should probably turn on the antialiasing hint to get better renders. Such applications include CAD systems and image manipulation tools. However, if an applications requires the fastest possible rendering speed, and aliasing will be less noticeable, antialiasing is undesirable. In twitch video games, for example, fast renders are much more important than jagged edges. As with many programming and engineering details, you as the application programmer need to understand the requirements of your problem domain and decide what is best for your particular application.

If it makes sense for your application, you could even leave the decision to the end user. For a CAD application, for instance, you could provide a switch to turn antialiasing on and off, depending on the user's needs. This switch would let the user change the rendering hints at runtime and then automatically repaint the graphics.

Clipping paths
Last month I told you that any text string can be used as a Shape. This allows you to perform several interesting graphics manipulations with text. One of the more useful of these manipulations is clipping.

Clipping allows you to use a specified path as a sort of stencil set on top of previously laid graphics. Java 2D supports clipping using any arbitrary Shape. You can clip with shapes you draw yourself, including shapes you create using a GeneralPath. Because you can use StyledString.getStringOutline() to get the Shape for a string of text, you can also clip with that text.

Example06 modifies the paint() method one more time. I've left antialiasing on, and draw the same four-sided path. This time, however, instead of drawing the text string, I use its shape to specify where the path-created object is filled. (Note that you also need to import the package containing StyledString by adding import java.awt.font.*; to the source file.)


001   /**
002    * We use StyledString.getStringOutline() to get the Shape of
003    * some text, which we then use to clip our GeneralPath.
004    **/
005   public void paint(Graphics g) {
006     Graphics2D g2d = (Graphics2D) g;
007
008     //Again, we use anti-aliasing if possible.
009     g2d.setRenderingHints(Graphics2D.ANTIALIASING,
010        Graphics2D.ANTIALIAS_ON);
011
012     GeneralPath path = new GeneralPath(GeneralPath.EVEN_ODD);
013     path.moveTo(0.0f,0.0f);
014     path.lineTo(0.0f,125.0f);
015     path.quadTo(100.0f,100.0f,225.0f,125.0f);
016     path.curveTo(260.0f,100.0f,130.0f,50.0f,225.0f,0.0f);
017     path.closePath();
018
019     AffineTransform at = new AffineTransform();
020     at.setToRotation(-Math.PI/8.0);
021     g2d.transform(at);
022     at.setToTranslation(0.0f,150.0f);
023     g2d.transform(at);
024
025     Font exFont = new Font("TimesRoman",Font.PLAIN,80);
026     g2d.setFont(exFont);
027
028     //Now, we want to create a string and then use its outline
029     //to clip our green GeneralPath object drawn above.  We
030     //do this by creating a StyledString, getting its StringOutline
031     //(which is a Shape object like GeneralPath), then setting
032     //the clipping shape to be the string and filling the path.
033     StyledString exString = new StyledString ("JavaWorld", exFont);
034     Shape exShape = exString.getStringOutline();
035     g2d.setClip(exShape);
036     g2d.setColor(Color.green);
037     g2d.fill(path);
038   }

Note that StyledString will be deprecated with JDK 1.2 beta 4 (currently listed by Sun as due some time in late July 1998). Members of the Java 2D team have stated on the java2d-interest mailing list that this functionality will be replaced by a couple of new classes in the beta 4 release. I will update the example code for the JDK 1.2 beta 4 changes and make it available on my personal Web site.

In the following figure, you can see a portion of the green object showing through the overlaid (stencil) portion of the clipping text. Only that portion of the object lying directly beneath the text string's letters can be seen through this stencil.


The string JavaWorld is used to clip the green shape. With Java 2D, you can use any Shape object to clip any other Shape.

Clipping can be used for a lot of interesting effects. You could, for instance, use a circle shape to clip a complicated 2D shape, and move this circle around to simulate a spotlight moving across your 2D graphics. All sorts of intersections and cuts can be created with fairly simple clipping operations. Powerful stuff, indeed!

Imaging in Java 2D
Java 2D expands upon Java 1.0- and 1.1-style producer-consumer imaging (see Resources for more on this model) by allowing any of the regular 2D manipulations to be performed on images -- such as affine transforms and clipping. In addition, Java 2D adds a new buffer-based imaging model. This model gives programmers better access to the image's color information, and control over the raster -- the array that specifies the color values for each individual pixel in the image.

Whereas in Java 1.1 a call to a java.awt.Toolkit's or AWT Component's createImage(int width, int height) method returns a platform-specific implementation of the java.awt.Image interface, a createImage() call to Java 1.2 toolkits and components returns a java.awt.image.BufferedImage object.

In this column, I will illustrate some of the new features available to all Image objects, including BufferedImages. I'll leave the discussion of the new BufferedImageOp manipulations for next month's image processing column.

Transforms galore
You can perform the same kinds of transformations on images that you can perform on shapes, text strings, and other 2D graphics objects -- including the translation, rotation, scaling, and shearing transformations that we discussed last month.

In addition, images are considered to have their own Image Space. This space is a third space, a coordinate system from which the images are converted first into User Space and ultimately into Device Space for rendering. (Recall from last month that Device Space is the area into which the graphics will be rendered on the screen. This is analogous to the coordinates that are used when you create regular AWT-style, Graphics-based 2D graphics. User Space, however, is a translatable, rotatable coordinate system that may be operated on by one or more AffineTransforms).

Just as AffineTransforms may be used to transform the conversion of User Space objects into Device Space, they may also be used to transform Image Space-to-User Space conversions.

Example07 illustrates some Image-to-User and User-to-Device image transformations. For a good chuckle, I decided to transform and distort an image of myself (my JavaWorld column photograph), so you will see three images of me drawn after applying various transformations.

Here is how I manipulated the image.


001   /**
002    * Creates an Image, then does multiple manipulations on
003    * it to demonstrate Java 2D image manipulation features.
004   **/
005   public void paint(Graphics g) {
006     Graphics2D g2d = (Graphics2D) g;
007
008     //Again, let's set the antialiasing rendering hint on.
009     g2d.setRenderingHints(Graphics2D.ANTIALIASING,
010        Graphics2D.ANTIALIAS_ON);
011
012     //Load the Image using the loadImage() helper method.  For this
013     //example, I copied my JavaWorld column photo from:
014     //  http://www.javaworld.com/javaworld/icons/a-day.jpg
015     //for the image bits.  I assume that this image is on your local
016     //machine, in the directory from which you are running Example07.
017     //If you would like to use a different image or directory, load the
018     //image from another machine, or use a different access protocol,
019     //change the following variables accordingly.
020     String imageProtocol = "file";
021     String imageMachine = "localhost";
022     String imageFilepath = "day.jpg";
023     Image myImage = loadImage(imageProtocol,imageMachine,imageFilepath);
024
025     //Let's display a copy of our image at x=25, y=75.
026     AffineTransform at = new AffineTransform();
027     at.translate(25.0f,75.0f);
028     g2d.transform(at);
029
030     //Note that we pass in an identity matrix as the default Image
031     //Space transform (the second parameter to drawImage).  The last
032     //parameter refers to the observer for this drawing operation,
033     //in this case our instantiated Example07 object.
034     g2d.drawImage(myImage,new AffineTransform(),this);
035
036     //Now, let's scale the image and display it to the right
037     //of the original for comparison.  We first use setToTranslation
038     //so that we can reset the transform before adding our new
039     //translation matrix, then we apply the scaling directly to the
040     //Image Space, rather than scaling the entire User Space.
041     at.setToTranslation(100.0f,0.0f);
042     g2d.transform(at);
043     AffineTransform atImageSpace = new AffineTransform();
044     atImageSpace.scale(1.2f,1.2f);
045     g2d.drawImage(myImage,atImageSpace,this);
046
047     //Now let's shear the image and re-display it so you can compare
048     //the sheared version with the previous two.  We re-use our
049     //previous User Space transform, while we re-set the Image Space
050     //transform to shear instead of scale myImage.
051     g2d.transform(at);
052     atImageSpace.setToShear(0.2f,0.1f);
053     g2d.drawImage(myImage,atImageSpace,this);
054   }

And here is the output of these manipulations.


Three images of Bill in various states of transformation.

Notice how I am careful to clear each AffineTransform by calling the setToxxx() methods. For instance, in line 027 I use translate() to set up the first translation, but in line 041 I use setToTranslation() so that the AffineTransform is reset to the identity matrix before the new translation matrix is concatenated.

The loadImage() method uses the standard Java 1.0/1.1 mechanisms to create a URL and use it in turn to create an Image object. Please review the source listing if you need more information on this method.

Again, Java 2D exhibits one of its major strengths: The major 2D graphics operations can be performed on all 2D objects, including more complicated ones like images.

Compositing
You can scomposite (blend together using pre-defined rules) 2D graphics objects using the new alpha compositing support in Java 2D. This support is accessed via java.awt.AlphaComposite.

The AlphaComposite class provides fields that allow you to specify which of the Porter-Duff compositing rules you would like to apply to your composited images. (Please refer to the the Java 2D API documentation on AlphaComposite for more on the Porter-Duff rules).

The most commonly used of the Porter-Duff compositing rules is

SRC_OVER, which blends a percentage of the object currently being rendered on top of the previously rendered object underneath. Example08 illustrates such a use. Here I blend a transformed copy of the infamous column photo over our old friend the green-filled shape in a technique known as alpha compositing. Alpha compositing blends colors from the rasters of two or more graphics objects.


001   /**
002    * Composites a transformed Image over an arbitrary Shape.
003   **/
004   public void paint(Graphics g) {
005     Graphics2D g2d = (Graphics2D) g;
006
007     //Again, let's set the antialiasing rendering hint on.
008     g2d.setRenderingHints(Graphics2D.ANTIALIASING,
009        Graphics2D.ANTIALIAS_ON);
010
011     //Reuse the GeneralPath filled shape from previous examples
012     GeneralPath path = new GeneralPath(GeneralPath.EVEN_ODD);
013     path.moveTo(0.0f,0.0f);
014     path.lineTo(0.0f,125.0f);
015     path.quadTo(100.0f,100.0f,225.0f,125.0f);
016     path.curveTo(260.0f,100.0f,130.0f,50.0f,225.0f,0.0f);
017     path.closePath();
018
019     AffineTransform at = new AffineTransform();
020     double rotation = -Math.PI/8.0;
021     at.rotate(rotation);
022     at.translate(0.0f,150.0f);
023     g2d.transform(at);
024     g2d.setColor(Color.green);
025     g2d.fill(path);
026
027     //Reuse our image from Example07.
028     String imageProtocol = "file";
029     String imageMachine = "localhost";
030     String imageFilepath = "day.jpg";
031     Image myImage = loadImage(imageProtocol,imageMachine,imageFilepath);
032
033     //Now, let's place a sheared and scaled copy of our image
034     //overlapping somewhat with the green shape.  We first undue
035     //the previous rotation of our User Space so that the image is
036     //not rotated relative to the Device Space.  Then, we do a
037     //User Space translation to set the image off from the shape
038     //object.  Lastly, we shear and scale the Image Space.
039     at.setToRotation(-rotation);
040     at.translate(50.0f,0.0f);
041     g2d.transform(at);
042     AffineTransform atImageSpace = new AffineTransform();
043     atImageSpace.shear(0.2f,0.1f);
044     atImageSpace.scale(1.4f,1.4f);
045
046     //We draw our image with its alpha channel set so that it is
047     //50% transparent (or 50% opaque, depending on how you look at it).
048     AlphaComposite myAlpha = AlphaComposite.getInstance(
049        AlphaComposite.SRC_OVER,0.5f);
050     g2d.setComposite(myAlpha);
051     g2d.drawImage(myImage,atImageSpace,this);
052   }


Alpha blending an image over an arbitrary 2D shape.

Java 2D has also added several other major features related to color models and spaces. Please refer to the color-related references and the javadoc documentation for the java.awt.color package for more information on color management using Java 2D and Java 1.2.

Java 2D applications, and Media meets SIGGRAPH
Next month, we will discuss a useful application for Java 2D -- image processing. Guest contributor Jonathan Knudsen and I will explain some of the basics of image processing (polish off those kernels, folks!) and show you how to do image convolutions using Java 2D.

Also, I will be at SIGGRAPH 98 from July 19 to July 24. I will be serving as a juror for the Web 3D Round-Up. I also plan to attend the Java 3D and OpenGL programming courses. Stop by and say hello, and let me know what you think about the Media Programming column.

Keep an eye out for SIGGRAPH 98 Java-related news and a full conference report in an upcoming issue of JavaWorld.

After SIGGRAPH, we'll shift our focus from 2D to 3D, with a multi-part discussion of 3D graphics APIs in Java. This will include an in-depth discussion of Java 3D as well as how it compares technically with OpenGL, VRML, and possibly Direct3D. If you would like me to discuss any other 3D graphics systems, especially those that have available Java APIs, please let me know.

In particular, I am considering how much attention I should devote to Microsoft Java technologies, such as WFC support for 2D graphics, and Direct3D comparisons with Java 3D. Please let me know which topics would be useful to you.

Reader questions about Java 2D
Finally, I received some wonderful reader feedback on last month's column. Several questions were generally applicable. I am addressing a couple of them below. I would like to continue to provide some general Q & A with each new column, so please keep those questions coming.

Q:Can I download a package of Java 2D class files that will enable me to use Java 2D with JDK 1.1?

A:Java 2D is a built-in part of the JFC APIs beginning with the Java 1.2 environment. The team at Sun has implemented Java 2D support that requires Java 1.2 to function. Unfortunately, this means that you cannot download a package of classes to add Java 2D support to a pre-1.2 runtime or JDK.

Note: The full JFC including Java 2D differs from the JFC/Swing Preview, which will run on JDK 1.1. This preview is a subset of the full set of JFC APIs -- for instance, it does not contain Java 2D support. The full JFC will require Java 1.2 platforms and beyond because some of its individual APIs, like Java 2D, require the 1.2 platform.

Q:Is it possible to save a Graphics object as a file in a pixel or vector format?

A:The short answer is no -- Java 2D does not currently provide any direct support for outputting any raster- (pixel-) or vector-based graphics file formats. You would need to use a third-party package to provide this support.

However, some support for JPEG streams was provided in JDK 1.2 beta 3 versions of Java 2D. This support will be moved out (for a number of mostly non-technical reasons) to a com.sun.image.codec.jpeg subpackage in subsequent beta and final releases of Java 1.2/Java 2D. JPEG streams can be wrapped in a FileOutputStream, as in Sun's JPEG.java example available from the Java 2D Web site.

More information on raster and vector image formats and Java 2D is available from the java2d-interest mailing list archives.


About the author
Bill Day is a software engineer at Silicon Graphics Computer Systems. In addition to writing for JavaWorld, Bill is authoring a book entitled Java Media Players for O'Reilly & Associates. When Bill is not writing or programming, he loves to travel with his wife, speak French, and enjoy life. Java, c'est magnifique!


Advertisement: Support JavaWorld, click here!


HOME |  FEATURED TUTORIALS |  COLUMNS |  NEWS & REVIEWS |  FORUM |  JW RESOURCES |  ABOUT JW |  FEEDBACK

Copyright © 2002 JavaWorld.com, an IDG Communications company