KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * @(#)Area.java 1.16 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 java.awt.Rectangle JavaDoc;
12 import java.util.Vector JavaDoc;
13 import java.util.Enumeration JavaDoc;
14 import java.util.NoSuchElementException JavaDoc;
15 import sun.awt.geom.Curve;
16 import sun.awt.geom.Crossings;
17 import sun.awt.geom.AreaOp;
18
19 /**
20  * The <code>Area</code> class is a device-independent specification of an
21  * arbitrarily-shaped area. The <code>Area</code> object is defined as an
22  * object that performs certain binary CAG (Constructive Area Geometry)
23  * operations on other area-enclosing geometries, such as rectangles,
24  * ellipses, and polygons. The CAG operations are Add(union), Subtract,
25  * Intersect, and ExclusiveOR. For example, an <code>Area</code> can be
26  * made up of the area of a rectangle minus the area of an ellipse.
27  */

28 public class Area implements Shape JavaDoc, Cloneable JavaDoc {
29     private static Vector JavaDoc EmptyCurves = new Vector JavaDoc();
30
31     private Vector JavaDoc curves;
32
33     /**
34      * Default constructor which creates an empty area.
35      */

36     public Area() {
37     curves = EmptyCurves;
38     }
39
40     /**
41      * The <code>Area</code> class creates an area geometry from the
42      * specified {@link Shape} object. The geometry is explicitly
43      * closed, if the <code>Shape</code> is not already closed. The
44      * fill rule (even-odd or winding) specified by the geometry of the
45      * <code>Shape</code> is used to determine the resulting enclosed area.
46      * @param s the <code>Shape</code> from which the area is constructed
47      */

48     public Area(Shape JavaDoc s) {
49     if (s instanceof Area JavaDoc) {
50         curves = ((Area JavaDoc) s).curves;
51         return;
52     }
53     curves = new Vector JavaDoc();
54     PathIterator JavaDoc pi = s.getPathIterator(null);
55     int windingRule = pi.getWindingRule();
56     // coords array is big enough for holding:
57
// coordinates returned from currentSegment (6)
58
// OR
59
// two subdivided quadratic curves (2+4+4=10)
60
// AND
61
// 0-1 horizontal splitting parameters
62
// OR
63
// 2 parametric equation derivative coefficients
64
// OR
65
// three subdivided cubic curves (2+6+6+6=20)
66
// AND
67
// 0-2 horizontal splitting parameters
68
// OR
69
// 3 parametric equation derivative coefficients
70
double coords[] = new double[23];
71     double movx = 0, movy = 0;
72     double curx = 0, cury = 0;
73     double newx, newy;
74     while (!pi.isDone()) {
75         switch (pi.currentSegment(coords)) {
76         case PathIterator.SEG_MOVETO:
77         Curve.insertLine(curves, curx, cury, movx, movy);
78         curx = movx = coords[0];
79         cury = movy = coords[1];
80         Curve.insertMove(curves, movx, movy);
81         break;
82         case PathIterator.SEG_LINETO:
83         newx = coords[0];
84         newy = coords[1];
85         Curve.insertLine(curves, curx, cury, newx, newy);
86         curx = newx;
87         cury = newy;
88         break;
89         case PathIterator.SEG_QUADTO:
90         newx = coords[2];
91         newy = coords[3];
92         Curve.insertQuad(curves, curx, cury, coords);
93         curx = newx;
94         cury = newy;
95         break;
96         case PathIterator.SEG_CUBICTO:
97         newx = coords[4];
98         newy = coords[5];
99         Curve.insertCubic(curves, curx, cury, coords);
100         curx = newx;
101         cury = newy;
102         break;
103         case PathIterator.SEG_CLOSE:
104         Curve.insertLine(curves, curx, cury, movx, movy);
105         curx = movx;
106         cury = movy;
107         break;
108         }
109         pi.next();
110     }
111     Curve.insertLine(curves, curx, cury, movx, movy);
112     AreaOp operator;
113     if (windingRule == PathIterator.WIND_EVEN_ODD) {
114         operator = new AreaOp.EOWindOp();
115     } else {
116         operator = new AreaOp.NZWindOp();
117     }
118     curves = operator.calculate(this.curves, EmptyCurves);
119     }
120
121     /**
122      * Adds the shape of the specified <code>Area</code> to the
123      * shape of this <code>Area</code>.
124      * Addition is achieved through union.
125      * @param rhs the <code>Area</code> to be added to the
126      * current shape
127      */

128     public void add(Area JavaDoc rhs) {
129     curves = new AreaOp.AddOp().calculate(this.curves, rhs.curves);
130     invalidateBounds();
131     }
132
133     /**
134      * Subtracts the shape of the specified <code>Area</code> from the
135      * shape of this <code>Area</code>.
136      * @param rhs the <code>Area</code> to be subtracted from the
137      * current shape
138      */

139     public void subtract(Area JavaDoc rhs) {
140     curves = new AreaOp.SubOp().calculate(this.curves, rhs.curves);
141     invalidateBounds();
142     }
143
144     /**
145      * Sets the shape of this <code>Area</code> to the intersection of
146      * its current shape and the shape of the specified <code>Area</code>.
147      * @param rhs the <code>Area</code> to be intersected with this
148      * <code>Area</code>
149      */

150     public void intersect(Area JavaDoc rhs) {
151     curves = new AreaOp.IntOp().calculate(this.curves, rhs.curves);
152     invalidateBounds();
153     }
154
155     /**
156      * Sets the shape of this <code>Area</code> to be the combined area
157      * of its current shape and the shape of the specified <code>Area</code>,
158      * minus their intersection.
159      * @param rhs the <code>Area</code> to be exclusive ORed with this
160      * <code>Area</code>.
161      */

162     public void exclusiveOr(Area JavaDoc rhs) {
163     curves = new AreaOp.XorOp().calculate(this.curves, rhs.curves);
164     invalidateBounds();
165     }
166
167     /**
168      * Removes all of the geometry from this <code>Area</code> and
169      * restores it to an empty area.
170      */

171     public void reset() {
172     curves = new Vector JavaDoc();
173     invalidateBounds();
174     }
175
176     /**
177      * Tests whether this <code>Area</code> object encloses any area.
178      * @return <code>true</code> if this <code>Area</code> object
179      * represents an empty area; <code>false</code> otherwise.
180      */

181     public boolean isEmpty() {
182     return (curves.size() == 0);
183     }
184
185     /**
186      * Tests whether this <code>Area</code> consists entirely of
187      * straight edged polygonal geometry.
188      * @return <code>true</code> if the geometry of this
189      * <code>Area</code> consists entirely of line segments;
190      * <code>false</code> otherwise.
191      */

192     public boolean isPolygonal() {
193     Enumeration JavaDoc enum_ = curves.elements();
194     while (enum_.hasMoreElements()) {
195         if (((Curve) enum_.nextElement()).getOrder() > 1) {
196         return false;
197         }
198     }
199     return true;
200     }
201
202     /**
203      * Tests whether this <code>Area</code> is rectangular in shape.
204      * @return <code>true</code> if the geometry of this
205      * <code>Area</code> is rectangular in shape; <code>false</code>
206      * otherwise.
207      */

208     public boolean isRectangular() {
209     int size = curves.size();
210     if (size == 0) {
211         return true;
212     }
213     if (size > 3) {
214         return false;
215     }
216     Curve c1 = (Curve) curves.get(1);
217     Curve c2 = (Curve) curves.get(2);
218     if (c1.getOrder() != 1 || c2.getOrder() != 1) {
219         return false;
220     }
221     if (c1.getXTop() != c1.getXBot() || c2.getXTop() != c2.getXBot()) {
222         return false;
223     }
224     if (c1.getYTop() != c2.getYTop() || c1.getYBot() != c2.getYBot()) {
225         // One might be able to prove that this is impossible...
226
return false;
227     }
228     return true;
229     }
230
231     /**
232      * Tests whether this <code>Area</code> is comprised of a single
233      * closed subpath. This method returns <code>true</code> if the
234      * path contains 0 or 1 subpaths, or <code>false</code> if the path
235      * contains more than 1 subpath. The subpaths are counted by the
236      * number of {@link PathIterator#SEG_MOVETO SEG_MOVETO} segments
237      * that appear in the path.
238      * @return <code>true</code> if the <code>Area</code> is comprised
239      * of a single basic geometry; <code>false</code> otherwise.
240      */

241     public boolean isSingular() {
242     if (curves.size() < 3) {
243         return true;
244     }
245     Enumeration JavaDoc enum_ = curves.elements();
246     enum_.nextElement(); // First Order0 "moveto"
247
while (enum_.hasMoreElements()) {
248         if (((Curve) enum_.nextElement()).getOrder() == 0) {
249         return false;
250         }
251     }
252     return true;
253     }
254
255     private Rectangle2D JavaDoc cachedBounds;
256     private void invalidateBounds() {
257     cachedBounds = null;
258     }
259     private Rectangle2D JavaDoc getCachedBounds() {
260     if (cachedBounds != null) {
261         return cachedBounds;
262     }
263     Rectangle2D JavaDoc r = new Rectangle2D.Double JavaDoc();
264     if (curves.size() > 0) {
265         Curve c = (Curve) curves.get(0);
266         // First point is always an order 0 curve (moveto)
267
r.setRect(c.getX0(), c.getY0(), 0, 0);
268         for (int i = 1; i < curves.size(); i++) {
269         ((Curve) curves.get(i)).enlarge(r);
270         }
271     }
272     return (cachedBounds = r);
273     }
274
275     /**
276      * Returns a high precision bounding {@link Rectangle2D} that
277      * completely encloses this <code>Area</code>.
278      * <p>
279      * The Area class will attempt to return the tightest bounding
280      * box possible for the Shape. The bounding box will not be
281      * padded to include the control points of curves in the outline
282      * of the Shape, but should tightly fit the actual geometry of
283      * the outline itself.
284      * @return the bounding <code>Rectangle2D</code> for the
285      * <code>Area</code>.
286      */

287     public Rectangle2D JavaDoc getBounds2D() {
288     return getCachedBounds().getBounds2D();
289     }
290
291     /**
292      * Returns a bounding {@link Rectangle} that completely encloses
293      * this <code>Area</code>.
294      * <p>
295      * The Area class will attempt to return the tightest bounding
296      * box possible for the Shape. The bounding box will not be
297      * padded to include the control points of curves in the outline
298      * of the Shape, but should tightly fit the actual geometry of
299      * the outline itself. Since the returned object represents
300      * the bounding box with integers, the bounding box can only be
301      * as tight as the nearest integer coordinates that encompass
302      * the geometry of the Shape.
303      * @return the bounding <code>Rectangle</code> for the
304      * <code>Area</code>.
305      */

306     public Rectangle JavaDoc getBounds() {
307     return getCachedBounds().getBounds();
308     }
309
310     /**
311      * Returns an exact copy of this <code>Area</code> object.
312      * @return Created clone object
313      */

314     public Object JavaDoc clone() {
315     return new Area JavaDoc(this);
316     }
317
318     /**
319      * Tests whether the geometries of the two <code>Area</code> objects
320      * are equal.
321      * @param other the <code>Area</code> to be compared to this
322      * <code>Area</code>
323      * @return <code>true</code> if the two geometries are equal;
324      * <code>false</code> otherwise.
325      */

326     public boolean equals(Area JavaDoc other) {
327     // REMIND: A *much* simpler operation should be possible...
328
// Should be able to do a curve-wise comparison since all Areas
329
// should evaluate their curves in the same top-down order.
330
if (other == this) {
331         return true;
332     }
333     if (other == null) {
334         return false;
335     }
336     Vector JavaDoc c = new AreaOp.XorOp().calculate(this.curves, other.curves);
337     return c.isEmpty();
338     }
339
340     /**
341      * Transforms the geometry of this <code>Area</code> using the specified
342      * {@link AffineTransform}. The geometry is transformed in place, which
343      * permanently changes the enclosed area defined by this object.
344      * @param t the transformation used to transform the area
345      */

346     public void transform(AffineTransform JavaDoc t) {
347     // REMIND: A simpler operation can be performed for some types
348
// of transform.
349
// REMIND: this could be simplified by "breaking out" the
350
// PathIterator code from the constructor
351
curves = new Area JavaDoc(t.createTransformedShape(this)).curves;
352     invalidateBounds();
353     }
354
355     /**
356      * Creates a new <code>Area</code> object that contains the same
357      * geometry as this <code>Area</code> transformed by the specified
358      * <code>AffineTransform</code>. This <code>Area</code> object
359      * is unchanged.
360      * @param t the specified <code>AffineTransform</code> used to transform
361      * the new <code>Area</code>
362      * @return a new <code>Area</code> object representing the transformed
363      * geometry.
364      */

365     public Area JavaDoc createTransformedArea(AffineTransform JavaDoc t) {
366     // REMIND: A simpler operation can be performed for some types
367
// of transform.
368
// REMIND: this could be simplified by "breaking out" the
369
// PathIterator code from the constructor
370
return new Area JavaDoc(t.createTransformedShape(this));
371     }
372
373     /**
374      * Tests if a specifed point lies inside the boundary of
375      * this <code>Area</code> object.
376      * @param x,&nbsp;y the specified point
377      * @return <code>true</code> if the point lies completely within the
378      * interior of the <code>Area</code>;
379      * <code>false</code> otherwise.
380      */

381     public boolean contains(double x, double y) {
382     if (!getCachedBounds().contains(x, y)) {
383         return false;
384     }
385     Enumeration JavaDoc enum_ = curves.elements();
386     int crossings = 0;
387     while (enum_.hasMoreElements()) {
388         Curve c = (Curve) enum_.nextElement();
389         crossings += c.crossingsFor(x, y);
390     }
391     return ((crossings & 1) == 1);
392     }
393
394     /**
395      * Tests if a specified {@link Point2D} lies inside the boundary of the
396      * this <code>Area</code> object.
397      * @param p the <code>Point2D</code> to test
398      * @return <code>true</code> if the specified <code>Point2D</code>
399      * lies completely within the interior of the <code>Area</code>;
400      * <code>false</code> otherwise.
401      */

402     public boolean contains(Point2D JavaDoc p) {
403     return contains(p.getX(), p.getY());
404     }
405
406     /**
407      * Tests whether or not the interior of this <code>Area</code> object
408      * completely contains the specified rectangular area.
409      * @param x,&nbsp;y the coordinates of the upper left corner of
410      * the specified rectangular area
411      * @param w the width of the specified rectangular area
412      * @param h the height of the specified rectangular area
413      * @return <code>true</code> if the specified rectangular area
414      * lies completely within the interior of the <code>Area</code>;
415      * <code>false</code> otherwise.
416      */

417     public boolean contains(double x, double y, double w, double h) {
418     if (w < 0 || h < 0) {
419         return false;
420     }
421     if (!getCachedBounds().contains(x, y, w, h)) {
422         return false;
423     }
424     Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h);
425     return (c != null && c.covers(y, y+h));
426     }
427
428     /**
429      * Tests whether or not the interior of this <code>Area</code> object
430      * completely contains the specified <code>Rectangle2D</code>.
431      * @param p the <code>Rectangle2D</code> to test
432      * @return <code>true</code> if the <code>Rectangle2D</code> lies
433      * completely within the interior of the <code>Area</code>;
434      * <code>false</code> otherwise.
435      */

436     public boolean contains(Rectangle2D JavaDoc p) {
437     return contains(p.getX(), p.getY(), p.getWidth(), p.getHeight());
438     }
439
440     /**
441      * Tests whether the interior of this <code>Area</code> object
442      * intersects the interior of the specified rectangular area.
443      * @param x,&nbsp;y the coordinates of the upper left corner of
444      * the specified rectangular area
445      * @param w the width of the specified rectangular area
446      * @param h the height of teh specified rectangular area
447      * @return <code>true</code> if the interior intersects the specified
448      * rectangular area; <code>false</code> otherwise;
449      */

450     public boolean intersects(double x, double y, double w, double h) {
451     if (w < 0 || h < 0) {
452         return false;
453     }
454     if (!getCachedBounds().intersects(x, y, w, h)) {
455         return false;
456     }
457     Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h);
458     return (c == null || !c.isEmpty());
459     }
460
461     /**
462      * Tests whether the interior of this <code>Area</code> object
463      * intersects the interior of the specified <code>Rectangle2D</code>.
464      * @param p the <code>Rectangle2D</code> to test for intersection
465      * @return <code>true</code> if the interior intersects the
466      * specified <code>Rectangle2D</code>;
467      * <code>false</code> otherwise.
468      */

