001/** 002 * 003 */ 004package icy.type.geom; 005 006import java.awt.Point; 007import java.awt.Polygon; 008import java.awt.Rectangle; 009import java.awt.Shape; 010import java.awt.geom.AffineTransform; 011import java.awt.geom.Line2D; 012import java.awt.geom.Path2D; 013import java.awt.geom.PathIterator; 014import java.awt.geom.Point2D; 015import java.awt.geom.Rectangle2D; 016import java.util.ArrayList; 017import java.util.List; 018 019/* 020 * Licensed to the Apache Software Foundation (ASF) under one or more 021 * contributor license agreements. See the NOTICE file distributed with 022 * this work for additional information regarding copyright ownership. 023 * The ASF licenses this file to You under the Apache License, Version 2.0 024 * (the "License"); you may not use this file except in compliance with 025 * the License. You may obtain a copy of the License at 026 * 027 * http://www.apache.org/licenses/LICENSE-2.0 028 * 029 * Unless required by applicable law or agreed to in writing, software 030 * distributed under the License is distributed on an "AS IS" BASIS, 031 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 032 * See the License for the specific language governing permissions and 033 * limitations under the License. 034 * 035 * Modified by Stephane Dallongeville 036 */ 037 038/** 039 * This class is a Polygon with double coordinates. 040 */ 041public class Polygon2D implements Shape, Cloneable 042{ 043 private static void addFarthestPoint(List<Point2D> result, List<Point2D> points, int start, int end, 044 double maxDeviation) 045 { 046 final Point2D p1 = points.get(start); 047 final Point2D p2 = points.get(end); 048 final double x1 = p1.getX(); 049 final double y1 = p1.getY(); 050 final double x2 = p2.getX(); 051 final double y2 = p2.getY(); 052 int farthest = -1; 053 // initialize with maximum allow deviation (squared) 054 double maxDist = maxDeviation * maxDeviation; 055 056 for (int i = start + 1; i < end; i++) 057 { 058 final Point2D p = points.get(i); 059 final double dist = Line2D.ptSegDistSq(x1, y1, x2, y2, p.getX(), p.getY()); 060 061 if (dist > maxDist) 062 { 063 farthest = i; 064 maxDist = dist; 065 } 066 } 067 068 // found a point to add ? 069 if (farthest != -1) 070 { 071 // search before point 072 addFarthestPoint(result, points, start, farthest, maxDeviation); 073 // add point 074 result.add(points.get(farthest)); 075 // search after point 076 addFarthestPoint(result, points, farthest, end, maxDeviation); 077 } 078 } 079 080 /** 081 * Returns a polygon2D corresponding to the polygon estimation of the given (closed) contour points with the 082 * specified <code>max deviation</code>. 083 * 084 * @param points 085 * the list of points representing the input closed contour to transform to polygon. 086 * @param maxDeviation 087 * maximum allowed deviation/distance of resulting polygon from the input contour (in pixel). 088 * @return the polygon estimation from input contour 089 */ 090 public static Polygon2D getPolygon2D(List<Point2D> points, double maxDeviation) 091 { 092 // just return 093 if (points.size() < 3) 094 return new Polygon2D(points); 095 096 final List<Point2D> result = new ArrayList<Point2D>(points.size() / 4); 097 098 int ind = points.size() / 2; 099 100 // close the contour 101 points.add(points.get(0)); 102 103 // add first point 104 result.add(points.get(0)); 105 // add points between first and medium 106 addFarthestPoint(result, points, 0, ind, maxDeviation); 107 // add medium point 108 result.add(points.get(ind)); 109 // add points between medium and end 110 addFarthestPoint(result, points, ind, points.size() - 1, maxDeviation); 111 112 // restore original contour 113 points.remove(points.size() - 1); 114 115 return new Polygon2D(result); 116 } 117 118 /** 119 * The total number of points. The value of <code>npoints</code> represents the number of valid points in this 120 * <code>Polygon</code>. 121 */ 122 public int npoints; 123 124 /** 125 * The array of <i>x</i> coordinates. The value of {@link #npoints} is equal to the 126 * number of points in this <code>Polygon2D</code>. 127 */ 128 public double[] xpoints; 129 130 /** 131 * The array of <i>y</i> coordinates. The value of {@link #npoints} is equal to the 132 * number of points in this <code>Polygon2D</code>. 133 */ 134 public double[] ypoints; 135 136 /** 137 * Bounds of the Polygon2D. 138 * 139 * @see #getBounds() 140 */ 141 protected Rectangle2D bounds; 142 143 protected Path2D.Double path; 144 protected Path2D.Double closedPath; 145 146 /** 147 * Creates an empty Polygon2D. 148 */ 149 public Polygon2D() 150 { 151 super(); 152 153 reset(); 154 } 155 156 /** 157 * Constructs and initializes a <code>Polygon2D</code> from the specified 158 * Rectangle2D. 159 * 160 * @param rec 161 * the Rectangle2D 162 * @exception NullPointerException 163 * rec is <code>null</code>. 164 */ 165 public Polygon2D(Rectangle2D rec) 166 { 167 super(); 168 169 if (rec == null) 170 throw new IllegalArgumentException("null Rectangle"); 171 172 npoints = 4; 173 xpoints = new double[4]; 174 ypoints = new double[4]; 175 176 xpoints[0] = rec.getMinX(); 177 ypoints[0] = rec.getMinY(); 178 xpoints[1] = rec.getMaxX(); 179 ypoints[1] = rec.getMinY(); 180 xpoints[2] = rec.getMaxX(); 181 ypoints[2] = rec.getMaxY(); 182 xpoints[3] = rec.getMinX(); 183 ypoints[3] = rec.getMaxY(); 184 185 calculatePath(); 186 } 187 188 /** 189 * Constructs and initializes a <code>Polygon2D</code> from the specified 190 * Polygon. 191 * 192 * @param pol 193 * the Polygon 194 * @exception NullPointerException 195 * pol is <code>null</code>. 196 */ 197 public Polygon2D(Polygon pol) 198 { 199 super(); 200 201 if (pol == null) 202 throw new IllegalArgumentException("null Polygon"); 203 204 this.npoints = pol.npoints; 205 this.xpoints = new double[pol.npoints]; 206 this.ypoints = new double[pol.npoints]; 207 208 for (int i = 0; i < pol.npoints; i++) 209 { 210 xpoints[i] = pol.xpoints[i]; 211 ypoints[i] = pol.ypoints[i]; 212 } 213 214 calculatePath(); 215 } 216 217 /** 218 * Constructs and initializes a <code>Polygon2D</code> from the specified 219 * parameters. 220 * 221 * @param xpoints 222 * an array of <i>x</i> coordinates 223 * @param ypoints 224 * an array of <i>y</i> coordinates 225 * @param npoints 226 * the total number of points in the <code>Polygon2D</code> 227 * @exception NegativeArraySizeException 228 * if the value of <code>npoints</code> is negative. 229 * @exception IndexOutOfBoundsException 230 * if <code>npoints</code> is 231 * greater than the length of <code>xpoints</code> or the length of <code>ypoints</code>. 232 * @exception NullPointerException 233 * if <code>xpoints</code> or <code>ypoints</code> is <code>null</code>. 234 */ 235 public Polygon2D(double[] xpoints, double[] ypoints, int npoints) 236 { 237 super(); 238 239 if (npoints > xpoints.length || npoints > ypoints.length) 240 throw new IndexOutOfBoundsException("npoints > xpoints.length || npoints > ypoints.length"); 241 242 this.npoints = npoints; 243 this.xpoints = new double[npoints]; 244 this.ypoints = new double[npoints]; 245 246 System.arraycopy(xpoints, 0, this.xpoints, 0, npoints); 247 System.arraycopy(ypoints, 0, this.ypoints, 0, npoints); 248 249 calculatePath(); 250 } 251 252 /** 253 * Constructs and initializes a <code>Polygon2D</code> from the specified 254 * parameters. 255 * 256 * @param xpoints 257 * an array of <i>x</i> coordinates 258 * @param ypoints 259 * an array of <i>y</i> coordinates 260 * @param npoints 261 * the total number of points in the <code>Polygon2D</code> 262 * @exception NegativeArraySizeException 263 * if the value of <code>npoints</code> is negative. 264 * @exception IndexOutOfBoundsException 265 * if <code>npoints</code> is 266 * greater than the length of <code>xpoints</code> or the length of <code>ypoints</code>. 267 * @exception NullPointerException 268 * if <code>xpoints</code> or <code>ypoints</code> is <code>null</code>. 269 */ 270 public Polygon2D(int[] xpoints, int[] ypoints, int npoints) 271 { 272 super(); 273 274 if (npoints > xpoints.length || npoints > ypoints.length) 275 throw new IndexOutOfBoundsException("npoints > xpoints.length || npoints > ypoints.length"); 276 277 this.npoints = npoints; 278 this.xpoints = new double[npoints]; 279 this.ypoints = new double[npoints]; 280 281 for (int i = 0; i < npoints; i++) 282 { 283 this.xpoints[i] = xpoints[i]; 284 this.ypoints[i] = ypoints[i]; 285 } 286 287 calculatePath(); 288 } 289 290 public Polygon2D(List<Point2D> points) 291 { 292 super(); 293 294 final int len = points.size(); 295 296 this.npoints = len; 297 this.xpoints = new double[len]; 298 this.ypoints = new double[len]; 299 300 for (int i = 0; i < len; i++) 301 { 302 final Point2D pt = points.get(i); 303 304 this.xpoints[i] = pt.getX(); 305 this.ypoints[i] = pt.getY(); 306 } 307 308 calculatePath(); 309 } 310 311 /** 312 * Resets this <code>Polygon</code> object to an empty polygon. 313 */ 314 public void reset() 315 { 316 npoints = 0; 317 xpoints = new double[0]; 318 ypoints = new double[0]; 319 bounds = new Rectangle2D.Double(); 320 path = null; 321 closedPath = null; 322 } 323 324 @Override 325 public Object clone() 326 { 327 Polygon2D pol = new Polygon2D(); 328 329 for (int i = 0; i < npoints; i++) 330 pol.addPoint(xpoints[i], ypoints[i]); 331 332 return pol; 333 } 334 335 public void calculatePath() 336 { 337 path = new Path2D.Double(); 338 339 path.moveTo(xpoints[0], ypoints[0]); 340 for (int i = 1; i < npoints; i++) 341 path.lineTo(xpoints[i], ypoints[i]); 342 343 bounds = path.getBounds2D(); 344 closedPath = null; 345 } 346 347 protected void updatePath(double x, double y) 348 { 349 if (path == null) 350 { 351 path = new Path2D.Double(Path2D.WIND_EVEN_ODD); 352 path.moveTo(x, y); 353 bounds = new Rectangle2D.Double(x, y, 0, 0); 354 } 355 else 356 { 357 path.lineTo(x, y); 358 359 double _xmax = bounds.getMaxX(); 360 double _ymax = bounds.getMaxY(); 361 double _xmin = bounds.getMinX(); 362 double _ymin = bounds.getMinY(); 363 364 if (x < _xmin) 365 _xmin = x; 366 else if (x > _xmax) 367 _xmax = x; 368 if (y < _ymin) 369 _ymin = y; 370 else if (y > _ymax) 371 _ymax = y; 372 373 bounds = new Rectangle2D.Double(_xmin, _ymin, _xmax - _xmin, _ymax - _ymin); 374 } 375 376 closedPath = null; 377 } 378 379 /* 380 * get the associated {@link Polyline2D}. 381 */ 382 public Polyline2D getPolyline2D() 383 { 384 Polyline2D pol = new Polyline2D(xpoints, ypoints, npoints); 385 386 pol.addPoint(xpoints[0], ypoints[0]); 387 388 return pol; 389 } 390 391 public Polygon getPolygon() 392 { 393 int[] _xpoints = new int[npoints]; 394 int[] _ypoints = new int[npoints]; 395 396 for (int i = 0; i < npoints; i++) 397 { 398 _xpoints[i] = (int) xpoints[i]; // todo maybe rounding is better ? 399 _ypoints[i] = (int) ypoints[i]; 400 } 401 402 return new Polygon(_xpoints, _ypoints, npoints); 403 } 404 405 public void addPoint(Point2D p) 406 { 407 addPoint(p.getX(), p.getY()); 408 } 409 410 /** 411 * Appends the specified coordinates to this <code>Polygon2D</code>. 412 * 413 * @param x 414 * the specified x coordinate 415 * @param y 416 * the specified y coordinate 417 */ 418 public void addPoint(double x, double y) 419 { 420 if (npoints == xpoints.length) 421 { 422 double[] tmp; 423 424 tmp = new double[(npoints * 2) + 1]; 425 System.arraycopy(xpoints, 0, tmp, 0, npoints); 426 xpoints = tmp; 427 428 tmp = new double[(npoints * 2) + 1]; 429 System.arraycopy(ypoints, 0, tmp, 0, npoints); 430 ypoints = tmp; 431 } 432 433 xpoints[npoints] = x; 434 ypoints[npoints] = y; 435 npoints++; 436 437 updatePath(x, y); 438 } 439 440 /** 441 * Determines whether the specified {@link Point} is inside this <code>Polygon</code>. 442 * 443 * @param p 444 * the specified <code>Point</code> to be tested 445 * @return <code>true</code> if the <code>Polygon</code> contains the <code>Point</code>; <code>false</code> 446 * otherwise. 447 * @see #contains(double, double) 448 */ 449 public boolean contains(Point p) 450 { 451 return contains(p.x, p.y); 452 } 453 454 /** 455 * Determines whether the specified coordinates are inside this <code>Polygon</code>. 456 * <p> 457 * 458 * @param x 459 * the specified x coordinate to be tested 460 * @param y 461 * the specified y coordinate to be tested 462 * @return <code>true</code> if this <code>Polygon</code> contains 463 * the specified coordinates, (<i>x</i>, <i>y</i>); <code>false</code> otherwise. 464 */ 465 public boolean contains(int x, int y) 466 { 467 return contains((double) x, (double) y); 468 } 469 470 /** 471 * Returns the high precision bounding box of the {@link Shape}. 472 * 473 * @return a {@link Rectangle2D} that precisely 474 * bounds the <code>Shape</code>. 475 */ 476 @Override 477 public Rectangle2D getBounds2D() 478 { 479 return (Rectangle2D) bounds.clone(); 480 } 481 482 @Override 483 public Rectangle getBounds() 484 { 485 return bounds.getBounds(); 486 } 487 488 /** 489 * Determines if the specified coordinates are inside this <code>Polygon</code>. For the definition of 490 * <i>insideness</i>, see the class comments of {@link Shape}. 491 * 492 * @param x 493 * the specified x coordinate 494 * @param y 495 * the specified y coordinate 496 * @return <code>true</code> if the <code>Polygon</code> contains the 497 * specified coordinates; <code>false</code> otherwise. 498 */ 499 @Override 500 public boolean contains(double x, double y) 501 { 502 if (npoints <= 2 || !bounds.contains(x, y)) 503 return false; 504 505 return updateComputingPath().contains(x, y); 506 } 507 508 protected Path2D.Double updateComputingPath() 509 { 510 Path2D.Double result = closedPath; 511 512 // need to recompute it ? 513 if (result == null) 514 { 515 if (path != null) 516 { 517 result = (Path2D.Double) path.clone(); 518 result.closePath(); 519 } 520 else 521 // empty path 522 result = new Path2D.Double(); 523 524 closedPath = result; 525 } 526 527 return result; 528 } 529 530 /** 531 * Tests if a specified {@link Point2D} is inside the boundary of this <code>Polygon</code>. 532 * 533 * @param p 534 * a specified <code>Point2D</code> 535 * @return <code>true</code> if this <code>Polygon</code> contains the 536 * specified <code>Point2D</code>; <code>false</code> otherwise. 537 * @see #contains(double, double) 538 */ 539 @Override 540 public boolean contains(Point2D p) 541 { 542 return contains(p.getX(), p.getY()); 543 } 544 545 /** 546 * Tests if the interior of this <code>Polygon</code> intersects the 547 * interior of a specified set of rectangular coordinates. 548 * 549 * @param x 550 * the x coordinate of the specified rectangular 551 * shape's top-left corner 552 * @param y 553 * the y coordinate of the specified rectangular 554 * shape's top-left corner 555 * @param w 556 * the width of the specified rectangular shape 557 * @param h 558 * the height of the specified rectangular shape 559 * @return <code>true</code> if the interior of this <code>Polygon</code> and the interior of the 560 * specified set of rectangular 561 * coordinates intersect each other; <code>false</code> otherwise. 562 */ 563 @Override 564 public boolean intersects(double x, double y, double w, double h) 565 { 566 if (npoints <= 0 || !bounds.intersects(x, y, w, h)) 567 return false; 568 569 return updateComputingPath().intersects(x, y, w, h); 570 } 571 572 /** 573 * Tests if the interior of this <code>Polygon</code> intersects the 574 * interior of a specified <code>Rectangle2D</code>. 575 * 576 * @param r 577 * a specified <code>Rectangle2D</code> 578 * @return <code>true</code> if this <code>Polygon</code> and the 579 * interior of the specified <code>Rectangle2D</code> intersect each other; <code>false</code> otherwise. 580 */ 581 @Override 582 public boolean intersects(Rectangle2D r) 583 { 584 return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); 585 } 586 587 /** 588 * Tests if the interior of this <code>Polygon</code> entirely 589 * contains the specified set of rectangular coordinates. 590 * 591 * @param x 592 * the x coordinate of the top-left corner of the 593 * specified set of rectangular coordinates 594 * @param y 595 * the y coordinate of the top-left corner of the 596 * specified set of rectangular coordinates 597 * @param w 598 * the width of the set of rectangular coordinates 599 * @param h 600 * the height of the set of rectangular coordinates 601 * @return <code>true</code> if this <code>Polygon</code> entirely 602 * contains the specified set of rectangular 603 * coordinates; <code>false</code> otherwise. 604 */ 605 @Override 606 public boolean contains(double x, double y, double w, double h) 607 { 608 if (npoints <= 0 || !bounds.intersects(x, y, w, h)) 609 return false; 610 611 return updateComputingPath().contains(x, y, w, h); 612 } 613 614 /** 615 * Tests if the interior of this <code>Polygon</code> entirely 616 * contains the specified <code>Rectangle2D</code>. 617 * 618 * @param r 619 * the specified <code>Rectangle2D</code> 620 * @return <code>true</code> if this <code>Polygon</code> entirely 621 * contains the specified <code>Rectangle2D</code>; <code>false</code> otherwise. 622 * @see #contains(double, double, double, double) 623 */ 624 @Override 625 public boolean contains(Rectangle2D r) 626 { 627 return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); 628 } 629 630 /** 631 * Returns an iterator object that iterates along the boundary of this <code>Polygon</code> and provides access to 632 * the geometry of the outline of this <code>Polygon</code>. An optional {@link AffineTransform} can be specified so 633 * that the coordinates returned in the iteration are transformed accordingly. 634 * 635 * @param at 636 * an optional <code>AffineTransform</code> to be applied to the 637 * coordinates as they are returned in the iteration, or <code>null</code> if untransformed coordinates are 638 * desired 639 * @return a {@link PathIterator} object that provides access to the 640 * geometry of this <code>Polygon</code>. 641 */ 642 @Override 643 public PathIterator getPathIterator(AffineTransform at) 644 { 645 return updateComputingPath().getPathIterator(at); 646 } 647 648 /** 649 * Returns an iterator object that iterates along the boundary of 650 * the <code>Polygon2D</code> and provides access to the geometry of the 651 * outline of the <code>Shape</code>. Only SEG_MOVETO, SEG_LINETO, and 652 * SEG_CLOSE point types are returned by the iterator. 653 * Since polygons are already flat, the <code>flatness</code> parameter 654 * is ignored. 655 * 656 * @param at 657 * an optional <code>AffineTransform</code> to be applied to the 658 * coordinates as they are returned in the iteration, or <code>null</code> if untransformed coordinates are 659 * desired 660 * @param flatness 661 * the maximum amount that the control points 662 * for a given curve can vary from colinear before a subdivided 663 * curve is replaced by a straight line connecting the 664 * endpoints. Since polygons are already flat the <code>flatness</code> parameter is ignored. 665 * @return a <code>PathIterator</code> object that provides access to the <code>Shape</code> object's geometry. 666 */ 667 @Override 668 public PathIterator getPathIterator(AffineTransform at, double flatness) 669 { 670 return getPathIterator(at); 671 } 672}