KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > awt > geom > GeneralPath


1 /*
2  * @(#)GeneralPath.java 1.59 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package java.awt.geom;
9
10 import java.awt.Shape JavaDoc;
11 import sun.awt.geom.Curve;
12 import sun.awt.geom.Crossings;
13
14 /**
15  * The <code>GeneralPath</code> class represents a geometric path
16  * constructed from straight lines, and quadratic and cubic
17  * (B&eacute;zier) curves. It can contain multiple subpaths.
18  * <p>
19  * The winding rule specifies how the interior of a path is
20  * determined. There are two types of winding rules:
21  * EVEN_ODD and NON_ZERO.
22  * <p>
23  * An EVEN_ODD winding rule means that enclosed regions
24  * of the path alternate between interior and exterior areas as
25  * traversed from the outside of the path towards a point inside
26  * the region.
27  * <p>
28  * A NON_ZERO winding rule means that if a ray is
29  * drawn in any direction from a given point to infinity
30  * and the places where the path intersects
31  * the ray are examined, the point is inside of the path if and only if
32  * the number of times that the path crosses the ray from
33  * left to right does not equal the number of times that the path crosses
34  * the ray from right to left.
35  * @version 1.59, 12/19/03
36  * @author Jim Graham
37  */

38 public final class GeneralPath implements Shape JavaDoc, Cloneable JavaDoc {
39     /**
40      * An even-odd winding rule for determining the interior of
41      * a path.
42      */

43     public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD;
44
45     /**
46      * A non-zero winding rule for determining the interior of a
47      * path.
48      */

49     public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO;
50     
51     // For code simplicity, copy these constants to our namespace
52
// and cast them to byte constants for easy storage.
53
private static final byte SEG_MOVETO = (byte) PathIterator.SEG_MOVETO;
54     private static final byte SEG_LINETO = (byte) PathIterator.SEG_LINETO;
55     private static final byte SEG_QUADTO = (byte) PathIterator.SEG_QUADTO;
56     private static final byte SEG_CUBICTO = (byte) PathIterator.SEG_CUBICTO;
57     private static final byte SEG_CLOSE = (byte) PathIterator.SEG_CLOSE;
58
59     byte[] pointTypes;
60     float[] pointCoords;
61     int numTypes;
62     int numCoords;
63     int windingRule;
64
65     static final int INIT_SIZE = 20;
66     static final int EXPAND_MAX = 500;
67
68     /**
69      * Constructs a new <code>GeneralPath</code> object.
70      * If an operation performed on this path requires the
71      * interior of the path to be defined then the default NON_ZERO
72      * winding rule is used.
73      * @see #WIND_NON_ZERO
74      */

75     public GeneralPath() {
76     this(WIND_NON_ZERO, INIT_SIZE, INIT_SIZE);
77     }
78
79     /**
80      * Constructs a new <code>GeneralPath</code> object with the specified
81      * winding rule to control operations that require the interior of the
82      * path to be defined.
83      * @param rule the winding rule
84      * @see #WIND_EVEN_ODD
85      * @see #WIND_NON_ZERO
86      */

87     public GeneralPath(int rule) {
88     this(rule, INIT_SIZE, INIT_SIZE);
89     }
90
91     /**
92      * Constructs a new <code>GeneralPath</code> object with the specified
93      * winding rule and the specified initial capacity to store path
94      * coordinates. This number is an initial guess as to how many path
95      * segments are in the path, but the storage is expanded
96      * as needed to store whatever path segments are added to this path.
97      * @param rule the winding rule
98      * @param initialCapacity the estimate for the number of path segments
99      * in the path
100      * @see #WIND_EVEN_ODD
101      * @see #WIND_NON_ZERO
102      */

103     public GeneralPath(int rule, int initialCapacity) {
104     this(rule, initialCapacity, initialCapacity);
105     }
106
107     /**
108      * Constructs a new <code>GeneralPath</code> object with the specified
109      * winding rule and the specified initial capacities to store point types
110      * and coordinates.
111      * These numbers are an initial guess as to how many path segments
112      * and how many points are to be in the path, but the
113      * storage is expanded as needed to store whatever path segments are
114      * added to this path.
115      * @param rule the winding rule
116      * @param initialTypes the estimate for the number of path segments
117      * in the path
118      * @param initialCapacity the estimate for the number of points
119      * @see #WIND_EVEN_ODD
120      * @see #WIND_NON_ZERO
121      */

122     GeneralPath(int rule, int initialTypes, int initialCoords) {
123     setWindingRule(rule);
124     pointTypes = new byte[initialTypes];
125     pointCoords = new float[initialCoords * 2];
126     }
127
128     /**
129      * Constructs a new <code>GeneralPath</code> object from an arbitrary
130      * {@link Shape} object.
131      * All of the initial geometry and the winding rule for this path are
132      * taken from the specified <code>Shape</code> object.
133      * @param s the specified <code>Shape</code> object
134      */

135     public GeneralPath(Shape JavaDoc s) {
136     this(WIND_NON_ZERO, INIT_SIZE, INIT_SIZE);
137     PathIterator JavaDoc pi = s.getPathIterator(null);
138     setWindingRule(pi.getWindingRule());
139     append(pi, false);
140     }
141  
142     private void needRoom(int newTypes, int newCoords, boolean needMove) {
143     if (needMove && numTypes == 0) {
144         throw new IllegalPathStateException JavaDoc("missing initial moveto "+
145                         "in path definition");
146     }
147     int size = pointCoords.length;
148     if (numCoords + newCoords > size) {
149         int grow = size;
150         if (grow > EXPAND_MAX * 2) {
151         grow = EXPAND_MAX * 2;
152         }
153         if (grow < newCoords) {
154         grow = newCoords;
155         }
156         float[] arr = new float[size + grow];
157         System.arraycopy(pointCoords, 0, arr, 0, numCoords);
158         pointCoords = arr;
159     }
160     size = pointTypes.length;
161     if (numTypes + newTypes > size) {
162         int grow = size;
163         if (grow > EXPAND_MAX) {
164         grow = EXPAND_MAX;
165         }
166         if (grow < newTypes) {
167         grow = newTypes;
168         }
169         byte[] arr = new byte[size + grow];
170         System.arraycopy(pointTypes, 0, arr, 0, numTypes);
171         pointTypes = arr;
172     }
173     }
174
175     /**
176      * Adds a point to the path by moving to the specified
177      * coordinates.
178      * @param x,&nbsp;y the specified coordinates
179      */

180     public synchronized void moveTo(float x, float y) {
181     if (numTypes > 0 && pointTypes[numTypes - 1] == SEG_MOVETO) {
182         pointCoords[numCoords - 2] = x;
183         pointCoords[numCoords - 1] = y;
184     } else {
185         needRoom(1, 2, false);
186         pointTypes[numTypes++] = SEG_MOVETO;
187         pointCoords[numCoords++] = x;
188         pointCoords[numCoords++] = y;
189     }
190     }
191
192     /**
193      * Adds a point to the path by drawing a straight line from the
194      * current coordinates to the new specified coordinates.
195      * @param x,&nbsp;y the specified coordinates
196      */

197     public synchronized void lineTo(float x, float y) {
198     needRoom(1, 2, true);
199     pointTypes[numTypes++] = SEG_LINETO;
200     pointCoords[numCoords++] = x;
201     pointCoords[numCoords++] = y;
202     }
203
204     /**
205      * Adds a curved segment, defined by two new points, to the path by
206      * drawing a Quadratic curve that intersects both the current
207      * coordinates and the coordinates (x2,&nbsp;y2), using the
208      * specified point (x1,&nbsp;y1) as a quadratic parametric control
209      * point.
210      * @param x1,&nbsp;y1 the coordinates of the first quadratic control
211      * point
212      * @param x2,&nbsp;y2 the coordinates of the final endpoint
213      */

214     public synchronized void quadTo(float x1, float y1, float x2, float y2) {
215     needRoom(1, 4, true);
216     pointTypes[numTypes++] = SEG_QUADTO;
217     pointCoords[numCoords++] = x1;
218     pointCoords[numCoords++] = y1;
219     pointCoords[numCoords++] = x2;
220     pointCoords[numCoords++] = y2;
221     }
222
223     /**
224      * Adds a curved segment, defined by three new points, to the path by
225      * drawing a B&eacute;zier curve that intersects both the current
226      * coordinates and the coordinates (x3,&nbsp;y3), using the
227      * specified points (x1,&nbsp;y1) and (x2,&nbsp;y2) as
228      * B&eacute;zier control points.
229      * @param x1,&nbsp;y1 the coordinates of the first B&eacute;ezier
230      * control point
231      * @param x2,&nbsp;y2 the coordinates of the second B&eacute;zier
232      * control point
233      * @param x3,&nbsp;y3 the coordinates of the final endpoint
234      */

235     public synchronized void curveTo(float x1, float y1,
236                      float x2, float y2,
237                      float x3, float y3) {
238     needRoom(1, 6, true);
239     pointTypes[numTypes++] = SEG_CUBICTO;
240     pointCoords[numCoords++] = x1;
241     pointCoords[numCoords++] = y1;
242     pointCoords[numCoords++] = x2;
243     pointCoords[numCoords++] = y2;
244     pointCoords[numCoords++] = x3;
245     pointCoords[numCoords++] = y3;
246     }
247
248     /**
249      * Closes the current subpath by drawing a straight line back to
250      * the coordinates of the last <code>moveTo</code>. If the path is already
251      * closed then this method has no effect.
252      */

253     public synchronized void closePath() {
254     if (numTypes == 0 || pointTypes[numTypes - 1] != SEG_CLOSE) {
255         needRoom(1, 0, true);
256         pointTypes[numTypes++] = SEG_CLOSE;
257     }
258     }
259
260     /**
261      * Appends the geometry of the specified <code>Shape</code> object to the
262      * path, possibly connecting the new geometry to the existing path
263      * segments with a line segment.
264      * If the <code>connect</code> parameter is <code>true</code> and the
265      * path is not empty then any initial <code>moveTo</code> in the
266      * geometry of the appended <code>Shape</code>
267      * is turned into a <code>lineTo</code> segment.
268      * If the destination coordinates of such a connecting <code>lineTo</code>
269      * segment match the ending coordinates of a currently open
270      * subpath then the segment is omitted as superfluous.
271      * The winding rule of the specified <code>Shape</code> is ignored
272      * and the appended geometry is governed by the winding
273      * rule specified for this path.
274      * @param s the <code>Shape</code> whose geometry is appended
275      * to this path
276      * @param connect a boolean to control whether or not to turn an
277      * initial <code>moveTo</code> segment into a <code>lineTo</code>
278      * segment to connect the new geometry to the existing path
279      */

280     public void append(Shape JavaDoc s, boolean connect) {
281     PathIterator JavaDoc pi = s.getPathIterator(null);
282         append(pi,connect);
283     }
284
285     /**
286      * Appends the geometry of the specified
287      * {@link PathIterator} object
288      * to the path, possibly connecting the new geometry to the existing
289      * path segments with a line segment.
290      * If the <code>connect</code> parameter is <code>true</code> and the
291      * path is not empty then any initial <code>moveTo</code> in the
292      * geometry of the appended <code>Shape</code> is turned into a
293      * <code>lineTo</code> segment.
294      * If the destination coordinates of such a connecting <code>lineTo</code>
295      * segment match the ending coordinates of a currently open
296      * subpath then the segment is omitted as superfluous.
297      * The winding rule of the specified <code>Shape</code> is ignored
298      * and the appended geometry is governed by the winding
299      * rule specified for this path.
300      * @param pi the <code>PathIterator</code> whose geometry is appended to
301      * this path
302      * @param connect a boolean to control whether or not to turn an
303      * initial <code>moveTo</code> segment into a <code>lineTo</code> segment
304      * to connect the new geometry to the existing path
305      */

306     public void append(PathIterator JavaDoc pi, boolean connect) {
307     float coords[] = new float[6];
308     while (!pi.isDone()) {
309         switch (pi.currentSegment(coords)) {
310         case SEG_MOVETO:
311         if (!connect || numTypes < 1 || numCoords < 2) {
312             moveTo(coords[0], coords[1]);
313             break;
314         }
315         if (pointTypes[numTypes - 1] != SEG_CLOSE &&
316             pointCoords[numCoords - 2] == coords[0] &&
317             pointCoords[numCoords - 1] == coords[1])
318         {
319             // Collapse out initial moveto/lineto
320
break;
321         }
322         // NO BREAK;
323
case SEG_LINETO:
324         lineTo(coords[0], coords[1]);
325         break;
326         case SEG_QUADTO:
327         quadTo(coords[0], coords[1],
328                coords[2], coords[3]);
329         break;
330         case SEG_CUBICTO:
331         curveTo(coords[0], coords[1],
332             coords[2], coords[3],
333             coords[4], coords[5]);
334         break;
335         case SEG_CLOSE:
336         closePath();
337         break;
338         }
339         pi.next();
340         connect = false;
341     }
342     }
343
344     /**
345      * Returns the fill style winding rule.
346      * @return an integer representing the current winding rule.
347      * @see #WIND_EVEN_ODD
348      * @see #WIND_NON_ZERO
349      * @see #setWindingRule
350      */

351     public synchronized int getWindingRule() {
352         return windingRule;
353     }
354
355     /**
356      * Sets the winding rule for this path to the specified value.
357      * @param rule an integer representing the specified
358      * winding rule
359      * @exception <code>IllegalArgumentException</code> if
360      * <code>rule</code> is not either
361      * <code>WIND_EVEN_ODD</code> or
362      * <code>WIND_NON_ZERO</code>
363      * @see #WIND_EVEN_ODD
364      * @see #WIND_NON_ZERO
365      * @see #getWindingRule
366      */

367     public void setWindingRule(int rule) {
368     if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) {
369         throw new IllegalArgumentException JavaDoc("winding rule must be "+
370                            "WIND_EVEN_ODD or "+
371                            "WIND_NON_ZERO");
372     }
373     windingRule = rule;
374     }
375
376     /**
377      * Returns the coordinates most recently added to the end of the path
378      * as a {@link Point2D} object.
379      * @return a <code>Point2D</code> object containing the ending
380      * coordinates of the path or <code>null</code> if there are no points
381      * in the path.
382      */

