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

Media Programming

Getting started with Java 2D

New support for 2D shapes, transforms, curves, and fonts enters the core environment with Java 1.2

Summary
Bill Day begins his technical discussion of the Media and Communication APIs by looking at a core part of the Java 1.2 environment, the Java 2D API. Read this month's column to learn how to draw curves, perform 2D transformations, fill arbitrary shapes, and manipulate text. (3,300 words)
By Bill Day


The Java 2D API is a core Java 1.2 platform API (see Resources for a variety of information on the API and its implementations). Implementations of the API are available as a part of the Java Foundation Classes (JFC) in the current beta releases of the Sun JDK for Windows NT/95 and Solaris. As Java 1.2 is finalized, Java 2D should become available on more platforms.

Note that although Java 2D has been developed somewhat independently of the other parts of the JFC, it is nonetheless a core part of the 1.2 AWT. We will make the distinction and point out 2D-specific features for discussion, but you should remember that this functionality is just as central to 1.2 graphics as the old 1.0 and 1.1 AWT support.

Java 2D extends the previous AWT mechanisms for drawing 2D graphics, manipulating text and fonts, loading and using images, and defining and dealing with colors and color spaces. We will be exploring these new mechanisms in this and future columns.

A note about nomenclature and conventions
For this column, my primary development platform will be a PC running Windows 95 or Windows NT. I hope to provide other platform-specific tips and tricks where possible, but I will focus on Windows since that's where I will be spending most of my time.

When I write a method name, it should always be of the form methodname(). The trailing parentheses are meant to set this apart as a method. The method may or may not take parameters. In practice, the context should always make this clear.

Source code listings will be given with line numbers included. I plan to use the line numbers to cross-reference the article text and the code listings as appropriate. This should also make it much easier for you to annotate the column, should you chose to print a copy. Please note, however, that the source files linked from the column will be regular *.java files (sans line numbers) so that you can download and develop with them.

Because I will be writing about many of the Media and Communications APIs in the coming months, I want to make sure that all of the sample code makes sense as a whole as well as in its individual parts. I will attempt to consistently name my examples and place them into sensical packages.

The top of my package hierarchy will be:


com.javaworld.media

Each API or topic that I write about will have at least one subpackage under this top level. For instance, all of the code for this Java 2D article will be in:


com.javaworld.media.j2d

So, to invoke the first example application on Java 2D, you would download the code, put it in your classpath, then use:


java com.javaworld.media.j2d.Example01

(If the namespace is too long for your liking or for some other reason you want to use the example code without having to use the fully qualified name, simply comment out the package line at the beginning of each source code file.)

I will generate a Java Archive (jar) file for each article's example code and class files. This archive will be made available in the Resources of each column, should you wish to download it and execute the examples from within the archive.

I will also keep an up-to-date jar file containing all of the code and classes from my current and previous Media Programming columns. This all-encompassing jar file will be available on my personal Web site.

One final point on the examples: I have chosen to make each example, unless I specifically note otherwise, a standalone application or applet. This will lead to some repetition of code from time to time, but I feel it best preserves the integrity of each individual example.

Enough about conventions. Let's get started programming with Java 2D!

Graphics2D: A better Graphics class
The central class within the Java 2D API is the java.awt.Graphics2D abstract class, which subclasses java.awt.Graphics to extend 2D rendering functionality. Graphics2D adds more uniform support for manipulations of a variety of shapes, in effect making text, lines, and all sorts of other two-dimensional shapes comparable in their capabilities and utility.

Let's start with a simple example showing how you get and use a Graphics2d reference.


