Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

The Java 2D API


Feb99: The Java 2D API

Bill is a senior software engineer at Stingray Software and can be contacted at [email protected]


Version 1.2 of the Java Development Kit (JDK) includes the Java 2D API -- a set of functions that is a more flexible and full-featured rendering package than previous versions of the Abstract Windowing Toolkit (AWT). The 2D API provides enhanced graphics, text and image handling, as well as color definition and composition, hit detection on shapes and text, and device-independent printing.

The 2D API is designed to be extensible. The graphics operations of previous versions of the API performed a few specific functions. The graphics operations included in JDK 1.2 expand on that original functionality by supporting more-general graphics operations that can be extended.

The advantages of the 2D API are many. JDK 1.1 gave you the ability to draw simple shapes like rectangles, ellipses, and polygons with a single-width line. With JDK 1.2, you can draw virtually any shape with different line styles, fill styles and textures. Two-dimensional transforms can be used to translate, rotate, scale, or shear any graphical object. Clipping regions can be applied when drawing the shapes. Overlapping colors can be blended together to create a transparency effect.

To demonstrate the Java 2D API, I'll start with a drawing program that doesn't use the Java 2D API -- you could compile and run TooDee under the JDK 1.1 without problems. Listing One presents the Too-Dee class, which extends Canvas and provides a white surface on which to draw. The main() method just creates a frame and sets up its dimensions. The paint() method merely takes the standard graphics context of the canvas and draws a blue rectangle on it.

Adding Java 2D

So, what does it take to start using the 2D API in a class such as TooDee? Not much. The 1.2 graphics functionality was designed with backward compatibility in mind.

The 1.2 graphics context class java.awt.Graphics2D adds most of the operations commonly used to draw shapes. Conveniently, it is derived from the original java.awt.Graphics class and maintains all of the original functionality. When using the JDK 1.2, the Graphics2D object is passed into methods like paint(), even though the method declaration still shows the old Graphics object. Therefore, to use the Graphics2D object all you really need to do to is cast the Graphics object that was passed to a Graphics2D object like this: Graphics2D g2 = (Graphics2D)g;.

Listing Two shows the paint() method of the TooDee canvas using the Graphics2D class to draw our blue rectangle. To show that I'm actually doing something different, I set the stroke object in the graphics context to draw the lines of the rectangle with a width of 4. (The stroke object in the Graphics2D context determines how lines are drawn.)

Drawing Shapes

There are three steps to rendering shapes in the 2D API. These remain basically unchanged from previous versions of the AWT:

  1. Set up the graphics context.

  2. Define the object to draw.

  3. Call one of the rendering methods.

You can see these three steps in the existing paint() method, with the exception of the second and third steps, which are lumped together in the call to drawRect(). To shift the paint() method to use the 2D API, start with the graphical objects that implement the java.awt.Shape interface.

The Shape interface is used to define geometric shapes for use by the 2D API. As long as you implement the methods defined by this interface, you can create any shape you like. Many common shapes are already provided for you in the java.awt.geom package, including lines, curves, ellipses, rectangles, and rounded rectangles.

There are several key methods in the Graphics2D class that operate on Shapes. The most important to us here is draw(), which will draw any Shape using the current settings of the graphics context. The paint() method in Listing Three demonstrates this by creating a Rectangle2D Shape and sending it to the draw() method. The paint() method now follows the standard three steps.

There are a couple of things to note about the predefined Shapes in the 2D API. The first is that the class names all end with "2D." The second is that each Shape has two subclasses, one named Float and one named Double. This lets you choose the precision of the stored values. In this case, to use floating-point numbers, I chose java.awt.geom.Rectangle2D.Float. Rectangle2D is an abstract superclass that cannot be instantiated.

So what about the java.awt.Rectangle object in Listing One? This is a holdover from JDK 1.1 and couldn't possibly implement the Shape interface, right? Actually, while the rectangle object is still in the java.awt package in JDK 1.2, it is now derived from Rectangle2D, and therefore can be passed into new methods, such as draw().

Stroking Shapes

The Graphics2D context has an object that implements java.awt.Stroke, which determines how lines are drawn. Once you set the Stroke in the graphics context using the setStroke() method, all subsequent lines drawn in the graphics context will have those characteristics until the Stroke is changed again.

Since Stroke is an interface, you can create your own custom Strokes. There is one Stroke object provided for your convenience -- java.awt.BasicStroke, which allows you to change the most common properties of a drawn line, such as the line width, the line style (solid, dotted, dashed, and so on), the line-end cap style, and the line-join style.

The one major piece of information left out of the BasicStroke object is the color of the line. This is maintained separately by the graphics context with a Paint object, and can be set by calling the setPaint() method. A Paint object is any class that implements the java.awt.Paint interface. Luckily, the java.awt.Color object implements the Paint interface and you can use the colors you are familiar with from JDK 1.1. In Listing Three, calling setColor() still sets the line color.