383     public synchronized Point2D JavaDoc getCurrentPoint() {
384     if (numTypes < 1 || numCoords < 2) {
385         return null;
386     }
387     int index = numCoords;
388     if (pointTypes[numTypes - 1] == SEG_CLOSE) {
389     loop:
390         for (int i = numTypes - 2; i > 0; i--) {
391         switch (pointTypes[i]) {
392         case SEG_MOVETO:
393             break loop;
394         case SEG_LINETO:
395             index -= 2;
396             break;
397         case SEG_QUADTO:
398             index -= 4;
399             break;
400         case SEG_CUBICTO:
401             index -= 6;
402             break;
403         case SEG_CLOSE:
404             break;
405         }
406         }
407     }
408     return new Point2D.Float JavaDoc(pointCoords[index - 2],
409                  pointCoords[index - 1]);
410     }
411
412     /**
413      * Resets the path to empty. The append position is set back to the
414      * beginning of the path and all coordinates and point types are
415      * forgotten.
416      */

417     public synchronized void reset() {
418     numTypes = numCoords = 0;
419     }
420
421     /**
422      * Transforms the geometry of this path using the specified
423      * {@link AffineTransform}.
424      * The geometry is transformed in place, which permanently changes the
425      * boundary defined by this object.
426      * @param at the <code>AffineTransform</code> used to transform the area
427      */

