A Quick Tour of Java 2D

Filling Shapes

The simplest path through the rendering pipeline is for filling shapes. For example, the following code creates an ellipse and fills it with a solid color. (This code would live inside a paint( ) method somewhere. We’ll present a complete, ready-to-run example a little later.)

Shape c = new Ellipse2D.Float(50, 25, 150, 150);
g2.setPaint(Color.blue);
g2.fill(c);

The Ellipse2D class is abstract, but is implemented by concrete inner subclasses, called Float and Double. The Rectangle2D class, for example, has concrete subclasses Rectangle2D.Float and Rectangle2D.Double.

In the call to setPaint( ), we tell the Graphics2D to use a solid color, blue, for all subsequent filling operations. Then, the call to fill( ) tells Graphics2D to fill the given shape.

All geometric shapes in the 2D API are represented by implementations of the java.awt.geom.Shape interface. This interface defines methods that are common to all shapes, like returning a rectangle bounding box or testing if a point is inside the shape. The java.awt.geom package is a smorgasbord of useful shape classes, including Rectangle2D, RoundRectangle2D (a rectangle with rounded corners), Arc2D, Ellipse2D, and others. In addition, a few classes in java.awt are Shapes: Rectangle, Polygon, and Area.

Drawing Shape Outlines

Drawing a shape’s outline is only a little bit more complicated. Consider this example:

Shape r = new Rectangle2D.Float(100, 75, 100, 100);
g2.setStroke(new BasicStroke(4));
g2.setPaint(Color.yellow);
g2.draw(r);

Here, we tell the Graphics2D to use a stroke that is four units wide and a solid color, yellow, for filling the stroke. When we call draw( ), Graphics2D uses the stroke to create a new shape, the outline, from the given rectangle. The outline shape is then filled, just as before; this effectively draws the rectangles’s outline. The rectangle itself is not filled.

Convenience Methods

Graphics2D includes quite a few convenience methods for drawing and filling common shapes; these methods are actually inherited from the Graphics class. Table 17.1 summarizes these methods. It’s a little easier to just call fillRect( ), rather than instantiating a rectangle shape and passing it to fill( ).

Table 17-1. Shape-Drawing Methods in the Graphics Class

Method

Description

draw3DRect( )

Draws a highlighted, 3D rectangle

drawArc( )

Draws an arc

drawLine( )

Draws a line

drawOval( )

Draws an oval

drawPolygon( )

Draws a polygon, closing it by connecting the endpoints

drawPolyline( )

Draws a line connecting a series of points, without closing it

drawRect( )

Draws a rectangle

drawRoundRect( )

Draws a rounded-corner rectangle

fill3DRect( )

Draws a filled, highlighted, 3D rectangle

fillArc( )

Draws a filled arc

fillOval( )

Draws a filled oval

fillPolygon( )

Draws a filled polygon

fillRect( )

Draws a filled rectangle

fillRoundRect( )

Draws a filled, rounded-corner rectangle

As you can see, for each of the fill( ) methods in the table, there is a corresponding draw( ) method that renders the shape as an unfilled line drawing. With the exception of fillArc( ) and fillPolygon( ), each method takes a simple x, y specification for the top left corner of the shape and a width and height for its size.

The most flexible convenience method draws a polygon, which is specified by two arrays that contain the x and y coordinates of the vertices. Methods in the Graphics class take two such arrays and draw the polygon’s outline, or fill the polygon.

The methods listed in Table 17.1 are shortcuts for more general methods in Graphics2D. The more general procedure is to first create a java.awt.geom.Shape object, and then pass it to the draw( ) or fill( ) method of Graphics2D. For example, you could create a Polygon object from coordinate arrays. Since a Polygon implements the Shape interface, you can pass it to Graphics2D’s general draw( ) or fill( ) method.

