text: Return to Java 2D GraphicsReturn to Java 2D Graphics
  Sample Chapter 4:
  In the last chapter, we saw how shapes are defined in the 2D API. But what can you do with shapes?
  - Painting is the process of filling the interior of the shape with a color, color gradient, or texture.
  - Stroking is the process of drawing the shape's outline. You can draw an outline using different line widths, line styles, and colors.
  Painting and stroking are very closely related. Stroking, in fact, is just the process of creating a shape that represents an outline and filling it. You can draw outlines using any type of paint. Figure 4-1 shows some examples. On the left, a circle has been filled with a color gradient. In the middle, the same circle's outline is drawn with a thick line using a solid color. On the right, the circle's outline is drawn with the same thick line using the color gradient.
  The following class produces the window shown in Figure 4-1. It uses some unfamiliar methods and classes:
  setPaint(),
  setStroke(),
  GradientPaint, and
  BasicStroke. I'll explain these classes and methods, and more, in the rest of the chapter. For now, this example should show you some of the potential of painting and stroking:
  import java.awt.*;
  import java.awt.geom.*;
  public class PaintingAndStroking
  extends ApplicationFrame {
  public static void main(String[] args) {
  PaintingAndStroking f = new PaintingAndStroking();
  f.setTitle("PaintingAndStroking);
  GradientPaint gp = new GradientPaint(75, 75, Color.white,
  95, 95, Color.gray, true);
  // Fill with a gradient.
  g2.setPaint(gp);
  g2.fill(e);
  // Stroke with a solid color.
  e.setFrame(x + 100, y, w, h);
  g2.setPaint(Color.black);
  g2.setStroke(new BasicStroke(8));
  g2.draw(e);
  // Stroke with a gradient.
  e.setFrame(x + 200, y, w, h);
  g2.setPaint(gp);
  g2.draw(e);
  }
  }
  Painting
  Filling the interior of a shape is a two-step process:
  - First, tell the
  Graphics2Dhow to fill shapes with a call to
  setPaint(). This method accepts any object that implements the
  java.awt.Paintinterface. The
  Graphics2Dstores the
  Paintaway as part of its state. When it comes time to fill a shape,
  Graphics2Dwill use the
  Paintto determine what colors should be used to fill the shape. The 2D API comes with three kinds of "canned" paints: solid colors, a linear color gradient, and a texture fill. You can add your own
  Paintimplementations if you wish.
  - Now you can tell
  Graphics2Dto fill a shape by passing it to
  fill().
  Paints are immutable, which means they can't be modified after they are created. The reason for this is to avoid funky behavior when rendering. Imagine, for example, if you wanted to fill a series of shapes with a solid color. First, you'd call
  setPaint()on the
  Graphics2D; then you would paint the shapes using
  fill(). But what if another part of your program changed the
  Paintthat
  Graphics2Dwas using? The results might be quite bizarre. For this reason, objects that implement
  Paintshould not allow themselves to be changed after they are created.
  Figure 4-2 shows the three types of painting supported by the 2D API. The figure contains three shapes:
  - The ellipse is filled with a solid color.
  - The rounded rectangle is filled with a color gradient.
  - The arc is filled with a texture, built from van Gogh's Starry Night.
  Solid Colors
  The
  java.awt.Colorclass implements the
  Paintinterface. Thus, any
  Colormay be used to fill the interior of a shape. The correct handling of color is a big can of worms--we'll cover it fully in Chapter 8, Color. In the meantime, let's take a quick look at
  Color. First, the
  Colorclass includes some useful colors as static member variables:
  public static final Color white;
  public static final Color lightGray;
  public static final Color gray;
  public static final Color darkGray;
  public static final Color black;
  public static final Color red;
  public static final Color pink;
  public static final Color orange;
  public static final Color yellow;
  public static final Color green;
  public static final Color magenta;
  public static final Color cyan;
  public static final Color blue;
  If you don't see a color you like, it's easy to create a new color by specifying red, green, and blue values. Colors created in this way are part of a default standard RGB color space called sRGB. We'll talk all about this concept in Chapter 8. You can create new colors using integers or floating point values:
  - public Color(int r, int g, int b)
  - This constructor creates a new
  Colorusing the specified values for red, green and blue. The values should range from 0 to 255, inclusive.
  - public Color(float r, float g, float b)
  - This constructor creates a new
  Colorusing the specified values for red, green and blue. The values should range from 0.0 to 1.0, inclusive.
  In the following example, a pie-shaped arc is filled with the color blue.
  import java.awt.*;
  import java.awt.geom.*;
  public class SolidPaint
  extends ApplicationFrame {
  public static void main(String[] args) {
  SolidPaint f = new SolidPaint();
  f.setTitle("SolidPaint v1.0");
  f.setSize(200, 200);
  f.center();
  f.setVisible(true);
  }
  public void paint(Graphics g) {
  Graphics2D g2 = (Graphics2D)g;
  Arc2D pie = new Arc2D.Float(0, 50, 150, 150, -30, 90, Arc2D.PIE);
  g2.setPaint(Color.blue);
  g2.fill(pie);
  }
  }
  You may remember that
  setColor(), defined in
  Graphics, could be used to affect the color of filled shapes. In the 2D API, it is now a convenience method; a call to
  setColor(c)on a
  Graphics2Dis equivalent to calling
  setPaint(c).
  Swing's Color Chooser Dialog
  If you want your users to be able to choose colors in your application, you're in luck. Swing has a ready-made dialog for this purpose. The name of the class is
  javax.swing.JColorChooser. You can use this dialog with one line of code, using the following static method:
  - public static Color showDialog(Component component, String title, Color initialColor)
  - This method displays a color chooser dialog. The supplied
  Componentis used as the parent component of the dialog. The dialog will have the supplied title; its controls will be initialized to show the given
  initialColor.
  The following example demonstrates the use of this dialog. You can invoke the color chooser by pressing the button. After you've made a selection, the background of the frame window changes to the selected color.
  import java.awt.*;
  import java.awt.event.*;
  import javax.swing.*;
  public class RedTown
  extends JFrame {
  public static void main(String[] args) {
  new RedTown();
  }
  public RedTown() {
  super("RedTown v1.0");
  createUI();
  setVisible(true);
  }
  protected void createUI() {
  setSize(400, 400);
  setLocation(100, 100);
  getContentPane().setLayout(new GridBagLayout());
  JButton colorButton = new JButton("Choose a color...");
  getContentPane().add(colorButton);
  colorButton.addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent ae) {
  Color c = JColorChooser.showDialog(
  RedTown.this, "Choose a color...", getBackground());
  if (c != null) getContentPane().setBackground(c);
  }
  });
  addWindowListener(new WindowAdapter() {
  public void windowClosing(WindowEvent we) {
  System.exit(0);
  }
  });
  }
  }
  For more information about the color chooser dialog, see Java Swing, by Robert Eckstein, Marc Loy, and Dave Wood (O'Reilly).
  GradientPaint
  A gradient is a smooth transition from one color to another. In the late evening on a clear day, the sky has a gradient from dark blue at the horizon to black overhead. The 2D API provides an implementation of a simple color gradient, called
  java.awt.GradientPaint. This class defines a color gradient using two points and two colors. The gradient smoothly shifts from one color to the other as you move along the line that connects the two points, which I'll call the gradient line. The
  GradientPaintcreates parallel bands of color, perpendicular to the gradient line. Figure 4-3 shows a gradient that runs from white to dark gray. The gradient line and its endpoints are also shown.
  GradientPaints may be cyclic or acyclic. In an acyclic
  GradientPaint, any points beyond the end of the gradient line are the same color as the endpoint of the line, as shown in Figure 4-3. In a cyclic
  GradientPaint, the colors continue to shift beyond the end of the gradient line, as though the gradient line segment were mirrored and replicated ad infinitum. Figure 4-4 shows what this looks like.
  NOTE:The appearance of a gradient depends on your screen settings. If your display uses fewer than 24 bits per pixel (bpp), for example, you may see bands of color in gradients, rather than a smooth transition from one color to another.
  To create a
  GradientPaint, you simply need to supply two points and two
  Colors. By default,
  GradientPaints are acyclic.
  - public GradientPaint(Point2D pt1, Color color1, Point2D pt2, Color color2)
  - public GradientPaint(float x1, float y1, Color color1, float x2, float y2, Color color2)
  - These constructors create acyclic
  GradientPaints. The gradient runs from
  color1at the first point to
  color2at the second point.
  - public GradientPaint(Point2D pt1, Color color1, Point2D pt2, Color color2, boolean cyclic)
  - public GradientPaint(float x1, float y1, Color color1, float x2, float y2, Color color2, boolean cyclic)
  - These constructors create
  GradientPaints that run from
  color1at the first point to
  color2at the second point. If the
  cyclicparameter is
  true, the gradient will be cyclic.
  You can retrieve the parameters of the gradient paint with the following methods:
  - public Point2D getPoint1()
  - public Color getColor1()
  - public Point2D getPoint2()
  - public Color getColor2()
  - public boolean isCyclic()
  The next example shows how to create a circle and fill it with a cyclic gradient:
  import java.awt.*;
  import java.awt.geom.*;
  public class GradientPaintFill
  extends ApplicationFrame {
  public static void main(String[] args) {
  GradientPaintFill f = new GradientPaintFill();
  f.setTitle("GradientPaintFill v1.0");
  f.setSize(200, 200);
  f.center();
  f.setVisible(true);
  }
  public void paint(Graphics g) {
  Graphics2D g2 = (Graphics2D)g;
  Ellipse2D e = new Ellipse2D.Float(40, 40, 120, 120);
  GradientPaint gp = new GradientPaint(75, 75, Color.white,
  95, 95, Color.gray, true);
  g2.setPaint(gp);
  g2.fill(e);
  }
  }
  TexturePaint
  The third type of
  Paintis a texture fill. In the 2D API, a texture is created using an image that is repeated over and over, like a kitchen floor tile. The
  java.awt.TexturePaintclass represents a texture. You can construct a
  TexturePaintwith two pieces of information:
  - The image to be used should be supplied as a
  BufferedImage. I'll talk about images in detail in Chapter 9, Images For now, just think of a
  BufferedImageas a rectangular picture.
  - A
  Rectangle2Dspecifies how the image will be replicated to form the texture. I'll call this the texture rectangle.
  The image is scaled to fit in the given rectangle. This rectangle is reproduced like a floor tile to build the texture. Figure 4-5 shows a
  TexturePaintand its texture rectangle. Note that the image is drawn to exactly fill the texture rectangle.
  Figure 4-6 shows a
  TexturePaintbuilt from the same image. In this case, however, the texture rectangle is smaller--and the image is scaled to fit.
  Creating a
  TexturePaintis as simple as specifying an image and a rectangle:
  - public TexturePaint(BufferedImage txtr, Rectangle2D anchor)
  - This constructor creates a
  TexturedImagethat replicates the supplied image using the
  anchorrectangle.
  You can retrieve the image and the rectangle of a
  TexturePaintwith the following methods:
  - public BufferedImage getImage()
  - public Rectangle2D getAnchorRect()
  The following example shows how to create a
  TexturePaintfrom a JPEG image file. The constructor takes care of these details, which I'll describe fully in Chapter 9. In the
  paint()method, the image is used to create a
  TexturePaint. Then the texture is used to fill a rounded rectangle.
  import java.awt.*;
  import java.awt.geom.*;
  import java.awt.image.BufferedImage;
  import java.io.*;
  import com.sun.image.codec.jpeg.*;
  public class TexturePaintFill
  extends ApplicationFrame {
  public static void main(String[] args) throws Exception {
  TexturePaintFill f = new TexturePaintFill("roa2.jpg");
  f.setTitle("TexturePaintFill v1.0");
  f.setSize(200, 200);
  f.center();
  f.setVisible(true);
  }
  private BufferedImage mImage;
  public TexturePaintFill(String filename)
  throws IOException, ImageFormatException {
  // Load the specified JPEG file.
  InputStream in = getClass().getResourceAsStream(filename);
  JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(in);
  mImage = decoder.decodeAsBufferedImage();
  in.close();
  }
  public void paint(Graphics g) {
  Graphics2D g2 = (Graphics2D)g;
  // Create a round rectangle.
  RoundRectangle2D r =
  new RoundRectangle2D.Float(25, 35, 150, 150, 25, 25);
  // Create a texture rectangle the same size as the texture image.
  Rectangle2D tr = new Rectangle2D.Double(0, 0,
  mImage.getWidth(), mImage.getHeight());
  // Create the TexturePaint.
  TexturePaint tp = new TexturePaint(mImage, tr);
  // Now fill the round rectangle.
  g2.setPaint(tp);
  g2.fill(r);
  }
  }
  Under the Hood
  How does the
  Paintinterface really work? Every
  Paintobject has an associated context, a
  java.awt.PaintContext, which knows what colors to put on a drawing surface. When
  Graphics2Dneeds to fill a shape, it asks its current
  Paintfor the corresponding
  PaintContext. Then it uses the
  PaintContextto actually put color on the drawing surface.
  The Transparency interface
  Paintis a subinterface of
  java.awt.Transparency, an interface which describes an object's use of alpha. It describes three modes, represented by constants:
  - public static final int OPAQUE
  - This constant represents objects whose pixels are all opaque (alpha is 1.0 everywhere).
  - public static final int BITMASK
  - This constant represents objects whose pixels are either opaque or completely transparent (alpha is 1.0 or 0.0).
  - public static final int TRANSLUCENT
  - This constant is used for objects whose pixels may have any values of alpha.
  The
  Transparencyinterface has only one method:
  - public int getTransparency()
  - This method returns the transparency mode, either
  OPAQUE,
  BITMASK, or
  TRANSLUCENT.
  The
  Transparencyinterface is implemented by the
  ColorModelclass, which is related to images and drawing surfaces (see Chapter 11, Image Guts).
  The Paint interface
  As I mentioned,
  Transparencyis the parent interface of
  Paint. The
  Paintinterface itself has only one method
  --it just knows how to generate a
  PaintContext:
  - public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints)
  - This method is called to create a
  PaintContext. The
  PaintContextis encouraged to produce colors in the given color model (see Chapter 11). The
  deviceBoundsrectangle indicates the bounds of the drawing surface, while
  userBoundsindicates the bounds of the shape that is being filled. The supplied
  AffineTransform(see Chapter 5, Rendering) indicates the transformation currently in effect, while
  hintscontains information that the
  PaintContextcan use to modify its behavior. The
  TexturePaintcontext, for example, is responsive to the
  KEY_INTERPOLATIONhint. See Chapter 5 for an explanation of rendering hints.
  Simple
  Paintimplementations can ignore a lot of the parameters that are passed to
  createContext(), as you'll see in an upcoming example.
  The PaintContext interface
  The
  PaintContextreturned by
  createContext()knows how to actually generate the colors of the
  Paint. The
  PaintContextinterface defines three methods:
  - public void dispose()
  - This method is called when the
  PaintContextis no longer needed. If you've allocated any images or other large objects, you can free up their references in this method.
  - public ColorModel getColorModel()
  - This method returns the color model that will be used for this context's output. This could be a different color model than the one suggested in the
  createContext()method, back in the
  Paintinterface.
  - public Raster getRaster(int x, int y, int w, int h)
  - This is the mother lode of the
  PaintContextinterface. This method returns a
  Rasterthat contains the color data that should be used to fill a shape. (The
  Rasterclass is part of 2D's image classes. See Chapter 11.)
  There's a lot of material here that I won't cover until later chapters. But to show you how simple a
  Paintcan be, I'll present a custom implementation of the
  Paintinterface.
  A radial color gradient
  The goal of this example is to create a round, or radial, gradient. This gradient defines a color at a point; the gradient blends into another color as a function of the distance from that point. The end result is a big, fuzzy spot. Figure C-2 shows a round rectangle that is filled with the radial gradient.
  The implementation consists of two classes,
  RoundGradientPaintand
  RoundGradientContext.
  RoundGradientPaintdoesn't do much except return a
  RoundGradientPaintContextfrom its
  createContext()method.
  Round-GradientPaint's constructor accepts a point and a color that describe the center of the gradient, a radius, and a background color. The gradient blends color from the center point to the background color over the length of the radius.
  import java.awt.*;
  import java.awt.geom.*;
  import java.awt.image.ColorModel;
  public class RoundGradientPaint
  implements Paint {
  protected Point2D mPoint;
  protected Point2D mRadius;
  protected Color mPointColor, mBackgroundColor;
  public RoundGradientPaint(double x, double y, Color pointColor,
  Point2D radius, Color backgroundColor) {
  if (radius.distance(0, 0) <= 0)
  throw new IllegalArgumentException("Radius must be greater than 0.");
  mPoint = new Point2D.Double(x, y);
  mPointColor = pointColor;
  mRadius = radius;
  mBackgroundColor = backgroundColor;
  }
  public PaintContext createContext(ColorModel cm,
  Rectangle deviceBounds, Rectangle2D userBounds,
  AffineTransform xform, RenderingHints hints) {
  Point2D transformedPoint = xform.transform(mPoint, null);
  Point2D transformedRadius = xform.deltaTransform(mRadius, null);
  return new RoundGradientContext(transformedPoint, mPointColor,
  transformedRadius, mBackgroundColor);
  }
  public int getTransparency() {
  int a1 = mPointColor.getAlpha();
  int a2 = mBackgroundColor.getAlpha();
  return (((a1 & a2) == 0xff) ? OPAQUE : TRANSLUCENT);
  }
  }
  The
  getTransparency()method, from the
  Transparencyinterface, returns either
  OPAQUEor
  TRANSLUCENT, depending on the colors that were passed to
  RoundGradientPaint's constructor. If both colors are fully opaque (alpha = 255), then the resulting
  RoundGradientPaintis also fully opaque. Otherwise, the pixels filled by the
  RoundGradientPaintwill have variable transparency, indicated by the
  TRANSLUCENTvalue.
  Instead of creating a
  RoundGradientPaintthat uses two opaque colors, you could also achieve an interesting effect using a single color. Consider what would happen if you used
  new Color(255, 0, 0, 255)as the point color, and
  new Color(255, 0, 0, 0)as the background color. The gradient would fade from opaque red at the point to a completely transparent red in the background.
  The implementation of
  RoundGradientContextis straightforward. The
  get-Raster()method iterates over each point in the requested rectangle, calculating the distance from the center point. It calculates a weighting factor, from 0.0 to 1.0, based on the ratio of this distance and the radius.
  for (int j = 0; j < h; j++) {
  for (int i = 0; i < w; i++) {
  double distance = mPoint.distance(x + i, y + j);
  double radius = mRadius.distance(0, 0);
  double ratio = distance / radius;
  if (ratio > 1.0)
  ratio = 1.0;
  Then it simply uses the weighting factor to linearly interpolate between the center color and the background color:()));
  Here's the entire class:
  import java.awt.*;
  import java.awt.geom.*;
  import java.awt.image.*;
  public class RoundGradientContext
  implements PaintContext {
  protected Point2D mPoint;
  protected Point2D mRadius;
  protected Color mC1, mC2;
  public RoundGradientContext(Point2D p, Color c1, Point2D r, Color c2) {
  mPoint = p;
  mC1 = c1;
  mRadius = r;
  mC2 = c2;
  }
  public void dispose() {}
  public ColorModel getColorModel() { return ColorModel.getRGBdefault(); }
  public Raster getRaster(int x, int y, int w, int h) {
  WritableRaster raster =
  getColorModel().createCompatibleWritableRaster(w, h);
  int[] data = new int[w * h * 4];
  for (int j = 0; j < h; j++) {
  for (int i = 0; i < w; i++) {
  double distance = mPoint.distance(x + i, y + j);
  double radius = mRadius.distance(0, 0);
  double ratio = distance / radius;
  if (ratio > 1.0)
  ratio = 1.0;
  int base = (j * w + i) * 4;()));
  }
  }
  raster.setPixels(0, 0, w, h, data);
  return raster;
  }
  }
  And here's a simple class that demonstrates how you can use
  Round-GradientPaintto fill a round rectangle:
  import java.awt.*;
  import java.awt.geom.*;
  public class RoundGradientPaintFill
  extends ApplicationFrame {
  public static void main(String[] args) {
  RoundGradientPaintFill f = new RoundGradientPaintFill();
  f.setTitle("RoundGradientPaintFill v1.0");
  f.setSize(200, 200);
  f.center();
  f.setVisible(true);
  }
  public void paint(Graphics g) {
  Graphics2D g2 = (Graphics2D)g;
  RoundRectangle2D r = new RoundRectangle2D.Float(25, 35, 150, 150, 25,
  25);
  RoundGradientPaint rgp = new RoundGradientPaint(75, 75, Color.magenta,
  new Point2D.Double(0, 85), Color.blue);
  g2.setPaint(rgp);
  g2.fill(r);
  }
  }
  When you run this example, you should see the window shown in Figure C-2.
  Stroking
  Stroking is the process of drawing the outline of a shape. Stroking is similar to painting:
  - First, tell the
  Graphics2Dhow you want the outline to be drawn by calling
  setStroke(). This method accepts any object that implements the
  java.awt.Strokeinterface. The 2D API comes with a class,
  java.awt.BasicStroke, that implements common stroking options.
  - Use
  setPaint()to tell the
  Graphics2Dhow the outline itself should be drawn. Outlines, like the interior of shapes, can be drawn using a color, a gradient, a texture, or anything else that implements the
  Paintinterface.
  - Draw the outline of the shape using
  Graphics2D's
  draw()method. The
  Graphics2Duses the
  Strokefrom step 1 to determine what the outline looks like. The
  Paintfrom step 2 is used to actually render the outline.
  Stroke
  Graphics2Duses a
  Stroketo figure out what the outline of a particular shape looks like. When you ask
  Graphics2Dto
  draw()a shape, it asks its
  Strokewhat the outline of the shape should look like. Interestingly,
  Strokereturns the stroked outline as another shape:
  - public abstract Shape createStrokedShape(Shape p)
  - This method returns a
  Shapethat represents the stroked outline of the supplied shape.
  This is the only method in
  Stroke. Usually, you won't call it yourself--a
  Graphics2Dwill call it on your behalf when you
  draw()a shape.
  At first glance, it seems strange that the outline of a
  Shapeis also a
  Shape. It may help to think of the outline of a
  Shapeas an infinitesimally thin line--to actually draw the outline, you need to give it some area, which is the process of stroking. Once you have the stroked outline, you can fill it in to draw the original shape's outline. In fact, calling
  draw()on a
  Graphics2Dis equivalent to the following code:
  public void longwindedDraw(Graphics2D g2, Shape s) {
  Stroke stroke = g2.getStroke();
  Shape strokedOutline = stroke.createStrokedShape(s);
  g2.fill(strokedOutline);
  }
  BasicStroke
  The 2D API comes with a
  Strokeimplementation called
  java.awt.BasicStroke. This class supports solid and dashed lines of any width.
  BasicStrokealso handles two details of drawing thick lines: end styles and join styles.
  End styles
  There are several ways to draw the end of a thick line.
  BasicStrokesupports three styles, represented by constants. Figure 4-7 shows an example of each end style.
  - public static final int CAP_BUTT
  - This end style adds no decoration to the end of line segments. Straight line segments stroked using this end style appear as rectangles.
  - public static final int CAP_ROUND
  - This end style caps a line segment with a semicircle whose radius is half the line width.
  - public static final int CAP_SQUARE
  - This end style extends the end of the line with a rectangle whose length is half the line width.
  Note that these end styles apply to the ends of any line segment, straight or curved.
  Join styles
  BasicStrokeoffers three different ways to join line segments. Again, these join styles are represented by constants in
  BasicStroke. Figure 4-8 shows how the different join styles work. Figure 4-9 shows an example of each join style.
  - public static final int JOIN_BEVEL
  - Lines are joined by connecting the outer edges of their ends.
  - public static final int JOIN_MITER
  - In this join style, the outer edges of lines are extended until they intersect. If the miter is longer than a supplied miter limit, however, the join will be rendered as a beveled join.
  - public static final int JOIN_ROUND
  - Each line segment is ended with a semicircle, as in the
  CAP_ROUNDend style. This creates a rounded effect at the intersection of line segments.
  The miter limit used with
  JOIN_MITERdoes not correspond directly to the length of the miter. The algorithm depends on three quantities:
  - halfLength
  - This is half of the line width.
  - miterLength
  - The length of the miter is measured from the intersection of the two (unstroked) lines to the tip of the miter.
  - miterLimit
  - This is the miter limit as specified in the
  BasicStrokeconstructor.
  If miterLength is greater than (miterLimit * halfWidth), a beveled join will be used instead of a mitered join. Otherwise, a mitered join is used. The purpose of the miter limit is to prevent awkward-looking miter joins from being drawn. If two lines are nearly parallel, a mitered join will extend far beyond the actual intersection of the lines. Figure 4-10 illustrates this effect. In it, the same shape is stroked with a miter limit of 10.0 and 25.0.
  Dashes
  BasicStrokeuses a dash array and a dash phase to stroke dashed lines. The dash array is an array of
  floats that represent the length of the solid and clear sections of the line. For example, an array consisting of
  { 12, 12 }would produce a line that was visible for 12 units and invisible for 12 units. The even elements of the array (starting at index 0) determine where the line is visible, while the odd elements determine where it's invisible. Figure 4-11 shows a
  GeneralPathstroked with two varieties of dotted lines. The shape on the left has been stroked with a solid line. In the middle, the shape has been stroked using a dash array of
  { 12, 12 }. The same shape is stroked again on the right, using a dash array of
  { 4, 4, 12, 4 }.
  The end style is used on each dash in a dashed line. Figure 4-12 shows the same dashed line drawn using the
  CAP_BUTT,
  CAP_ROUND, and
  CAP_SQUAREend styles.
  The dash phase acts as an offset into the dash pattern. Consider, for example, the dash array
  { 12, 12 }. When the dash phase is 0, lines stroked with this dash pattern will begin with 12 units of visible outline followed by 12 units that are invisible. Suppose, instead, that the dash phase is set to 8. Now, lines stroked with the dash pattern will begin with 4 units of visible line, followed by 12 units of invisible, followed by 12 of visible, and so forth. Figure 4-13 shows how this works for two straight lines.
  Constructors
  Once you understand the end styles, join styles, and dashes, it's easy to create a
  BasicStrokethat does exactly what you want it to do. Like
  Paintobjects,
  Strokes cannot be changed after they have been constructed.
  - public BasicStroke()
  - This constructor creates a stroke object with all the default settings: a solid stroke with a line width of 1.0, an end style of
  CAP_SQUARE, a join style of
  JOIN_MITER, a miter limit of 10.0.
  - public BasicStroke(float width)
  - This constructor creates a stroke object with the supplied width. The other settings are set to their defaults, as described in the previous constructor.
  - public BasicStroke(float width, int cap, int join)
  - This constructor creates a solid stroke object with the given width, end style, and join style. If the join style is
  JOIN_MITER, the default miter limit of 10.0 is used.
  - public BasicStroke(float width, int cap, int join, float miterlimit)
  - This constructor is the same as the previous constructor, except that the given miter limit will be used if the join style is
  JOIN_MITER.
  - public BasicStroke(float width, int cap, int join, float miterlimit, float[] dash,
  - float dash_phase)
  - This constructor creates a dashed stroke object. The supplied dash array and phase are used to determine the dashing pattern.
  The following example creates a square and strokes it:
  Rectangle2D r = new Rectangle2D.Double(50, 50, 100, 100);
  Stroke stroke = new BasicStroke(8,
  BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0,
  new float[] { 12, 12 }, 0);
  g2.setStroke(stroke);
  g2.draw(r);
  Overlap
  What happens if you both stroke and paint a shape? Interestingly, the results depend on whether you stroke or paint first. A shape's outline actually overlaps its interior, as shown in Figure 4-14. The shape's real outline, strictly speaking, is an infinitesimally thin line, shown black in the figure. The process of stroking creates an outline shape around the real outline. Some of the stroked outline extends outside the shape, and some of the stroked outline overlaps with the shape's interior.
  If you're using opaque colors, then you'll get different results depending on the order in which you do things. If you use partially transparent colors, then you'll be able to observe the overlap of stroked outlines and filled interiors in your results. The following shows how this happens in practice. First, it strokes the outline and fills the interior of a circle using a partially transparent color:
  Color smokey = new Color(128, 128, 128, 128);
  g2.setPaint(smokey);
  g2.fill(e);
  g2.draw(e);
  Then, in a different location, it strokes the circle's outline using a solid black color and fills the interior using a solid gray:
  g2.setPaint(Color.black);
  g2.draw(e);
  g2.setPaint(Color.gray);
  g2.fill(e);
  Finally, it fills the circle with gray and then strokes the outline with black:
  g2.setPaint(Color.gray);
  g2.fill(e);
  g2.setPaint(Color.black);
  g2.draw(e);
  The results of this example are shown in Figure 4-15.
  Here's the source code:
  import java.awt.*;
  import java.awt.geom.*;
  public class Overlap
  extends ApplicationFrame {
  public static void main(String[] args) {
  Overlap f = new Overlap();
  f.setTitle("Overlap);
  g2.setStroke(new BasicStroke(8));
  // Stroke and paint.
  Color smokey = new Color(128, 128, 128, 128);
  g2.setPaint(smokey);
  g2.fill(e);
  g2.draw(e);
  // Stroke, then paint.
  e.setFrame(x + 100, y, w, h);
  g2.setPaint(Color.black);
  g2.draw(e);
  g2.setPaint(Color.gray);
  g2.fill(e);
  // Paint, then stroke.
  e.setFrame(x + 200, y, w, h);
  g2.setPaint(Color.gray);
  g2.fill(e);
  g2.setPaint(Color.black);
  g2.draw(e);
  }
  }
  © 2001, O'Reilly & Associates, Inc.