428     public void transform(AffineTransform JavaDoc at) {
429     at.transform(pointCoords, 0, pointCoords, 0, numCoords / 2);
430     }
431
432     /**
433      * Returns a new transformed <code>Shape</code>.
434      * @param at the <code>AffineTransform</code> used to transform a
435      * new <code>Shape</code>.
436      * @return a new <code>Shape</code>, transformed with the specified
437      * <code>AffineTransform</code>.
438      */

439     public synchronized Shape JavaDoc createTransformedShape(AffineTransform JavaDoc at) {
440     GeneralPath JavaDoc gp = (GeneralPath JavaDoc) clone();
441     if (at != null) {
442         gp.transform(at);
443     }
444     return gp;
445     }
446
447     /**
448      * Return the bounding box of the path.
449      * @return a {@link java.awt.Rectangle} object that
450      * bounds the current path.
451      */

452     public java.awt.Rectangle JavaDoc getBounds() {
453     return getBounds2D().getBounds();
454     }
455
456     /**
457      * Returns the bounding box of the path.
458      * @return a {@link Rectangle2D} object that
459      * bounds the current path.
460      */

461     public synchronized Rectangle2D JavaDoc getBounds2D() {
462     float x1, y1, x2, y2;
463     int i = numCoords;
464     if (i > 0) {
465         y1 = y2 = pointCoords[--i];
466         x1 = x2 = pointCoords[--i];
467         while (i > 0) {
468         float y = pointCoords[--i];
469         float x = pointCoords[--i];
470         if (x < x1) x1 = x;
471         if (y < y1) y1 = y;
472         if (x > x2) x2 = x;
473         if (y > y2) y2 = y;
474         }
475     } else {
476         x1 = y1 = x2 = y2 = 0.0f;
477     }
478     return new Rectangle2D.Float JavaDoc(x1, y1, x2 - x1, y2 - y1);
479     }
480
481     /**
482      * Tests if the specified coordinates are inside the boundary of
483      * this <code>Shape</code>.
484      * @param x,&nbsp;y the specified coordinates
485      * @return <code>true</code> if the specified coordinates are inside this
486      * <code>Shape</code>; <code>false</code> otherwise
487      */

