![]() |
|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|
![]() |
![]() |
Media Programming
SummaryBy Bill Day
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)
ast month, I introduced some basic notions of Java 2D: that
all 2D objects can be manipulated using
AffineTransform
s, that
arbitrary paths can be constructed and shapes filled using
GeneralPath
s, 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 |
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 BufferedImage
s. 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 AffineTransform
s 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!
TextLayout
for internationalization
support in the 1.2 Java platform. Accompanied by excellent illustrations that
help to simplify the more convoluted concepts in the text. Font.properties
file. ![]() |
![]() |
|
![]() |
Copyright © 2002 JavaWorld.com, an IDG Communications company |
![]() |
![]() |
![]() |