Listing Four shows a more advanced use of the BasicStroke object. Here, I set up a Stroke object to draw lines with a width of 4, a rounded-end cap style, a bevel-join style, and a dashing style where there are lines of length 20 followed by spaces of length 10 (as defined in the dash[] array). I've made the rectangle a little bigger so that when the program is run, all of the effects are noticeable. The rectangle is obviously dashed, the dashed lines have rounded ends, and the corners of the rectangle are beveled where the lines meet. You can play around with the values passed into the BasicStroke constructor and witness the effects when TooDee is executed.

Filling Shapes

Filling Shapes works on the same principles as stroking Shapes. The Paint object in the graphics context also determines how Shapes are filled. To change the fill style, you just pass in a new Paint object to the setPaint() method of the Graphics2D object. It is important to note that the standard draw() method does not automatically fill a Shape for you -- it handles Stroking only. Instead, you need to call the separate fill() method, which also takes any Shape as a parameter.

Since Paint is an interface, you can create your own custom fills by creating new classes that implement it. There are several Paint objects already available for you to work with: a solid color, color gradient, and texture paint.

Filling with a solid color is straightforward, since the old java.awt.Color implements the Paint interface. In Listing Five, all you need to do is call setPaint() with the Color of your choice and call fill() with the Shape you wish to fill.

The java.awt.GradientPaint object implements the Paint interface and provides a few useful constructors for setting the starting coordinates, starting Color, ending coordinates, and ending Color of the gradient. Listing Six shows the same circle from Listing Five being filled, but with a color gradient beginning in the top-left corner with blue, and then fading to green in the bottom-right corner.

Texture fills are accomplished with the help of the java.awt.TexturePaint object. A TexturePaint takes a BufferedImage that acts as the texture and a rectangle that defines the area of the BufferedImage to replicate for the texture. In the paint() method in Listing Seven, you first create a BufferedImage in memory and draw a simple pattern onto it. Then you create a TexturePaint object out of the BufferedImage and add it to the graphics context as our Paint object.

When you were drawing into the buffered image's graphics context, you used the old JDK 1.1 methods setColor() and fillRect(). Remember that Graphics2D is derived from the original Graphics object and you can still use all of its drawing methods. Whether you use the old methods or the new methods is a matter of what works for you. In this case, it was convenient to make the two calls to fillRect() rather than creating two different Rectangle2D objects and calling draw() on each.

Another important fact to keep in mind is that the same setPaint() method sets up the graphics context for both Strokes and Fills. Therefore, it makes sense that you could draw lines that are drawn with textures or gradients. This is exactly what is demonstrated in the paint() method in Listing Eight. Here, a Stroke of width 10 is defined so that you can see what is going on, and the GradientPaint from Listing Six is used to draw an outline of the circle with the blue-green gradient. This is a good example of just how flexible the 2D API is.

Rendering Hints

There are a set of options with which you can set priorities between rendering speed and quality, called "Rendering Hints." These are available so that you can determine which is more important to your Java program -- looking better or drawing faster. All the hints are kept in a single RenderingHint object maintained by the graphics context.

There are many rendering hints, and you distinguish between them by passing the proper key into the setRenderingHint() method in the graphics context along with the value to set for the hint. The two most commonly used keys are KEY_RENDERING and KEY_ANTIALIASING. The former lets you choose an overall preference between speed and quality, and the latter whether or not to smooth out graphics as they are drawn. Most of the remaining hints relate to image processing.

For example, to set the hint for rendering quality, you make a call with the key and the value: g2.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);.

If you want to set the hint to prefer speed to quality, you would pass in RenderingHints.VALUE_RENDER_SPEED instead. Listing Nine takes the previous gradient stroke example and adds in our two hints for quality. You should be able to see the difference, especially on the interior of the Stroke.

Not all platforms support all rendering hints. The ability of a particular machine to implement a hint depends on the available hardware.

Transformations

Any Shape or image can be drawn translated, rotated, scaled, or sheared through the use of a transformation. The graphics context maintains a java.awt.geom.AffineTransform object that determines how the coordinate system of the drawing surface will be transformed before rendering takes place. Basically, an AffineTransform is a two-dimensional matrix that transforms the values of the geometry being drawn from the normal coordinate system into a modified one.

For instance, a rectangle drawn from (100,100) to (200,200) without a transform will be drawn just how you expect. However, if you set the transform in the graphics context to be rotated by 45 degrees, the coordinates of the rectangle will remain the same, but they will be transformed to the rotated coordinate space just before the rectangle is drawn. This makes it appear at a 45-degree angle with respect to the origin.