488     public boolean contains(double x, double y) {
489     if (numTypes < 2) {
490         return false;
491     }
492     int cross = Curve.crossingsForPath(getPathIterator(null), x, y);
493     if (windingRule == WIND_NON_ZERO) {
494         return (cross != 0);
495     } else {
496         return ((cross & 1) != 0);
497     }
498     }
499
500     /**
501      * Tests if the specified <code>Point2D</code> is inside the boundary
502      * of this <code>Shape</code>.
503      * @param p the specified <code>Point2D</code>
504      * @return <code>true</code> if this <code>Shape</code> contains the
505      * specified <code>Point2D</code>, <code>false</code> otherwise.
506      */

507     public boolean contains(Point2D JavaDoc p) {
508     return contains(p.getX(), p.getY());
509     }
510
511     /**
512      * Tests if the specified rectangular area is inside the boundary of
513      * this <code>Shape</code>.
514      * @param x,&nbsp;y the specified coordinates
515      * @param w the width of the specified rectangular area
516      * @param h the height of the specified rectangular area
517      * @return <code>true</code> if this <code>Shape</code> contains
518      * the specified rectangluar area; <code>false</code> otherwise.
519      */

520     public boolean contains(double x, double y, double w, double h) {
521     Crossings c = Crossings.findCrossings(getPathIterator(null),
522                           x, y, x+w, y+h);
523     return (c != null && c.covers(y, y+h));
524     }
525
526     /**
527      * Tests if the specified <code>Rectangle2D</code>
528      * is inside the boundary of this <code>Shape</code>.
529      * @param r a specified <code>Rectangle2D</code>
530      * @return <code>true</code> if this <code>Shape</code> bounds the
531      * specified <code>Rectangle2D</code>; <code>false</code> otherwise.
532      */

