- The Basic Java 2D Recipe
- Set the Graphics2D Context...
- ...and Render Something
- Rendering on Components
- Shape Primitives
- Graphics Stroking
- Fill Attributes and Painting
- Transparency and Compositing
- Text
- Clipping
- Coordinate Space Transformations
- Techniques for Graphical User Input
- Double Buffering
- Comprehensive Example: Kspace Visualization
- Summary

## Shape Primitives

So far we have only drawn rectangles in our examples, but there are, in fact,
nine shape primitives available to us. These shapes are contained almost
entirely in the `java.awt.geom` package and can be used to draw pretty
much anything in two dimensions. All shape primitives implement the
`Shape` interface, a set of methods for describing shapes that is part of
the `java.awt package`. In addition, all shape primitives implement the
`PathIterator` object that specifies the outline of the shape. Before
explaining the `PathIterator` interface, we will introduce the shape
primitives:

`Arc2D``Area``CubicCurve2D``Ellipse2D``GeneralPath``Line2D``QuadCurve2D``Rectangle2D``RoundRectangle2D`

This set of primitives can be divided into four categories based on their properties and common lineage.

`Rectangle2D`, `RoundRectangle2D`, `Arc2D`, and
`Ellipse2D` are derived from the abstract class `RectangularShape`
based on the common ability to describe these primitives through a rectangular
bounding box.

`Line`, `QuadCurve2D`, and `CubicCurve2D` are line
segments described by their two endpoints with the requisite control points.

`GeneralPath` allows for the specification of a series of points that
can be connected with any combination of the straight, cubic, or quadratic line
segments. In the next section, `GeneralPath` is introduced as a general
way to understand all geometric shapes.

Finally, `Area` allows the creation of new shapes through the use of
intersection, union, and subtraction of other shapes. Area operations are
discussed next.

Note that all the classes mentioned previously are abstract classes; that is,
they cannot be instantiated directly but rather are instantiated through a
subclass. With the exception of `Area` and `RoundRectangle2D`, the
classes are actually instantiated using the ending .Float or .Double depending
on the desired precision. For example, in the class `BasicRecipeJ2D`:

g2d.draw(new Rectangle2D.Float(0.0f,0.0f,75.0f,75.0f)); g2d.draw(new Rectangle2D.Double(0,0,75,75));

For brevity, only `GeneralShape` and `Area` are discussed in
any detail here. You will find it easy to test other shapes by modifying the
BasicRecipe.java application and are encouraged to do so. See the following URL
for complete documentation on all geometric shapes:
http://java.sun.com/j2se/1.3/docs/api/java/awt/geom/package-summary.html

### Understanding Shapes Through `GeneralPath` and the
`PathIterator` Interfaces

The `GeneralPath` Shape and the `PathIterator` interface
together form an important key to understanding most geometric operations in
Java 2D including area operations, arbitrary shapes drawing, and hit testing, to
name but a few. The challenge is to understand iteration objects, which are
individual instances of lines and curves (specifically, quadratic and cubic
Bezier splines) that describe the connecting paths encountered as you move
(iterates) around the boundary of a geometric object. In other words, imagine
yourself standing at the intersection of two lines that are part of a shape. The
iteration object is the description you would use to move to the next
interaction of the shape; for example "a line from here to 75, 75" or
"a quadratic curve to 100, 200 with a control point at 150, 150."

Note the conceptual similarities between a `PathIterator` and the
`Shape` class `GeneralPath`. A `GeneralPath` is a series of
curves and lines that is combined to make any arbitrary shape. As such, all
geometric shapes, including rectangles and arcs, can be specified the long way;
that is, by creating series move and draw commands. For example, Listing 3.2
makes an arbitrary shape that looks like the one shown in Figure 3.2. The method
`reportGP()` at the end of the `myCustomCanvas` class is used to
loop over the `PathIterator` object derived from the `GeneralPath`
and report the type of current segment as well as the coordinates of each
element in the `GeneralPath`.

**Listing 3.2 **PathIteratorEx.java

