/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * Licensed 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. */ package org.arakhne.afc.math.discrete.object2d; import java.lang.ref.SoftReference; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; import org.arakhne.afc.math.MathConstants; import org.arakhne.afc.math.generic.Path2D; import org.arakhne.afc.math.generic.PathElementType; import org.arakhne.afc.math.generic.PathWindingRule; import org.arakhne.afc.math.generic.Point2D; import org.arakhne.afc.math.geometry.d2.afp.Segment2afp; import org.arakhne.afc.math.matrix.Transform2D; import org.arakhne.afc.vmutil.ReflectionUtil; /** A generic path with integer coordinates. * * @author $Author: sgalland$ * @author $Author: fozgul$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @deprecated see {@link org.arakhne.afc.math.geometry.d2.i.Path2i} */ @Deprecated @SuppressWarnings("all") public class Path2i extends AbstractShape2i<Path2i> implements Path2D<Shape2i,Rectangle2i,PathElement2i,PathIterator2i> { private static final long serialVersionUID = -4229773257722403127L; /** Multiple of cubic & quad curve size. */ static final int GROW_SIZE = 24; /** * Calculates the number of times the given path * crosses the given segment extending to the right. * * @param pi is the description of the path. * @param x1 is the first point of the segment. * @param y1 is the first point of the segment. * @param x2 is the first point of the segment. * @param y2 is the first point of the segment. * @return the crossing or {@link MathConstants#SHAPE_INTERSECTS}. */ public static int computeCrossingsFromSegment(PathIterator2i pi, int x1, int y1, int x2, int y2) { return computeCrossingsFromSegment(0, pi, x1, y1, x2, y2, true); } /** * Calculates the number of times the given path * crosses the given circle extending to the right. * * @param crossings is the initial value for crossing. * @param pi is the description of the path. * @param x1 is the first point of the segment. * @param y1 is the first point of the segment. * @param x2 is the first point of the segment. * @param y2 is the first point of the segment. * @param closeable indicates if the shape is automatically closed or not. * @return the crossing */ static int computeCrossingsFromSegment(int crossings, PathIterator2i pi, int x1, int y1, int x2, int y2, boolean closeable) { // Copied from the AWT API if (!pi.hasNext()) return 0; PathElement2i element; element = pi.next(); if (element.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); //$NON-NLS-1$ } int movx = element.toX; int movy = element.toY; int curx = movx; int cury = movy; int endx, endy; int numCrosses = crossings; while (numCrosses!=MathConstants.SHAPE_INTERSECTS && pi.hasNext()) { element = pi.next(); switch (element.type) { case MOVE_TO: movx = curx = element.toX; movy = cury = element.toY; break; case LINE_TO: endx = element.toX; endy = element.toY; numCrosses = Segment2i.computeCrossingsFromSegment( numCrosses, x1, y1, x2, y2, curx, cury, endx, endy); curx = endx; cury = endy; break; case QUAD_TO: { endx = element.toX; endy = element.toY; Path2i localPath = new Path2i(); localPath.moveTo(element.fromX, element.fromY); localPath.quadTo( element.ctrlX1, element.ctrlY1, endx, endy); numCrosses = computeCrossingsFromSegment( numCrosses, localPath.getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO), x1, y1, x2, y2, false); curx = endx; cury = endy; break; } case CURVE_TO: endx = element.toX; endy = element.toY; Path2i localPath = new Path2i(); localPath.moveTo(element.fromX, element.fromY); localPath.curveTo( element.ctrlX1, element.ctrlY1, element.ctrlX2, element.ctrlY2, endx, endy); numCrosses = computeCrossingsFromSegment( numCrosses, localPath.getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO), x1, y1, x2, y2, false); curx = endx; cury = endy; break; case CLOSE: if (cury != movy || curx != movx) { numCrosses = Segment2i.computeCrossingsFromSegment( numCrosses, x1, y1, x2, y2, curx, cury, movx, movy); } curx = movx; cury = movy; break; default: } } if (numCrosses!=MathConstants.SHAPE_INTERSECTS && closeable && cury != movy) { numCrosses = Segment2i.computeCrossingsFromSegment( numCrosses, x1, y1, x2, y2, curx, cury, movx, movy); } return numCrosses; } /** * Calculates the number of times the given path * crosses the given ellipse extending to the right. * * @param pi is the description of the path. * @param cx is the center of the circle. * @param cy is the center of the circle. * @param radius is the radius of the circle. * @return the crossing or {@link MathConstants#SHAPE_INTERSECTS}. */ public static int computeCrossingsFromCircle(PathIterator2i pi, int cx, int cy, int radius) { return computeCrossingsFromCircle(0, pi, cx, cy, radius, true); } /** * Calculates the number of times the given path * crosses the given circle extending to the right. * * @param crossings is the initial value for crossing. * @param pi is the description of the path. * @param cx is the center of the circle. * @param cy is the center of the circle. * @param radius is the radius of the circle. * @param closeable indicates if the shape is automatically closed or not. * @return the crossing */ static int computeCrossingsFromCircle(int crossings, PathIterator2i pi, int cx, int cy, int radius, boolean closeable) { // Copied from the AWT API if (!pi.hasNext()) return 0; PathElement2i element; element = pi.next(); if (element.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); //$NON-NLS-1$ } int movx = element.toX; int movy = element.toY; int curx = movx; int cury = movy; int endx, endy; int numCrosses = crossings; while (numCrosses!=MathConstants.SHAPE_INTERSECTS && pi.hasNext()) { element = pi.next(); switch (element.type) { case MOVE_TO: movx = curx = element.toX; movy = cury = element.toY; break; case LINE_TO: endx = element.toX; endy = element.toY; numCrosses = Segment2i.computeCrossingsFromCircle( numCrosses, cx, cy, radius, curx, cury, endx, endy); curx = endx; cury = endy; break; case QUAD_TO: { endx = element.toX; endy = element.toY; Path2i localPath = new Path2i(); localPath.moveTo(element.fromX, element.fromY); localPath.quadTo( element.ctrlX1, element.ctrlY1, endx, endy); numCrosses = computeCrossingsFromCircle( numCrosses, localPath.getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO), cx, cy, radius, false); curx = endx; cury = endy; break; } case CURVE_TO: endx = element.toX; endy = element.toY; Path2i localPath = new Path2i(); localPath.moveTo(element.fromX, element.fromY); localPath.curveTo( element.ctrlX1, element.ctrlY1, element.ctrlX2, element.ctrlY2, endx, endy); numCrosses = computeCrossingsFromCircle( numCrosses, localPath.getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO), cx, cy, radius, false); curx = endx; cury = endy; break; case CLOSE: if (cury != movy || curx != movx) { numCrosses = Segment2i.computeCrossingsFromCircle( numCrosses, cx, cy, radius, curx, cury, movx, movy); } curx = movx; cury = movy; break; default: } } if (numCrosses!=MathConstants.SHAPE_INTERSECTS && closeable && cury != movy) { numCrosses = Segment2i.computeCrossingsFromCircle( numCrosses, cx, cy, radius, curx, cury, movx, movy); } return numCrosses; } /** * Calculates the number of times the given path * crosses the ray extending to the right from (px,py). * If the point lies on a part of the path, * then no crossings are counted for that intersection. * +1 is added for each crossing where the Y coordinate is increasing * -1 is added for each crossing where the Y coordinate is decreasing * The return value is the sum of all crossings for every segment in * the path. * The path must start with a MOVE_TO, otherwise an exception is * thrown. * * @param pi is the description of the path. * @param px is the reference point to test. * @param py is the reference point to test. * @return the crossing */ public static int computeCrossingsFromPoint(PathIterator2i pi, int px, int py) { return computeCrossingsFromPoint(pi, px, py, true); } /** * Calculates the number of times the given path * crosses the ray extending to the right from (px,py). * If the point lies on a part of the path, * then no crossings are counted for that intersection. * +1 is added for each crossing where the Y coordinate is increasing * -1 is added for each crossing where the Y coordinate is decreasing * The return value is the sum of all crossings for every segment in * the path. * The path must start with a MOVE_TO, otherwise an exception is * thrown. * * @param pi is the description of the path. * @param px is the reference point to test. * @param py is the reference point to test. * @param autoClose indicates if the shape is automatically assumed as closed. * @return the crossing, or {@link MathConstants#SHAPE_INTERSECTS} */ static int computeCrossingsFromPoint(PathIterator2i pi, int px, int py, boolean autoClose) { // Copied and adapted from the AWT API if (!pi.hasNext()) return 0; PathElement2i element; element = pi.next(); if (element.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); //$NON-NLS-1$ } int movx = element.toX; int movy = element.toY; int curx = movx; int cury = movy; int endx, endy; int crossings = 0; while (pi.hasNext()) { element = pi.next(); switch (element.type) { case MOVE_TO: movx = curx = element.toX; movy = cury = element.toY; break; case LINE_TO: endx = element.toX; endy = element.toY; crossings = Segment2i.computeCrossingsFromPoint( crossings, px, py, curx, cury, endx, endy); if (crossings==MathConstants.SHAPE_INTERSECTS) { return crossings; } curx = endx; cury = endy; break; case QUAD_TO: endx = element.toX; endy = element.toY; Path2i curve = new Path2i(); curve.moveTo(element.fromX, element.fromY); curve.quadTo(element.ctrlX1, element.ctrlY1, endx, endy); int numCrosses = computeCrossingsFromPoint( curve.getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO), px, py, false); if (numCrosses==MathConstants.SHAPE_INTERSECTS) { return numCrosses; } crossings += numCrosses; curx = endx; cury = endy; break; case CURVE_TO: endx = element.toX; endy = element.toY; curve = new Path2i(); curve.moveTo(element.fromX, element.fromY); curve.curveTo( element.ctrlX1, element.ctrlY1, element.ctrlX2, element.ctrlY2, endx, endy); numCrosses = computeCrossingsFromPoint( curve.getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO), px, py, false); if (numCrosses==MathConstants.SHAPE_INTERSECTS) { return numCrosses; } crossings += numCrosses; curx = endx; cury = endy; break; case CLOSE: if (cury != movy || curx != movx) { crossings = Segment2i.computeCrossingsFromPoint( crossings, px, py, curx, cury, movx, movy); if (crossings==MathConstants.SHAPE_INTERSECTS) { return crossings; } } curx = movx; cury = movy; break; default: } } if (autoClose && cury != movy && curx != movx) { crossings = Segment2i.computeCrossingsFromPoint( crossings, px, py, curx, cury, movx, movy); } return crossings; } /** * Tests if the specified coordinates are inside the closed * boundary of the specified {@link PathIterator2i}. * <p> * This method provides a basic facility for implementors of * the {@link Shape2i} interface to implement support for the * {@link Shape2i#contains(int, int)} method. * * @param pi the specified {@code PathIterator2f} * @param x the specified X coordinate * @param y the specified Y coordinate * @return {@code true} if the specified coordinates are inside the * specified {@code PathIterator2f}; {@code false} otherwise */ public static boolean contains(PathIterator2i pi, int x, int y) { // Copied from the AWT API int mask = (pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 1); int cross = computeCrossingsFromPoint(pi, x, y); return ((cross & mask) != 0); } /** * Accumulate the number of times the path crosses the shadow * extending to the right of the rectangle. See the comment * for the SHAPE_INTERSECTS constant for more complete details. * The return value is the sum of all crossings for both the * top and bottom of the shadow for every segment in the path, * or the special value SHAPE_INTERSECTS if the path ever enters * the interior of the rectangle. * The path must start with a SEG_MOVETO, otherwise an exception is * thrown. * The caller must check r[xy]{min,max} for NaN values. * * @param pi is the iterator on the path elements. * @param rxmin is the first corner of the rectangle. * @param rymin is the first corner of the rectangle. * @param rxmax is the second corner of the rectangle. * @param rymax is the second corner of the rectangle. * @return the crossings. */ public static int computeCrossingsFromRect(PathIterator2i pi, int rxmin, int rymin, int rxmax, int rymax) { return __computeCrossingsFromRect(pi, rxmin, rymin, rxmax, rymax, true, true); } private static int crossingHelper1( int crossings, int rxmin, int rymin, int rxmax, int rymax, int curx, int cury, int movx, int movy, boolean intersectingBehavior) { int crosses = Segment2i.computeCrossingsFromRect(crossings, rxmin, rymin, rxmax, rymax, curx, cury, movx, movy); if (!intersectingBehavior && crosses==MathConstants.SHAPE_INTERSECTS) { int x1 = rxmin+1; int x2 = rxmax-1; int y1 = rymin+1; int y2 = rymax-1; crosses = Segment2i.computeCrossingsFromRect(crossings, x1, y1, x2, y2, curx, cury, movx, movy); } return crosses; } /** * Accumulate the number of times the path crosses the shadow * extending to the right of the rectangle. See the comment * for the SHAPE_INTERSECTS constant for more complete details. * The return value is the sum of all crossings for both the * top and bottom of the shadow for every segment in the path, * or the special value SHAPE_INTERSECTS if the path ever enters * the interior of the rectangle. * The path must start with a SEG_MOVETO, otherwise an exception is * thrown. * The caller must check r[xy]{min,max} for NaN values. * * @param pi is the iterator on the path elements. * @param rxmin is the first corner of the rectangle. * @param rymin is the first corner of the rectangle. * @param rxmax is the second corner of the rectangle. * @param rymax is the second corner of the rectangle. * @param autoClose indicates if the line from the last point to the last move * point must be include in the crossing computation. * @param intersectingBehavior indicates the function is called to determine if the rectangle * is inside the shape or not. This function determines * {@link MathConstants#SHAPE_INTERSECTS} in a different way if the * function is used for containing or intersecting tests. * @return the crossings count or {@link MathConstants#SHAPE_INTERSECTS}. */ static int __computeCrossingsFromRect(PathIterator2i pi, int rxmin, int rymin, int rxmax, int rymax, boolean autoClose, boolean intersectingBehavior) { // Copied from AWT API if (rxmax <= rxmin || rymax <= rymin) return 0; if (!pi.hasNext()) return 0; PathElement2i pathElement = pi.next(); if (pathElement.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); //$NON-NLS-1$ } int curx, cury, movx, movy, endx, endy; curx = movx = pathElement.toX; cury = movy = pathElement.toY; int crossings = 0; while (crossings != MathConstants.SHAPE_INTERSECTS && pi.hasNext()) { pathElement = pi.next(); switch (pathElement.type) { case MOVE_TO: // Count should always be a multiple of 2 here. // assert((crossings & 1) != 0); movx = curx = pathElement.toX; movy = cury = pathElement.toY; break; case LINE_TO: endx = pathElement.toX; endy = pathElement.toY; crossings = crossingHelper1(crossings, rxmin, rymin, rxmax, rymax, curx, cury, endx, endy, intersectingBehavior); if (crossings==MathConstants.SHAPE_INTERSECTS) { return MathConstants.SHAPE_INTERSECTS; } curx = endx; cury = endy; break; case QUAD_TO: endx = pathElement.toX; endy = pathElement.toY; Path2i curve = new Path2i(); curve.moveTo(pathElement.fromX, pathElement.fromY); curve.quadTo(pathElement.ctrlX1, pathElement.ctrlY1, endx, endy); int numCrosses = __computeCrossingsFromRect( curve.getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO), rxmin, rymin, rxmax, rymax, false, intersectingBehavior); if (numCrosses==MathConstants.SHAPE_INTERSECTS) { return MathConstants.SHAPE_INTERSECTS; } crossings += numCrosses; curx = endx; cury = endy; break; case CURVE_TO: endx = pathElement.toX; endy = pathElement.toY; curve = new Path2i(); curve.moveTo(pathElement.fromX, pathElement.fromY); curve.curveTo(pathElement.ctrlX1, pathElement.ctrlY1, pathElement.ctrlX2, pathElement.ctrlY2, endx, endy); numCrosses = __computeCrossingsFromRect( curve.getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO), rxmin, rymin, rxmax, rymax, false, intersectingBehavior); if (numCrosses==MathConstants.SHAPE_INTERSECTS) { return MathConstants.SHAPE_INTERSECTS; } crossings += numCrosses; curx = endx; cury = endy; break; case CLOSE: if (curx != movx || cury != movy) { crossings = crossingHelper1(crossings, rxmin, rymin, rxmax, rymax, curx, cury, movx, movy, intersectingBehavior); if (crossings==MathConstants.SHAPE_INTERSECTS) { return crossings; } } curx = movx; cury = movy; // Count should always be a multiple of 2 here. // assert((crossings & 1) != 0); break; default: } } if (autoClose && crossings != MathConstants.SHAPE_INTERSECTS && (curx != movx || cury != movy)) { crossings = crossingHelper1(crossings, rxmin, rymin, rxmax, rymax, curx, cury, movx, movy, intersectingBehavior); } // Count should always be a multiple of 2 here. // assert((crossings & 1) != 0); return crossings; } /** * Tests if the specified rectangle is inside the closed * boundary of the specified {@link PathIterator2i}. * <p> * This method provides a basic facility for implementors of * the {@link Shape2i} interface to implement support for the * {@link Shape2i#contains(Rectangle2i)} method. * * @param pi the specified {@code PathIterator2f} * @param rx the lowest corner of the rectangle. * @param ry the lowest corner of the rectangle. * @param rwidth is the width of the rectangle. * @param rheight is the width of the rectangle. * @return {@code true} if the specified rectangle is inside the * specified {@code PathIterator2f}; {@code false} otherwise. */ public static boolean contains(PathIterator2i pi, int rx, int ry, int rwidth, int rheight) { // Copied and adapted from AWT API if (rwidth <= 0 || rheight <= 0) { return false; } int mask = (pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2); int crossings = __computeCrossingsFromRect(pi, rx, ry, rx+rwidth, ry+rheight, true, false); return (crossings != MathConstants.SHAPE_INTERSECTS && (crossings & mask) != 0); } /** * Tests if the interior of the specified {@link PathIterator2i} * intersects the interior of a specified set of rectangular * coordinates. * <p> * This method provides a basic facility for implementors of * the {@link Shape2i} interface to implement support for the * {@code intersects()} method. * <p> * This method object may conservatively return true in * cases where the specified rectangular area intersects a * segment of the path, but that segment does not represent a * boundary between the interior and exterior of the path. * Such a case may occur if some set of segments of the * path are retraced in the reverse direction such that the * two sets of segments cancel each other out without any * interior area between them. * To determine whether segments represent true boundaries of * the interior of the path would require extensive calculations * involving all of the segments of the path and the winding * rule and are thus beyond the scope of this implementation. * * @param pi the specified {@code PathIterator} * @param x the specified X coordinate * @param y the specified Y coordinate * @param w the width of the specified rectangular coordinates * @param h the height of the specified rectangular coordinates * @return {@code true} if the specified {@code PathIterator} and * the interior of the specified set of rectangular * coordinates intersect each other; {@code false} otherwise. */ public static boolean intersects(PathIterator2i pi, int x, int y, int w, int h) { if (w <= 0f || h <= 0f) { return false; } int mask = (pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2); int crossings = __computeCrossingsFromRect(pi, x, y, x+w, y+h, true, true); return (crossings == MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0); } /** Array of types. */ PathElementType[] types; /** Array of coords. */ int[] coords; /** Number of types in the array. */ int numTypes = 0; /** Number of coords in the array. */ int numCoords = 0; /** Winding rule for the path. */ PathWindingRule windingRule; /** Indicates if the path is empty. * The path is empty when there is no point inside, or * all the points are at the same coordinate, or * when the path does not represents a drawable path * (a path with a line or a curve). */ private Boolean isEmpty = Boolean.TRUE; /** Indicates if the path contains base primitives * (no curve). */ private Boolean isPolyline = Boolean.TRUE; /** Buffer for the bounds of the path that corresponds * to the points really on the path (eg, the pixels * drawn). The control points of the curves are * not considered in this bounds. */ private SoftReference<Rectangle2i> graphicalBounds = null; /** Buffer for the bounds of the path that corresponds * to all the points added in the path. */ private SoftReference<Rectangle2i> logicalBounds = null; /** */ public Path2i() { this(PathWindingRule.NON_ZERO); } /** * @param iterator */ public Path2i(Iterator<PathElement2i> iterator) { this(PathWindingRule.NON_ZERO, iterator); } /** * @param windingRule */ public Path2i(PathWindingRule windingRule) { assert(windingRule!=null); this.types = new PathElementType[GROW_SIZE]; this.coords = new int[GROW_SIZE]; this.windingRule = windingRule; } /** * @param windingRule * @param iterator */ public Path2i(PathWindingRule windingRule, Iterator<PathElement2i> iterator) { assert(windingRule!=null); this.types = new PathElementType[GROW_SIZE]; this.coords = new int[GROW_SIZE]; this.windingRule = windingRule; add(iterator); } /** * @param p */ public Path2i(Path2i p) { this.coords = p.coords.clone(); this.isEmpty = p.isEmpty; this.isPolyline = p.isPolyline; this.numCoords = p.numCoords; this.types = p.types.clone(); this.windingRule = p.windingRule; Rectangle2i box; box = p.graphicalBounds==null ? null : p.graphicalBounds.get(); if (box!=null) { this.graphicalBounds = new SoftReference<>(box.clone()); } box = p.logicalBounds==null ? null : p.logicalBounds.get(); if (box!=null) { this.logicalBounds = new SoftReference<>(box.clone()); } } @Override public void clear() { this.types = new PathElementType[GROW_SIZE]; this.coords = new int[GROW_SIZE]; this.windingRule = PathWindingRule.NON_ZERO; this.numCoords = 0; this.numTypes = 0; this.isEmpty = Boolean.TRUE; this.isPolyline = Boolean.TRUE; this.logicalBounds = null; this.graphicalBounds = null; } @Override public String toString() { return ReflectionUtil.toString(this); } @Override public Path2i clone() { Path2i clone = super.clone(); clone.coords = this.coords.clone(); clone.types = this.types.clone(); return clone; } @Override public PathWindingRule getWindingRule() { return this.windingRule; } /** Set the winding rule for the path. * * @param r is the winding rule for the path. */ public void setWindingRule(PathWindingRule r) { assert(r!=null); this.windingRule = r; } /** Add the elements replied by the iterator into this path. * * @param iterator */ public void add(Iterator<PathElement2i> iterator) { PathElement2i element; while (iterator.hasNext()) { element = iterator.next(); switch(element.type) { case MOVE_TO: moveTo(element.toX, element.toY); break; case LINE_TO: lineTo(element.toX, element.toY); break; case QUAD_TO: quadTo(element.ctrlX1, element.ctrlY1, element.toX, element.toY); break; case CURVE_TO: curveTo(element.ctrlX1, element.ctrlY1, element.ctrlX2, element.ctrlY2, element.toX, element.toY); break; case CLOSE: closePath(); break; default: } } } private void ensureSlots(boolean needMove, int n) { if (needMove && this.numTypes==0) { throw new IllegalStateException("missing initial moveto in path definition"); //$NON-NLS-1$ } if (this.types.length==this.numTypes) { this.types = Arrays.copyOf(this.types, this.types.length+GROW_SIZE); } while ((this.numCoords+n)>=this.coords.length) { this.coords = Arrays.copyOf(this.coords, this.coords.length+GROW_SIZE); } } /** * Adds a point to the path by moving to the specified * coordinates specified in float precision. * * @param x the specified X coordinate * @param y the specified Y coordinate */ public void moveTo(int x, int y) { if (this.numTypes>0 && this.types[this.numTypes-1]==PathElementType.MOVE_TO) { this.coords[this.numCoords-2] = x; this.coords[this.numCoords-1] = y; } else { ensureSlots(false, 2); this.types[this.numTypes++] = PathElementType.MOVE_TO; this.coords[this.numCoords++] = x; this.coords[this.numCoords++] = y; } this.graphicalBounds = null; this.logicalBounds = null; } /** * Adds a point to the path by drawing a straight line from the * current coordinates to the new specified coordinates * specified in float precision. * * @param x the specified X coordinate * @param y the specified Y coordinate */ public void lineTo(int x, int y) { ensureSlots(true, 2); this.types[this.numTypes++] = PathElementType.LINE_TO; this.coords[this.numCoords++] = x; this.coords[this.numCoords++] = y; this.isEmpty = null; this.graphicalBounds = null; this.logicalBounds = null; } /** * 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 specified coordinates {@code (x2,y2)}, * using the specified point {@code (x1,y1)} as a quadratic * parametric control point. * All coordinates are specified in float precision. * * @param x1 the X coordinate of the quadratic control point * @param y1 the Y coordinate of the quadratic control point * @param x2 the X coordinate of the final end point * @param y2 the Y coordinate of the final end point */ public void quadTo(int x1, int y1, int x2, int y2) { ensureSlots(true, 4); this.types[this.numTypes++] = PathElementType.QUAD_TO; this.coords[this.numCoords++] = x1; this.coords[this.numCoords++] = y1; this.coords[this.numCoords++] = x2; this.coords[this.numCoords++] = y2; this.isEmpty = null; this.isPolyline = Boolean.FALSE; this.graphicalBounds = null; this.logicalBounds = null; } /** * 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 specified coordinates {@code (x3,y3)}, * using the specified points {@code (x1,y1)} and {@code (x2,y2)} as * Bézier control points. * All coordinates are specified in float precision. * * @param x1 the X coordinate of the first Bézier control point * @param y1 the Y coordinate of the first Bézier control point * @param x2 the X coordinate of the second Bézier control point * @param y2 the Y coordinate of the second Bézier control point * @param x3 the X coordinate of the final end point * @param y3 the Y coordinate of the final end point */ public void curveTo(int x1, int y1, int x2, int y2, int x3, int y3) { ensureSlots(true, 6); this.types[this.numTypes++] = PathElementType.CURVE_TO; this.coords[this.numCoords++] = x1; this.coords[this.numCoords++] = y1; this.coords[this.numCoords++] = x2; this.coords[this.numCoords++] = y2; this.coords[this.numCoords++] = x3; this.coords[this.numCoords++] = y3; this.isEmpty = null; this.isPolyline = Boolean.FALSE; this.graphicalBounds = null; this.logicalBounds = null; } /** * Closes the current subpath by drawing a straight line back to * the coordinates of the last {@code moveTo}. If the path is already * closed or if the previous coordinates are for a {@code moveTo} * then this method has no effect. */ public void closePath() { if (this.numTypes<=0 || (this.types[this.numTypes-1]!=PathElementType.CLOSE &&this.types[this.numTypes-1]!=PathElementType.MOVE_TO)) { ensureSlots(true, 0); this.types[this.numTypes++] = PathElementType.CLOSE; } } @Override public PathIterator2i getPathIterator(float flatness) { return new FlatteningPathIterator(getWindingRule(), getPathIterator(null), flatness, 10); } /** Replies an iterator on the path elements. * <p> * Only {@link PathElementType#MOVE_TO}, * {@link PathElementType#LINE_TO}, and * {@link PathElementType#CLOSE} types are returned by the iterator. * <p> * The amount of subdivision of the curved segments is controlled by the * flatness parameter, which specifies the maximum distance that any point * on the unflattened transformed curve can deviate from the returned * flattened path segments. Note that a limit on the accuracy of the * flattened path might be silently imposed, causing very small flattening * parameters to be treated as larger values. This limit, if there is one, * is defined by the particular implementation that is used. * <p> * The iterator for this class is not multi-threaded safe. * * @param transform is an optional affine Transform2D to be applied to the * coordinates as they are returned in the iteration, or <code>null</code> if * untransformed coordinates are desired. * @param flatness is 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 an iterator on the path elements. */ public PathIterator2i getPathIterator(Transform2D transform, float flatness) { return new FlatteningPathIterator(getWindingRule(), getPathIterator(transform), flatness, 10); } /** {@inheritDoc} */ @Override public PathIterator2i getPathIterator(Transform2D transform) { if (transform == null) { return new CopyPathIterator(); } return new TransformPathIterator(transform); } /** Transform the current path. * This function changes the current path. * * @param transform is the affine transformation to apply. * @see #createTransformedShape(Transform2D) */ public void transform(Transform2D transform) { if (transform!=null) { Point2D p = new Point2i(); for(int i=0; i<this.numCoords;i+=2) { p.set(this.coords[i], this.coords[i+1]); transform.transform(p); this.coords[i] = p.x(); this.coords[i+1] = p.y(); } this.graphicalBounds = null; this.logicalBounds = null; } } /** {@inheritDoc} */ @Override public void translate(int dx, int dy) { for(int i=0; i<this.numCoords;i+=2) { this.coords[i] += dx; this.coords[i+1] += dy; } Rectangle2i bb; bb = this.logicalBounds==null ? null : this.logicalBounds.get(); if (bb!=null) bb.translate(dx, dy); bb = this.graphicalBounds==null ? null : this.graphicalBounds.get(); if (bb!=null) bb.translate(dx, dy); } /** {@inheritDoc} */ @Override public Shape2i createTransformedShape(Transform2D transform) { Path2i newPath = new Path2i(getWindingRule()); PathIterator2i pi = getPathIterator(); Point2i p = new Point2i(); Point2i t1 = new Point2i(); Point2i t2 = new Point2i(); PathElement2i e; while (pi.hasNext()) { e = pi.next(); switch(e.type) { case MOVE_TO: p.set(e.toX, e.toY); transform.transform(p); newPath.moveTo(p.x(), p.y()); break; case LINE_TO: p.set(e.toX, e.toY); transform.transform(p); newPath.lineTo(p.x(), p.y()); break; case QUAD_TO: t1.set(e.ctrlX1, e.ctrlY1); transform.transform(t1); p.set(e.toX, e.toY); transform.transform(p); newPath.quadTo(t1.x(), t1.y(), p.x(), p.y()); break; case CURVE_TO: t1.set(e.ctrlX1, e.ctrlY1); transform.transform(t1); t2.set(e.ctrlX2, e.ctrlY2); transform.transform(t2); p.set(e.toX, e.toY); transform.transform(p); newPath.curveTo(t1.x(), t1.y(), t2.x(), t2.y(), p.x(), p.y()); break; case CLOSE: newPath.closePath(); break; default: } } return newPath; } @Override public float distanceSquared(Point2D p) { Point2D c = getClosestPointTo(p); return c.distanceSquared(p); } @Override public float distanceL1(Point2D p) { Point2D c = getClosestPointTo(p); return c.distanceL1(p); } @Override public float distanceLinf(Point2D p) { Point2D c = getClosestPointTo(p); return c.distanceLinf(p); } @Override public boolean contains(int x, int y) { return contains(getPathIterator(), x, y); } @Override public boolean contains(Rectangle2i r) { return contains(getPathIterator(), r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight()); } @Override public boolean intersects(Rectangle2i s) { // Copied from AWT API if (s.isEmpty()) return false; int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2); int crossings = __computeCrossingsFromRect( getPathIterator(), s.getMinX(), s.getMinY(), s.getMaxX(), s.getMaxY(), true, true); return (crossings == MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0); } @Override public boolean intersects(Circle2i s) { int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2); int crossings = computeCrossingsFromCircle( getPathIterator(), s.getX(), s.getY(), s.getRadius()); return (crossings == MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0); } @Override public boolean intersects(Segment2i s) { int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2); int crossings = computeCrossingsFromSegment( getPathIterator(), s.getX1(), s.getY1(), s.getX2(), s.getY2()); return (crossings == MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0); } private static boolean buildGraphicalBoundingBox(PathIterator2i iterator, Rectangle2i box) { boolean foundOneLine = false; int xmin = Integer.MAX_VALUE; int ymin = Integer.MAX_VALUE; int xmax = Integer.MIN_VALUE; int ymax = Integer.MIN_VALUE; PathElement2i element; Path2i subPath; while (iterator.hasNext()) { element = iterator.next(); switch(element.type) { case LINE_TO: if (element.fromX<xmin) xmin = element.fromX; if (element.fromY<ymin) ymin = element.fromY; if (element.fromX>xmax) xmax = element.fromX; if (element.fromY>ymax) ymax = element.fromY; if (element.toX<xmin) xmin = element.toX; if (element.toY<ymin) ymin = element.toY; if (element.toX>xmax) xmax = element.toX; if (element.toY>ymax) ymax = element.toY; foundOneLine = true; break; case CURVE_TO: subPath = new Path2i(); subPath.moveTo(element.fromX, element.fromY); subPath.curveTo( element.ctrlX1, element.ctrlY1, element.ctrlX2, element.ctrlY2, element.toX, element.toY); if (buildGraphicalBoundingBox( subPath.getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO), box)) { if (box.getMinX()<xmin) xmin = box.getMinX(); if (box.getMinY()<ymin) ymin = box.getMinY(); if (box.getMaxX()>xmax) xmax = box.getMaxX(); if (box.getMinX()>ymax) ymax = box.getMinX(); foundOneLine = true; } break; case QUAD_TO: subPath = new Path2i(); subPath.moveTo(element.fromX, element.fromY); subPath.quadTo( element.ctrlX1, element.ctrlY1, element.toX, element.toY); if (buildGraphicalBoundingBox( subPath.getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO), box)) { if (box.getMinX()<xmin) xmin = box.getMinX(); if (box.getMinY()<ymin) ymin = box.getMinY(); if (box.getMaxX()>xmax) xmax = box.getMaxX(); if (box.getMinX()>ymax) ymax = box.getMinX(); foundOneLine = true; } break; case MOVE_TO: case CLOSE: default: } } if (foundOneLine) { box.setFromCorners(xmin, ymin, xmax, ymax); } else { box.clear(); } return foundOneLine; } private boolean buildLogicalBoundingBox(Rectangle2i box) { if (this.numCoords>0) { int xmin = Integer.MAX_VALUE; int ymin = Integer.MAX_VALUE; int xmax = Integer.MIN_VALUE; int ymax = Integer.MIN_VALUE; for(int i=0; i<this.numCoords; i+= 2) { if (this.coords[i]<xmin) xmin = this.coords[i]; if (this.coords[i+1]<ymin) ymin = this.coords[i+1]; if (this.coords[i]>xmax) xmax = this.coords[i]; if (this.coords[i+1]>ymax) ymax = this.coords[i+1]; } box.setFromCorners(xmin, ymin, xmax, ymax); return true; } return false; } /** * {@inheritDoc} * <p> * The replied bounding box does not consider the control points * of the path. Only the "visible" points are considered. * * @see #toBoundingBoxWithCtrlPoints() */ @Override public Rectangle2i toBoundingBox() { Rectangle2i bb = this.graphicalBounds==null ? null : this.graphicalBounds.get(); if (bb==null) { bb = new Rectangle2i(); buildGraphicalBoundingBox( getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO), bb); this.graphicalBounds = new SoftReference<>(bb); } return bb; } /** Replies the bounding box of all the points added in this path. * <p> * The replied bounding box includes the (invisible) control points. * * @return the bounding box with the control points. * @see #toBoundingBox() */ public Rectangle2i toBoundingBoxWithCtrlPoints() { Rectangle2i bb = this.logicalBounds==null ? null : this.logicalBounds.get(); if (bb==null) { bb = new Rectangle2i(); buildLogicalBoundingBox(bb); this.logicalBounds = new SoftReference<>(bb); } return bb; } /** * {@inheritDoc} * <p> * The replied bounding box does not consider the control points * of the path. Only the "visible" points are considered. * * @see #toBoundingBoxWithCtrlPoints(Rectangle2i) */ @Override public void toBoundingBox(Rectangle2i box) { Rectangle2i bb = this.graphicalBounds==null ? null : this.graphicalBounds.get(); if (bb==null) { bb = new Rectangle2i(); buildGraphicalBoundingBox( getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO), bb); this.graphicalBounds = new SoftReference<>(bb); } box.set(bb); } /** Compute the bounding box of all the points added in this path. * <p> * The replied bounding box includes the (invisible) control points. * * @param box is the rectangle to set with the bounds. * @see #toBoundingBox() */ public void toBoundingBoxWithCtrlPoints(Rectangle2i box) { Rectangle2i bb = this.logicalBounds==null ? null : this.logicalBounds.get(); if (bb==null) { bb = new Rectangle2i(); buildLogicalBoundingBox(bb); this.logicalBounds = new SoftReference<>(bb); } box.set(bb); } @Override public Point2D getClosestPointTo(Point2D p) { Point2D solution = new Point2i(); float bestDist = Float.POSITIVE_INFINITY; Point2D candidate; PathIterator2i pi = getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO); PathElement2i pe; Segment2i seg = new Segment2i(); int mask = (pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 1); int crossings = 0; boolean isClosed = false; int moveX, moveY, currentX, currentY; moveX = moveY = currentX = currentY = 0; while (pi.hasNext()) { pe = pi.next(); candidate = null; currentX = pe.toX; currentY = pe.toY; switch(pe.type) { case MOVE_TO: moveX = pe.toX; moveY = pe.toY; isClosed = false; break; case LINE_TO: { seg.set(pe.fromX, pe.fromY, pe.toX, pe.toY); isClosed = false; candidate = seg.getClosestPointTo(p); if (crossings!=MathConstants.SHAPE_INTERSECTS) { crossings = Segment2i.computeCrossingsFromPoint( crossings, p.x(), p.y(), pe.fromX, pe.fromY, pe.toX, pe.toY); } break; } case CLOSE: isClosed = true; if (!pe.isEmpty()) { seg.set(pe.fromX, pe.fromY, pe.toX, pe.toY); candidate = seg.getClosestPointTo(p); if (crossings!=MathConstants.SHAPE_INTERSECTS) { crossings = Segment2i.computeCrossingsFromPoint( crossings, p.x(), p.y(), pe.fromX, pe.fromY, pe.toX, pe.toY); } } break; case QUAD_TO: case CURVE_TO: default: throw new IllegalStateException(); } if (candidate!=null) { float d = p.distanceSquared(candidate); if (d<=0f) return candidate; if (d<bestDist) { bestDist = d; solution.set(candidate); } } } if (!isClosed && crossings!=MathConstants.SHAPE_INTERSECTS) { crossings = Segment2i.computeCrossingsFromPoint( crossings, p.x(), p.y(), currentX, currentY, moveX, moveY); } if (crossings==MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0) return p; return solution; } @Override public boolean equals(Object obj) { if (obj instanceof Path2i) { Path2i p = (Path2i)obj; return (this.numCoords==p.numCoords &&this.numTypes==p.numTypes &&Arrays.equals(this.coords, p.coords) &&Arrays.equals(this.types, p.types) &&this.windingRule==p.windingRule); } return false; } @Override public int hashCode() { long bits = 1L; bits = 31L * bits + this.numCoords; bits = 31L * bits + this.numTypes; bits = 31L * bits + Arrays.hashCode(this.coords); bits = 31L * bits + Arrays.hashCode(this.types); bits = 31L * bits + this.windingRule.ordinal(); return (int) (bits ^ (bits >> 32)); } /** Replies the coordinates of this path in an array of * integers. * * @return the coordinates. */ public final int[] toIntArray() { return toIntArray(null); } /** Replies the coordinates of this path in an array of * integers. * * @param transform is the transformation to apply to all the coordinates. * @return the coordinates. */ public int[] toIntArray(Transform2D transform) { if (transform==null) { return Arrays.copyOf(this.coords, this.numCoords); } Point2i p = new Point2i(); int[] clone = new int[this.numCoords]; for(int i=0; i<clone.length;i+=2) { p.x = this.coords[i]; p.y = this.coords[i+1]; transform.transform(p); clone[i] = p.x; clone[i+1] = p.y; } return clone; } /** Replies the points of this path in an array. * * @return the points. */ public final Point2D[] toPointArray() { return toPointArray(null); } /** Replies the points of this path in an array. * * @param transform is the transformation to apply to all the points. * @return the points. */ public Point2D[] toPointArray(Transform2D transform) { Point2D[] clone = new Point2D[this.numCoords/2]; if (transform==null) { for(int i=0, j=0; j<this.numCoords; ++i,j+=2) { clone[i] = new Point2i( this.coords[j], this.coords[j+1]); } } else { for(int i=0, j=0; j<clone.length; ++i,j+=2) { clone[i] = new Point2i( this.coords[j], this.coords[j+1]); transform.transform(clone[i]); } } return clone; } /** Replies the collection that is contains all the points of the path. * * @return the point collection. */ public final Collection<Point2D> toCollection() { return new PointCollection(); } /** Replies the coordinate at the given index. * The index is in [0;{@link #size()}*2). * * @param index * @return the coordinate at the given index. */ public float getCoordAt(int index) { return this.coords[index]; } /** Replies the point at the given index. * The index is in [0;{@link #size()}). * * @param index * @return the point at the given index. */ public Point2i getPointAt(int index) { return new Point2i( this.coords[index*2], this.coords[index*2+1]); } /** Replies the number of points in the path. * * @return the number of points in the path. */ public int size() { return this.numCoords/2; } /** Replies if this path is empty. * The path is empty when there is no point inside, or * all the points are at the same coordinate, or * when the path does not represents a drawable path * (a path with a line or a curve). * * @return <code>true</code> if the path does not contain * a coordinate; otherwise <code>false</code>. */ @Override public boolean isEmpty() { if (this.isEmpty==null) { this.isEmpty = Boolean.TRUE; PathIterator2i pi = getPathIterator(); PathElement2i pe; while (this.isEmpty()==Boolean.TRUE && pi.hasNext()) { pe = pi.next(); if (pe.isDrawable()) { this.isEmpty = Boolean.FALSE; } } } return this.isEmpty; } /** Replies if the given points exists in the coordinates of this path. * * @param p * @return <code>true</code> if the point is a control point of the path. */ boolean containsPoint(Point2D p) { float x, y; for(int i=0; i<this.numCoords;i+=2) { x = this.coords[i]; y = this.coords[i+1]; if (x==p.getX() && y==p.getY()) { return true; } } return false; } /** Remove the point with the given coordinates. * * @param x * @param y * @return <code>true</code> if the point was removed; <code>false</code> otherwise. */ boolean remove(int x, int y) { for(int i=0, j=0; i<this.numCoords && j<this.numTypes;) { switch(this.types[j]) { case MOVE_TO: case LINE_TO: if (x==this.coords[i] && y==this.coords[i+1]) { this.numCoords -= 2; --this.numTypes; System.arraycopy(this.coords, i+2, this.coords, i, this.numCoords); System.arraycopy(this.types, j+1, this.types, j, this.numTypes); this.isEmpty = null; return true; } i += 2; ++j; break; case CURVE_TO: if ((x==this.coords[i] && y==this.coords[i+1]) ||(x==this.coords[i+2] && y==this.coords[i+3]) ||(x==this.coords[i+4] && y==this.coords[i+5])) { this.numCoords -= 6; --this.numTypes; System.arraycopy(this.coords, i+6, this.coords, i, this.numCoords); System.arraycopy(this.types, j+1, this.types, j, this.numTypes); this.isEmpty = null; this.isPolyline = null; return true; } i += 6; ++j; break; case QUAD_TO: if ((x==this.coords[i] && y==this.coords[i+1]) ||(x==this.coords[i+2] && y==this.coords[i+3])) { this.numCoords -= 4; --this.numTypes; System.arraycopy(this.coords, i+4, this.coords, i, this.numCoords); System.arraycopy(this.types, j+1, this.types, j, this.numTypes); this.isEmpty = null; this.isPolyline = null; return true; } i += 4; ++j; break; case CLOSE: ++j; break; default: break; } } return false; } /** Remove the last action. */ public void removeLast() { if (this.numTypes>0) { switch(this.types[this.numTypes-1]) { case CLOSE: // no coord to remove break; case MOVE_TO: case LINE_TO: this.numCoords -= 2; break; case CURVE_TO: this.numCoords -= 6; this.isPolyline = null; break; case QUAD_TO: this.numCoords -= 4; this.isPolyline = null; break; default: throw new IllegalStateException(); } --this.numTypes; this.isEmpty = null; this.graphicalBounds = null; this.logicalBounds = null; } } /** Change the coordinates of the last inserted point. * * @param x * @param y */ public void setLastPoint(int x, int y) { if (this.numCoords>=2) { this.coords[this.numCoords-2] = x; this.coords[this.numCoords-1] = y; this.logicalBounds = null; this.graphicalBounds = null; } } @Override public void set(Shape2i s) { clear(); add(s.getPathIterator()); } /** Replies the points along the path. * <p> * This function is equivalent to a * call to {@link #getPathIterator(float)} * with the default flatness. * * @return the points */ @Override public Iterator<Point2i> getPointIterator() { PathIterator2i pathIterator = getPathIterator((float)MathConstants.SPLINE_APPROXIMATION_RATIO); return new PixelIterator(pathIterator); } @Override public boolean isPolyline() { if (this.isPolyline==null) { this.isPolyline = Boolean.TRUE; PathIterator2i pi = getPathIterator(); PathElement2i pe; PathElementType t; while (this.isPolyline==Boolean.TRUE && pi.hasNext()) { pe = pi.next(); t = pe.getType(); if (t==PathElementType.CURVE_TO || t==PathElementType.QUAD_TO) { this.isPolyline = Boolean.FALSE; } } } return this.isPolyline.booleanValue(); } /** A path iterator that does not transform the coordinates. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class CopyPathIterator implements PathIterator2i { private final Point2D p1 = new Point2i(); private final Point2D p2 = new Point2i(); private int iType = 0; private int iCoord = 0; private int movex, movey; /** */ public CopyPathIterator() { // } @Override public boolean hasNext() { return this.iType<Path2i.this.numTypes; } @Override public PathElement2i next() { int type = this.iType; if (this.iType>=Path2i.this.numTypes) { throw new NoSuchElementException(); } PathElement2i element = null; switch(Path2i.this.types[type]) { case MOVE_TO: if (this.iCoord+2>Path2i.this.numCoords) { throw new NoSuchElementException(); } this.movex = Path2i.this.coords[this.iCoord++]; this.movey = Path2i.this.coords[this.iCoord++]; this.p2.set(this.movex, this.movey); element = new PathElement2i.MovePathElement2i( this.p2.x(), this.p2.y()); break; case LINE_TO: if (this.iCoord+2>Path2i.this.numCoords) { throw new NoSuchElementException(); } this.p1.set(this.p2); this.p2.set( Path2i.this.coords[this.iCoord++], Path2i.this.coords[this.iCoord++]); element = new PathElement2i.LinePathElement2i( this.p1.x(), this.p1.y(), this.p2.x(), this.p2.y()); break; case QUAD_TO: { if (this.iCoord+4>Path2i.this.numCoords) { throw new NoSuchElementException(); } this.p1.set(this.p2); int ctrlx = Path2i.this.coords[this.iCoord++]; int ctrly = Path2i.this.coords[this.iCoord++]; this.p2.set( Path2i.this.coords[this.iCoord++], Path2i.this.coords[this.iCoord++]); element = new PathElement2i.QuadPathElement2i( this.p1.x(), this.p1.y(), ctrlx, ctrly, this.p2.x(), this.p2.y()); } break; case CURVE_TO: { if (this.iCoord+6>Path2i.this.numCoords) { throw new NoSuchElementException(); } this.p1.set(this.p2); int ctrlx1 = Path2i.this.coords[this.iCoord++]; int ctrly1 = Path2i.this.coords[this.iCoord++]; int ctrlx2 = Path2i.this.coords[this.iCoord++]; int ctrly2 = Path2i.this.coords[this.iCoord++]; this.p2.set( Path2i.this.coords[this.iCoord++], Path2i.this.coords[this.iCoord++]); element = new PathElement2i.CurvePathElement2i( this.p1.x(), this.p1.y(), ctrlx1, ctrly1, ctrlx2, ctrly2, this.p2.x(), this.p2.y()); } break; case CLOSE: this.p1.set(this.p2); this.p2.set(this.movex, this.movey); element = new PathElement2i.ClosePathElement2i( this.p1.x(), this.p1.y(), this.p2.x(), this.p2.y()); break; default: } if (element==null) throw new NoSuchElementException(); ++this.iType; return element; } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public PathWindingRule getWindingRule() { return Path2i.this.getWindingRule(); } @Override public boolean isPolyline() { return Path2i.this.isPolyline(); } } // class CopyPathIterator /** A path iterator that transforms the coordinates. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class TransformPathIterator implements PathIterator2i { private final Transform2D transform; private final Point2D p1 = new Point2i(); private final Point2D p2 = new Point2i(); private final Point2D ptmp1 = new Point2i(); private final Point2D ptmp2 = new Point2i(); private int iType = 0; private int iCoord = 0; private int movex, movey; /** * @param transform */ public TransformPathIterator(Transform2D transform) { assert(transform!=null); this.transform = transform; } @Override public boolean hasNext() { return this.iType<Path2i.this.numTypes; } @Override public PathElement2i next() { if (this.iType>=Path2i.this.numTypes) { throw new NoSuchElementException(); } PathElement2i element = null; switch(Path2i.this.types[this.iType++]) { case MOVE_TO: this.movex = Path2i.this.coords[this.iCoord++]; this.movey = Path2i.this.coords[this.iCoord++]; this.p2.set(this.movex, this.movey); this.transform.transform(this.p2); element = new PathElement2i.MovePathElement2i( this.p2.x(), this.p2.y()); break; case LINE_TO: this.p1.set(this.p2); this.p2.set( Path2i.this.coords[this.iCoord++], Path2i.this.coords[this.iCoord++]); this.transform.transform(this.p2); element = new PathElement2i.LinePathElement2i( this.p1.x(), this.p1.y(), this.p2.x(), this.p2.y()); break; case QUAD_TO: { this.p1.set(this.p2); this.ptmp1.set( Path2i.this.coords[this.iCoord++], Path2i.this.coords[this.iCoord++]); this.transform.transform(this.ptmp1); this.p2.set( Path2i.this.coords[this.iCoord++], Path2i.this.coords[this.iCoord++]); this.transform.transform(this.p2); element = new PathElement2i.QuadPathElement2i( this.p1.x(), this.p1.y(), this.ptmp1.x(), this.ptmp1.y(), this.p2.x(), this.p2.y()); } break; case CURVE_TO: { this.p1.set(this.p2); this.ptmp1.set( Path2i.this.coords[this.iCoord++], Path2i.this.coords[this.iCoord++]); this.transform.transform(this.ptmp1); this.ptmp2.set( Path2i.this.coords[this.iCoord++], Path2i.this.coords[this.iCoord++]); this.transform.transform(this.ptmp2); this.p2.set( Path2i.this.coords[this.iCoord++], Path2i.this.coords[this.iCoord++]); this.transform.transform(this.p2); element = new PathElement2i.CurvePathElement2i( this.p1.x(), this.p1.y(), this.ptmp1.x(), this.ptmp1.y(), this.ptmp2.x(), this.ptmp2.y(), this.p2.x(), this.p2.y()); } break; case CLOSE: this.p1.set(this.p2); this.p2.set(this.movex, this.movey); this.transform.transform(this.p2); element = new PathElement2i.ClosePathElement2i( this.p1.x(), this.p1.y(), this.p2.x(), this.p2.y()); break; default: } if (element==null) throw new NoSuchElementException(); return element; } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public PathWindingRule getWindingRule() { return Path2i.this.getWindingRule(); } @Override public boolean isPolyline() { return Path2i.this.isPolyline(); } } // class TransformPathIterator /** An collection of the points of the path. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class PointCollection implements Collection<Point2D> { /** */ public PointCollection() { // } @Override public int size() { return Path2i.this.size(); } @Override public boolean isEmpty() { return Path2i.this.size()<=0; } @Override public boolean contains(Object o) { if (o instanceof Point2D) { return Path2i.this.containsPoint((Point2D)o); } return false; } @Override public Iterator<Point2D> iterator() { return new PointIterator(); } @Override public Object[] toArray() { return Path2i.this.toPointArray(); } @Override public <T> T[] toArray(T[] a) { Iterator<Point2D> iterator = new PointIterator(); for(int i=0; i<a.length && iterator.hasNext(); ++i) { a[i] = (T)iterator.next(); } return a; } @Override public boolean add(Point2D e) { if (e!=null) { if (Path2i.this.size()==0) { Path2i.this.moveTo(e.x(), e.y()); } else { Path2i.this.lineTo(e.x(), e.y()); } return true; } return false; } @Override public boolean remove(Object o) { if (o instanceof Point2D) { Point2D p = (Point2D)o; return Path2i.this.remove(p.x(), p.y()); } return false; } @Override public boolean containsAll(Collection<?> c) { for(Object obj : c) { if ((!(obj instanceof Point2D)) ||(!Path2i.this.containsPoint((Point2D)obj))) { return false; } } return true; } @Override public boolean addAll(Collection<? extends Point2D> c) { boolean changed = false; for(Point2D pts : c) { if (add(pts)) { changed = true; } } return changed; } @Override public boolean removeAll(Collection<?> c) { boolean changed = false; for(Object obj : c) { if (obj instanceof Point2D) { Point2D pts = (Point2D)obj; if (Path2i.this.remove(pts.x(), pts.y())) { changed = true; } } } return changed; } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public void clear() { Path2i.this.clear(); } } // class PointCollection /** Iterator on the points of the path. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class PointIterator implements Iterator<Point2D> { private int index = 0; private Point2D lastReplied = null; /** */ public PointIterator() { // } @Override public boolean hasNext() { return this.index<Path2i.this.size(); } @Override public Point2D next() { try { this.lastReplied = Path2i.this.getPointAt(this.index++); return this.lastReplied; } catch(Throwable exception) { throw new NoSuchElementException(); } } @Override public void remove() { Point2D p = this.lastReplied; this.lastReplied = null; if (p==null) throw new NoSuchElementException(); Path2i.this.remove(p.x(), p.y()); } } /** Iterator on the pixels of the path. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private static class PixelIterator implements Iterator<Point2i> { private final PathIterator2i pathIterator; private Iterator<Point2i> lineIterator = null; private Point2i next = null; public PixelIterator(PathIterator2i pi) { this.pathIterator = pi; searchNext(); } private void searchNext() { Point2i old = this.next; this.next = null; while (this.pathIterator.hasNext() && (this.lineIterator==null || !this.lineIterator.hasNext())) { this.lineIterator = null; PathElement2i elt = this.pathIterator.next(); if (elt.isDrawable()) { switch(elt.type) { case LINE_TO: this.lineIterator = new Segment2i( elt.fromX, elt.fromY, elt.toX, elt.toY).getPointIterator(); break; case MOVE_TO: case CLOSE: case CURVE_TO: case QUAD_TO: default: throw new IllegalStateException(); } } } if (this.lineIterator!=null && this.lineIterator.hasNext()) { this.next = this.lineIterator.next(); while (this.next.equals(old)) { this.next = this.lineIterator.next(); } } } @Override public boolean hasNext() { return this.next!=null; } @Override public Point2i next() { Point2i n = this.next; if (n==null) throw new NoSuchElementException(); searchNext(); return n; } @Override public void remove() { throw new UnsupportedOperationException(); } } // class PixelIterator /** A path iterator that is flattening the path. * This iterator was copied from AWT FlatteningPathIterator. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private static class FlatteningPathIterator implements PathIterator2i { /** Winding rule of the path. */ private final PathWindingRule windingRule; /** The source iterator. */ private final Iterator<PathElement2i> pathIterator; /** * Square of the flatness parameter for testing against squared lengths. */ private final float squaredFlatness; /** * Maximum number of recursion levels. */ private final int limit; /** The recursion level at which each curve being held in storage was generated. */ private int levels[]; /** The cache of interpolated coords. * Note that this must be long enough * to store a full cubic segment and * a relative cubic segment to avoid * aliasing when copying the coords * of a curve to the end of the array. * This is also serendipitously equal * to the size of a full quad segment * and 2 relative quad segments. */ private float hold[] = new float[14]; /** The index of the last curve segment being held for interpolation. */ private int holdEnd; /** * The index of the curve segment that was last interpolated. This * is the curve segment ready to be returned in the next call to * next(). */ private int holdIndex; /** The ending x of the last segment. */ private float currentX; /** The ending y of the last segment. */ private float currentY; /** The x of the last move segment. */ private float moveX; /** The y of the last move segment. */ private float moveY; /** The index of the entry in the * levels array of the curve segment * at the holdIndex */ private int levelIndex; /** True when iteration is done. */ private boolean done; /** The type of the path element. */ private PathElementType holdType; /** The x of the last move segment replied by next. */ private int lastNextX; /** The y of the last move segment replied by next. */ private int lastNextY; /** * @param windingRule is the winding rule of the path. * @param pathIterator is the path iterator that may be used to initialize the path. * @param flatness the maximum allowable distance between the * control points and the flattened curve * @param limit the maximum number of recursive subdivisions * allowed for any curved segment */ public FlatteningPathIterator(PathWindingRule windingRule, Iterator<PathElement2i> pathIterator, float flatness, int limit) { assert(windingRule!=null); assert(flatness>=0f); assert(limit>=0); this.windingRule = windingRule; this.pathIterator = pathIterator; this.squaredFlatness = flatness * flatness; this.limit = limit; this.levels = new int[limit + 1]; searchNext(true); } /** * Ensures that the hold array can hold up to (want) more values. * It is currently holding (hold.length - holdIndex) values. */ private void ensureHoldCapacity(int want) { if (this.holdIndex - want < 0) { int have = this.hold.length - this.holdIndex; int newsize = this.hold.length + GROW_SIZE; float newhold[] = new float[newsize]; System.arraycopy(this.hold, this.holdIndex, newhold, this.holdIndex + GROW_SIZE, have); this.hold = newhold; this.holdIndex += GROW_SIZE; this.holdEnd += GROW_SIZE; } } /** * Returns the square of the flatness, or maximum distance of a * control point from the line connecting the end points, of the * quadratic curve specified by the control points stored in the * indicated array at the indicated index. * @param coords an array containing coordinate values * @param offset the index into <code>coords</code> from which to * to start getting the values from the array * @return the flatness of the quadratic curve that is defined by the * values in the specified array at the specified index. */ private static float getQuadSquaredFlatness(float coords[], int offset) { return (float) Segment2afp.calculatesDistanceSquaredLinePoint( coords[offset + 2], coords[offset + 3], coords[offset + 0], coords[offset + 1], coords[offset + 4], coords[offset + 5]); } /** * Subdivides the quadratic curve specified by the coordinates * stored in the <code>src</code> array at indices * <code>srcoff</code> through <code>srcoff</code> + 5 * and stores the resulting two subdivided curves into the two * result arrays at the corresponding indices. * Either or both of the <code>left</code> and <code>right</code> * arrays can be <code>null</code> or a reference to the same array * and offset as the <code>src</code> array. * Note that the last point in the first subdivided curve is the * same as the first point in the second subdivided curve. Thus, * it is possible to pass the same array for <code>left</code> and * <code>right</code> and to use offsets such that * <code>rightoff</code> equals <code>leftoff</code> + 4 in order * to avoid allocating extra storage for this common point. * @param src the array holding the coordinates for the source curve * @param srcoff the offset into the array of the beginning of the * the 6 source coordinates * @param left the array for storing the coordinates for the first * half of the subdivided curve * @param leftoff the offset into the array of the beginning of the * the 6 left coordinates * @param right the array for storing the coordinates for the second * half of the subdivided curve * @param rightoff the offset into the array of the beginning of the * the 6 right coordinates */ private static void subdivideQuad(float src[], int srcoff, float left[], int leftoff, float right[], int rightoff) { float x1 = src[srcoff + 0]; float y1 = src[srcoff + 1]; float ctrlx = src[srcoff + 2]; float ctrly = src[srcoff + 3]; float x2 = src[srcoff + 4]; float y2 = src[srcoff + 5]; if (left != null) { left[leftoff + 0] = x1; left[leftoff + 1] = y1; } if (right != null) { right[rightoff + 4] = x2; right[rightoff + 5] = y2; } x1 = (x1 + ctrlx) / 2f; y1 = (y1 + ctrly) / 2f; x2 = (x2 + ctrlx) / 2f; y2 = (y2 + ctrly) / 2f; ctrlx = (x1 + x2) / 2f; ctrly = (y1 + y2) / 2f; if (left != null) { left[leftoff + 2] = x1; left[leftoff + 3] = y1; left[leftoff + 4] = ctrlx; left[leftoff + 5] = ctrly; } if (right != null) { right[rightoff + 0] = ctrlx; right[rightoff + 1] = ctrly; right[rightoff + 2] = x2; right[rightoff + 3] = y2; } } /** * Returns the square of the flatness of the cubic curve specified * by the control points stored in the indicated array at the * indicated index. The flatness is the maximum distance * of a control point from the line connecting the end points. * @param coords an array containing coordinates * @param offset the index of <code>coords</code> from which to begin * getting the end points and control points of the curve * @return the square of the flatness of the <code>CubicCurve2D</code> * specified by the coordinates in <code>coords</code> at * the specified offset. */ private static float getCurveSquaredFlatness(float coords[], int offset) { return Math.max( (float) Segment2afp.calculatesDistanceSquaredLinePoint( coords[offset + 0], coords[offset + 1], coords[offset + 6], coords[offset + 7], coords[offset + 2], coords[offset + 3]), (float) Segment2afp.calculatesDistanceSquaredLinePoint( coords[offset + 0], coords[offset + 1], coords[offset + 6], coords[offset + 7], coords[offset + 4], coords[offset + 5])); } /** * Subdivides the cubic curve specified by the coordinates * stored in the <code>src</code> array at indices <code>srcoff</code> * through (<code>srcoff</code> + 7) and stores the * resulting two subdivided curves into the two result arrays at the * corresponding indices. * Either or both of the <code>left</code> and <code>right</code> * arrays may be <code>null</code> or a reference to the same array * as the <code>src</code> array. * Note that the last point in the first subdivided curve is the * same as the first point in the second subdivided curve. Thus, * it is possible to pass the same array for <code>left</code> * and <code>right</code> and to use offsets, such as <code>rightoff</code> * equals (<code>leftoff</code> + 6), in order * to avoid allocating extra storage for this common point. * @param src the array holding the coordinates for the source curve * @param srcoff the offset into the array of the beginning of the * the 6 source coordinates * @param left the array for storing the coordinates for the first * half of the subdivided curve * @param leftoff the offset into the array of the beginning of the * the 6 left coordinates * @param right the array for storing the coordinates for the second * half of the subdivided curve * @param rightoff the offset into the array of the beginning of the * the 6 right coordinates */ private static void subdivideCurve( float src[], int srcoff, float left[], int leftoff, float right[], int rightoff) { float x1 = src[srcoff + 0]; float y1 = src[srcoff + 1]; float ctrlx1 = src[srcoff + 2]; float ctrly1 = src[srcoff + 3]; float ctrlx2 = src[srcoff + 4]; float ctrly2 = src[srcoff + 5]; float x2 = src[srcoff + 6]; float y2 = src[srcoff + 7]; if (left != null) { left[leftoff + 0] = x1; left[leftoff + 1] = y1; } if (right != null) { right[rightoff + 6] = x2; right[rightoff + 7] = y2; } x1 = (x1 + ctrlx1) / 2f; y1 = (y1 + ctrly1) / 2f; x2 = (x2 + ctrlx2) / 2f; y2 = (y2 + ctrly2) / 2f; float centerx = (ctrlx1 + ctrlx2) / 2f; float centery = (ctrly1 + ctrly2) / 2f; ctrlx1 = (x1 + centerx) / 2f; ctrly1 = (y1 + centery) / 2f; ctrlx2 = (x2 + centerx) / 2f; ctrly2 = (y2 + centery) / 2f; centerx = (ctrlx1 + ctrlx2) / 2f; centery = (ctrly1 + ctrly2) / 2f; if (left != null) { left[leftoff + 2] = x1; left[leftoff + 3] = y1; left[leftoff + 4] = ctrlx1; left[leftoff + 5] = ctrly1; left[leftoff + 6] = centerx; left[leftoff + 7] = centery; } if (right != null) { right[rightoff + 0] = centerx; right[rightoff + 1] = centery; right[rightoff + 2] = ctrlx2; right[rightoff + 3] = ctrly2; right[rightoff + 4] = x2; right[rightoff + 5] = y2; } } private void searchNext(boolean isFirst) { do { flattening(); } while (!this.done && !isFirst && isSame()); } private boolean isSame() { PathElementType type = this.holdType; int x, y; if (type==PathElementType.CLOSE) { x = Math.round(this.moveX); y = Math.round(this.moveY); } else { x = Math.round(this.hold[this.holdIndex + 0]); y = Math.round(this.hold[this.holdIndex + 1]); } return x==this.lastNextX && y==this.lastNextY; } private void flattening() { int level; if (this.holdIndex >= this.holdEnd) { if (!this.pathIterator.hasNext()) { this.done = true; return; } PathElement2i pathElement = this.pathIterator.next(); this.holdType = pathElement.type; pathElement.toArray(this.hold); this.levelIndex = 0; this.levels[0] = 0; } switch (this.holdType) { case MOVE_TO: case LINE_TO: this.currentX = this.hold[0]; this.currentY = this.hold[1]; if (this.holdType == PathElementType.MOVE_TO) { this.moveX = this.currentX; this.moveY = this.currentY; } this.holdIndex = 0; this.holdEnd = 0; break; case CLOSE: this.currentX = this.moveX; this.currentY = this.moveY; this.holdIndex = 0; this.holdEnd = 0; break; case QUAD_TO: if (this.holdIndex >= this.holdEnd) { // Move the coordinates to the end of the array. this.holdIndex = this.hold.length - 6; this.holdEnd = this.hold.length - 2; this.hold[this.holdIndex + 0] = this.currentX; this.hold[this.holdIndex + 1] = this.currentY; this.hold[this.holdIndex + 2] = this.hold[0]; this.hold[this.holdIndex + 3] = this.hold[1]; this.hold[this.holdIndex + 4] = this.currentX = this.hold[2]; this.hold[this.holdIndex + 5] = this.currentY = this.hold[3]; } level = this.levels[this.levelIndex]; while (level < this.limit) { if (getQuadSquaredFlatness(this.hold, this.holdIndex) < this.squaredFlatness) { break; } ensureHoldCapacity(4); subdivideQuad( this.hold, this.holdIndex, this.hold, this.holdIndex - 4, this.hold, this.holdIndex); this.holdIndex -= 4; // Now that we have subdivided, we have constructed // two curves of one depth lower than the original // curve. One of those curves is in the place of // the former curve and one of them is in the next // set of held coordinate slots. We now set both // curves level values to the next higher level. level++; this.levels[this.levelIndex] = level; this.levelIndex++; this.levels[this.levelIndex] = level; } // This curve segment is flat enough, or it is too deep // in recursion levels to try to flatten any more. The // two coordinates at holdIndex+4 and holdIndex+5 now // contain the endpoint of the curve which can be the // endpoint of an approximating line segment. this.holdIndex += 4; this.levelIndex--; break; case CURVE_TO: if (this.holdIndex >= this.holdEnd) { // Move the coordinates to the end of the array. this.holdIndex = this.hold.length - 8; this.holdEnd = this.hold.length - 2; this.hold[this.holdIndex + 0] = this.currentX; this.hold[this.holdIndex + 1] = this.currentY; this.hold[this.holdIndex + 2] = this.hold[0]; this.hold[this.holdIndex + 3] = this.hold[1]; this.hold[this.holdIndex + 4] = this.hold[2]; this.hold[this.holdIndex + 5] = this.hold[3]; this.hold[this.holdIndex + 6] = this.currentX = this.hold[4]; this.hold[this.holdIndex + 7] = this.currentY = this.hold[5]; } level = this.levels[this.levelIndex]; while (level < this.limit) { if (getCurveSquaredFlatness(this.hold,this. holdIndex) < this.squaredFlatness) { break; } ensureHoldCapacity(6); subdivideCurve( this.hold, this.holdIndex, this.hold, this.holdIndex - 6, this.hold, this.holdIndex); this.holdIndex -= 6; // Now that we have subdivided, we have constructed // two curves of one depth lower than the original // curve. One of those curves is in the place of // the former curve and one of them is in the next // set of held coordinate slots. We now set both // curves level values to the next higher level. level++; this.levels[this.levelIndex] = level; this.levelIndex++; this.levels[this.levelIndex] = level; } // This curve segment is flat enough, or it is too deep // in recursion levels to try to flatten any more. The // two coordinates at holdIndex+6 and holdIndex+7 now // contain the endpoint of the curve which can be the // endpoint of an approximating line segment. this.holdIndex += 6; this.levelIndex--; break; default: } } @Override public boolean hasNext() { return !this.done; } @Override public PathElement2i next() { if (this.done) { throw new NoSuchElementException("flattening iterator out of bounds"); //$NON-NLS-1$ } PathElement2i element; PathElementType type = this.holdType; if (type!=PathElementType.CLOSE) { int x = Math.round(this.hold[this.holdIndex + 0]); int y = Math.round(this.hold[this.holdIndex + 1]); if (type == PathElementType.MOVE_TO) { element = new PathElement2i.MovePathElement2i(x, y); } else { element = new PathElement2i.LinePathElement2i( this.lastNextX, this.lastNextY, x, y); } this.lastNextX = x; this.lastNextY = y; } else { int x = Math.round(this.moveX); int y = Math.round(this.moveY); element = new PathElement2i.ClosePathElement2i( this.lastNextX, this.lastNextY, x, y); this.lastNextX = x; this.lastNextY = y; } searchNext(false); return element; } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public PathWindingRule getWindingRule() { return this.windingRule; } @Override public boolean isPolyline() { return false; // Because the iterator flats the path, this is no curve inside. } } // class FlatteningPathIterator }