469     public boolean intersects(Rectangle2D JavaDoc p) {
470     return intersects(p.getX(), p.getY(), p.getWidth(), p.getHeight());
471     }
472
473     /**
474      * Creates a {@link PathIterator} for the outline of this
475      * <code>Area</code> object. This <code>Area</code> object is unchanged.
476      * @param at an optional <code>AffineTransform</code> to be applied to
477      * the coordinates as they are returned in the iteration, or
478      * <code>null</code> if untransformed coordinates are desired
479      * @return the <code>PathIterator</code> object that returns the
480      * geometry of the outline of this <code>Area</code>, one
481      * segment at a time.
482      */

483     public PathIterator JavaDoc getPathIterator(AffineTransform JavaDoc at) {
484     return new AreaIterator(curves, at);
485     }
486
487     /**
488      * Creates a <code>PathIterator</code> for the flattened outline of
489      * this <code>Area</code> object. Only uncurved path segments
490      * represented by the SEG_MOVETO, SEG_LINETO, and SEG_CLOSE point
491      * types are returned by the iterator. This <code>Area</code>
492      * object is unchanged.
493      * @param at an optional <code>AffineTransform</code> to be
494      * applied to the coordinates as they are returned in the
495      * iteration, or <code>null</code> if untransformed coordinates
496      * are desired
497      * @param flatness the maximum amount that the control points
498      * for a given curve can vary from colinear before a subdivided
499      * curve is replaced by a straight line connecting the endpoints
500      * @return the <code>PathIterator</code> object that returns the
501      * geometry of the outline of this <code>Area</code>, one segment
502      * at a time.
503      */

