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. 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. invalidateBounds();
  163. }
  164. /**
  165. * Tests whether this <code>Area</code> object encloses any area.
  166. * @return <code>true</code> if this <code>Area</code> object
  167. * represents an empty area; <code>false</code> otherwise.
  168. */
  169. public boolean isEmpty() {
  170. return (curves.size() == 0);
  171. }
  172. /**
  173. * Tests whether this <code>Area</code> consists entirely of
  174. * straight edged polygonal geometry.
  175. * @return <code>true</code> if the geometry of this
  176. * <code>Area</code> consists entirely of line segments;
  177. * <code>false</code> otherwise.
  178. */
  179. public boolean isPolygonal() {
  180. Enumeration enum_ = curves.elements();
  181. while (enum_.hasMoreElements()) {
  182. if (((Curve) enum_.nextElement()).getOrder() > 1) {
  183. return false;
  184. }
  185. }
  186. return true;
  187. }
  188. /**
  189. * Tests whether this <code>Area</code> is rectangular in shape.
  190. * @return <code>true</code> if the geometry of this
  191. * <code>Area</code> is rectangular in shape; <code>false</code>
  192. * otherwise.
  193. */
  194. public boolean isRectangular() {
  195. int size = curves.size();
  196. if (size == 0) {
  197. return true;
  198. }
  199. if (size > 3) {
  200. return false;
  201. }
  202. Curve c1 = (Curve) curves.get(1);
  203. Curve c2 = (Curve) curves.get(2);
  204. if (c1.getOrder() != 1 || c2.getOrder() != 1) {
  205. return false;
  206. }
  207. if (c1.getXTop() != c1.getXBot() || c2.getXTop() != c2.getXBot()) {
  208. return false;
  209. }
  210. if (c1.getYTop() != c2.getYTop() || c1.getYBot() != c2.getYBot()) {
  211. // One might be able to prove that this is impossible...
  212. return false;
  213. }
  214. return true;
  215. }
  216. /**
  217. * Tests whether this <code>Area</code> is comprised of a single
  218. * closed subpath. This method returns <code>true</code> if the
  219. * path contains 0 or 1 subpaths, or <code>false</code> if the path
  220. * contains more than 1 subpath. The subpaths are counted by the
  221. * number of {@link PathIterator#SEG_MOVETO SEG_MOVETO} segments
  222. * that appear in the path.
  223. * @return <code>true</code> if the <code>Area</code> is comprised
  224. * of a single basic geometry; <code>false</code> otherwise.
  225. */
  226. public boolean isSingular() {
  227. if (curves.size() < 3) {
  228. return true;
  229. }
  230. Enumeration enum_ = curves.elements();
  231. enum_.nextElement(); // First Order0 "moveto"
  232. while (enum_.hasMoreElements()) {
  233. if (((Curve) enum_.nextElement()).getOrder() == 0) {
  234. return false;
  235. }
  236. }
  237. return true;
  238. }
  239. private Rectangle2D cachedBounds;
  240. private void invalidateBounds() {
  241. cachedBounds = null;
  242. }
  243. private Rectangle2D getCachedBounds() {
  244. if (cachedBounds != null) {
  245. return cachedBounds;
  246. }
  247. Rectangle2D r = new Rectangle2D.Double();
  248. if (curves.size() > 0) {
  249. Curve c = (Curve) curves.get(0);
  250. // First point is always an order 0 curve (moveto)
  251. r.setRect(c.getX0(), c.getY0(), 0, 0);
  252. for (int i = 1; i < curves.size(); i++) {
  253. ((Curve) curves.get(i)).enlarge(r);
  254. }
  255. }
  256. return (cachedBounds = r);
  257. }
  258. /**
  259. * Returns a high precision bounding {@link Rectangle2D} that
  260. * completely encloses this <code>Area</code>.
  261. * <p>
  262. * The Area class will attempt to return the tightest bounding
  263. * box possible for the Shape. The bounding box will not be
  264. * padded to include the control points of curves in the outline
  265. * of the Shape, but should tightly fit the actual geometry of
  266. * the outline itself.
  267. * @return the bounding <code>Rectangle2D</code> for the
  268. * <code>Area</code>.
  269. */
  270. public Rectangle2D getBounds2D() {
  271. return getCachedBounds().getBounds2D();
  272. }
  273. /**
  274. * Returns a bounding {@link Rectangle} that completely encloses
  275. * this <code>Area</code>.
  276. * <p>
  277. * The Area class will attempt to return the tightest bounding
  278. * box possible for the Shape. The bounding box will not be
  279. * padded to include the control points of curves in the outline
  280. * of the Shape, but should tightly fit the actual geometry of
  281. * the outline itself. Since the returned object represents
  282. * the bounding box with integers, the bounding box can only be
  283. * as tight as the nearest integer coordinates that encompass
  284. * the geometry of the Shape.
  285. * @return the bounding <code>Rectangle</code> for the
  286. * <code>Area</code>.
  287. */
  288. public Rectangle getBounds() {
  289. return getCachedBounds().getBounds();
  290. }
  291. /**
  292. * Returns an exact copy of this <code>Area</code> object.
  293. * @return Created clone object
  294. */
  295. public Object clone() {
  296. return new Area(this);
  297. }
  298. /**
  299. * Tests whether the geometries of the two <code>Area</code> objects
  300. * are equal.
  301. * @param other the <code>Area</code> to be compared to this
  302. * <code>Area</code>
  303. * @return <code>true</code> if the two geometries are equal;
  304. * <code>false</code> otherwise.
  305. */
  306. public boolean equals(Area other) {
  307. // REMIND: A *much* simpler operation should be possible...
  308. // Should be able to do a curve-wise comparison since all Areas
  309. // should evaluate their curves in the same top-down order.
  310. if (other == this) {
  311. return true;
  312. }
  313. if (other == null) {
  314. return false;
  315. }
  316. Vector c = new AreaOp.XorOp().calculate(this.curves, other.curves);
  317. return c.isEmpty();
  318. }
  319. /**
  320. * Transforms the geometry of this <code>Area</code> using the specified
  321. * {@link AffineTransform}. The geometry is transformed in place, which
  322. * permanently changes the enclosed area defined by this object.
  323. * @param t the transformation used to transform the area
  324. */
  325. public void transform(AffineTransform t) {
  326. // REMIND: A simpler operation can be performed for some types
  327. // of transform.
  328. // REMIND: this could be simplified by "breaking out" the
  329. // PathIterator code from the constructor
  330. curves = new Area(t.createTransformedShape(this)).curves;
  331. invalidateBounds();
  332. }
  333. /**
  334. * Creates a new <code>Area</code> object that contains the same
  335. * geometry as this <code>Area</code> transformed by the specified
  336. * <code>AffineTransform</code>. This <code>Area</code> object
  337. * is unchanged.
  338. * @param t the specified <code>AffineTransform</code> used to transform
  339. * the new <code>Area</code>
  340. * @return a new <code>Area</code> object representing the transformed
  341. * geometry.
  342. */
  343. public Area createTransformedArea(AffineTransform t) {
  344. // REMIND: A simpler operation can be performed for some types
  345. // of transform.
  346. // REMIND: this could be simplified by "breaking out" the
  347. // PathIterator code from the constructor
  348. return new Area(t.createTransformedShape(this));
  349. }
  350. /**
  351. * Tests if a specifed point lies inside the boundary of
  352. * this <code>Area</code> object.
  353. * @param x, y the specified point
  354. * @return <code>true</code> if the point lies completely within the
  355. * interior of the <code>Area</code>
  356. * <code>false</code> otherwise.
  357. */
  358. public boolean contains(double x, double y) {
  359. if (!getCachedBounds().contains(x, y)) {
  360. return false;
  361. }
  362. Enumeration enum_ = curves.elements();
  363. int crossings = 0;
  364. while (enum_.hasMoreElements()) {
  365. Curve c = (Curve) enum_.nextElement();
  366. crossings += c.crossingsFor(x, y);
  367. }
  368. return ((crossings & 1) == 1);
  369. }
  370. /**
  371. * Tests if a specified {@link Point2D} lies inside the boundary of the
  372. * this <code>Area</code> object.
  373. * @param p the <code>Point2D</code> to test
  374. * @return <code>true</code> if the specified <code>Point2D</code>
  375. * lies completely within the interior of the <code>Area</code>
  376. * <code>false</code> otherwise.
  377. */
  378. public boolean contains(Point2D p) {
  379. return contains(p.getX(), p.getY());
  380. }
  381. /**
  382. * Tests whether or not the interior of this <code>Area</code> object
  383. * completely contains the specified rectangular area.
  384. * @param x, y the coordinates of the upper left corner of
  385. * the specified rectangular area
  386. * @param w the width of the specified rectangular area
  387. * @param h the height of the specified rectangular area
  388. * @return <code>true</code> if the specified rectangular area
  389. * lies completely within the interior of the <code>Area</code>
  390. * <code>false</code> otherwise.
  391. */
  392. public boolean contains(double x, double y, double w, double h) {
  393. if (w < 0 || h < 0) {
  394. return false;
  395. }
  396. if (!getCachedBounds().contains(x, y, w, h)) {
  397. return false;
  398. }
  399. Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h);
  400. return (c != null && c.covers(y, y+h));
  401. }
  402. /**
  403. * Tests whether or not the interior of this <code>Area</code> object
  404. * completely contains the specified <code>Rectangle2D</code>.
  405. * @param p the <code>Rectangle2D</code> to test
  406. * @return <code>true</code> if the <code>Rectangle2D</code> lies
  407. * completely within the interior of the <code>Area</code>
  408. * <code>false</code> otherwise.
  409. */
  410. public boolean contains(Rectangle2D p) {
  411. return contains(p.getX(), p.getY(), p.getWidth(), p.getHeight());
  412. }
  413. /**
  414. * Tests whether the interior of this <code>Area</code> object
  415. * intersects the interior of the specified rectangular area.
  416. * @param x, y the coordinates of the upper left corner of
  417. * the specified rectangular area
  418. * @param w the width of the specified rectangular area
  419. * @param h the height of teh specified rectangular area
  420. * @return <code>true</code> if the interior intersects the specified
  421. * rectangular area; <code>false</code> otherwise;
  422. */
  423. public boolean intersects(double x, double y, double w, double h) {
  424. if (w < 0 || h < 0) {
  425. return false;
  426. }
  427. if (!getCachedBounds().intersects(x, y, w, h)) {
  428. return false;
  429. }
  430. Crossings c = Crossings.findCrossings(curves, x, y, x+w, y+h);
  431. return (c == null || !c.isEmpty());
  432. }
  433. /**
  434. * Tests whether the interior of this <code>Area</code> object
  435. * intersects the interior of the specified <code>Rectangle2D</code>.
  436. * @param p the <code>Rectangle2D</code> to test for intersection
  437. * @return <code>true</code> if the interior intersects the
  438. * specified <code>Rectangle2D</code>
  439. * <code>false</code> otherwise.
  440. */
  441. public boolean intersects(Rectangle2D p) {
  442. return intersects(p.getX(), p.getY(), p.getWidth(), p.getHeight());
  443. }
  444. /**
  445. * Creates a {@link PathIterator} for the outline of this
  446. * <code>Area</code> object. This <code>Area</code> object is unchanged.
  447. * @param at an optional <code>AffineTransform</code> to be applied to
  448. * the coordinates as they are returned in the iteration, or
  449. * <code>null</code> if untransformed coordinates are desired
  450. * @return the <code>PathIterator</code> object that returns the
  451. * geometry of the outline of this <code>Area</code>, one
  452. * segment at a time.
  453. */
  454. public PathIterator getPathIterator(AffineTransform at) {
  455. return new AreaIterator(curves, at);
  456. }
  457. /**
  458. * Creates a <code>PathIterator</code> for the flattened outline of
  459. * this <code>Area</code> object. Only uncurved path segments
  460. * represented by the SEG_MOVETO, SEG_LINETO, and SEG_CLOSE point
  461. * types are returned by the iterator. This <code>Area</code>
  462. * object is unchanged.
  463. * @param at an optional <code>AffineTransform</code> to be
  464. * applied to the coordinates as they are returned in the
  465. * iteration, or <code>null</code> if untransformed coordinates
  466. * are desired
  467. * @param flatness the maximum amount that the control points
  468. * for a given curve can vary from colinear before a subdivided
  469. * curve is replaced by a straight line connecting the endpoints
  470. * @return the <code>PathIterator</code> object that returns the
  471. * geometry of the outline of this <code>Area</code>, one segment
  472. * at a time.
  473. */
  474. public PathIterator getPathIterator(AffineTransform at, double flatness) {
  475. return new FlatteningPathIterator(getPathIterator(at), flatness);
  476. }
  477. }
  478. class AreaIterator implements PathIterator {
  479. private AffineTransform transform;
  480. private Vector curves;
  481. private int index;
  482. private Curve prevcurve;
  483. private Curve thiscurve;
  484. public AreaIterator(Vector curves, AffineTransform at) {
  485. this.curves = curves;
  486. this.transform = at;
  487. if (curves.size() >= 1) {
  488. thiscurve = (Curve) curves.get(0);
  489. }
  490. }
  491. public int getWindingRule() {
  492. // REMIND: Which is better, EVEN_ODD or NON_ZERO?
  493. // The paths calculated could be classified either way.
  494. //return WIND_EVEN_ODD;
  495. return WIND_NON_ZERO;
  496. }
  497. public boolean isDone() {
  498. return (prevcurve == null && thiscurve == null);
  499. }
  500. public void next() {
  501. if (prevcurve != null) {
  502. prevcurve = null;
  503. } else {
  504. prevcurve = thiscurve;
  505. index++;
  506. if (index < curves.size()) {
  507. thiscurve = (Curve) curves.get(index);
  508. if (thiscurve.getOrder() != 0 &&
  509. prevcurve.getX1() == thiscurve.getX0() &&
  510. prevcurve.getY1() == thiscurve.getY0())
  511. {
  512. prevcurve = null;
  513. }
  514. } else {
  515. thiscurve = null;
  516. }
  517. }
  518. }
  519. public int currentSegment(float coords[]) {
  520. double dcoords[] = new double[6];
  521. int segtype = currentSegment(dcoords);
  522. int numpoints = (segtype == SEG_CLOSE ? 0
  523. : (segtype == SEG_QUADTO ? 2
  524. : (segtype == SEG_CUBICTO ? 3
  525. : 1)));
  526. for (int i = 0; i < numpoints * 2; i++) {
  527. coords[i] = (float) dcoords[i];
  528. }
  529. return segtype;
  530. }
  531. public int currentSegment(double coords[]) {
  532. int segtype;
  533. int numpoints;
  534. if (prevcurve != null) {
  535. // Need to finish off junction between curves
  536. if (thiscurve == null || thiscurve.getOrder() == 0) {
  537. return SEG_CLOSE;
  538. }
  539. coords[0] = thiscurve.getX0();
  540. coords[1] = thiscurve.getY0();
  541. segtype = SEG_LINETO;
  542. numpoints = 1;
  543. } else if (thiscurve == null) {
  544. throw new NoSuchElementException("area iterator out of bounds");
  545. } else {
  546. segtype = thiscurve.getSegment(coords);
  547. numpoints = thiscurve.getOrder();
  548. if (numpoints == 0) {
  549. numpoints = 1;
  550. }
  551. }
  552. if (transform != null) {
  553. transform.transform(coords, 0, coords, 0, numpoints);
  554. }
  555. return segtype;
  556. }
  557. }