001/* 002 * @(#)Area.java 1.21 06/02/24 003 * 004 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 005 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 006 */ 007package sec.sun.awt.geom; 008 009import armyc2.c2sd.graphics2d.*; 010import sec.geo.ShapeObject; 011 012/** 013 * An <code>Area</code> object stores and manipulates a resolution-independent 014 * description of an enclosed area of 2-dimensional space. <code>Area</code> 015 * objects can be transformed and can perform various Constructive Area Geometry 016 * (CAG) operations when combined with other <code>Area</code> objects. The CAG 017 * operations include area {@link #add addition}, {@link #subtract subtraction}, 018 * {@link #intersect intersection}, and {@link #exclusiveOr exclusive or}. See 019 * the linked method documentation for examples of the various operations. 020 * <p> 021 * The <code>Area</code> class implements the <code>Shape</code> interface and 022 * provides full support for all of its hit-testing and path iteration 023 * facilities, but an <code>Area</code> is more specific than a generalized path 024 * in a number of ways: 025 * <ul> 026 * <li>Only closed paths and sub-paths are stored. <code>Area</code> objects 027 * constructed from unclosed paths are implicitly closed during construction as 028 * if those paths had been filled by the <code>Graphics2D.fill</code> method. 029 * <li>The interiors of the individual stored sub-paths are all non-empty and 030 * non-overlapping. Paths are decomposed during construction into separate 031 * component non-overlapping parts, empty pieces of the path are discarded, and 032 * then these non-empty and non-overlapping properties are maintained through 033 * all subsequent CAG operations. Outlines of different component sub-paths may 034 * touch each other, as long as they do not cross so that their enclosed areas 035 * overlap. 036 * <li>The geometry of the path describing the outline of the <code>Area</code> 037 * resembles the path from which it was constructed only in that it describes 038 * the same enclosed 2-dimensional area, but may use entirely different types 039 * and ordering of the path segments to do so. 040 * </ul> 041 * Interesting issues which are not always obvious when using the 042 * <code>Area</code> include: 043 * <ul> 044 * <li>Creating an <code>Area</code> from an unclosed (open) <code>Shape</code> 045 * results in a closed outline in the <code>Area</code> object. 046 * <li>Creating an <code>Area</code> from a <code>Shape</code> which encloses no 047 * area (even when "closed") produces an empty <code>Area</code>. A common 048 * example of this issue is that producing an <code>Area</code> from a line will 049 * be empty since the line encloses no area. An empty <code>Area</code> will 050 * iterate no geometry in its <code>PathIterator</code> objects. 051 * <li>A self-intersecting <code>Shape</code> may be split into two (or more) 052 * sub-paths each enclosing one of the non-intersecting portions of the original 053 * path. 054 * <li>An <code>Area</code> may take more path segments to describe the same 055 * geometry even when the original outline is simple and obvious. The analysis 056 * that the <code>Area</code> class must perform on the path may not reflect the 057 * same concepts of "simple and obvious" as a human being perceives. 058 * </ul> 059 * 060 * @since 1.2 061 */ 062public class Area /* implements Shape, Cloneable */ { 063 064 private static Vector EmptyCurves = new Vector(); 065 private static final boolean normalizeGeoPoints = true; 066 private Vector curves; 067 068 /** 069 * Default constructor which creates an empty area. 070 * 071 * @since 1.2 072 */ 073 public Area() { 074 curves = EmptyCurves; 075 } 076 077 /** 078 * The <code>Area</code> class creates an area geometry from the specified 079 * {@link Shape} object. The geometry is explicitly closed, if the 080 * <code>Shape</code> is not already closed. The fill rule (even-odd or 081 * winding) specified by the geometry of the <code>Shape</code> is used to 082 * determine the resulting enclosed area. 083 * 084 * @param s the <code>Shape</code> from which the area is constructed 085 * @throws NullPointerException if <code>s</code> is null 086 * @since 1.2 087 */ 088 public Area(ShapeObject s) { 089// if (s instanceof Area) { 090// curves = ((Area) s).curves; 091// } 092// else 093// { 094 curves = pathToCurves(s.getPathIterator(null)); 095// } 096 } 097 098 private static Vector pathToCurves(PathIterator pi) { 099 Vector curves = new Vector(); 100 int windingRule = pi.getWindingRule(); 101 // coords array is big enough for holding: 102 // coordinates returned from currentSegment (6) 103 // OR 104 // two subdivided quadratic curves (2+4+4=10) 105 // AND 106 // 0-1 horizontal splitting parameters 107 // OR 108 // 2 parametric equation derivative coefficients 109 // OR 110 // three subdivided cubic curves (2+6+6+6=20) 111 // AND 112 // 0-2 horizontal splitting parameters 113 // OR 114 // 3 parametric equation derivative coefficients 115 double coords[] = new double[23]; 116 double movx = 0, movy = 0; 117 double curx = 0, cury = 0; 118 double newx, newy; 119 while (!pi.isDone()) { 120 switch (pi.currentSegment(coords)) { 121 case PathIterator.SEG_MOVETO: 122 if (normalizeGeoPoints == true) { 123 if (movx > 0) { 124 movx -= 360; 125 } 126 if (curx > 0) { 127 curx -= 360; 128 } 129 } 130 Curve.insertLine(curves, curx, cury, movx, movy); 131 curx = movx = coords[0]; 132 cury = movy = coords[1]; 133 if (normalizeGeoPoints == true) { 134 if (movx > 0) { 135 movx -= 360; 136 } 137 } 138 Curve.insertMove(curves, movx, movy); 139 break; 140 case PathIterator.SEG_LINETO: 141 newx = coords[0]; 142 newy = coords[1]; 143 if (normalizeGeoPoints == true) { 144 if (newx > 0) { 145 newx -= 360; 146 } 147 if (curx > 0) { 148 curx -= 360; 149 } 150 } 151 Curve.insertLine(curves, curx, cury, newx, newy); 152 curx = newx; 153 cury = newy; 154 break; 155 case PathIterator.SEG_QUADTO: 156 newx = coords[2]; 157 newy = coords[3]; 158 if (normalizeGeoPoints == true) { 159 if (curx > 0) { 160 curx -= 360; 161 } 162 } 163 Curve.insertQuad(curves, curx, cury, coords); 164 curx = newx; 165 cury = newy; 166 break; 167 case PathIterator.SEG_CUBICTO: 168 newx = coords[4]; 169 newy = coords[5]; 170 if (normalizeGeoPoints == true) { 171 if (curx > 0) { 172 curx -= 360; 173 } 174 } 175 Curve.insertCubic(curves, curx, cury, coords); 176 curx = newx; 177 cury = newy; 178 break; 179 case PathIterator.SEG_CLOSE: 180 if (normalizeGeoPoints == true) { 181 if (movx > 0) { 182 movx -= 360; 183 } 184 if (curx > 0) { 185 curx -= 360; 186 } 187 } 188 Curve.insertLine(curves, curx, cury, movx, movy); 189 curx = movx; 190 cury = movy; 191 break; 192 } 193 pi.next(); 194 } 195 if (normalizeGeoPoints == true) { 196 if (movx > 0) { 197 movx -= 360; 198 } 199 if (curx > 0) { 200 curx -= 360; 201 } 202 } 203 Curve.insertLine(curves, curx, cury, movx, movy); 204 AreaOp2 operator2 = null; 205 if (windingRule == PathIterator.WIND_EVEN_ODD) { 206 operator2 = new AreaOp2(AreaOp2.EOWINDOP); 207 } else { 208 operator2 = new AreaOp2(AreaOp2.NZWINDOP); 209 } 210 return operator2.calculate(curves, EmptyCurves); 211 } 212 213 /** 214 * Adds the shape of the specified <code>Area</code> to the shape of this 215 * <code>Area</code>. The resulting shape of this <code>Area</code> will 216 * include the union of both shapes, or all areas that were contained in 217 * either this or the specified <code>Area</code>. 218 * <pre> 219 * // Example: 220 * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); 221 * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); 222 * a1.add(a2); 223 * 224 * a1(before) + a2 = a1(after) 225 * 226 * ################ ################ ################ 227 * ############## ############## ################ 228 * ############ ############ ################ 229 * ########## ########## ################ 230 * ######## ######## ################ 231 * ###### ###### ###### ###### 232 * #### #### #### #### 233 * ## ## ## ## 234 * </pre> 235 * 236 * @param rhs the <code>Area</code> to be added to the current shape 237 * @throws NullPointerException if <code>rhs</code> is null 238 * @since 1.2 239 */ 240 public void add(Area rhs) { 241 //curves = new AreaOp.AddOp().calculate(this.curves, rhs.curves); 242 //curves = new AddOp().calculate(this.curves, rhs.curves); 243 curves = new SomeOp(SomeOp.ADDOP).calculate(this.curves, rhs.curves); 244 invalidateBounds(); 245 } 246 247 /** 248 * Subtracts the shape of the specified <code>Area</code> from the shape of 249 * this <code>Area</code>. The resulting shape of this <code>Area</code> 250 * will include areas that were contained only in this <code>Area</code> and 251 * not in the specified <code>Area</code>. 252 * <pre> 253 * // Example: 254 * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); 255 * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); 256 * a1.subtract(a2); 257 * 258 * a1(before) - a2 = a1(after) 259 * 260 * ################ ################ 261 * ############## ############## ## 262 * ############ ############ #### 263 * ########## ########## ###### 264 * ######## ######## ######## 265 * ###### ###### ###### 266 * #### #### #### 267 * ## ## ## 268 * </pre> 269 * 270 * @param rhs the <code>Area</code> to be subtracted from the current shape 271 * @throws NullPointerException if <code>rhs</code> is null 272 * @since 1.2 273 */ 274 public void subtract(Area rhs) { 275 //curves = new AreaOp.SubOp().calculate(this.curves, rhs.curves); 276 curves = new SomeOp(SomeOp.SUBOP).calculate(this.curves, rhs.curves); 277 invalidateBounds(); 278 } 279 280 /** 281 * Sets the shape of this <code>Area</code> to the intersection of its 282 * current shape and the shape of the specified <code>Area</code>. The 283 * resulting shape of this <code>Area</code> will include only areas that 284 * were contained in both this <code>Area</code> and also in the specified 285 * <code>Area</code>. 286 * <pre> 287 * // Example: 288 * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); 289 * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); 290 * a1.intersect(a2); 291 * 292 * a1(before) intersect a2 = a1(after) 293 * 294 * ################ ################ ################ 295 * ############## ############## ############ 296 * ############ ############ ######## 297 * ########## ########## #### 298 * ######## ######## 299 * ###### ###### 300 * #### #### 301 * ## ## 302 * </pre> 303 * 304 * @param rhs the <code>Area</code> to be intersected with this 305 * <code>Area</code> 306 * @throws NullPointerException if <code>rhs</code> is null 307 * @since 1.2 308 */ 309 public void intersect(Area rhs) { 310 //curves = new AreaOp.IntOp().calculate(this.curves, rhs.curves); 311 curves = new SomeOp(SomeOp.INTOP).calculate(this.curves, rhs.curves); 312 invalidateBounds(); 313 } 314 315 /** 316 * Sets the shape of this <code>Area</code> to be the combined area of its 317 * current shape and the shape of the specified <code>Area</code>, minus 318 * their intersection. The resulting shape of this <code>Area</code> will 319 * include only areas that were contained in either this <code>Area</code> 320 * or in the specified <code>Area</code>, but not in both. 321 * <pre> 322 * // Example: 323 * Area a1 = new Area([triangle 0,0 => 8,0 => 0,8]); 324 * Area a2 = new Area([triangle 0,0 => 8,0 => 8,8]); 325 * a1.exclusiveOr(a2); 326 * 327 * a1(before) xor a2 = a1(after) 328 * 329 * ################ ################ 330 * ############## ############## ## ## 331 * ############ ############ #### #### 332 * ########## ########## ###### ###### 333 * ######## ######## ################ 334 * ###### ###### ###### ###### 335 * #### #### #### #### 336 * ## ## ## ## 337 * </pre> 338 * 339 * @param rhs the <code>Area</code> to be exclusive ORed with this 340 * <code>Area</code>. 341 * @throws NullPointerException if <code>rhs</code> is null 342 * @since 1.2 343 */ 344 public void exclusiveOr(Area rhs) { 345 //curves = new AreaOp.XorOp().calculate(this.curves, rhs.curves); 346 curves = new SomeOp(SomeOp.XOROP).calculate(this.curves, rhs.curves); 347 invalidateBounds(); 348 } 349 350 /** 351 * Removes all of the geometry from this <code>Area</code> and restores it 352 * to an empty area. 353 * 354 * @since 1.2 355 */ 356 public void reset() { 357 curves = new Vector(); 358 invalidateBounds(); 359 } 360 361 /** 362 * Tests whether this <code>Area</code> object encloses any area. 363 * 364 * @return <code>true</code> if this <code>Area</code> object represents an 365 * empty area; <code>false</code> otherwise. 366 * @since 1.2 367 */ 368 public boolean isEmpty() { 369 //return (curves.size() == 0); 370 return (curves.isEmpty()); 371 } 372 373 /** 374 * Tests whether this <code>Area</code> consists entirely of straight edged 375 * polygonal geometry. 376 * 377 * @return <code>true</code> if the geometry of this <code>Area</code> 378 * consists entirely of line segments; <code>false</code> otherwise. 379 * @since 1.2 380 */ 381 public boolean isPolygonal() { 382 Enumeration enum_ = curves.elements(); 383 while (enum_.hasMoreElements()) { 384 if (((CurveObject) enum_.nextElement()).getOrder() > 1) { 385 return false; 386 } 387 } 388 return true; 389 } 390 391 /** 392 * Tests whether this <code>Area</code> is rectangular in shape. 393 * 394 * @return <code>true</code> if the geometry of this <code>Area</code> is 395 * rectangular in shape; <code>false</code> otherwise. 396 * @since 1.2 397 */ 398 public boolean isRectangular() { 399 int size = curves.size(); 400 if (size == 0) { 401 return true; 402 } 403 if (size > 3) { 404 return false; 405 } 406 CurveObject c1 = (CurveObject) curves.get(1); 407 CurveObject c2 = (CurveObject) curves.get(2); 408 if (c1.getOrder() != 1 || c2.getOrder() != 1) { 409 return false; 410 } 411 if (c1.getXTop() != c1.getXBot() || c2.getXTop() != c2.getXBot()) { 412 return false; 413 } 414 if (c1.getYTop() != c2.getYTop() || c1.getYBot() != c2.getYBot()) { 415 // One might be able to prove that this is impossible... 416 return false; 417 } 418 return true; 419 } 420 421 /** 422 * Tests whether this <code>Area</code> is comprised of a single closed 423 * subpath. This method returns <code>true</code> if the path contains 0 or 424 * 1 subpaths, or <code>false</code> if the path contains more than 1 425 * subpath. The subpaths are counted by the number of 426 * {@link PathIterator#SEG_MOVETO SEG_MOVETO} segments that appear in the 427 * path. 428 * 429 * @return <code>true</code> if the <code>Area</code> is comprised of a 430 * single basic geometry; <code>false</code> otherwise. 431 * @since 1.2 432 */ 433 public boolean isSingular() { 434 if (curves.size() < 3) { 435 return true; 436 } 437 Enumeration enum_ = curves.elements(); 438 enum_.nextElement(); // First Order0 "moveto" 439 while (enum_.hasMoreElements()) { 440 if (((CurveObject) enum_.nextElement()).getOrder() == 0) { 441 return false; 442 } 443 } 444 return true; 445 } 446 447 private Rectangle2D cachedBounds; 448 449 private void invalidateBounds() { 450 cachedBounds = null; 451 } 452 453 /** 454 * Tests whether the geometries of the two <code>Area</code> objects are 455 * equal. This method will return false if the argument is null. 456 * 457 * @param other the <code>Area</code> to be compared to this 458 * <code>Area</code> 459 * @return <code>true</code> if the two geometries are equal; 460 * <code>false</code> otherwise. 461 * @since 1.2 462 */ 463 public boolean equals(Area other) { 464 // REMIND: A *much* simpler operation should be possible... 465 // Should be able to do a curve-wise comparison since all Areas 466 // should evaluate their curves in the same top-down order. 467 if (other == this) { 468 return true; 469 } 470 if (other == null) { 471 return false; 472 } 473 //Vector c = new AreaOp.XorOp().calculate(this.curves, other.curves); 474 Vector c = new SomeOp(SomeOp.XOROP).calculate(this.curves, other.curves); 475 return c.isEmpty(); 476 } 477 478 /** 479 * Creates a {@link PathIterator} for the outline of this <code>Area</code> 480 * object. This <code>Area</code> object is unchanged. 481 * 482 * @param at an optional <code>AffineTransform</code> to be applied to the 483 * coordinates as they are returned in the iteration, or <code>null</code> 484 * if untransformed coordinates are desired 485 * @return the <code>PathIterator</code> object that returns the geometry of 486 * the outline of this <code>Area</code>, one segment at a time. 487 * @since 1.2 488 */ 489 public AreaIterator getPathIterator(AffineTransform at) { //did return PathIterator 490 return new AreaIterator(curves, at); 491 } 492 493}