001 package com.javaworld.media.j2d;
002
003 import java.awt.*;
004 import java.awt.event.*;
005
006 public class Example01 extends Frame {
007   /**
008    * Instantiates an Example01 object.
009    **/
010   public static void main(String args[]) {
011     new Example01();
012   }
013
014   /**
015    * Our Example01 constructor sets the frame's size, adds the
016    * visual components, and then makes them visible to the user.
017    * It uses an adapter class to deal with the user closing
018    * the frame.
019    **/
020   public Example01() {
021     //Title our frame.
022     super("Java 2D Example01");
023
024     //Set the size for the frame.
025     setSize(400,300);
026
027     //We need to turn on the visibility of our frame
028     //by setting the Visible parameter to true.
029     setVisible(true);
030
031     //Now, we want to be sure we properly dispose of resources
032     //this frame is using when the window is closed.  We use
033     //an anonymous inner class adapter for this.
034     addWindowListener(new WindowAdapter()
035       {public void windowClosing(WindowEvent e)
036          {dispose(); System.exit(0);}
037       }
038     );
039   }
040
041   /**
042    * The paint method provides the real magic.  Here we
043    * cast the Graphics object to Graphics2D to illustrate
044    * that we may use the same old graphics capabilities with
045    * Graphics2D that we are used to using with Graphics.
046    **/
047   public void paint(Graphics g) {
048     //Here is how we used to draw a square with width
049     //of 200, height of 200, and starting at x=50, y=50.
050     g.setColor(Color.red);
051     g.drawRect(50,50,200,200);
052
053     //Let's set the Color to blue and then use the Graphics2D
054     //object to draw a rectangle, offset from the square.
055     //So far, we've not done anything using Graphics2D that
056     //we could not also do using Graphics.  (We are actually
057     //using Graphics2D methods inherited from Graphics.)
058     Graphics2D g2d = (Graphics2D)g;
059     g2d.setColor(Color.blue);
060     g2d.drawRect(75,75,300,200);
061   }
062 }

When you execute Example01, you should see a red square and blue rectangle, as shown in the figure below. Note that there is a known performance problem with the Windows NT/95 version of the JDK 1.2 Beta 3 (the most current 1.2 release as of this column). If this example is painfully slow on your system, you may need to work around the bug as documented in JavaWorld Java Tip 55 (see Resources below for this tip).


Simple graphics using Graphics and Graphics2D

Note that just as you do not directly instantiate a Graphics object, you do not instantiate a Graphics2D object either. Rather, the Java runtime constructs a rendering object and passes it to paint() (line 047 in the Example01 code listing), and on Java 1.2 platforms and beyond, this object implements the Graphics2D abstract class as well.

So far we haven't done anything particularly special with our 2D graphics capabilities. Let's add some code to the end of our previous example's paint() method and bring in several features new to Java 2D (Example02):


001   /**
002    * Here we use new Java 2D API features such as affine
003    * transforms and Shape objects (in this case a generic
004    * one, GeneralPath).
005    **/
006   public void paint(Graphics g) {
007     g.setColor(Color.red);
008     g.drawRect(50,50,200,200);
009
010     Graphics2D g2d = (Graphics2D)g;
011     g2d.setColor(Color.blue);
012     g2d.drawRect(75,75,300,200);
013
014     //Now, let's draw another rectangle, but this time, let's
015     //use a GeneralPath to specify it segment by segment.
016     //Furthermore, we're going to translate and rotate this
017     //rectangle relative to the Device Space (and thus, to
018     //the first two quadrilaterals) using an AffineTransform.
019     //We also will change its color.
020     GeneralPath path = new GeneralPath(GeneralPath.EVEN_ODD);
021     path.moveTo(0.0f,0.0f);
022     path.lineTo(0.0f,125.0f);
023     path.lineTo(225.0f,125.0f);
024     path.lineTo(225.0f,0.0f);
025     path.closePath();
026
027     AffineTransform at = new AffineTransform();
028     at.setToRotation(-Math.PI/8.0);
029     g2d.transform(at);
030     at.setToTranslation(50.0f,200.0f);
031     g2d.transform(at);
032
033     g2d.setColor(Color.green);
034     g2d.fill(path);
035   }

Note that since GeneralPath is located in the java.awt.geom package, we need to be sure we add an import line as well:


import java.awt.geom.*;

The output of Example02 is shown in the following figure.


GeneralPath and AffineTransform illustrate drawing and transformations in Java 2D

Java 2D allows for the specification of arbitrary shapes using the java.awt.Shape interface. A variety of default shapes such as rectangles, polygons, 2D lines, etc., implement this interface. One of the most interesting of these in terms of flexibility is java.awt.geom.GeneralPath.

GeneralPaths let you describe a path with an arbitrary number of edges and a potentially extremely complex shape. In Example02, we have created a rectangle (lines 020-025), but we just as easily could have added another side or sides to make a pentagon, or heptagon, or some other multi-sided polygon. Also note that unlike standard Graphics code, Java 2D allows us to specify coordinates using floating-point numbers instead of integers. CAD vendors of the world, rejoice! In fact, Java 2D supports integer, double, and floating arithmetic in many places.