533     public boolean contains(Rectangle2D JavaDoc r) {
534     return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
535     }
536
537     /**
538      * Tests if the interior of this <code>Shape</code> intersects the
539      * interior of a specified set of rectangular coordinates.
540      * @param x,&nbsp;y the specified coordinates
541      * @param w the width of the specified rectangular coordinates
542      * @param h the height of the specified rectangular coordinates
543      * @return <code>true</code> if this <code>Shape</code> and the
544      * interior of the specified set of rectangular coordinates intersect
545      * each other; <code>false</code> otherwise.
546      */

547     public boolean intersects(double x, double y, double w, double h) {
548     Crossings c = Crossings.findCrossings(getPathIterator(null),
549                           x, y, x+w, y+h);
550     return (c == null || !c.isEmpty());
551     }
552
553     /**
554      * Tests if the interior of this <code>Shape</code> intersects the
555      * interior of a specified <code>Rectangle2D</code>.
556      * @param r the specified <code>Rectangle2D</code>
557      * @return <code>true</code> if this <code>Shape</code> and the interior
558      * of the specified <code>Rectangle2D</code> intersect each
559      * other; <code>false</code> otherwise.
560      */

561     public boolean intersects(Rectangle2D JavaDoc r) {
562     return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
563     }
564
565     /**
566      * Returns a <code>PathIterator</code> object that iterates along the
567      * boundary of this <code>Shape</code> and provides access to the
568      * geometry of the outline of this <code>Shape</code>.
569      * The iterator for this class is not multi-threaded safe,
570      * which means that this <code>GeneralPath</code> class does not
571      * guarantee that modifications to the geometry of this
572      * <code>GeneralPath</code> object do not affect any iterations of
573      * that geometry that are already in process.
574      * @param at an <code>AffineTransform</code>
575      * @return a new <code>PathIterator</code> that iterates along the
576      * boundary of this <code>Shape</code> and provides access to the
577      * geometry of this <code>Shape</code>'s outline
578      */

