|
|
|
|
Java 2D GraphicsBy Jonathan Knudsen1st Edition May 1999 1-56592-484-3, Order Number: 4843 366 pages, $29.95 |
Return to Java 2D GraphicsSample Chapter 4:
Painting and Stroking
In this chapter:
Painting
Stroking
Overlap
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.
Figure 4-1. Examples of painting and stroking
![]()
The following class produces the window shown in Figure 4-1. It uses some unfamiliar methods and classes:
setPaint(),setStroke(),GradientPaint, andBasicStroke. 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 PaintingAndStrokingextends ApplicationFrame {public static void main(String[] args) {PaintingAndStroking f = new PaintingAndStroking();f.setTitle("PaintingAndStroking v1.0");f.setSize(300, 150);f.center();f.setVisible(true);}public void paint(Graphics g) {Graphics2D g2 = (Graphics2D)g;double x = 15, y = 50, w = 70, h = 70;Ellipse2D e = new Ellipse2D.Double(x, y, w, h);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 tosetPaint(). This method accepts any object that implements thejava.awt.Paintinterface. TheGraphics2Dstores thePaintaway as part of its state. When it comes time to fill a shape,Graphics2Dwill use thePaintto 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 ownPaintimplementations if you wish.- Now you can tell
Graphics2Dto fill a shape by passing it tofill().
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 callsetPaint()on theGraphics2D; then you would paint the shapes usingfill(). But what if another part of your program changed thePaintthatGraphics2Dwas using? The results might be quite bizarre. For this reason, objects that implementPaintshould 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.
Figure 4-2. Three shapes and three paints
![]()
Solid Colors
The
java.awt.Colorclass implements thePaintinterface. Thus, anyColormay 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 atColor. First, theColorclass 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 SolidPaintextends 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 inGraphics, could be used to affect the color of filled shapes. In the 2D API, it is now a convenience method; a call tosetColor(c)on aGraphics2Dis equivalent to callingsetPaint(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 giveninitialColor.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 RedTownextends 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. TheGradientPaintcreates 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.
Figure 4-3. An acyclic linear gradient
![]()
GradientPaints may be cyclic or acyclic. In an acyclicGradientPaint, 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 cyclicGradientPaint, 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.
Figure 4-4. A cyclic linear gradient
![]()
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 twoColors. 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 fromcolor1at the first point tocolor2at 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 fromcolor1at the first point tocolor2at the second point. If thecyclicparameter istrue, 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 GradientPaintFillextends 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. Thejava.awt.TexturePaintclass represents a texture. You can construct aTexturePaintwith 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 aBufferedImageas 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-5. A texture fill
![]()
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.
Figure 4-6. A texture with a small texture rectangle
![]()
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 theanchorrectangle.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 thepaint()method, the image is used to create aTexturePaint. 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 TexturePaintFillextends 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? EveryPaintobject has an associated context, ajava.awt.PaintContext, which knows what colors to put on a drawing surface. WhenGraphics2Dneeds to fill a shape, it asks its currentPaintfor the correspondingPaintContext. Then it uses thePaintContextto actually put color on the drawing surface.The Transparency interface
Paintis a subinterface ofjava.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, orTRANSLUCENT.The
Transparencyinterface is implemented by theColorModelclass, which is related to images and drawing surfaces (see Chapter 11, Image Guts).The Paint interface
As I mentioned,
Transparencyis the parent interface ofPaint. ThePaintinterface itself has only one method--it just knows how to generate aPaintContext:
- public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints)
- This method is called to create a
PaintContext. ThePaintContextis encouraged to produce colors in the given color model (see Chapter 11). ThedeviceBoundsrectangle indicates the bounds of the drawing surface, whileuserBoundsindicates the bounds of the shape that is being filled. The suppliedAffineTransform(see Chapter 5, Rendering) indicates the transformation currently in effect, whilehintscontains information that thePaintContextcan use to modify its behavior. TheTexturePaintcontext, for example, is responsive to theKEY_INTERPOLATIONhint. See Chapter 5 for an explanation of rendering hints.Simple
Paintimplementations can ignore a lot of the parameters that are passed tocreateContext(), as you'll see in an upcoming example.The PaintContext interface
The
PaintContextreturned bycreateContext()knows how to actually generate the colors of thePaint. ThePaintContextinterface 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 thePaintinterface.- public Raster getRaster(int x, int y, int w, int h)
- This is the mother lode of the
PaintContextinterface. This method returns aRasterthat contains the color data that should be used to fill a shape. (TheRasterclass 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 thePaintinterface.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,
RoundGradientPaintandRoundGradientContext.RoundGradientPaintdoesn't do much except return aRoundGradientPaintContextfrom itscreateContext()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 RoundGradientPaintimplements 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 theTransparencyinterface, returns eitherOPAQUEorTRANSLUCENT, depending on the colors that were passed toRoundGradientPaint's constructor. If both colors are fully opaque (alpha = 255), then the resultingRoundGradientPaintis also fully opaque. Otherwise, the pixels filled by theRoundGradientPaintwill have variable transparency, indicated by theTRANSLUCENTvalue.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 usednew Color(255, 0, 0, 255)as the point color, andnew 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. Theget-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:
data[base + 0] = (int)(mC1.getRed() + ratio *(mC2.getRed() - mC1.getRed()));data[base + 1] = (int)(mC1.getGreen() + ratio *(mC2.getGreen() - mC1.getGreen()));data[base + 2] = (int)(mC1.getBlue() + ratio *(mC2.getBlue() - mC1.getBlue()));data[base + 3] = (int)(mC1.getAlpha() + ratio *(mC2.getAlpha() - mC1.getAlpha()));Here's the entire class:
import java.awt.*;import java.awt.geom.*;import java.awt.image.*;public class RoundGradientContextimplements 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;data[base + 0] = (int)(mC1.getRed() + ratio *(mC2.getRed() - mC1.getRed()));data[base + 1] = (int)(mC1.getGreen() + ratio *(mC2.getGreen() - mC1.getGreen()));data[base + 2] = (int)(mC1.getBlue() + ratio *(mC2.getBlue() - mC1.getBlue()));data[base + 3] = (int)(mC1.getAlpha() + ratio *(mC2.getAlpha() - mC1.getAlpha()));}}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 RoundGradientPaintFillextends 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 callingsetStroke(). This method accepts any object that implements thejava.awt.Strokeinterface. The 2D API comes with a class,java.awt.BasicStroke, that implements common stroking options.- Use
setPaint()to tell theGraphics2Dhow 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 thePaintinterface.- Draw the outline of the shape using
Graphics2D'sdraw()method. TheGraphics2Duses theStrokefrom step 1 to determine what the outline looks like. ThePaintfrom step 2 is used to actually render the outline.Stroke
Graphics2Duses aStroketo figure out what the outline of a particular shape looks like. When you askGraphics2Dtodraw()a shape, it asks itsStrokewhat 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--aGraphics2Dwill call it on your behalf when youdraw()a shape.At first glance, it seems strange that the outline of a
Shapeis also aShape. It may help to think of the outline of aShapeas 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, callingdraw()on aGraphics2Dis 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 calledjava.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.
Figure 4-7. End styles
![]()
- 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 inBasicStroke. Figure 4-8 shows how the different join styles work. Figure 4-9 shows an example of each join style.
Figure 4-8. Join styles
![]()
Figure 4-9. Join styles in action: JOIN_BEVEL, JOIN_MITER, and JOIN_ROUND
![]()
- 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.
Figure 4-10. Mitered joins can be unruly
![]()
Dashes
BasicStrokeuses a dash array and a dash phase to stroke dashed lines. The dash array is an array offloats 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 aGeneralPathstroked 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 }.
Figure 4-11. Stroking dashed lines
![]()
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, andCAP_SQUAREend styles.
Figure 4-12. End styles apply to dashes, too
![]()
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.
Figure 4-13. Same dashes, different dash phases
![]()
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. LikePaintobjects,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 ofJOIN_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.
Figure 4-14. The stroked outline and interior of a circle
![]()
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.
Figure 4-15. Stroking and painting overlap
![]()
Here's the source code:
import java.awt.*;import java.awt.geom.*;public class Overlapextends ApplicationFrame {public static void main(String[] args) {Overlap f = new Overlap();f.setTitle("Overlap v1.0");f.setSize(300, 150);f.center();f.setVisible(true);}public void paint(Graphics g) {Graphics2D g2 = (Graphics2D)g;double x = 15, y = 50, w = 70, h = 70;Ellipse2D e = new Ellipse2D.Double(x, y, w, h);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.