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