The fillArc( ) method requires six integer arguments. The first four specify the bounding box for an oval—just like the fillOval( ) method. The final two arguments specify what portion of the oval we want to draw, as a starting angular position and an offset. Both the starting angular position and the offset are specified in degrees. Zero degrees is at three o’clock; a positive angle is clockwise. For example, to draw the right half of a circle, you might call:

g.fillArc(0, 0, radius * 2, radius * 2, -90, 180);

draw3DRect( ) automatically chooses colors by “darkening” the current color. So you should set the color to something other than black, which is the default (maybe gray or white); if you don’t, you’ll just get black rectangle with a thick outline.

Drawing Text

Like drawing a shape’s outline, drawing text is just a simple variation on filling a shape. When you ask a Graphics2D to draw text, it determines the shapes that need to be drawn and fills them. The shapes that represent characters are called glyphs . A font is a collection of glyphs. Here’s an example of drawing text:

g2.setFont(new Font("Times New Roman", Font.PLAIN, 64));
g2.setPaint(Color.red);
g2.drawString("Hello, 2D!", 50, 150);

When we call drawString( ), the Graphics2D uses the current font to retrieve the glyphs that correspond to the characters in the string. Then the glyphs (which are really just Shapes) are filled using the current Paint.

Drawing Images

Images are treated a little differently than shapes. In particular, the current Paint is not used to render an image because the image contains its own color information. The following example loads an image from a file and displays it (you’ll have to use your own file here):

Image i = Toolkit.getDefaultToolkit( ).getImage("camel.gif");
g2.drawImage(i, 75, 50, this);

In this case, the call to drawImage( ) tells the Graphics2D to place the image at the given location.

Go Crazy

Four parts of the pipeline affect every graphics operation. In particular, all rendering is transformed, composited, and clipped. Rendering hints are used to affect all of a Graphics2D’s rendering.

This example shows how to modify the current transformation with a translation and a rotation :

g2.translate(50, 0);
g2.rotate(Math.PI / 6);

Every graphics primitive drawn by g2 will now have this transformation applied to it. We can have a similarly global effect on compositing:

AlphaComposite ac = AlphaComposite.getInstance(
    AlphaComposite.SRC_OVER, (float).5);
g2.setComposite(ac);

Now every graphics primitive we draw will be half transparent—we’ll explain more about this later.

All drawing operations are clipped by the current clipping shape, which is any object implementing the Shape interface. In the following example, the clipping shape is set to an ellipse:

Shape e = new Ellipse2D.Float(50, 25, 250, 150);
g2.clip(e);

You can obtain the current clipping shape using getClip( ); this is handy if you want to restore it later using the setClip( ) method.

Finally, the rendering hints are used for all drawing operations. In the following example, we tell the Graphics2D to use antialiasing, a technique that smooths out the rough pixel edges of shapes and text:

g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
    RenderingHints.VALUE_ANTIALIAS_ON);

The RenderingHints class contains other keys and values, representing other rendering hints. If you really like to fiddle with knobs and dials, this is a good class to check out.

The Whole Iguana

Let’s put everything together now, just to show how graphics primitives travel through the rendering pipeline. The following example demonstrates the use of Graphics2D from the beginning to the end of the rendering pipeline. With very few lines of code, we are able to draw some pretty complicated stuff (see Figure 17.2):

Exercising the 2D API

Figure 17-2. Exercising the 2D API

//file: Iguana.java
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;

public class Iguana extends JComponent {
  private Image image;
  private int theta;

  public Iguana( ) {
    image = Toolkit.getDefaultToolkit( ).getImage(
        "Piazza di Spagna.small.jpg");
    theta = 0;
    addMouseListener(new MouseAdapter( ) {
      public void mousePressed(MouseEvent me) {
        theta = (theta + 15) % 360;
        repaint( );
      }
    });
  }
  