504     public PathIterator JavaDoc getPathIterator(AffineTransform JavaDoc at, double flatness) {
505     return new FlatteningPathIterator JavaDoc(getPathIterator(at), flatness);
506     }
507 }
508
509 class AreaIterator implements PathIterator JavaDoc {
510     private AffineTransform JavaDoc transform;
511     private Vector JavaDoc curves;
512     private int index;
513     private Curve prevcurve;
514     private Curve thiscurve;
515
516     public AreaIterator(Vector JavaDoc curves, AffineTransform JavaDoc at) {
517     this.curves = curves;
518     this.transform = at;
519     if (curves.size() >= 1) {
520         thiscurve = (Curve) curves.get(0);
521     }
522     }
523
524     public int getWindingRule() {
525     // REMIND: Which is better, EVEN_ODD or NON_ZERO?
526
// The paths calculated could be classified either way.
527
//return WIND_EVEN_ODD;
528
return WIND_NON_ZERO;
529     }
530
531     public boolean isDone() {
532     return (prevcurve == null && thiscurve == null);
533     }
534
535     public void next() {
536     if (prevcurve != null) {
537         prevcurve = null;
538     } else {
539         prevcurve = thiscurve;
540         index++;
541         if (index < curves.size()) {
542         thiscurve = (Curve) curves.get(index);
543         if (thiscurve.getOrder() != 0 &&
544             prevcurve.getX1() == thiscurve.getX0() &&
545             prevcurve.getY1() == thiscurve.getY0())
546         {
547             prevcurve = null;
548         }
549         } else {
550         thiscurve = null;
551         }
552     }
553     }
554
555     public int currentSegment(float coords[]) {
556     double dcoords[] = new double[6];
557     int segtype = currentSegment(dcoords);
558     int numpoints = (segtype == SEG_CLOSE ? 0
559              : (segtype == SEG_QUADTO ? 2
560                 : (segtype == SEG_CUBICTO ? 3
561                    : 1)));
562     for (int i = 0; i < numpoints * 2; i++) {
563         coords[i] = (float) dcoords[i];
564     }
565     return segtype;
566     }
567
568     public int currentSegment(double coords[]) {
569     int segtype;
570     int numpoints;
571     if (prevcurve != null) {
572         // Need to finish off junction between curves
573
if (thiscurve == null || thiscurve.getOrder() == 0) {
574         return SEG_CLOSE;
575         }
576         coords[0] = thiscurve.getX0();
577         coords[1] = thiscurve.getY0();
578         segtype = SEG_LINETO;
579         numpoints = 1;
580     } else if (thiscurve == null) {
581         throw new NoSuchElementException JavaDoc("area iterator out of bounds");
582     } else {
583         segtype = thiscurve.getSegment(coords);
584         numpoints = thiscurve.getOrder();
585         if (numpoints == 0) {
586         numpoints = 1;
587         }
588     }
589     if (transform != null) {
590         transform.transform(coords, 0, coords, 0, numpoints);
591     }
592     return segtype;
593     }
594 }
595
Popular Tags