Getting started with Java 2D

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

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:

  • lineTo() for straight segments (specify only end points)
  • quadTo() for quadratic curves (specify one control point)
  • curveTo() for third-ordered curves (specify two control points, drawn using cubic Bezier curve)
1 2 Page 1