You probably also noticed that when we created the path we passed a parameter, GeneralPath.EVEN_ODD, into the constructor (line 020). This parameter represents a winding rule that tells the renderer how to determine the inside of the shape specified by our path. Please refer to the Java 2D javadoc documentation referenced in the Resources for more on Java 2D winding rules.

The other major innovation in Example02 revolves around the use of a java.awt.geom.AffineTransforms (lines 027-031). I'll leave the specifics of such transforms to the reader (see Resources for articles that discuss this in greater detail), but suffice it to say that AffineTransforms allow you to operate on any Java 2D graphic to translate (move) it, rotate it, scale it, shear it, or perform combinations of the these manipulations.

The key to AffineTransform lies in the concept of Device Space and User Space. Device Space is that area into which the graphics will be rendered on the screen. This is analogous to the coordinates that are used when one creates 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.

Device Space and User Space coordinate systems initially overlap, with the origin at the upper left of the rendering surface (here, a Frame). The positive x axis moves right from the origin, while the positive y axis moves down.


Device Coordinate Space and User Coordinate Space initially overlap

After the first transformation in Example02 (lines 028 and 029), the User Space coordinate system has been rotated 22.5 degrees counterclockwise relative to the Device Space. Both still share the same origin. (Note that rotations are specified in radians, with -PI/8 radians equaling -22.5 degrees, or 22.5 degrees CCW.) If we were to stop here and draw the rectangle, it would be rotated mostly out of our field of view in the application Frame.


AffineTransform is used to rotate the User Space 22.5 degrees counterclockwise

We next apply a second transformation (lines 030 and 031), this one a translation, after the rotation is complete. This moves the User Space coordinate system relative to the Device Space, shifting it down 200.0 (float) units and right 50.0 (float) units.


User Space is moved into its final position via AffineTransform translation

When we fill in the green rectangle, it is translated and rotated relative to the Device Space.

Of Bezier and higher-ordered curves
Now that we have examined how transforms can be used to manipulate graphical objects, let's reexamine how we build complex and interesting arbitrary shapes.

Curves are used throughout mathematics and computer graphics to approximate complex shapes using a finite, well-defined (and ideally small) number of mathematical points. Whereas the standard AWT did not directly support drawing with arbitrary curves in the past (Java 1.0 or 1.1 platforms), Java 2D adds built-in support for first-, second-, and third-order curves. You can draw curves with two end points and zero, one, or two control points. Java 2D computes first- and second-order curves using linear and quadratic formulas and cubic, or third-order, curves using Bezier curves.

(Bezier curves are a type of parametric polynomial curve that have some very desirable properties related to computation of closed curves and surfaces. They are used in numerous graphics applications. Please refer to the Resources for more information on the use of parametric polynomials and Bezier curves in computer graphics.) The GeneralPath methods that draw each of these curves are:

Each of these methods draws from the previous location of our path end point to the final pair of float parameters, our next end point. For lineTo(), this is the only pair given. For quadTo(), the first pair of floats refer to the one and only control point. For curveTo(), the first pair is the first control point and the middle pair is the second control point.

Confused? A simple example should help. Example03 modifies our previous paint() method slightly, drawing two of the green rectangle's edges as non-linear curves.


001   /**
002    * Here we use first-, second-, and third-order
003    * curves together in our GeneralPath.
004    **/
005   public void paint(Graphics g) {
006     g.setColor(Color.red);
007     g.drawRect(50,50,200,200);
008
009     Graphics2D g2d = (Graphics2D)g;
010     g2d.setColor(Color.blue);
011     g2d.drawRect(75,75,300,200);
012
013     //This time our GeneralPath will have nonlinear
014     //segments, one second-order (quadratic) and another
015     //third-order (cubic).  We translate and rotate this shape
016     //again, as we did before.
017     GeneralPath path = new GeneralPath(GeneralPath.EVEN_ODD);
018     path.moveTo(0.0f,0.0f);
019     path.lineTo(0.0f,125.0f);
020     path.quadTo(100.0f,100.0f,225.0f,125.0f);
021     path.curveTo(260.0f,100.0f,130.0f,50.0f,225.0f,0.0f);
022     path.closePath();
023
024     AffineTransform at = new AffineTransform();
025     at.setToRotation(-Math.PI/8.0);
026     g2d.transform(at);
027     at.setToTranslation(50.0f,200.0f);
028     g2d.transform(at);
029
030     g2d.setColor(Color.green);
031     g2d.fill(path);
032   }

