/* * @(#)GeneralPath.java 1.59 03/12/19 * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package java.awt.geom; import java.awt.Shape; import sun.awt.geom.Curve; import sun.awt.geom.Crossings; /** * The GeneralPath class represents a geometric path * constructed from straight lines, and quadratic and cubic * (Bézier) curves. It can contain multiple subpaths. *

* The winding rule specifies how the interior of a path is * determined. There are two types of winding rules: * EVEN_ODD and NON_ZERO. *

* An EVEN_ODD winding rule means that enclosed regions * of the path alternate between interior and exterior areas as * traversed from the outside of the path towards a point inside * the region. *

* A NON_ZERO winding rule means that if a ray is * drawn in any direction from a given point to infinity * and the places where the path intersects * the ray are examined, the point is inside of the path if and only if * the number of times that the path crosses the ray from * left to right does not equal the number of times that the path crosses * the ray from right to left. * @version 1.59, 12/19/03 * @author Jim Graham */ public final class GeneralPath implements Shape, Cloneable { /** * An even-odd winding rule for determining the interior of * a path. */ public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD; /** * A non-zero winding rule for determining the interior of a * path. */ public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO; // For code simplicity, copy these constants to our namespace // and cast them to byte constants for easy storage. private static final byte SEG_MOVETO = (byte) PathIterator.SEG_MOVETO; private static final byte SEG_LINETO = (byte) PathIterator.SEG_LINETO; private static final byte SEG_QUADTO = (byte) PathIterator.SEG_QUADTO; private static final byte SEG_CUBICTO = (byte) PathIterator.SEG_CUBICTO; private static final byte SEG_CLOSE = (byte) PathIterator.SEG_CLOSE; byte[] pointTypes; float[] pointCoords; int numTypes; int numCoords; int windingRule; static final int INIT_SIZE = 20; static final int EXPAND_MAX = 500; /** * Constructs a new GeneralPath object. * If an operation performed on this path requires the * interior of the path to be defined then the default NON_ZERO * winding rule is used. * @see #WIND_NON_ZERO */ public GeneralPath() { this(WIND_NON_ZERO, INIT_SIZE, INIT_SIZE); } /** * Constructs a new GeneralPath object with the specified * winding rule to control operations that require the interior of the * path to be defined. * @param rule the winding rule * @see #WIND_EVEN_ODD * @see #WIND_NON_ZERO */ public GeneralPath(int rule) { this(rule, INIT_SIZE, INIT_SIZE); } /** * Constructs a new GeneralPath object with the specified * winding rule and the specified initial capacity to store path * coordinates. This number is an initial guess as to how many path * segments are in the path, but the storage is expanded * as needed to store whatever path segments are added to this path. * @param rule the winding rule * @param initialCapacity the estimate for the number of path segments * in the path * @see #WIND_EVEN_ODD * @see #WIND_NON_ZERO */ public GeneralPath(int rule, int initialCapacity) { this(rule, initialCapacity, initialCapacity); } /** * Constructs a new GeneralPath object with the specified * winding rule and the specified initial capacities to store point types * and coordinates. * These numbers are an initial guess as to how many path segments * and how many points are to be in the path, but the * storage is expanded as needed to store whatever path segments are * added to this path. * @param rule the winding rule * @param initialTypes the estimate for the number of path segments * in the path * @param initialCapacity the estimate for the number of points * @see #WIND_EVEN_ODD * @see #WIND_NON_ZERO */ GeneralPath(int rule, int initialTypes, int initialCoords) { setWindingRule(rule); pointTypes = new byte[initialTypes]; pointCoords = new float[initialCoords * 2]; } /** * Constructs a new GeneralPath object from an arbitrary * {@link Shape} object. * All of the initial geometry and the winding rule for this path are * taken from the specified Shape object. * @param s the specified Shape object */ public GeneralPath(Shape s) { this(WIND_NON_ZERO, INIT_SIZE, INIT_SIZE); PathIterator pi = s.getPathIterator(null); setWindingRule(pi.getWindingRule()); append(pi, false); } private void needRoom(int newTypes, int newCoords, boolean needMove) { if (needMove && numTypes == 0) { throw new IllegalPathStateException("missing initial moveto "+ "in path definition"); } int size = pointCoords.length; if (numCoords + newCoords > size) { int grow = size; if (grow > EXPAND_MAX * 2) { grow = EXPAND_MAX * 2; } if (grow < newCoords) { grow = newCoords; } float[] arr = new float[size + grow]; System.arraycopy(pointCoords, 0, arr, 0, numCoords); pointCoords = arr; } size = pointTypes.length; if (numTypes + newTypes > size) { int grow = size; if (grow > EXPAND_MAX) { grow = EXPAND_MAX; } if (grow < newTypes) { grow = newTypes; } byte[] arr = new byte[size + grow]; System.arraycopy(pointTypes, 0, arr, 0, numTypes); pointTypes = arr; } } /** * Adds a point to the path by moving to the specified * coordinates. * @param x, y the specified coordinates */ public synchronized void moveTo(float x, float y) { if (numTypes > 0 && pointTypes[numTypes - 1] == SEG_MOVETO) { pointCoords[numCoords - 2] = x; pointCoords[numCoords - 1] = y; } else { needRoom(1, 2, false); pointTypes[numTypes++] = SEG_MOVETO; pointCoords[numCoords++] = x; pointCoords[numCoords++] = y; } } /** * Adds a point to the path by drawing a straight line from the * current coordinates to the new specified coordinates. * @param x, y the specified coordinates */ public synchronized void lineTo(float x, float y) { needRoom(1, 2, true); pointTypes[numTypes++] = SEG_LINETO; pointCoords[numCoords++] = x; pointCoords[numCoords++] = y; } /** * Adds a curved segment, defined by two new points, to the path by * drawing a Quadratic curve that intersects both the current * coordinates and the coordinates (x2, y2), using the * specified point (x1, y1) as a quadratic parametric control * point. * @param x1, y1 the coordinates of the first quadratic control * point * @param x2, y2 the coordinates of the final endpoint */ public synchronized void quadTo(float x1, float y1, float x2, float y2) { needRoom(1, 4, true); pointTypes[numTypes++] = SEG_QUADTO; pointCoords[numCoords++] = x1; pointCoords[numCoords++] = y1; pointCoords[numCoords++] = x2; pointCoords[numCoords++] = y2; } /** * Adds a curved segment, defined by three new points, to the path by * drawing a Bézier curve that intersects both the current * coordinates and the coordinates (x3, y3), using the * specified points (x1, y1) and (x2, y2) as * Bézier control points. * @param x1, y1 the coordinates of the first Béezier * control point * @param x2, y2 the coordinates of the second Bézier * control point * @param x3, y3 the coordinates of the final endpoint */ public synchronized void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) { needRoom(1, 6, true); pointTypes[numTypes++] = SEG_CUBICTO; pointCoords[numCoords++] = x1; pointCoords[numCoords++] = y1; pointCoords[numCoords++] = x2; pointCoords[numCoords++] = y2; pointCoords[numCoords++] = x3; pointCoords[numCoords++] = y3; } /** * Closes the current subpath by drawing a straight line back to * the coordinates of the last moveTo. If the path is already * closed then this method has no effect. */ public synchronized void closePath() { if (numTypes == 0 || pointTypes[numTypes - 1] != SEG_CLOSE) { needRoom(1, 0, true); pointTypes[numTypes++] = SEG_CLOSE; } } /** * Appends the geometry of the specified Shape object to the * path, possibly connecting the new geometry to the existing path * segments with a line segment. * If the connect parameter is true and the * path is not empty then any initial moveTo in the * geometry of the appended Shape * is turned into a lineTo segment. * If the destination coordinates of such a connecting lineTo * segment match the ending coordinates of a currently open * subpath then the segment is omitted as superfluous. * The winding rule of the specified Shape is ignored * and the appended geometry is governed by the winding * rule specified for this path. * @param s the Shape whose geometry is appended * to this path * @param connect a boolean to control whether or not to turn an * initial moveTo segment into a lineTo * segment to connect the new geometry to the existing path */ public void append(Shape s, boolean connect) { PathIterator pi = s.getPathIterator(null); append(pi,connect); } /** * Appends the geometry of the specified * {@link PathIterator} object * to the path, possibly connecting the new geometry to the existing * path segments with a line segment. * If the connect parameter is true and the * path is not empty then any initial moveTo in the * geometry of the appended Shape is turned into a * lineTo segment. * If the destination coordinates of such a connecting lineTo * segment match the ending coordinates of a currently open * subpath then the segment is omitted as superfluous. * The winding rule of the specified Shape is ignored * and the appended geometry is governed by the winding * rule specified for this path. * @param pi the PathIterator whose geometry is appended to * this path * @param connect a boolean to control whether or not to turn an * initial moveTo segment into a lineTo segment * to connect the new geometry to the existing path */ public void append(PathIterator pi, boolean connect) { float coords[] = new float[6]; while (!pi.isDone()) { switch (pi.currentSegment(coords)) { case SEG_MOVETO: if (!connect || numTypes < 1 || numCoords < 2) { moveTo(coords[0], coords[1]); break; } if (pointTypes[numTypes - 1] != SEG_CLOSE && pointCoords[numCoords - 2] == coords[0] && pointCoords[numCoords - 1] == coords[1]) { // Collapse out initial moveto/lineto break; } // NO BREAK; case SEG_LINETO: lineTo(coords[0], coords[1]); break; case SEG_QUADTO: quadTo(coords[0], coords[1], coords[2], coords[3]); break; case SEG_CUBICTO: curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); break; case SEG_CLOSE: closePath(); break; } pi.next(); connect = false; } } /** * Returns the fill style winding rule. * @return an integer representing the current winding rule. * @see #WIND_EVEN_ODD * @see #WIND_NON_ZERO * @see #setWindingRule */ public synchronized int getWindingRule() { return windingRule; } /** * Sets the winding rule for this path to the specified value. * @param rule an integer representing the specified * winding rule * @exception IllegalArgumentException if * rule is not either * WIND_EVEN_ODD or * WIND_NON_ZERO * @see #WIND_EVEN_ODD * @see #WIND_NON_ZERO * @see #getWindingRule */ public void setWindingRule(int rule) { if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) { throw new IllegalArgumentException("winding rule must be "+ "WIND_EVEN_ODD or "+ "WIND_NON_ZERO"); } windingRule = rule; } /** * Returns the coordinates most recently added to the end of the path * as a {@link Point2D} object. * @return a Point2D object containing the ending * coordinates of the path or null if there are no points * in the path. */ public synchronized Point2D getCurrentPoint() { if (numTypes < 1 || numCoords < 2) { return null; } int index = numCoords; if (pointTypes[numTypes - 1] == SEG_CLOSE) { loop: for (int i = numTypes - 2; i > 0; i--) { switch (pointTypes[i]) { case SEG_MOVETO: break loop; case SEG_LINETO: index -= 2; break; case SEG_QUADTO: index -= 4; break; case SEG_CUBICTO: index -= 6; break; case SEG_CLOSE: break; } } } return new Point2D.Float(pointCoords[index - 2], pointCoords[index - 1]); } /** * Resets the path to empty. The append position is set back to the * beginning of the path and all coordinates and point types are * forgotten. */ public synchronized void reset() { numTypes = numCoords = 0; } /** * Transforms the geometry of this path using the specified * {@link AffineTransform}. * The geometry is transformed in place, which permanently changes the * boundary defined by this object. * @param at the AffineTransform used to transform the area */ public void transform(AffineTransform at) { at.transform(pointCoords, 0, pointCoords, 0, numCoords / 2); } /** * Returns a new transformed Shape. * @param at the AffineTransform used to transform a * new Shape. * @return a new Shape, transformed with the specified * AffineTransform. */ public synchronized Shape createTransformedShape(AffineTransform at) { GeneralPath gp = (GeneralPath) clone(); if (at != null) { gp.transform(at); } return gp; } /** * Return the bounding box of the path. * @return a {@link java.awt.Rectangle} object that * bounds the current path. */ public java.awt.Rectangle getBounds() { return getBounds2D().getBounds(); } /** * Returns the bounding box of the path. * @return a {@link Rectangle2D} object that * bounds the current path. */ public synchronized Rectangle2D getBounds2D() { float x1, y1, x2, y2; int i = numCoords; if (i > 0) { y1 = y2 = pointCoords[--i]; x1 = x2 = pointCoords[--i]; while (i > 0) { float y = pointCoords[--i]; float x = pointCoords[--i]; if (x < x1) x1 = x; if (y < y1) y1 = y; if (x > x2) x2 = x; if (y > y2) y2 = y; } } else { x1 = y1 = x2 = y2 = 0.0f; } return new Rectangle2D.Float(x1, y1, x2 - x1, y2 - y1); } /** * Tests if the specified coordinates are inside the boundary of * this Shape. * @param x, y the specified coordinates * @return true if the specified coordinates are inside this * Shape; false otherwise */ public boolean contains(double x, double y) { if (numTypes < 2) { return false; } int cross = Curve.crossingsForPath(getPathIterator(null), x, y); if (windingRule == WIND_NON_ZERO) { return (cross != 0); } else { return ((cross & 1) != 0); } } /** * Tests if the specified Point2D is inside the boundary * of this Shape. * @param p the specified Point2D * @return true if this Shape contains the * specified Point2D, false otherwise. */ public boolean contains(Point2D p) { return contains(p.getX(), p.getY()); } /** * Tests if the specified rectangular area is inside the boundary of * this Shape. * @param x, y the specified coordinates * @param w the width of the specified rectangular area * @param h the height of the specified rectangular area * @return true if this Shape contains * the specified rectangluar area; false otherwise. */ public boolean contains(double x, double y, double w, double h) { Crossings c = Crossings.findCrossings(getPathIterator(null), x, y, x+w, y+h); return (c != null && c.covers(y, y+h)); } /** * Tests if the specified Rectangle2D * is inside the boundary of this Shape. * @param r a specified Rectangle2D * @return true if this Shape bounds the * specified Rectangle2D; false otherwise. */ public boolean contains(Rectangle2D r) { return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } /** * Tests if the interior of this Shape intersects the * interior of a specified set of rectangular coordinates. * @param x, y the specified coordinates * @param w the width of the specified rectangular coordinates * @param h the height of the specified rectangular coordinates * @return true if this Shape and the * interior of the specified set of rectangular coordinates intersect * each other; false otherwise. */ public boolean intersects(double x, double y, double w, double h) { Crossings c = Crossings.findCrossings(getPathIterator(null), x, y, x+w, y+h); return (c == null || !c.isEmpty()); } /** * Tests if the interior of this Shape intersects the * interior of a specified Rectangle2D. * @param r the specified Rectangle2D * @return true if this Shape and the interior * of the specified Rectangle2D intersect each * other; false otherwise. */ public boolean intersects(Rectangle2D r) { return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } /** * Returns a PathIterator object that iterates along the * boundary of this Shape and provides access to the * geometry of the outline of this Shape. * The iterator for this class is not multi-threaded safe, * which means that this GeneralPath class does not * guarantee that modifications to the geometry of this * GeneralPath object do not affect any iterations of * that geometry that are already in process. * @param at an AffineTransform * @return a new PathIterator that iterates along the * boundary of this Shape and provides access to the * geometry of this Shape's outline */ public PathIterator getPathIterator(AffineTransform at) { return new GeneralPathIterator(this, at); } /** * Returns a PathIterator object that iterates along the * boundary of the flattened Shape and provides access to the * geometry of the outline of the Shape. * The iterator for this class is not multi-threaded safe, * which means that this GeneralPath class does not * guarantee that modifications to the geometry of this * GeneralPath object do not affect any iterations of * that geometry that are already in process. * @param at an AffineTransform * @param flatness the maximum distance that the line segments used to * approximate the curved segments are allowed to deviate * from any point on the original curve * @return a new PathIterator that iterates along the flattened * Shape boundary. */ public PathIterator getPathIterator(AffineTransform at, double flatness) { return new FlatteningPathIterator(getPathIterator(at), flatness); } /** * Creates a new object of the same class as this object. * * @return a clone of this instance. * @exception OutOfMemoryError if there is not enough memory. * @see java.lang.Cloneable * @since 1.2 */ public Object clone() { try { GeneralPath copy = (GeneralPath) super.clone(); copy.pointTypes = (byte[]) pointTypes.clone(); copy.pointCoords = (float[]) pointCoords.clone(); return copy; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } } GeneralPath(int windingRule, byte[] pointTypes, int numTypes, float[] pointCoords, int numCoords) { // used to construct from native this.windingRule = windingRule; this.pointTypes = pointTypes; this.numTypes = numTypes; this.pointCoords = pointCoords; this.numCoords = numCoords; } }