1. /*
  2. * @(#)Area.java 1.4 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.awt.geom;
  8. import java.awt.Shape;
  9. import java.awt.Rectangle;
  10. import java.util.Vector;
  11. import java.util.Enumeration;
  12. import java.util.NoSuchElementException;
  13. import sun.awt.geom.Curve;
  14. import sun.awt.geom.Crossings;
  15. import sun.awt.geom.AreaOp;
  16. /**
  17. * The <code>Area</code> class is a device-independent specification of an
  18. * arbitrarily-shaped area. The <code>Area</code> object is defined as an
  19. * object that performs certain binary CAG (Constructive Area Geometry)
  20. * operations on other area-enclosing geometries, such as rectangles,
  21. * ellipses, and polygons. The CAG operations are Add(union), Subtract,
  22. * Intersect, and ExclusiveOR. For example, an <code>Area</code> can be
  23. * made up of the area of a rectangle minus the area of an ellipse.
  24. */
  25. public class Area implements Shape, Cloneable {
  26. private static Vector EmptyCurves = new Vector();
  27. private Vector curves;
  28. /**
  29. * Default constructor which creates an empty area.
  30. */
  31. public Area() {
  32. curves = EmptyCurves;
  33. }
  34. /**
  35. * The <code>Area</code> class creates an area geometry from the
  36. * specified {@link Shape} object. The geometry is explicitly
  37. * closed, if the <code>Shape</code> is not already closed. The
  38. * fill rule (even-odd or winding) specified by the geometry of the
  39. * <code>Shape</code> is used to determine the resulting enclosed area.
  40. * @param s the <code>Shape</code> from which the area is constructed
  41. */
  42. public Area(Shape s) {
  43. if (s instanceof Area) {
  44. curves = ((Area) s).curves;
  45. return;
  46. }
  47. curves = new Vector();
  48. PathIterator pi = s.getPathIterator(null);
  49. int windingRule = pi.getWindingRule();
  50. // coords array is big enough for holding:
  51. // coordinates returned from currentSegment (6)
  52. // OR
  53. // two subdivided quadratic curves (2+4+4=10)
  54. // AND
  55. // 0-1 horizontal splitting parameters
  56. // OR
  57. // 2 parametric equation derivative coefficients
  58. // OR
  59. // three subdivided cubic curves (2+6+6+6=20)
  60. // AND
  61. // 0-2 horizontal splitting parameters
  62. // OR
  63. // 3 parametric equation derivative coefficients
  64. double coords[] = new double[23];
  65. double movx = 0, movy = 0;
  66. double curx = 0, cury = 0;
  67. double newx, newy;
  68. while (!pi.isDone()) {
  69. switch (pi.currentSegment(coords)) {
  70. case PathIterator.SEG_MOVETO:
  71. Curve.insertLine(curves, curx, cury, movx, movy);
  72. curx = movx = coords[0];
  73. cury = movy = coords[1];
  74. Curve.insertMove(curves, movx, movy);
  75. break;
  76. case PathIterator.SEG_LINETO:
  77. newx = coords[0];
  78. newy = coords[1];
  79. Curve.insertLine(curves, curx, cury, newx, newy);
  80. curx = newx;
  81. cury = newy;
  82. break;
  83. case PathIterator.SEG_QUADTO:
  84. newx = coords[2];
  85. newy = coords[3];
  86. Curve.insertQuad(curves, curx, cury, coords);
  87. curx = newx;
  88. cury = newy;
  89. break;
  90. case PathIterator.SEG_CUBICTO:
  91. newx = coords[4];
  92. newy = coords[5];
  93. Curve.insertCubic(curves, curx, cury, coords);
  94. curx = newx;
  95. cury = newy;
  96. break;
  97. case PathIterator.SEG_CLOSE:
  98. Curve.insertLine(curves, curx, cury, movx, movy);
  99. curx = movx;
  100. cury = movy;
  101. break;
  102. }
  103. pi.next();
  104. }
  105. Curve.insertLine(curves, curx, cury, movx, movy);
  106. AreaOp operator;
  107. if (windingRule == PathIterator.WIND_EVEN_ODD) {
  108. operator = new AreaOp.EOWindOp();
  109. } else {
  110. operator = new AreaOp.NZWindOp();
  111. }
  112. curves = operator.calculate(this.curves, EmptyCurves);
  113. }
  114. /**
  115. * Adds the shape of the specified <code>Area</code> to the
  116. * shape of this <code>Area</code>.
  117. * Addition is achieved through union.
  118. * @param rhs the <code>Area</code> to be added to the
  119. * current shape
  120. */
  121. public void add(Area rhs) {
  122. curves = new AreaOp.AddOp().calculate(this.curves, rhs.curves);
  123. invalidateBounds();
  124. }
  125. /**
  126. * Subtracts the shape of the specified <code>Area</code> from the
  127. * shape of this <code>Area</code>.
  128. * @param rhs the <code>Area</code> to be subtracted from the
  129. * current shape
  130. */
  131. public void subtract(Area rhs) {
  132. curves = new AreaOp.SubOp().calculate(this.curves, rhs.curves);
  133. invalidateBounds();
  134. }
  135. /**
  136. * Sets the shape of this <code>Area</code> to the intersection of
  137. * its current shape and the shape of the specified <code>Area</code>.
  138. * @param rhs the <code>Area</code> to be intersected with this
  139. * <code>Area</code>
  140. */
  141. public void intersect(Area rhs) {
  142. curves = new AreaOp.IntOp().calculate(this.curves, rhs.curves);
  143. invalidateBounds();
  144. }
  145. /**
  146. * Sets the shape of this <code>Area</code> to be the combined area
  147. * of its current shape and the shape of the specified <code>Area</code>,
  148. * minus their intersection.
  149. * @param rhs the <code>Area</code> to be exclusive ORed with this
  150. * <code>Area</code>.
  151. */
  152. public void exclusiveOr(Area rhs) {
  153. curves = new AreaOp.XorOp().calculate(this.curves, rhs.curves);
  154. invalidateBounds();
  155. }
  156. /**
  157. * Removes all of the geometry from this <code>Area</code> and
  158. * restores it to an empty area.
  159. */
  160. public void reset() {
  161. curves = new Vector();
  162. }
  163. /**
  164. * Tests whether this <code>Area</code> object encloses any area.
  165. * @return <code>true</code> if this <code>Area</code> object
  166. * represents an empty area; <code>false</code> otherwise.
  167. */
  168. public boolean isEmpty() {
  169. return (curves.size() == 0);
  170. }
  171. /**
  172. * Tests whether this <code>Area</code> consists entirely of
  173. * straight edged polygonal geometry.
  174. * @return <code>true</code> if the geometry of this
  175. * <code>Area</code> consists entirely of line segments;
  176. * <code>false</code> otherwise.
  177. */
  178. public boolean isPolygonal() {
  179. Enumeration enum = curves.elements();
  180. while (enum.hasMoreElements()) {
  181. if (((Curve) enum.nextElement()).getOrder() > 1) {
  182. return false;
  183. }
  184. }
  185. return true;
  186. }
  187. /**
  188. * Tests whether this <code>Area</code> is rectangular in shape.
  189. * @return <code>true</code> if the geometry of this
  190. * <code>Area</code> is rectangular in shape; <code>false</code>
  191. * otherwise.
  192. */
  193. public boolean isRectangular() {
  194. int size = curves.size();
  195. if (size == 0) {
  196. return true;
  197. }
  198. if (size > 3) {
  199. return false;
  200. }
  201. Curve c1 = (Curve) curves.get(1);
  202. Curve c2 = (Curve) curves.get(2);
  203. if (c1.getOrder() != 1 || c2.getOrder() != 1) {
  204. return false;
  205. }
  206. if (c1.getXTop() != c1.getXBot() || c2.getXTop() != c2.getXBot()) {
  207. return false;
  208. }
  209. if (c1.getYTop() != c2.getYTop() || c1.getYBot() != c2.getYBot()) {
  210. // One might be able to prove that this is impossible...
  211. return false;
  212. }
  213. return true;
  214. }
  215. /**
  216. * Tests whether this <code>Area</code> is comprised of a single
  217. * closed subpath.
  218. * @return <code>true</code> if the <code>Area</code> is comprised
  219. * of a single basic geometry; <code>false</code> otherwise.
  220. */
  221. public boolean isSingular() {
  222. if (curves.size() < 3) {
  223. return true;
  224. }
  225. Enumeration enum = curves.elements();
  226. enum.nextElement(); // First Order0 "moveto"
  227. while (enum.hasMoreElements()) {
  228. if (((Curve) enum.nextElement()).getOrder() == 0) {
  229. return false;
  230. }
  231. }
  232. return true;
  233. }
  234. private Rectangle2D cachedBounds;
  235. private void invalidateBounds() {
  236. cachedBounds = null;
  237. }
  238. private Rectangle2D getCachedBounds() {
  239. if (cachedBounds != null) {
  240. return cachedBounds;
  241. }
  242. Rectangle2D r = new Rectangle2D.Double();
  243. if (curves.size() > 0) {
  244. Curve c = (Curve) curves.get(0);
  245. // First point is always an order 0 curve (moveto)
  246. r.setRect(c.getX0(), c.getY0(), 0, 0);
  247. for (int i = 1; i < curves.size(); i++) {
  248. ((Curve) curves.get(i)).enlarge(r);
  249. }
  250. }
  251. return (cachedBounds = r);
  252. }
  253. /**
  254. * Returns a high precision bounding {@link Rectangle2D} that
  255. * completely encloses this <code>Area</code>.
  256. * @return the bounding <code>Rectangle2D</code> for the
  257. * <code>Area</code>.
  258. */
  259. public Rectangle2D getBounds2D() {
  260. return getCachedBounds().getBounds2D();
  261. }
  262. /**
  263. * Returns a bounding {@link Rectangle} that completely encloses
  264. * this <code>Area</code>.
  265. * @return the bounding <code>Rectangle</code> for the
  266. * <code>Area</code>.
  267. */
  268. public Rectangle getBounds() {
  269. return getCachedBounds().getBounds();
  270. }
  271. /**
  272. * Returns an exact copy of this <code>Area</code> object.
  273. * @return Created clone object
  274. */
  275. public Object clone() {
  276. return new Area(this);
  277. }
  278. /**
  279. * Tests whether the geometries of the two <code>Area</code> objects
  280. * are equal.
  281. * @param rhs the <code>Area</code> to be compared to this
  282. * <code>Area</code>
  283. * @return <code>true</code> if the two geometries are equal;
  284. * <code>false</code> otherwise.
  285. */
  286. public boolean equals(Area other) {
  287. // REMIND: A *much* simpler operation should be possible...
  288. // Should be able to do a curve-wise comparison since all Areas
  289. // should evaluate their curves in the same top-down order.
  290. if (other == this) {
  291. return true;
  292. }
  293. Vector c = new AreaOp.XorOp().calculate(this.curves, other.curves);
  294. return c.isEmpty();
  295. }
  296. /**
  297. * Transforms the geometry of this <code>Area</code> using the specified
  298. * {@link AffineTransform}. The geometry is transformed in place, which
  299. * permanently changes the enclosed area defined by this object.
  300. * @param t the transformation used to transform the area
  301. */
  302. public void transform(AffineTransform t) {
  303. // REMIND: A simpler operation can be performed for some types
  304. // of transform.
  305. // REMIND: this could be simplified by "breaking out" the
  306. // PathIterator code from the constructor
  307. curves = new Area(t.createTransformedShape(this)).curves;
  308. }
  309. /**
  310. * Creates a new <code>Area</code> object that contains the same
  311. * geometry as this <code>Area</code> transformed by the specified
  312. * <code>AffineTransform</code>. This <code>Area</code> object
  313. * is unchanged.
  314. * @param t the specified <code>AffineTransform</code> used to transform
  315. * the new <code>Area</code>
  316. * @return a new <code>Area</code> object representing the transformed
  317. * geometry.
  318. */
  319. public Area createTransformedArea(AffineTransform t) {
  320. // REMIND: A simpler operation can be performed for some types
  321. // of transform.
  322. // REMIND: this could be simplified by "breaking out" the
  323. // PathIterator code from the constructor
  324. return new Area(t.createTransformedShape(this));
  325. }
  326. /**
  327. * Tests if a specifed point lies inside the boundary of
  328. * this <code>Area</code> object.
  329. * @param x, y the specified point
  330. * @return <code>true</code> if the point lies completely within the
  331. * interior of the <code>Area</code>
  332. * <code>false</code> otherwise.
  333. */
  334. public boolean contains(double x, double y) {
  335. if (!getCachedBounds().contains(x, y)) {
  336. return false;
  337. }
  338. Enumeration enum = curves.elements();
  339. int crossings = 0;
  340. while (enum.hasMoreElements()) {
  341. Curve c = (Curve) enum.nextElement();
  342. crossings += c.crossingsFor(x, y);
  343. }
  344. return ((crossings & 1) == 1);
  345. }
  346. /**
  347. * Tests if a specified {@link Point2D} lies inside the boundary of the
  348. * this <code>Area</code> object.
  349. * @param p the <code>Point2D</code> to test
  350. * @return <code>true</code> if the specified <code>Point2D</code>
  351. * lies completely within the interior of the <code>Area</code>
  352. * <code>false</code> otherwise.
  353. */
  354. public boolean contains(Point2D p) {
  355. return contains(p.getX(), p.getY());
  356. }
  357. /**
  358. * Tests whether or not the interior of this <code>Area</code> object
  359. * completely contains the specified rectangular area.
  360. * @param x, y the coordinates of the upper left corner of
  361. * the specified rectangular area
  362. * @param w the width of the specified rectangular area
  363. * @param h the height of the specified rectangular area
  364. * @return <code>true</code> if the specified rectangular area
  365. * lies completely within the interior of the <code>Area</code>
  366. * <code>false</code> otherwise.
  367. */
  368. public boolean contains(double x, double y, double w, double h) {
  369. if (w < 0 || h < 0) {
  370. return false;
  371. }
  372. if (!getCachedBounds().contains(x, y, w, h)) {
  373. return false;
  374. }
  375. Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h);
  376. return (c != null && c.covers(y, y+h));
  377. }
  378. /**
  379. * Tests whether or not the interior of this <code>Area</code> object
  380. * completely contains the specified <code>Rectangle2D</code>.
  381. * @param r the <code>Rectangle2D</code> to test
  382. * @return <code>true</code> if the <code>Rectangle2D</code> lies
  383. * completely within the interior of the <code>Area</code>
  384. * <code>false</code> otherwise.
  385. */
  386. public boolean contains(Rectangle2D p) {
  387. return contains(p.getX(), p.getY(), p.getWidth(), p.getHeight());
  388. }
  389. /**
  390. * Tests whether the interior of this <code>Area</code> object
  391. * intersects the interior of the specified rectangular area.
  392. * @param x, y the coordinates of the upper left corner of
  393. * the specified rectangular area
  394. * @param w the width of the specified rectangular area
  395. * @param h the height of teh specified rectangular area
  396. * @return <code>true</code> if the interior intersects the specified
  397. * rectangular area; <code>false</code> otherwise;
  398. */
  399. public boolean intersects(double x, double y, double w, double h) {
  400. if (w < 0 || h < 0) {
  401. return false;
  402. }
  403. if (!getCachedBounds().intersects(x, y, w, h)) {
  404. return false;
  405. }
  406. Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h);
  407. return (c == null || !c.isEmpty());
  408. }
  409. /**
  410. * Tests whether the interior of this <code>Area</code> object
  411. * intersects the interior of the specified <code>Rectangle2D</code>.
  412. * @param r the <code>Rectangle2D</code> to test for intersection
  413. * @return <code>true</code> if the interior intersects the
  414. * specified <code>Rectangle2D</code>
  415. * <code>false</code> otherwise.
  416. */
  417. public boolean intersects(Rectangle2D p) {
  418. return intersects(p.getX(), p.getY(), p.getWidth(), p.getHeight());
  419. }
  420. /**
  421. * Creates a {@link PathIterator} for the outline of this
  422. * <code>Area</code> object. This <code>Area</code> object is unchanged.
  423. * @param t an optional <code>AffineTransform</code> to be applied to the
  424. * coordinates as they are returned in the iteration, or <code>null</code>
  425. * if untransformed coordinates are desired
  426. * @return the <code>PathIterator</code> object that returns the
  427. * geometry of the outline of this <code>Area</code>, one
  428. * segment at a time.
  429. */
  430. public PathIterator getPathIterator(AffineTransform at) {
  431. return new AreaIterator(curves, at);
  432. }
  433. /**
  434. * Creates a <code>PathIterator</code> for the flattened outline of
  435. * this <code>Area</code> object. Only uncurved path segments
  436. * represented by the SEG_MOVETO, SEG_LINETO, and SEG_CLOSE point
  437. * types are returned by the iterator. This <code>Area</code>
  438. * object is unchanged.
  439. * @param t an optional <code>AffineTransform</code> to be applied to the
  440. * coordinates as they are returned in the iteration, or <code>null</code>
  441. * if untransformed coordinates are desired
  442. * @param flatness the maximum amount that the control points
  443. * for a given curve can vary from colinear before a subdivided
  444. * curve is replaced by a straight line connecting the endpoints
  445. * @return the <code>PathIterator</code> object that returns the
  446. * geometry of the outline of this <code>Area</code>, one segment
  447. * at a time.
  448. */
  449. public PathIterator getPathIterator(AffineTransform at, double flatness) {
  450. return new FlatteningPathIterator(getPathIterator(at), flatness);
  451. }
  452. }
  453. class AreaIterator implements PathIterator {
  454. private AffineTransform transform;
  455. private Vector curves;
  456. private int index;
  457. private Curve prevcurve;
  458. private Curve thiscurve;
  459. public AreaIterator(Vector curves, AffineTransform at) {
  460. this.curves = curves;
  461. this.transform = at;
  462. if (curves.size() >= 1) {
  463. thiscurve = (Curve) curves.get(0);
  464. }
  465. }
  466. public int getWindingRule() {
  467. // REMIND: Which is better, EVEN_ODD or NON_ZERO?
  468. // The paths calculated could be classified either way.
  469. //return WIND_EVEN_ODD;
  470. return WIND_NON_ZERO;
  471. }
  472. public boolean isDone() {
  473. return (prevcurve == null && thiscurve == null);
  474. }
  475. public void next() {
  476. if (prevcurve != null) {
  477. prevcurve = null;
  478. } else {
  479. prevcurve = thiscurve;
  480. index++;
  481. if (index < curves.size()) {
  482. thiscurve = (Curve) curves.get(index);
  483. if (thiscurve.getOrder() != 0 &&
  484. prevcurve.getX1() == thiscurve.getX0() &&
  485. prevcurve.getY1() == thiscurve.getY0())
  486. {
  487. prevcurve = null;
  488. }
  489. } else {
  490. thiscurve = null;
  491. }
  492. }
  493. }
  494. public int currentSegment(float coords[]) {
  495. double dcoords[] = new double[6];
  496. int segtype = currentSegment(dcoords);
  497. int numpoints = (segtype == SEG_CLOSE ? 0
  498. : (segtype == SEG_QUADTO ? 2
  499. : (segtype == SEG_CUBICTO ? 3
  500. : 1)));
  501. for (int i = 0; i < numpoints * 2; i++) {
  502. coords[i] = (float) dcoords[i];
  503. }
  504. return segtype;
  505. }
  506. public int currentSegment(double coords[]) {
  507. int segtype;
  508. int numpoints;
  509. if (prevcurve != null) {
  510. // Need to finish off junction between curves
  511. if (thiscurve == null || thiscurve.getOrder() == 0) {
  512. return SEG_CLOSE;
  513. }
  514. coords[0] = thiscurve.getX0();
  515. coords[1] = thiscurve.getY0();
  516. segtype = SEG_LINETO;
  517. numpoints = 1;
  518. } else if (thiscurve == null) {
  519. throw new NoSuchElementException("area iterator out of bounds");
  520. } else {
  521. segtype = thiscurve.getSegment(coords);
  522. numpoints = thiscurve.getOrder();
  523. if (numpoints == 0) {
  524. numpoints = 1;
  525. }
  526. }
  527. if (transform != null) {
  528. transform.transform(coords, 0, coords, 0, numpoints);
  529. }
  530. return segtype;
  531. }
  532. }