  public void paint(Graphics g) {
    Graphics2D g2 = (Graphics2D)g;
    
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    
    int cx = getSize( ).width / 2;
    int cy = getSize( ).height / 2;
    
    g2.translate(cx, cy);
    g2.rotate(theta * Math.PI / 180);
    
    Shape oldClip = g2.getClip( );
    Shape e = new Ellipse2D.Float(-cx, -cy, cx * 2, cy * 2);
    g2.clip(e);
    
    Shape c = new Ellipse2D.Float(-cx, -cy, cx * 3 / 4, cy * 2);
    g2.setPaint(new GradientPaint(40, 40, Color.blue,
        60, 50, Color.white, true));
    g2.fill(c);
    
    g2.setPaint(Color.yellow);
    g2.fillOval(cx / 4, 0, cx, cy);

    g2.setClip(oldClip);
    
    g2.setFont(new Font("Times New Roman", Font.PLAIN, 64));
    g2.setPaint(new GradientPaint(-cx, 0, Color.red,
        cx, 0, Color.black, false));
    g2.drawString("Hello, 2D!", -cx * 3 / 4, cy / 4);
    
    AlphaComposite ac = AlphaComposite.getInstance(
        AlphaComposite.SRC_OVER, (float).75);
    g2.setComposite(ac);
    
    Shape r = new RoundRectangle2D.Float(0, -cy * 3 / 4,
        cx * 3 / 4, cy * 3 / 4, 20, 20);
    g2.setStroke(new BasicStroke(4));
    g2.setPaint(Color.magenta);
    g2.fill(r);
    g2.setPaint(Color.green);
    g2.draw(r);
    
    g2.drawImage(image, -cx / 2, -cy / 2, this);
  }

  public static void main(String[] args) {
    JFrame f = new JFrame("Iguana");
    Container c = f.getContentPane( );
    c.setLayout(new BorderLayout( ));
    c.add(new Iguana( ), BorderLayout.CENTER);
    f.setSize(300, 300);
    f.setLocation(100, 100);
    f.addWindowListener(new WindowAdapter( ) {
      public void windowClosing(WindowEvent e) { System.exit(0); }
    });
    f.setVisible(true);
  }
}

The Iguana class itself is a subclass of JComponent with a fancy paint( ) method. The main( ) method takes care of creating a JFrame that holds the Iguana component.

Iguana’s constructor loads a small image (we’ll talk more about this later) and sets up a mouse event handler. This handler changes a member variable, theta, and repaints the component. Each time you click, the entire drawing is rotated by 15 degrees.

Iguana’s paint( ) method does some pretty tricky stuff, but none of it is very difficult. First, user space is transformed so that the origin is at the center of the component. Then user space is rotated by theta:

g2.translate(cx, cy);
g2.rotate(theta * Math.PI / 180);

Iguana saves the current (default) clipping shape before setting it to a large ellipse. Then Iguana draws two filled ellipses. The first is drawn by instantiating an Ellipse2D and filling it; the second is drawn using the fillOval( ) convenience method. (We’ll talk about the color gradient in the first ellipse in the next section.) As you can see in Figure 17.2, both ellipses are clipped by the elliptical clipping shape. After filling the two ellipses, Iguana restores the old clipping shape.

Iguana draws some text next; we’ll talk about this in more detail later. The next action is to modify the compositing rule as follows:

AlphaComposite ac = AlphaComposite.getInstance(
    AlphaComposite.SRC_OVER, (float).75);
g2.setComposite(ac);

All this means is that we want everything to be drawn with transparency. The AlphaComposite class defines constants representing different compositing rules, much the way the Color class contains constants representing different predefined colors. In this case, we’re asking for the source over destination rule (SRC_OVER), but with an additional alpha multiplier of 0.75. Source over destination means that whatever we’re drawing (the source) should be placed on top of whatever’s already there (the destination). The alpha multiplier means that everything we draw will be treated at 0.75 or three quarters of its normal opacity, allowing the existing drawing to “show through.”

You can see the effect of the new compositing rule in the rounded rectangle and the image, which both allow previously drawn elements to show through.

Get Learning Java now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.