001/******************************************************************************* 002 * This software is provided as a supplement to the authors' textbooks on digital 003 * image processing published by Springer-Verlag in various languages and editions. 004 * Permission to use and distribute this software is granted under the BSD 2-Clause 005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause). 006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved. 007 * Visit https://imagingbook.com for additional details. 008 ******************************************************************************/ 009package imagingbook.common.geometry.basic; 010 011import imagingbook.common.geometry.shape.ShapeProducer; 012import imagingbook.common.math.PrintPrecision; 013import org.apache.commons.math3.geometry.euclidean.twod.Vector2D; 014import org.apache.commons.math3.linear.MatrixUtils; 015import org.apache.commons.math3.linear.RealVector; 016 017import java.awt.Point; 018import java.awt.Shape; 019import java.awt.geom.Ellipse2D; 020import java.awt.geom.Point2D; 021import java.util.Locale; 022 023import static imagingbook.common.math.Arithmetic.isZero; 024import static imagingbook.common.math.Arithmetic.sqr; 025 026/** 027 * Interface specifying the behavior of simple 2D points. It is used to adapt to different (legacy) point 028 * implementations with a common API. To some extent this is similar to the functionality provided by {@link Point2D} 029 * and {@link Point} but was re-implemented to avoid dependency on AWT and for more flexibility in naming and class 030 * structure. Since defined as an interface, {@link Pnt2d} can be easily implemented by other point-like structures, 031 * e.g. corners (see {@link imagingbook.common.corners.Corner}). <br> Two concrete (nested) classes are defined for 032 * points with {@code double} and {@code int} coordinates, respectively. See {@link Pnt2d.PntDouble} and 033 * {@link Pnt2d.PntInt} for how to instantiate such point objects. 034 * 035 * @author WB 036 */ 037public interface Pnt2d extends ShapeProducer, Primitive2d { 038 039 /** 040 * The default tolerance for matching coordinates (1E-6). 041 */ 042 public final static double TOLERANCE = 1E-6; 043 044 /** 045 * Returns the x-coordinate of this point. 046 * @return the x-coordinate value 047 */ 048 double getX(); 049 050 /** 051 * Returns the y-coordinate of this point. 052 * @return the y-coordinate value 053 */ 054 double getY(); 055 056 /** 057 * Returns the x-coordinate of this point as a (truncated) integer value. 058 * 059 * @return the integer x-coordinate of this point. 060 */ 061 public default int getXint() { 062 return (int) this.getX(); 063 } 064 065 /** 066 * Returns the y-coordinate of this point as a (truncated) integer value. 067 * 068 * @return the integer y-coordinate of this point. 069 */ 070 public default int getYint() { 071 return (int) this.getY(); 072 } 073 074 // ---------------------------------------------------------- 075 076 /** 077 * Creates and returns a new point of type {@link Pnt2d.PntInt} with the specified coordinates. See also 078 * {@link PntInt#from(int, int)}. 079 * 080 * @param x coordinate 081 * @param y coordinate 082 * @return the new point 083 */ 084 public static Pnt2d from(int x, int y) { 085 return PntInt.from(x, y); 086 } 087 088 /** 089 * Creates and returns a new point of type {@link Pnt2d.PntInt} with the specified coordinates. See also 090 * {@link PntInt#from(int[])}. 091 * 092 * @param xy coordinates 093 * @return the new point 094 */ 095 public static Pnt2d from(int[] xy) { 096 return PntInt.from(xy); 097 } 098 099 /** 100 * Creates and returns a new point of type {@link Pnt2d.PntDouble} with the specified coordinates. See also 101 * {@link PntDouble#from(double, double)}. 102 * 103 * @param x coordinate 104 * @param y coordinate 105 * @return the new point 106 */ 107 public static Pnt2d from(double x, double y) { 108 return PntDouble.from(x, y); 109 } 110 111 /** 112 * Creates and returns a new point of type {@link Pnt2d.PntDouble} with the specified coordinates. See also 113 * {@link PntDouble#from(double[])}. 114 * 115 * @param xy coordinates 116 * @return the new point 117 */ 118 public static Pnt2d from(double[] xy) { 119 return PntDouble.from(xy[0], xy[1]); 120 } 121 122 public static Pnt2d from(Pnt2d p) { 123 if (p instanceof PntInt) { 124 return PntInt.from(p); 125 } 126 else { 127 return PntDouble.from(p); 128 } 129 } 130 131 public static Pnt2d from(Point2D p) { 132 if (p instanceof Point) { 133 Point pi = (Point) p; 134 return PntInt.from(pi.x, pi.y); 135 } 136 else { 137 return PntDouble.from(p.getX(), p.getY()); 138 } 139 } 140 141 // ---------------------------------------------------------- 142 143 /** 144 * Returns this point's coordinates as a new 2-element {@code double} array. 145 * @return the array 146 */ 147 public default double[] toDoubleArray() { 148 return new double[] {this.getX(), this.getY()}; 149 } 150 151 /** 152 * Returns this point's coordinates as a new AWT {@link java.awt.geom.Point2D.Double} point. 153 * @return the new point 154 */ 155 public default Point2D.Double toAwtPoint2D() { 156 return new Point2D.Double(this.getX(), this.getY()); 157 } 158 159 /** 160 * Returns this point's coordinates as a new {@link Vector2D} instance (for interfacing with Apache Commons Math). 161 * 162 * @return a new vector 163 */ 164 public default Vector2D toVector2D() { 165 return new Vector2D(getX(), getY()); 166 } 167 168 /** 169 * Returns this point's coordinates as a new {@link RealVector} instance (for interfacing with Apache Commons 170 * Math). 171 * 172 * @return a new vector 173 */ 174 public default RealVector toRealVector() { 175 return MatrixUtils.createRealVector(this.toDoubleArray()); 176 } 177 178 /** 179 * Returns a copy of this point which is of the same type as the original. 180 * @return a new instance. 181 */ 182 public default Pnt2d duplicate() { 183 throw new UnsupportedOperationException(); 184 } 185 186 /** 187 * Returns a new point whose coordinates are the sum of this point and the given point. The concrete type of the 188 * returned object depends on the type of the original points. 189 * 190 * @param p the point to be added 191 * @return a new point 192 */ 193 public default Pnt2d plus(Pnt2d p) { 194 return this.plus(p.getX(), p.getY()); 195 } 196 197 /** 198 * Returns a new point whose coordinates are the sum of this point and the specified {@code double} coordinates. 199 * 200 * @param dx the x-coordinate to be added 201 * @param dy the y-coordinate to be added 202 * @return a new point 203 */ 204 public default Pnt2d plus(double dx, double dy) { 205 return new PntDouble(this.getX() + dx, this.getY() + dy); 206 } 207 208 /** 209 * Returns a new point whose coordinates are the difference of this point and the given point. 210 * 211 * @param p the point to be subtracted 212 * @return a new point 213 */ 214 public default Pnt2d minus(Pnt2d p) { 215 return this.minus(p.getX(), p.getY()); 216 } 217 218 /** 219 * Returns a new point whose coordinates are multiplied by the specified scalar value. 220 * 221 * @param s some scalar value 222 * @return a new point 223 */ 224 public default Pnt2d mult(double s) { 225 return new PntDouble(s * this.getX(), s * this.getY()); 226 } 227 228 /** 229 * Returns a new point whose coordinates are the difference of this point and the given point (specified as a 230 * coordinate pair). 231 * 232 * @param dx x-coordinate 233 * @param dy y-coordinate 234 * @return a new point 235 */ 236 public default Pnt2d minus(double dx, double dy) { 237 return new PntDouble(this.getX() - dx, this.getY() - dy); 238 } 239 240 /** 241 * Returns a point (vector) perpendicular to this point (vector). 242 * @return a perpendicular vector 243 */ 244 public default Pnt2d perp() { 245 return new PntDouble(this.getY(), -this.getX()); 246 } 247 248 /** 249 * Calculates and returns the dot product of this point and another point (both points are interpreted as 2D 250 * vectors) 251 * 252 * @param other the point (vector) to be multiplied by this vector 253 * @return a new point (vector) 254 */ 255 public default double dot(Pnt2d other) { 256 return this.getX() * other.getX() + this.getY() * other.getY(); 257 } 258 259 /** 260 * Calculates and returns the 2D pseudo cross product of this point and another point (both points are interpreted 261 * as 2D vectors). The result of {@code a.cross(b)} is equivalent to {@code a.perp().dot(b)}. 262 * 263 * @param other the point (vector) to be multiplied to the perpendicular vector of this vector 264 * @return a new point (vector) 265 */ 266 public default double cross(Pnt2d other) { 267 return this.getY() * other.getX() - this.getX() * other.getY(); 268 } 269 270 271 // ---------------------------------------------------------- 272 273 /** 274 * Tests if this point matches the given point, i.e., if both coordinate differences are zero (< than the 275 * specified tolerance). 276 * 277 * @param p the point to be matched to 278 * @param tolerance the tolerance (see also {@link #TOLERANCE}). 279 * @return true if both points match 280 */ 281 public default boolean equals(Pnt2d p, double tolerance) { 282 return isZero(this.getX() - p.getX(), tolerance) 283 && isZero(this.getY() - p.getY(), tolerance); 284 } 285 286 // ---------------------------------------------------------- 287 288 /** 289 * Returns the squared L2 distance between this point and the given point. 290 * 291 * @param other the other point 292 * @return the squared distance 293 */ 294 public default double distanceSq(Pnt2d other) { 295 return sqr(this.getX() - other.getX()) + sqr(this.getY() - other.getY()); 296 } 297 298 /** 299 * Returns the L2 (Euclidean) distance between this point and the given point. This method is equivalent to 300 * {@link #distL2(Pnt2d)}. 301 * 302 * @param other the other point 303 * @return the distance 304 */ 305 public default double distance(Pnt2d other) { 306 return Math.sqrt(this.distanceSq(other)); 307 } 308 309 310 /** 311 * Implementation required by {@link Primitive2d} interface. 312 */ 313 @Override 314 public default double getDistance(Pnt2d other) { 315 return Math.sqrt(this.distanceSq(other)); 316 } 317 318 /** 319 * Returns the L2 (Euclidean) distance between this point and the given point. This method is equivalent to 320 * {@link #distance(Pnt2d)}. 321 * 322 * @param other the other point 323 * @return the distance 324 */ 325 public default double distL2(Pnt2d other) { 326 return distance(other); 327 } 328 329 /** 330 * Returns the L1 (Manhattan) distance between this point and the given point. 331 * 332 * @param other the other point 333 * @return the distance 334 */ 335 public default double distL1(Pnt2d other) { 336 return Math.abs(this.getX() - other.getX()) + Math.abs(this.getY() - other.getY()); 337 } 338 339 public static final double DefaultDotRadius = 1.0; 340 341 /** 342 * Returns a round dot ({@link Shape} instance) for drawing this point. 343 * 344 * @param scale the dot radius 345 * @return the shape 346 */ 347 @Override 348 public default Shape getShape(double scale) { 349 double rad = scale * DefaultDotRadius; 350// return new Arc2D.Double(getX() - rad, getY() - rad, 2 * rad, 2 * rad, 0, 360, Arc2D.CHORD); 351 return new Ellipse2D.Double(getX() - rad, getY() - rad, 2 * rad, 2 * rad); 352 } 353 354 // ---------------------------------------------------------- 355 356 /** 357 * Immutable 2D point implementation with {@code double} coordinates. This class implements the {@link Pnt2d} 358 * interface. A protected constructor is provided but the preferred way of instantiation is by one of the static 359 * factory methods, such as {@link #from(double, double)}, {@link #from(double[])}, etc. Access to the coordinate 360 * values is provided by the methods {@link #getX()} and {@link #getY()}, but the actual field variables {@link #x}, 361 * {@link #y} are also publicly accessible (for better performance and less clutter). 362 */ 363 public class PntDouble implements Pnt2d { 364 365 /** 366 * Singleton point instance with zero coordinates. 367 */ 368 public static final PntDouble ZERO = PntDouble.from(0.0, 0.0); 369 370 /** The (immutable) x-coordinate of this point */ 371 public final double x; 372 /** The (immutable) y-coordinate of this point */ 373 public final double y; 374 375 /** 376 * Constructor. 377 * @param x x-coordinate 378 * @param y y-coordinate 379 */ 380 protected PntDouble(double x, double y) { 381 this.x = x; 382 this.y = y; 383 } 384 385 @Override 386 public PntDouble duplicate() { 387 return new PntDouble(this.x, this.y); 388 } 389 390 // static factory methods --------------------------------- 391 392 /** 393 * Returns a new {@link PntDouble} instance. 394 * @param xy x/y-coordinate array 395 * @return the new point 396 */ 397 public static PntDouble from(double[] xy) { 398 return new PntDouble(xy[0], xy[1]); 399 } 400 401 /** 402 * Returns a new {@link PntDouble} instance. 403 * @param x x-coordinate value 404 * @param y y-coordinate value 405 * @return the new point 406 */ 407 public static PntDouble from(double x, double y) { 408 return new PntDouble(x, y); 409 } 410 411 /** 412 * Returns a new {@link PntDouble} instance with the same coordinates as the given point. Equivalent to 413 * {@link #duplicate()}. 414 * 415 * @param p the original point 416 * @return the new point 417 */ 418 public static PntDouble from(Pnt2d p) { 419 return new PntDouble(p.getX(), p.getY()); 420 } 421 422 /** 423 * Returns a new {@link PntDouble} instance with the same coordinates as the given AWT {@link Point2D}. 424 * 425 * @param p the original AWT point 426 * @return the new point 427 */ 428 public static PntDouble from(Point2D p) { 429 return new PntDouble(p.getX(), p.getY()); 430 } 431 432 /** 433 * Returns a new {@link PntDouble} instance with the same coordinates as the given Apache Commons Math 434 * {@link Vector2D}. 435 * 436 * @param vec the original coordinate vector 437 * @return the new point 438 */ 439 public static PntDouble from(Vector2D vec) { 440 return new PntDouble(vec.getX(), vec.getY()); 441 } 442 443 // getter methods 444 445 @Override 446 public double getX() { 447 return this.x; 448 } 449 450 @Override 451 public double getY() { 452 return this.y; 453 } 454 455 @Override 456 public int getXint() { 457 return (int) this.x; 458 } 459 460 @Override 461 public int getYint() { 462 return (int) this.y; 463 } 464 465 466 // addition ----------------------------------- 467 468 @Override 469 public PntDouble plus(double dx, double dy) { 470 return new PntDouble(this.x + dx, this.y + dy); 471 } 472 473 // subtraction ----------------------------------- 474 475 @Override 476 public PntDouble minus(Pnt2d p) { 477 return new PntDouble(this.x - p.getX(), this.y - p.getY()); 478 } 479 480 // equality ----------------------------------- 481 482 @Override 483 public boolean equals(Object p) { 484 if (this == p) { 485 return true; 486 } 487 if (p instanceof Pnt2d) { 488 return this.equals((Pnt2d) p, TOLERANCE); 489 } 490 return false; 491 } 492 493 // misc ----------------------------------- 494 495 /** 496 * {@inheritDoc} The number of output digits is specified by the current settings of {@link PrintPrecision}. Use 497 * {@link PrintPrecision#set(int)} or {@link PrintPrecision#reset()} to change. 498 */ 499 @Override 500 public String toString() { 501 String fStr = PrintPrecision.getFormatStringFloat(); 502 return String.format(Locale.US, "%s[" + fStr + ", " + fStr + "]", 503 getClass().getSimpleName(), x, y); 504 } 505 506 @Override 507 public int hashCode() { // taken from awt.Point2D.Double 508 long bits = java.lang.Double.doubleToLongBits(this.x); 509 bits ^= java.lang.Double.doubleToLongBits(this.y) * 31; 510 return (((int) bits) ^ ((int) (bits >> 32))); 511 } 512 513 } 514 515 // ---------------------------------------------------------- 516 517 /** 518 * Immutable 2D point implementation with {@code int} coordinates. This class implements the {@link Pnt2d} 519 * interface. A protected constructor is provided but the preferred way of instantiation is by one of the static 520 * factory methods, such as {@link #from(int, int)}, {@link #from(int[])}, etc. The {@code int} coordinates can only 521 * be retrieved via the publicly accessible field variables {@link #x}, {@link #y}, while the methods 522 * {@link #getX()} and {@link #getY()} return {@code double} values for compatibility reasons. 523 */ 524 public class PntInt implements Pnt2d { 525 526 /** 527 * Singleton point instance with zero coordinates. 528 */ 529 public static final PntInt ZERO = PntInt.from(0, 0); 530 531 /** The (immutable) x-coordinate of this point */ 532 public final int x; 533 /** The (immutable) y-coordinate of this point */ 534 public final int y; 535 536 537 /** 538 * Constructor. 539 * @param x x-coordinate 540 * @param y y-coordinate 541 */ 542 protected PntInt(int x, int y) { 543 this.x = x; 544 this.y = y; 545 } 546 547 @Override 548 public PntInt duplicate() { 549 return new PntInt(this.x, this.y); 550 } 551 552 // static factory methods -------------------------------------- 553 554 /** 555 * Returns a new {@link PntInt} instance from a given point. 556 * 557 * @param p the original point 558 * @return the new point 559 */ 560 public static PntInt from(PntInt p) { 561 return new PntInt(p.x, p.y); 562 } 563 564 /** 565 * Returns a new {@link PntInt} instance. 566 * @param x x-coordinate value 567 * @param y y-coordinate value 568 * @return the new point 569 */ 570 public static PntInt from(int x, int y) { 571 return new PntInt(x, y); 572 } 573 574 /** 575 * Returns a new {@link PntInt} instance. 576 * @param xy x/y-coordinates 577 * @return the new point 578 */ 579 public static PntInt from(int[] xy) { 580 return new PntInt(xy[0], xy[1]); 581 } 582 583 /** 584 * Returns a new {@link PntInt} from a given {@link Pnt2d} instance. This only works if the argument is of type 585 * {@link Pnt2d.PntInt}, otherwise an exception is thrown. 586 * 587 * @param p a point of type {@link Pnt2d.PntInt} 588 * @return the new point 589 */ 590 public static PntInt from(Pnt2d p) { 591 if (p instanceof PntInt) { 592 return ((PntInt) p).duplicate(); 593 } 594 else { 595 throw new IllegalArgumentException("cannot convert to " + 596 PntInt.class.getSimpleName()); 597 } 598 } 599 600 /** 601 * Returns a new {@link PntDouble} instance with the same coordinates as the given AWT {@link Point}. 602 * 603 * @param p the original AWT point 604 * @return the new point 605 */ 606 public static PntInt from(Point p) { 607 return new PntInt(p.x, p.y); 608 } 609 610 // getter methods 611 612 @Override 613 public double getX() { 614 return this.x; 615 } 616 617 @Override 618 public double getY() { 619 return this.y; 620 } 621 622 @Override 623 public int getXint() { 624 return this.x; 625 } 626 627 @Override 628 public int getYint() { 629 return this.y; 630 } 631 632 // addition ----------------------------------- 633 634 public PntInt plus(int dx, int dy) { 635 return new PntInt(this.x + dx, this.y + dy); 636 } 637 638 public PntInt plus(PntInt p) { 639 return this.plus(p.x, p.y); 640 } 641 642 public PntInt plus(int[] dxy) { 643 return this.plus(dxy[0], dxy[1]); 644 } 645 646 @Override 647 public Pnt2d plus(Pnt2d p) { 648 if (p instanceof PntInt) { 649 return this.plus((PntInt)p); // new PntInt(this.x + ((PntInt)p).x, this.y + ((PntInt)p).y); 650 } 651 else { 652 return this.plus(p.getX(), p.getY()); 653 } 654 } 655 656 // subtraction ----------------------------------- 657 658 public PntInt minus(PntInt p) { 659 return this.minus(p.x, p.y); 660 } 661 662 public PntInt minus(int dx, int dy) { 663 return new PntInt(this.x - dx, this.y - dy); 664 } 665 666 @Override 667 public Pnt2d minus(Pnt2d p) { 668 if (p instanceof PntInt) { 669 return this.minus((PntInt)p); 670 } 671 else { 672 return this.minus(p.getX(), p.getY()); 673 } 674 } 675 676 // distance ----------------------------------- 677 678 /** 679 * Returns the L1 (Manhattan) distance between this point and the given point. 680 * 681 * @param p the other point 682 * @return the distance 683 */ 684 public int distL1(PntInt p) { 685 return Math.abs(this.x - p.x) + Math.abs(this.y - p.y); 686 } 687 688 /** 689 * Returns the squared L2 distance between this point and the given point. 690 * 691 * @param other the other point 692 * @return the squared distance 693 */ 694 public int distanceSq(PntInt other) { 695 return sqr(this.x - other.x) + sqr(this.y - other.y); 696 } 697 698 // equality ----------------------------------- 699 700 @Override 701 public boolean equals(Object p) { 702 if (this == p) { 703 return true; 704 } 705 if (p instanceof PntInt) { 706 PntInt pp = (PntInt) p; 707 return (this.x == pp.x) && (this.y == pp.y); 708 } 709 if (p instanceof Pnt2d) { 710 return this.equals((Pnt2d) p, TOLERANCE); 711 } 712 return false; 713 } 714 715 // misc ----------------------------------- 716 717 @Override 718 public String toString() { 719 return String.format(Locale.US, "%s[%d, %d]", 720 getClass().getSimpleName(), x, y); 721 } 722 723 // @Override 724 // public int hashCode() { 725 // return (17 + this.x) * 37 + this.y; 726 // } 727 728 @Override 729 public int hashCode() { 730 long bits = java.lang.Double.doubleToLongBits(getX()); 731 bits ^= java.lang.Double.doubleToLongBits(getY()) * 31; 732 return (((int) bits) ^ ((int) (bits >> 32))); 733 } 734 735 /** 736 * Returns this point's coordinates as a new 2-element {@code int} array. 737 * @return the array 738 */ 739 public int[] toIntArray() { 740 return new int[] {this.x, this.y}; 741 } 742 743 /** 744 * Returns this point's coordinates as a new AWT {@link Point} point. 745 * @return the new point 746 */ 747 public Point toAwtPoint() { 748 return new Point(this.x, this.y); 749 } 750 751 752 753 } 754 755}