579     public PathIterator JavaDoc getPathIterator(AffineTransform JavaDoc at) {
580     return new GeneralPathIterator JavaDoc(this, at);
581     }
582
583     /**
584      * Returns a <code>PathIterator</code> object that iterates along the
585      * boundary of the flattened <code>Shape</code> and provides access to the
586      * geometry of the outline of the <code>Shape</code>.
587      * The iterator for this class is not multi-threaded safe,
588      * which means that this <code>GeneralPath</code> class does not
589      * guarantee that modifications to the geometry of this
590      * <code>GeneralPath</code> object do not affect any iterations of
591      * that geometry that are already in process.
592      * @param at an <code>AffineTransform</code>
593      * @param flatness the maximum distance that the line segments used to
594      * approximate the curved segments are allowed to deviate
595      * from any point on the original curve
596      * @return a new <code>PathIterator</code> that iterates along the flattened
597      * <code>Shape</code> boundary.
598      */

599     public PathIterator JavaDoc getPathIterator(AffineTransform JavaDoc at, double flatness) {
600     return new FlatteningPathIterator JavaDoc(getPathIterator(at), flatness);
601     }
602
603     /**
604      * Creates a new object of the same class as this object.
605      *
606      * @return a clone of this instance.
607      * @exception OutOfMemoryError if there is not enough memory.
608      * @see java.lang.Cloneable
609      * @since 1.2
610      */

611     public Object JavaDoc clone() {
612     try {
613         GeneralPath JavaDoc copy = (GeneralPath JavaDoc) super.clone();
614         copy.pointTypes = (byte[]) pointTypes.clone();
615         copy.pointCoords = (float[]) pointCoords.clone();
616         return copy;
617     } catch (CloneNotSupportedException JavaDoc e) {
618         // this shouldn't happen, since we are Cloneable
619
throw new InternalError JavaDoc();
620     }
621     }
622
623     GeneralPath(int windingRule,
624         byte[] pointTypes,
625         int numTypes,
626         float[] pointCoords,
627         int numCoords) {
628
629     // used to construct from native
630

631     this.windingRule = windingRule;
632     this.pointTypes = pointTypes;
633     this.numTypes = numTypes;
634     this.pointCoords = pointCoords;
635     this.numCoords = numCoords;
636     }
637 }
638
Popular Tags