Note the quadratic and cubic edges in the output.


GeneralPath is used to create arbitrary shapes using non-linear curve segments

Please consult the Resources below if you need more information on curves in Java 2D.

Fonts and text hit the big time
Java 2D puts fonts and text strings on the same footing as other 2D graphical objects. Text is transformable using AffineTransforms, just like any other 2D object. You can also get a Shape object describing the edges of an arbitrary string of text, then use this to clip other 2D objects or do anything else you can normally do with a Shape.

Let's try out some transforms on a simple text string in Example04.


001   /**
002    * In this example, I draw some text using Font and Graphics2D.
003    * You can see how Font has been extended within Java 2D
004    * with new capabilities, such as being transformable
005    * using AffineTransform.
006    **/
007   public void paint(Graphics g) {
008     //We've cleaned out some cruft (removed the two simple
009     //rectangles) from our Frame.  We are going to reuse
010     //our GeneralPath filled shape, however.  We translate
011     //and rotate this shape as we did before.
012     Graphics2D g2d = (Graphics2D) g;
013
014     GeneralPath path = new GeneralPath(GeneralPath.EVEN_ODD);
015     path.moveTo(0.0f,0.0f);
016     path.lineTo(0.0f,125.0f);
017     path.quadTo(100.0f,100.0f,225.0f,125.0f);
018     path.curveTo(260.0f,100.0f,130.0f,50.0f,225.0f,0.0f);
019     path.closePath();
020
021     AffineTransform at = new AffineTransform();
022     at.setToRotation(-Math.PI/8.0);
023     g2d.transform(at);
024     at.setToTranslation(0.0f,150.0f);
025     g2d.transform(at);
026
027     g2d.setColor(Color.green);
028     g2d.fill(path);
029
030     //Now, let's use some of the Java font and text support.
031     //Note that you need to be sure you have the same fonts I
032     //use in the example (Times New Roman True Type) if you
033     //execute this example code.
034     Font exFont = new Font("TimesRoman",Font.PLAIN,40);
035
036     //Un-comment the following diagnostic println's if you
037     //want to see what font was returned.  This can be useful
038     //when you have limited font support on your system and
039     //are not sure which font the Java runtime may have
040     //substituted for your requested font.
041     //System.out.println(exFont.getFamily());
042     //System.out.println(exFont.isPlain());
043     //System.out.println(exFont.getSize());
044
045     g2d.setFont(exFont);
046     g2d.setColor(Color.black);
047     g2d.drawString("JavaWorld",0.0f,0.0f);
048   }

The following figure shows the output of Example04.


Java 2D translates and rotates text strings using AffineTransforms

Note: There is a bug on at least some Windows NT 4.0 systems running JDK 1.2 Beta 3 whereby drawing the text string is seen to erase the green shape from the output. After this paint() completes, only the text remains showing. Forcing a repaint of the Frame (by resizing the window, for instance) results in the text and shape being drawn correctly. This bug is still under investigation as of this writing, however you can see the latest information on it by reading the java2d-interest mailing list thread discussing it (see Resources).

You probably noticed two things about our output:

  1. Our text string is sitting above the rectangle, which you might not expect
  2. Our text looks very blocky, or jaggy

The first problem occurs because text strings, unlike other Java 2D objects, have their positive y axis flipped relative to the User Space coordinates. This is done to ensure that when you draw text strings, you get readable (right-side up) strings when and where you expect. This flipping of the y axis for text can cause problems from time to time, and is a definite gotcha lurking within Java 2D.

The second problem is slightly more complex in nature but equally well understood: The text has jagged edges because of an aliasing effect. But fear not, all is not lost. We'll have a solution for this dilemma in next month's continuation of our Java 2D series.

Conclusions
Next month I will resume my discussion of Java 2D. I will start by presenting the aliasing workaround built into Java 2D, then show how you can get the Shape of a text string and use it to clip a second shape. I will also show you how to create and manipulate buffered images and how to use the new compositing capabilities in Java 2D to vary the opacity of an image and blend images together.

My thanks to Jonathan Knudsen for reviewing drafts of this Java 2D column and to fellow JavaWorld columnist Mark Johnson for use of his line-numbering script.

As always, please let me know about topics you would like to see covered. I received some wonderful feedback on the debut column, and I hope you will continue to send me your ideas and suggestions. If you have ideas for things you would like to see in a future installment of Media Programming, please let me know.


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