. . . class myCustomCanvas extends Canvas { GeneralPath gp; //add a constructor public myCustomCanvas() { } //end of constructor public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.setColor(Color.green); //setting context gp = new GeneralPath(); int cwidth=this.getSize().width; int cheight=this.getSize().height; gp.moveTo((int)cwidth/2,(int)cheight/2); //initial starting point gp.append(new Rectangle2D.Float((float)cwidth/2,(float)cheight/2, _ 10.f,10.f),true); gp.lineTo((int)cwidth/4,(int)cheight/4); gp.lineTo((int)(.9*cwidth),(int)(cheight/4)); gp.append(new Ellipse2D.Float((float)(.9*cwidth), (float)(.25*cwidth), 10.f,10.f),true); gp.closePath(); //closes path based on most recent moveTo g2d.draw(gp); reportGP(); } //end of paint public void reportGP() { System.out.println("**Reporting GeneralPath after repaint**"); //make an empty AffineTransform to pass to PathIterator AffineTransform at = new AffineTransform(); //note: using non-xformed path PathIterator pi = gp.getPathIterator(at); int segnumber=0; while (pi.isDone() == false) { segnumber++; System.out.println("**GETTING DATA FOR SEGMENT#: " + segnumber + "**"); float[] coords = new float[6]; //the following tells us whether the current segment is: // SEG_MOVETO, SEG_LINETO, SEG_QUADTO, // SEG_CUBICTO, or SEG_CLOSE //coords will be filled with sequential pairs of x,y coords System.out.println("currentSegment type: " + pi.currentSegment(coords)); for (int j=0;j<6;j++) { System.out.println("j: " + j + " coords[j]: " + coords[j] ); } //end of for pi.next(); } //end while pi.isDone() == false }

**Figure 3.2 This shows the screen output from PathIteratorEx.java. When
changing the screen size, the GeneralPath object is changed and
reportGP() is called.**

You should now attempt to draw different `GeneralPaths` and observe
the corresponding changes in the `PathIterator` object.

One related class that is often overlooked is the
`FlatteningPathIterator`. The utility of `FlatterningPathIterator`
stems from the fact that whenever any curved shape is rendered, there is an
intermediate step in the pipeline for converting curves into straight-line
segments (part of the process of rasterization). By specifying a flatness
parameter, the application has control over the number of straight-line segments
used to approximate curves. The advantage of flattening is that there is a
reduced need for resource intensive interpolations to be performed. In many
cases, the improvement in performance won't be noticeable; however, in
situations in which a great number of curved lines are present, flattening can
make a dramatic difference.

### Winding Rules and Testing for Containment

A frequent problem encountered in graphics development is testing for containment—that is, determining whether a point or shape is inside another shape. This is obviously critical for operations such as filling, texture mapping, and determining whether the user has clicked on a shape or area. When the shape is simple and has edges that intersect only at the vertices (such as a rectangle or circle) the problem is trivial. In non-trivial cases, however, it becomes necessary to develop an algorithm. Consider the following arbitrary geometric shape (shown in Figure 3.3), in which there is some ambiguity about which points are inside and outside the shape.

**Figure 3.3 Form WindingEx showing how winding rules can yield different
results in tests for containment.**

There are two common methods for determining if any point is inside a
geometric shape. The first, called the *odd-even rule*, is based on drawing
a line (ray) from the point to be classified to any point well outside the
shape. If the number of edge crossings is odd, the point is inside the shape;
otherwise it is not. The second approach is termed the *non-zero winding
rule* and likewise determines the number of edge crossings that occur for a
ray drawn to a distant point. However, in the non-zero winding rule scheme, the
left to right crossings add to the total number of crossing whereas the right to
left crossing subtracts from the total number of crossings. If the sum of left
to right and right to left crossing isn't equal to zero, the point is
determined to be inside. Figure 3.3 shows an example of applying the two rules.
Indeed the odd-even and non-zero winding rules give different answers for the
ambiguous area labeled 1.

Listing 3.3 demonstrates winding rules and is another example of using a
`GeneralPath`. The application generates a random `GeneralPath`
each time the New Path button is pushed. The user can then click anywhere inside
or outside the shape. The results are often the same for the two methods, but it
is a worthwhile exercise to try to predict in which cases they differ.

**Listing 3.3 **WindingEx.java

. . . public class WindingEx extends JFrame { myCustomCanvas mc; JButton newpath; public WindingEX() { super("Winding Examples"); //layout manager for the frame BorderLayout f1 = new BorderLayout(); Panel uipanel = new Panel(); newpath = new JButton("New Path"); uipanel.add(newpath); mc = new myCustomCanvas(this); mc.setSize(800,600); ButtonHandler bhandler = new ButtonHandler(mc); MouseHandler mhandler = new MouseHandler(mc); newpath.addActionListener(bhandler); mc.addMouseListener(mhandler); this.getContentPane().setLayout(f1); this.getContentPane().add(mc,BorderLayout.CENTER); this.getContentPane().add(uipanel,BorderLayout.NORTH); this.setSize(800,600); this.show(); addWindowListener(new WindowEventHandler()); } class WindowEventHandler extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } public static void main(String[] args) { new WindingEX(); } } class MouseHandler implements MouseListener { myCustomCanvas mc; . . . public void mousePressed(MouseEvent e) { mc.drawPoint(e.getX(),e.getY()); } } class ButtonHandler implements ActionListener { myCustomCanvas mc; public ButtonHandler(myCustomCanvas mc) { this.mc = mc; } public void actionPerformed(ActionEvent e) { mc.generateGP(); } } class myCustomCanvas extends Canvas { WindingEx wex; String insider; String even_oddMessage = "Click on a Point"; String non_zeroMessage = " "; Random r; GeneralPath gp; public myCustomCanvas(WindingEX wex) { r = new Random(); this.wex = wex; this.setSize(800,600); generateGP(); } public void generateGP() { gp = new GeneralPath(); gp.moveTo(r.nextInt(this.getSize().width), r.nextInt(this.getSize().height)); for (int i=1;i<10;i++) { //choose 10 random points gp.lineTo(r.nextInt(this.getSize().width), r.nextInt(this.getSize().height)); } gp.closePath(); gp.drawPoint(r.nextInt(this.getSize().width), r.nextInt(this.getSize().height)); repaint(); } public void drawPoint(int x, int y) { this.x = x; this.y = y; gp.setWindingRule(GeneralPath.WIND_EVEN_ODD); even_oddMessage = "EVEN_ODD RULE: ".concat(isInside(x,y)); gp.setWindingRule(GeneralPath.WIND_NON_ZERO); non_zeroMessage = "NON_ZERO RULE: ".concat(isInside(x,y)); repaint(); } public String isInside(int x, int y) { if (gp.contains(new Point(x,y))) insider="INSIDE"; else insider="OUTSIDE"; return insider; } public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.drawString(even_oddMessage,440,80); g2d.drawString(non_zeroMessage,440,100); g2d.setColor(Color.blue); g2d.fill(new Rectangle2D.Double(x,y,5,5)); // step two-set the graphics context g2d.setColor(Color.red); //setting context float dash [] = {5.5f}; BasicStroke stk = new BasicStroke(4.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.f, dash, 2.0f); g2d.setStroke(stk); g2d.draw(gp); } }

### Basics of Constructive Geometry Using the `Area` Class

As mentioned at the beginning of this section, constructive area geometry is the making of an arbitrary shape using the intersection, subtraction, or union of other primitives and arbitrary shapes. Simply stated, the goal is to make a new shape from the combination of other shapes. The need for constructive area geometry arises from the fact that drawing an arbitrary shape using line segments and specifying points can be tedious. Often the shape can be drawn using the intersection of just a few shape primitives. Further, it is often easier to change a shape created with constructive area geometry than to respecify the path.

The `Area` class defines a special shape that supports Boolean
operations and is useful in constructive geometry. To make a shape that looks
like a Venn diagram, for example, the designer might insert the following into
the `paint()` method of the BasicRecipeJ2D.java class:

Area area1 = new Area(new Ellipse2D.Double()); Area area2 = new Area(new Ellipse2D.Double()); Area area3 = new Area(new Ellipse2D.Double()); g2.setColor(Color.blue); g2.fill(area1); g2.setColor(Color.green); g2.fill(area2); g2.setColor(Color.yellow); g2.fill(area3); area1.intersect(area2); area1.intersect(area3); setColor(Color.red); g2.fill(area1);

Note that each call of the `intersect()` method sets the current shape
to the result of the operation. Therefore, the intersections accumulate. (That
is, area1 first becomes the intersection of itself and area2, and then it
becomes the intersection of that result and area3.) The same is true of the
`subtract()`, `add()`, and `exclusiveOr()` methods.