These transforms are for the entire coordinate system of the window. This means the rotation described earlier will rotate the rectangle around the origin, which is defined as the top-left corner of the canvas. The rectangle will not be rotated in place!

To set the transform in the graphics context you must first create and initialize an AffineTransform object, and then pass it to the setTransform method. The AffineTransform object has many convenient methods for setting up the transformation. Usually, all you need to do is call one method, such as rotate(), and you are good to go.

Listing Ten demonstrates the use of transformations by rotating a rectangle 10 degrees around the origin.

Creating Custom Shapes

The Shapes provided by the 2D API are sufficient for many programs. If you get into more advanced graphics work, however, chances are you will need to create your own custom shapes. One of the ways to do this is to implement the Shape interface in your own object. You could then set up your Shape with whatever geometry you have in mind.

Another way to make a custom Shape is to use the java.awt.geom.GeneralPath object. This is a Shape that lets you piece together other Shapes to form a single object. This is useful for tacking together lines and curves to create a custom outline.

To add a new Shape to a GeneralPath, you call the append() method, which takes a Shape to append plus a Boolean indicating if the geometry passed in should be connected to the existing geometry in the GeneralPath. Listing Eleven is an example of two lines and a curve combined together to create a custom shape.

The 2D API also provides a set of operations that perform constructive-area geometry to create new Areas out of two existing Shapes. The Area object is another class which implements the Shape interface and can perform the Boolean operations add, subtract, intersect, and exclusive OR. These operations can only be performed with Area objects, but it is easy to create Areas out of Shapes.

The Area constructor takes a Shape as a parameter and gathers all of the necessary information from it. Once you have two Area objects, you may use the Boolean operations to combine the areas. The call would look something like:

area1 = new Area(shape1);

area1.intersect(new Area(shape2));

This would place the results of the Boolean intersection in the area1 object. Listing Twelve shows this technique being used to create a square with a circular hollow within it.

Conclusion

The Java 2D API is a powerful and extensible graphics enhancement to the JDK that makes life much easier if you have advanced graphics needs. It is simple to move your JDK 1.1 code to use the graphics system in the JDK 1.2 because such great efforts were made to make the 2D API backward compatible. Your existing programs will work without problems, but you now have the ability to utilize the new capabilities.

DDJ

Listing One

import java.awt.*;import java.awt.event.*;
public class TooDee extends Canvas {
    public TooDee() {
        setBackground(Color.white);
    }
    public void paint(Graphics g) {
        g.setColor(Color.blue);
        g.drawRect(100,100,100,100);
    }
    public static void main(String argv[]) {
        WindowListener l = new WindowAdapter() {
            public void windowClosing(WindowEvent e) {System.exit(0);}
        };
        Frame f = new Frame("Too Dee");
        f.addWindowListener(l);
        f.add("Center", new TooDee());
        f.pack();
        f.setSize(new Dimension(400,400));
        f.show();
    }
}

Back to Article

Listing Two

public void paint(Graphics g) {    Graphics2D g2 = (Graphics2D)g;
    g2.setColor(Color.blue);
    g2.setStroke(new BasicStroke(4.0f));
    g2.drawRect(100,100,100,100);
}

Back to Article

Listing Three

 ...import java.awt.geom.*; // need the geometry package for Shapes
 ...
public void paint(Graphics g) {
    Graphics2D g2 = (Graphics2D)g;
    // Step 1: Set up the graphics context
    g2.setColor(Color.blue);
    g2.setStroke(new BasicStroke(4.0f));
    // Step 2: Define the object to draw
    Rectangle2D r = new 
Rectangle2D.Float(100.0f,100.0f,100.0f,100.0f);
    // Step 3: Call one of the rendering methods
    g2.draw(r);
}

Back to Article

Listing Four

public void paint(Graphics g) {    Graphics2D g2 = (Graphics2D)g;
    float dash[] = { 20.0f, 10.0f };
    g2.setPaint(Color.blue);
   g2.setStroke(new BasicStroke(
        4.0f,                   // line width
        BasicStroke.CAP_ROUND,  // end cap style
        BasicStroke.JOIN_BEVEL, // join style
        0.0f,                   // miter limit
        dash,                   // dash array
        0.0f));                 // dash phase
    Rectangle2D r = new
        Rectangle2D.Float(100.0f,100.0f,200.0f,200.0f);
    g2.draw(r);
}

Back to Article

Listing Five

public void paint(Graphics g) {    Graphics2D g2 = (Graphics2D)g;
    Ellipse2D e = new Ellipse2D.Float(100.0f,100.0f,100.0f,100.0f);
    // draw the interior in blue
    g2.setPaint(Color.blue);
    g2.fill(e);
    // draw the outline in red
    g2.setPaint(Color.red);
    g2.draw(e);
}

Back to Article

Listing Six

