/** * */ package icy.type.geom; import java.awt.Point; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.List; /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Modified by Stephane Dallongeville */ /** * This class is a Polygon with double coordinates. */ public class Polygon2D implements Shape, Cloneable { private static void addFarthestPoint(List<Point2D> result, List<Point2D> points, int start, int end, double maxDeviation) { final Point2D p1 = points.get(start); final Point2D p2 = points.get(end); final double x1 = p1.getX(); final double y1 = p1.getY(); final double x2 = p2.getX(); final double y2 = p2.getY(); int farthest = -1; // initialize with maximum allow deviation (squared) double maxDist = maxDeviation * maxDeviation; for (int i = start + 1; i < end; i++) { final Point2D p = points.get(i); final double dist = Line2D.ptSegDistSq(x1, y1, x2, y2, p.getX(), p.getY()); if (dist > maxDist) { farthest = i; maxDist = dist; } } // found a point to add ? if (farthest != -1) { // search before point addFarthestPoint(result, points, start, farthest, maxDeviation); // add point result.add(points.get(farthest)); // search after point addFarthestPoint(result, points, farthest, end, maxDeviation); } } /** * Returns a polygon2D corresponding to the polygon estimation of the given (closed) contour points with the * specified <code>max deviation</code>. * * @param points * the list of points representing the input closed contour to transform to polygon. * @param maxDeviation * maximum allowed deviation/distance of resulting polygon from the input contour (in pixel). * @return the polygon estimation from input contour */ public static Polygon2D getPolygon2D(List<Point2D> points, double maxDeviation) { // just return if (points.size() < 3) return new Polygon2D(points); final List<Point2D> result = new ArrayList<Point2D>(points.size() / 4); int ind = points.size() / 2; // close the contour points.add(points.get(0)); // add first point result.add(points.get(0)); // add points between first and medium addFarthestPoint(result, points, 0, ind, maxDeviation); // add medium point result.add(points.get(ind)); // add points between medium and end addFarthestPoint(result, points, ind, points.size() - 1, maxDeviation); // restore original contour points.remove(points.size() - 1); return new Polygon2D(result); } /** * The total number of points. The value of <code>npoints</code> represents the number of valid points in this * <code>Polygon</code>. */ public int npoints; /** * The array of <i>x</i> coordinates. The value of {@link #npoints} is equal to the * number of points in this <code>Polygon2D</code>. */ public double[] xpoints; /** * The array of <i>y</i> coordinates. The value of {@link #npoints} is equal to the * number of points in this <code>Polygon2D</code>. */ public double[] ypoints; /** * Bounds of the Polygon2D. * * @see #getBounds() */ protected Rectangle2D bounds; protected Path2D.Double path; protected Path2D.Double closedPath; /** * Creates an empty Polygon2D. */ public Polygon2D() { super(); reset(); } /** * Constructs and initializes a <code>Polygon2D</code> from the specified * Rectangle2D. * * @param rec * the Rectangle2D * @exception NullPointerException * rec is <code>null</code>. */ public Polygon2D(Rectangle2D rec) { super(); if (rec == null) throw new IllegalArgumentException("null Rectangle"); npoints = 4; xpoints = new double[4]; ypoints = new double[4]; xpoints[0] = rec.getMinX(); ypoints[0] = rec.getMinY(); xpoints[1] = rec.getMaxX(); ypoints[1] = rec.getMinY(); xpoints[2] = rec.getMaxX(); ypoints[2] = rec.getMaxY(); xpoints[3] = rec.getMinX(); ypoints[3] = rec.getMaxY(); calculatePath(); } /** * Constructs and initializes a <code>Polygon2D</code> from the specified * Polygon. * * @param pol * the Polygon * @exception NullPointerException * pol is <code>null</code>. */ public Polygon2D(Polygon pol) { super(); if (pol == null) throw new IllegalArgumentException("null Polygon"); this.npoints = pol.npoints; this.xpoints = new double[pol.npoints]; this.ypoints = new double[pol.npoints]; for (int i = 0; i < pol.npoints; i++) { xpoints[i] = pol.xpoints[i]; ypoints[i] = pol.ypoints[i]; } calculatePath(); } /** * Constructs and initializes a <code>Polygon2D</code> from the specified * parameters. * * @param xpoints * an array of <i>x</i> coordinates * @param ypoints * an array of <i>y</i> coordinates * @param npoints * the total number of points in the <code>Polygon2D</code> * @exception NegativeArraySizeException * if the value of <code>npoints</code> is negative. * @exception IndexOutOfBoundsException * if <code>npoints</code> is * greater than the length of <code>xpoints</code> or the length of <code>ypoints</code>. * @exception NullPointerException * if <code>xpoints</code> or <code>ypoints</code> is <code>null</code>. */ public Polygon2D(double[] xpoints, double[] ypoints, int npoints) { super(); if (npoints > xpoints.length || npoints > ypoints.length) throw new IndexOutOfBoundsException("npoints > xpoints.length || npoints > ypoints.length"); this.npoints = npoints; this.xpoints = new double[npoints]; this.ypoints = new double[npoints]; System.arraycopy(xpoints, 0, this.xpoints, 0, npoints); System.arraycopy(ypoints, 0, this.ypoints, 0, npoints); calculatePath(); } /** * Constructs and initializes a <code>Polygon2D</code> from the specified * parameters. * * @param xpoints * an array of <i>x</i> coordinates * @param ypoints * an array of <i>y</i> coordinates * @param npoints * the total number of points in the <code>Polygon2D</code> * @exception NegativeArraySizeException * if the value of <code>npoints</code> is negative. * @exception IndexOutOfBoundsException * if <code>npoints</code> is * greater than the length of <code>xpoints</code> or the length of <code>ypoints</code>. * @exception NullPointerException * if <code>xpoints</code> or <code>ypoints</code> is <code>null</code>. */ public Polygon2D(int[] xpoints, int[] ypoints, int npoints) { super(); if (npoints > xpoints.length || npoints > ypoints.length) throw new IndexOutOfBoundsException("npoints > xpoints.length || npoints > ypoints.length"); this.npoints = npoints; this.xpoints = new double[npoints]; this.ypoints = new double[npoints]; for (int i = 0; i < npoints; i++) { this.xpoints[i] = xpoints[i]; this.ypoints[i] = ypoints[i]; } calculatePath(); } public Polygon2D(List<Point2D> points) { super(); final int len = points.size(); this.npoints = len; this.xpoints = new double[len]; this.ypoints = new double[len]; for (int i = 0; i < len; i++) { final Point2D pt = points.get(i); this.xpoints[i] = pt.getX(); this.ypoints[i] = pt.getY(); } calculatePath(); } /** * Resets this <code>Polygon</code> object to an empty polygon. */ public void reset() { npoints = 0; xpoints = new double[0]; ypoints = new double[0]; bounds = new Rectangle2D.Double(); path = null; closedPath = null; } @Override public Object clone() { Polygon2D pol = new Polygon2D(); for (int i = 0; i < npoints; i++) pol.addPoint(xpoints[i], ypoints[i]); return pol; } public void calculatePath() { path = new Path2D.Double(); path.moveTo(xpoints[0], ypoints[0]); for (int i = 1; i < npoints; i++) path.lineTo(xpoints[i], ypoints[i]); bounds = path.getBounds2D(); closedPath = null; } protected void updatePath(double x, double y) { if (path == null) { path = new Path2D.Double(Path2D.WIND_EVEN_ODD); path.moveTo(x, y); bounds = new Rectangle2D.Double(x, y, 0, 0); } else { path.lineTo(x, y); double _xmax = bounds.getMaxX(); double _ymax = bounds.getMaxY(); double _xmin = bounds.getMinX(); double _ymin = bounds.getMinY(); if (x < _xmin) _xmin = x; else if (x > _xmax) _xmax = x; if (y < _ymin) _ymin = y; else if (y > _ymax) _ymax = y; bounds = new Rectangle2D.Double(_xmin, _ymin, _xmax - _xmin, _ymax - _ymin); } closedPath = null; } /* * get the associated {@link Polyline2D}. */ public Polyline2D getPolyline2D() { Polyline2D pol = new Polyline2D(xpoints, ypoints, npoints); pol.addPoint(xpoints[0], ypoints[0]); return pol; } public Polygon getPolygon() { int[] _xpoints = new int[npoints]; int[] _ypoints = new int[npoints]; for (int i = 0; i < npoints; i++) { _xpoints[i] = (int) xpoints[i]; // todo maybe rounding is better ? _ypoints[i] = (int) ypoints[i]; } return new Polygon(_xpoints, _ypoints, npoints); } public void addPoint(Point2D p) { addPoint(p.getX(), p.getY()); } /** * Appends the specified coordinates to this <code>Polygon2D</code>. * * @param x * the specified x coordinate * @param y * the specified y coordinate */ public void addPoint(double x, double y) { if (npoints == xpoints.length) { double[] tmp; tmp = new double[(npoints * 2) + 1]; System.arraycopy(xpoints, 0, tmp, 0, npoints); xpoints = tmp; tmp = new double[(npoints * 2) + 1]; System.arraycopy(ypoints, 0, tmp, 0, npoints); ypoints = tmp; } xpoints[npoints] = x; ypoints[npoints] = y; npoints++; updatePath(x, y); } /** * Determines whether the specified {@link Point} is inside this <code>Polygon</code>. * * @param p * the specified <code>Point</code> to be tested * @return <code>true</code> if the <code>Polygon</code> contains the <code>Point</code>; <code>false</code> * otherwise. * @see #contains(double, double) */ public boolean contains(Point p) { return contains(p.x, p.y); } /** * Determines whether the specified coordinates are inside this <code>Polygon</code>. * <p> * * @param x * the specified x coordinate to be tested * @param y * the specified y coordinate to be tested * @return <code>true</code> if this <code>Polygon</code> contains * the specified coordinates, (<i>x</i>, <i>y</i>); <code>false</code> otherwise. */ public boolean contains(int x, int y) { return contains((double) x, (double) y); } /** * Returns the high precision bounding box of the {@link Shape}. * * @return a {@link Rectangle2D} that precisely * bounds the <code>Shape</code>. */ @Override public Rectangle2D getBounds2D() { return (Rectangle2D) bounds.clone(); } @Override public Rectangle getBounds() { return bounds.getBounds(); } /** * Determines if the specified coordinates are inside this <code>Polygon</code>. For the definition of * <i>insideness</i>, see the class comments of {@link Shape}. * * @param x * the specified x coordinate * @param y * the specified y coordinate * @return <code>true</code> if the <code>Polygon</code> contains the * specified coordinates; <code>false</code> otherwise. */ @Override public boolean contains(double x, double y) { if (npoints <= 2 || !bounds.contains(x, y)) return false; return updateComputingPath().contains(x, y); } protected Path2D.Double updateComputingPath() { Path2D.Double result = closedPath; // need to recompute it ? if (result == null) { if (path != null) { result = (Path2D.Double) path.clone(); result.closePath(); } else // empty path result = new Path2D.Double(); closedPath = result; } return result; } /** * Tests if a specified {@link Point2D} is inside the boundary of this <code>Polygon</code>. * * @param p * a specified <code>Point2D</code> * @return <code>true</code> if this <code>Polygon</code> contains the * specified <code>Point2D</code>; <code>false</code> otherwise. * @see #contains(double, double) */ @Override public boolean contains(Point2D p) { return contains(p.getX(), p.getY()); } /** * Tests if the interior of this <code>Polygon</code> intersects the * interior of a specified set of rectangular coordinates. * * @param x * the x coordinate of the specified rectangular * shape's top-left corner * @param y * the y coordinate of the specified rectangular * shape's top-left corner * @param w * the width of the specified rectangular shape * @param h * the height of the specified rectangular shape * @return <code>true</code> if the interior of this <code>Polygon</code> and the interior of the * specified set of rectangular * coordinates intersect each other; <code>false</code> otherwise. */ @Override public boolean intersects(double x, double y, double w, double h) { if (npoints <= 0 || !bounds.intersects(x, y, w, h)) return false; return updateComputingPath().intersects(x, y, w, h); } /** * Tests if the interior of this <code>Polygon</code> intersects the * interior of a specified <code>Rectangle2D</code>. * * @param r * a specified <code>Rectangle2D</code> * @return <code>true</code> if this <code>Polygon</code> and the * interior of the specified <code>Rectangle2D</code> intersect each other; <code>false</code> otherwise. */ @Override public boolean intersects(Rectangle2D r) { return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } /** * Tests if the interior of this <code>Polygon</code> entirely * contains the specified set of rectangular coordinates. * * @param x * the x coordinate of the top-left corner of the * specified set of rectangular coordinates * @param y * the y coordinate of the top-left corner of the * specified set of rectangular coordinates * @param w * the width of the set of rectangular coordinates * @param h * the height of the set of rectangular coordinates * @return <code>true</code> if this <code>Polygon</code> entirely * contains the specified set of rectangular * coordinates; <code>false</code> otherwise. */ @Override public boolean contains(double x, double y, double w, double h) { if (npoints <= 0 || !bounds.intersects(x, y, w, h)) return false; return updateComputingPath().contains(x, y, w, h); } /** * Tests if the interior of this <code>Polygon</code> entirely * contains the specified <code>Rectangle2D</code>. * * @param r * the specified <code>Rectangle2D</code> * @return <code>true</code> if this <code>Polygon</code> entirely * contains the specified <code>Rectangle2D</code>; <code>false</code> otherwise. * @see #contains(double, double, double, double) */ @Override public boolean contains(Rectangle2D r) { return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } /** * Returns an iterator object that iterates along the boundary of this <code>Polygon</code> and provides access to * the geometry of the outline of this <code>Polygon</code>. An optional {@link AffineTransform} can be specified so * that the coordinates returned in the iteration are transformed accordingly. * * @param at * an optional <code>AffineTransform</code> to be applied to the * coordinates as they are returned in the iteration, or <code>null</code> if untransformed coordinates are * desired * @return a {@link PathIterator} object that provides access to the * geometry of this <code>Polygon</code>. */ @Override public PathIterator getPathIterator(AffineTransform at) { return updateComputingPath().getPathIterator(at); } /** * Returns an iterator object that iterates along the boundary of * the <code>Polygon2D</code> and provides access to the geometry of the * outline of the <code>Shape</code>. Only SEG_MOVETO, SEG_LINETO, and * SEG_CLOSE point types are returned by the iterator. * Since polygons are already flat, the <code>flatness</code> parameter * is ignored. * * @param at * an optional <code>AffineTransform</code> to be applied to the * coordinates as they are returned in the iteration, or <code>null</code> if untransformed coordinates are * desired * @param flatness * the maximum amount that the control points * for a given curve can vary from colinear before a subdivided * curve is replaced by a straight line connecting the * endpoints. Since polygons are already flat the <code>flatness</code> parameter is ignored. * @return a <code>PathIterator</code> object that provides access to the <code>Shape</code> object's geometry. */ @Override public PathIterator getPathIterator(AffineTransform at, double flatness) { return getPathIterator(at); } }