public void paint(Graphics g) {    Graphics2D g2 = (Graphics2D)g;
    Ellipse2D e = new Ellipse2D.Float(100.0f,100.0f,100.0f,100.0f);
    GradientPaint gp = new GradientPaint(
        100.0f, 100.0f, // starting point
        Color.blue,     // starting color
        200.0f, 200.0f, // ending point
        Color.green);   // ending color
    g2.setPaint(gp);
    g2.fill(e);
    g2.setPaint(Color.red);
    g2.draw(e);
}

Back to Article

Listing Seven

 ...import java.awt.image.*; // needed for BufferedImage
 ...
public void paint(Graphics g) {
    Graphics2D g2 = (Graphics2D)g;
    Ellipse2D e = new Ellipse2D.Float(100.0f,100.0f,100.0f,100.0f);
    BufferedImage bi = new BufferedImage(
        5,  // width
        5,  // height
        BufferedImage.TYPE_INT_RGB);    // RGB image
    // Get the graphics context of the image and draw into it
    Graphics2D big = bi.createGraphics();
    big.setColor(Color.blue);
    big.fillRect(0,0,6,6);
    big.setColor(Color.green);
    big.fillRect(2,2,4,4);
    // Create the texture from the complete image
    Rectangle2D r = new Rectangle2D.Float(0.0f,0.0f,6.0f,6.0f);
    TexturePaint tp = new TexturePaint(bi, r);


</p>
    g2.setPaint(tp);
    g2.fill(e);
    g2.setPaint(Color.red);
    g2.draw(e);
}

Back to Article

Listing Eight

public void paint(Graphics g) {    Graphics2D g2 = (Graphics2D)g;
    Ellipse2D e = new Ellipse2D.Float(100.0f,100.0f,100.0f,100.0f);
    GradientPaint gp = new GradientPaint(
        100.0f, 100.0f, // starting point
        Color.blue,     // starting color
        200.0f, 200.0f, // ending point
        Color.green);   // ending color
    g2.setPaint(gp);
    g2.setStroke(new BasicStroke(10.0f));
    g2.draw(e);
}

Back to Article

Listing Nine

public void paint(Graphics g) {    Graphics2D g2 = (Graphics2D)g;


</p>
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setRenderingHint(RenderingHints.KEY_RENDERING,
                        RenderingHints.VALUE_RENDER_QUALITY);
    Ellipse2D e = new Ellipse2D.Float(100.0f,100.0f,100.0f,100.0f);
    GradientPaint gp = new GradientPaint(
        100.0f, 100.0f, // starting point
        Color.blue,     // starting color
        200.0f, 200.0f, // ending point
        Color.green);   // ending color
    g2.setPaint(gp);
    g2.setStroke(new BasicStroke(10.0f));
    g2.draw(e);
}

Back to Article

Listing Ten

public void paint(Graphics g) {    Graphics2D g2 = (Graphics2D)g;
    Rectangle r = new Rectangle(100,100,100,100);
    // Set up a rotation of 10 degrees around the origin.
    // Note that we need to convert from degrees to radians.
    AffineTransform t = new AffineTransform();
    t.rotate(10*java.lang.Math.PI/180);
    g2.setTransform(t);


</p>
    g2.setPaint(Color.blue);
    g2.fill(r);
    g2.setPaint(Color.red);
    g2.draw(r);
}

Back to Article

Listing Eleven

public void paint(Graphics g) {    Graphics2D g2 = (Graphics2D)g;


</p>
    GeneralPath gp = new GeneralPath();
    Line2D l1 = new Line2D.Float(50.0f,100.0f,100.0f,100.0f);
    gp.append(l1, true);
    CubicCurve2D cc = new CubicCurve2D.Float(
        100.0f,100.0f,  // starting point
        125.0f,125.0f,  // control point 1
        150.0f,125.0f,  // control point 2
        175.0f,100.0f); // ending point
    gp.append(cc, true);


</p>
    Line2D l2 = new Line2D.Float(175.0f,100.0f,225.0f,100.0f);
    gp.append(l2, true);


</p>
    g2.setStroke(new BasicStroke(4.0f));
    g2.setPaint(Color.blue);
    g2.draw(gp);
}

Back to Article

Listing Twelve

public void paint(Graphics g) {    Graphics2D g2 = (Graphics2D)g;
    Rectangle r = new Rectangle(100,100,100,100);
    Area a = new Area(r);


</p>
    Ellipse2D e = new Ellipse2D.Float(150.0f,150.0f,25.0f,25.0f);
    a.subtract(new Area(e));


</p>
    g2.setPaint(Color.blue);
    g2.fill(a);


</p>
    g2.setPaint(Color.red);
    g2.draw(a);
}

Back to Article

DDJ


Copyright © 1999, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.