/*
* Copyright (c) 2006, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.javafx.geom;
import com.sun.javafx.geom.transform.BaseTransform;
import java.util.Arrays;
The Path2D
class provides a simple, yet flexible shape which represents an arbitrary geometric path. It can fully represent any path which can be iterated by the PathIterator
interface including all of its segment types and winding rules and it implements all of the basic hit testing methods of the Shape
interface. Use Path2D
when dealing with data that can be represented and used with floating point precision.
Path2D
provides exactly those facilities required for basic construction and management of a geometric path and implementation of the above interfaces with little added interpretation. If it is useful to manipulate the interiors of closed geometric shapes beyond simple hit testing then the Area
class provides additional capabilities specifically targeted at closed figures. While both classes nominally implement the Shape
interface, they differ in purpose and together they provide two useful views of a geometric shape where Path2D
deals primarily with a trajectory formed by path segments and Area
deals more with interpretation and manipulation of enclosed regions of 2D geometric space.
The PathIterator
interface has more detailed descriptions of the types of segments that make up a path and the winding rules that control how to determine which regions are inside or outside the path.
Version: 1.10, 05/05/07
/**
* The {@code Path2D} class provides a simple, yet flexible
* shape which represents an arbitrary geometric path.
* It can fully represent any path which can be iterated by the
* {@link PathIterator} interface including all of its segment
* types and winding rules and it implements all of the
* basic hit testing methods of the {@link Shape} interface.
* <p>
* Use {@link Path2D} when dealing with data that can be represented
* and used with floating point precision.
* <p>
* {@code Path2D} provides exactly those facilities required for
* basic construction and management of a geometric path and
* implementation of the above interfaces with little added
* interpretation.
* If it is useful to manipulate the interiors of closed
* geometric shapes beyond simple hit testing then the
* {@link Area} class provides additional capabilities
* specifically targeted at closed figures.
* While both classes nominally implement the {@code Shape}
* interface, they differ in purpose and together they provide
* two useful views of a geometric shape where {@code Path2D}
* deals primarily with a trajectory formed by path segments
* and {@code Area} deals more with interpretation and manipulation
* of enclosed regions of 2D geometric space.
* <p>
* The {@link PathIterator} interface has more detailed descriptions
* of the types of segments that make up a path and the winding rules
* that control how to determine which regions are inside or outside
* the path.
*
* @version 1.10, 05/05/07
*/
public class Path2D extends Shape implements PathConsumer2D {
static final int curvecoords[] = {2, 2, 4, 6, 0};
public enum CornerPrefix {
CORNER_ONLY,
MOVE_THEN_CORNER,
LINE_THEN_CORNER
}
An even-odd winding rule for determining the interior of
a path.
See Also: - WIND_EVEN_ODD.WIND_EVEN_ODD
/**
* An even-odd winding rule for determining the interior of
* a path.
*
* @see PathIterator#WIND_EVEN_ODD
*/
public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD;
A non-zero winding rule for determining the interior of a
path.
See Also: - WIND_NON_ZERO.WIND_NON_ZERO
/**
* A non-zero winding rule for determining the interior of a
* path.
*
* @see PathIterator#WIND_NON_ZERO
*/
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;
int numTypes;
int numCoords;
int windingRule;
static final int INIT_SIZE = 20;
static final int EXPAND_MAX = 500;
static final int EXPAND_MAX_COORDS = EXPAND_MAX * 2;
float floatCoords[];
float moveX, moveY;
float prevX, prevY;
float currX, currY;
Constructs a new empty single precision Path2D
object with a default winding rule of WIND_NON_ZERO
. /**
* Constructs a new empty single precision {@code Path2D} object
* with a default winding rule of {@link #WIND_NON_ZERO}.
*/
public Path2D() {
this(WIND_NON_ZERO, INIT_SIZE);
}
Constructs a new empty single precision Path2D
object with the specified winding rule to control operations that require the interior of the path to be defined. Params: - rule – the winding rule
See Also:
/**
* Constructs a new empty single precision {@code Path2D} 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 Path2D(int rule) {
this(rule, INIT_SIZE);
}
Constructs a new empty single precision Path2D
object with the specified winding rule and the specified initial capacity to store path segments. This number is an initial guess as to how many path segments will be added to the path, but the storage is expanded as needed to store whatever path segments are added. Params: - rule – the winding rule
- initialCapacity – the estimate for the number of path segments
in the path
See Also:
/**
* Constructs a new empty single precision {@code Path2D} object
* with the specified winding rule and the specified initial
* capacity to store path segments.
* This number is an initial guess as to how many path segments
* will be added to the path, but the storage is expanded as
* needed to store whatever path segments are added.
*
* @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 Path2D(int rule, int initialCapacity) {
setWindingRule(rule);
this.pointTypes = new byte[initialCapacity];
floatCoords = new float[initialCapacity * 2];
}
Constructs a new single precision Path2D
object from an arbitrary Shape
object. All of the initial geometry and the winding rule for this path are taken from the specified Shape
object. Params: - s – the specified
Shape
object
/**
* Constructs a new single precision {@code Path2D} object
* from an arbitrary {@link Shape} object.
* All of the initial geometry and the winding rule for this path are
* taken from the specified {@code Shape} object.
*
* @param s the specified {@code Shape} object
*/
public Path2D(Shape s) {
this(s, null);
}
Constructs a new single precision Path2D
object from an arbitrary Shape
object, transformed by an BaseTransform
object. All of the initial geometry and the winding rule for this path are taken from the specified Shape
object and transformed by the specified BaseTransform
object. Params: - s – the specified
Shape
object - tx – the specified
BaseTransform
object
/**
* Constructs a new single precision {@code Path2D} object
* from an arbitrary {@link Shape} object, transformed by an
* {@link BaseTransform} object.
* All of the initial geometry and the winding rule for this path are
* taken from the specified {@code Shape} object and transformed
* by the specified {@code BaseTransform} object.
*
* @param s the specified {@code Shape} object
* @param tx the specified {@code BaseTransform} object
*/
public Path2D(Shape s, BaseTransform tx) {
if (s instanceof Path2D) {
Path2D p2d = (Path2D) s;
setWindingRule(p2d.windingRule);
this.numTypes = p2d.numTypes;
this.pointTypes = Arrays.copyOf(p2d.pointTypes, numTypes);
this.numCoords = p2d.numCoords;
if (tx == null || tx.isIdentity()) {
this.floatCoords = Arrays.copyOf(p2d.floatCoords, numCoords);
this.moveX = p2d.moveX;
this.moveY = p2d.moveY;
this.prevX = p2d.prevX;
this.prevY = p2d.prevY;
this.currX = p2d.currX;
this.currY = p2d.currY;
} else {
this.floatCoords = new float[numCoords + 6];
tx.transform(p2d.floatCoords, 0, this.floatCoords, 0, numCoords / 2);
floatCoords[numCoords + 0] = moveX;
floatCoords[numCoords + 1] = moveY;
floatCoords[numCoords + 2] = prevX;
floatCoords[numCoords + 3] = prevY;
floatCoords[numCoords + 4] = currX;
floatCoords[numCoords + 5] = currY;
tx.transform(this.floatCoords, numCoords, this.floatCoords, numCoords, 3);
moveX = floatCoords[numCoords + 0];
moveY = floatCoords[numCoords + 1];
prevX = floatCoords[numCoords + 2];
prevY = floatCoords[numCoords + 3];
currX = floatCoords[numCoords + 4];
currY = floatCoords[numCoords + 5];
}
} else {
PathIterator pi = s.getPathIterator(tx);
setWindingRule(pi.getWindingRule());
this.pointTypes = new byte[INIT_SIZE];
this.floatCoords = new float[INIT_SIZE * 2];
append(pi, false);
}
}
Construct a Path2D from pre-composed data.
Used by internal font code which has obtained the path data
for a glyph outline, and which promises not to
mess with the arrays, dropping all other references,
so there's no need to clone them here.
/**
* Construct a Path2D from pre-composed data.
* Used by internal font code which has obtained the path data
* for a glyph outline, and which promises not to
* mess with the arrays, dropping all other references,
so there's no need to clone them here.
*/
public Path2D(int windingRule,
byte[] pointTypes,
int numTypes,
float[] pointCoords,
int numCoords)
{
this.windingRule = windingRule;
this.pointTypes = pointTypes;
this.numTypes = numTypes;
this.floatCoords = pointCoords;
this.numCoords = numCoords;
}
Point2D getPoint(int coordindex) {
return new Point2D(floatCoords[coordindex],
floatCoords[coordindex+1]);
}
private boolean close(int ix, float fx, float tolerance) {
return (Math.abs(ix - fx) <= tolerance);
}
Check and return if the fillable interior of the path is a simple rectangle on nearly integer bounds and initialize the indicated Rectangle
with the integer representation of the rectangle if it is. The method will return false if the path is not rectangular, or if the horizontal and linear segments are not within the indicated tolerance of an integer coordinate, or if the resulting rectangle cannot be safely represented by the integer attributes of the Rectangle
object. Params: - retrect – the
Rectangle
to return the rectangular area, or null - tolerance – the maximum difference from an integer allowed
for any edge of the rectangle
Returns: true iff the path is a simple rectangle
/**
* Check and return if the fillable interior of the path is a simple
* rectangle on nearly integer bounds and initialize the indicated
* {@link Rectangle} with the integer representation of the rectangle
* if it is.
* The method will return false if the path is not rectangular, or if
* the horizontal and linear segments are not within the indicated
* tolerance of an integer coordinate, or if the resulting rectangle
* cannot be safely represented by the integer attributes of the
* {@code Rectangle} object.
*
* @param retrect the {@code Rectangle} to return the rectangular area,
* or null
* @param tolerance the maximum difference from an integer allowed
* for any edge of the rectangle
* @return true iff the path is a simple rectangle
*/
public boolean checkAndGetIntRect(Rectangle retrect, float tolerance) {
// Valid rectangular paths are:
// 4 segs: MOVE, LINE, LINE, LINE (implicit CLOSE)
// 5 segs: MOVE, LINE, LINE, LINE, LINE
// 5 segs: MOVE, LINE, LINE, LINE, CLOSE
// 6 segs: MOVE, LINE, LINE, LINE, LINE, CLOSE
if (numTypes == 5) {
// points[4] can be LINETO or CLOSE
if (pointTypes[4] != SEG_LINETO && pointTypes[4] != SEG_CLOSE) {
return false;
}
} else if (numTypes == 6) {
// points[4] must be LINETO and
// points[5] must be CLOSE
if (pointTypes[4] != SEG_LINETO) return false;
if (pointTypes[5] != SEG_CLOSE) return false;
} else if (numTypes != 4) {
return false;
}
if (pointTypes[0] != SEG_MOVETO) return false;
if (pointTypes[1] != SEG_LINETO) return false;
if (pointTypes[2] != SEG_LINETO) return false;
if (pointTypes[3] != SEG_LINETO) return false;
int x0 = (int) (floatCoords[0] + 0.5f);
int y0 = (int) (floatCoords[1] + 0.5f);
if (!close(x0, floatCoords[0], tolerance)) return false;
if (!close(y0, floatCoords[1], tolerance)) return false;
int x1 = (int) (floatCoords[2] + 0.5f);
int y1 = (int) (floatCoords[3] + 0.5f);
if (!close(x1, floatCoords[2], tolerance)) return false;
if (!close(y1, floatCoords[3], tolerance)) return false;
int x2 = (int) (floatCoords[4] + 0.5f);
int y2 = (int) (floatCoords[5] + 0.5f);
if (!close(x2, floatCoords[4], tolerance)) return false;
if (!close(y2, floatCoords[5], tolerance)) return false;
int x3 = (int) (floatCoords[6] + 0.5f);
int y3 = (int) (floatCoords[7] + 0.5f);
if (!close(x3, floatCoords[6], tolerance)) return false;
if (!close(y3, floatCoords[7], tolerance)) return false;
if (numTypes > 4 && pointTypes[4] == SEG_LINETO) {
if (!close(x0, floatCoords[8], tolerance)) return false;
if (!close(y0, floatCoords[9], tolerance)) return false;
}
if ((x0 == x1 && x2 == x3 && y0 == y3 && y1 == y2) ||
(y0 == y1 && y2 == y3 && x0 == x3 && x1 == x2))
{
// We can use either diagonal to calculate the rectangle:
// (x0, y0) -> (x2, y2)
// (x1, y1) -> (x3, y3)
// We also need to deal with upside down and/or backwards rectangles
int x, y, w, h;
if (x2 < x0) { x = x2; w = x0 - x2; }
else { x = x0; w = x2 - x0; }
if (y2 < y0) { y = y2; h = y0 - y2; }
else { y = y0; h = y2 - y0; }
// Overflow protection...
if (w < 0) return false;
if (h < 0) return false;
if (retrect != null) {
retrect.setBounds(x, y, w, h);
}
return true;
}
return false;
}
void needRoom(boolean needMove, int newCoords) {
if (needMove && (numTypes == 0)) {
throw new IllegalPathStateException("missing initial moveto "+
"in path definition");
}
int size = pointTypes.length;
if (size == 0) {
pointTypes = new byte[2];
} else if (numTypes >= size) {
pointTypes = expandPointTypes(pointTypes, 1);
}
size = floatCoords.length;
if (numCoords > (floatCoords.length - newCoords)) {
floatCoords = expandCoords(floatCoords, newCoords);
}
}
static byte[] expandPointTypes(byte[] oldPointTypes, int needed) {
final int oldSize = oldPointTypes.length;
final int newSizeMin = oldSize + needed;
if (newSizeMin < oldSize) {
// hard overflow failure - we can't even accommodate
// new items without overflowing
throw new ArrayIndexOutOfBoundsException(
"pointTypes exceeds maximum capacity !");
}
// growth algorithm computation
int grow = oldSize;
if (grow > EXPAND_MAX) {
grow = Math.max(EXPAND_MAX, oldSize >> 3); // 1/8th min
} else if (grow < INIT_SIZE) {
grow = INIT_SIZE; // ensure > 6 (cubics)
}
assert grow > 0;
int newSize = oldSize + grow;
if (newSize < newSizeMin) {
// overflow in growth algorithm computation
newSize = Integer.MAX_VALUE;
}
while (true) {
try {
// try allocating the larger array
return Arrays.copyOf(oldPointTypes, newSize);
} catch (OutOfMemoryError oome) {
if (newSize == newSizeMin) {
throw oome;
}
}
newSize = newSizeMin + (newSize - newSizeMin) / 2;
}
}
static float[] expandCoords(float[] oldCoords, int needed) {
final int oldSize = oldCoords.length;
final int newSizeMin = oldSize + needed;
if (newSizeMin < oldSize) {
// hard overflow failure - we can't even accommodate
// new items without overflowing
throw new ArrayIndexOutOfBoundsException(
"coords exceeds maximum capacity !");
}
// growth algorithm computation
int grow = oldSize;
if (grow > EXPAND_MAX_COORDS) {
grow = Math.max(EXPAND_MAX_COORDS, oldSize >> 3); // 1/8th min
} else if (grow < INIT_SIZE) {
grow = INIT_SIZE; // ensure > 6 (cubics)
}
assert grow > needed;
int newSize = oldSize + grow;
if (newSize < newSizeMin) {
// overflow in growth algorithm computation
newSize = Integer.MAX_VALUE;
}
while (true) {
try {
// try allocating the larger array
return Arrays.copyOf(oldCoords, newSize);
} catch (OutOfMemoryError oome) {
if (newSize == newSizeMin) {
throw oome;
}
}
newSize = newSizeMin + (newSize - newSizeMin) / 2;
}
}
Adds a point to the path by moving to the specified
coordinates specified in float precision.
Params: - x – the specified X coordinate
- y – the specified Y coordinate
/**
* 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 final void moveTo(float x, float y) {
if (numTypes > 0 && pointTypes[numTypes - 1] == SEG_MOVETO) {
floatCoords[numCoords-2] = moveX = prevX = currX = x;
floatCoords[numCoords-1] = moveY = prevY = currY = y;
} else {
needRoom(false, 2);
pointTypes[numTypes++] = SEG_MOVETO;
floatCoords[numCoords++] = moveX = prevX = currX = x;
floatCoords[numCoords++] = moveY = prevY = currY = y;
}
}
Adds a point to the path by moving to the specified coordinates
relative to the current point, specified in float precision.
Params: - relx – the specified relative X coordinate
- rely – the specified relative Y coordinate
See Also:
/**
* Adds a point to the path by moving to the specified coordinates
* relative to the current point, specified in float precision.
*
* @param relx the specified relative X coordinate
* @param rely the specified relative Y coordinate
* @see Path2D#moveTo
*/
public final void moveToRel(float relx, float rely) {
if (numTypes > 0 && pointTypes[numTypes - 1] == SEG_MOVETO) {
floatCoords[numCoords-2] = moveX = prevX = (currX += relx);
floatCoords[numCoords-1] = moveY = prevY = (currY += rely);
} else {
needRoom(true, 2);
pointTypes[numTypes++] = SEG_MOVETO;
floatCoords[numCoords++] = moveX = prevX = (currX += relx);
floatCoords[numCoords++] = moveY = prevY = (currY += rely);
}
}
Adds a point to the path by drawing a straight line from the
current coordinates to the new coordinates.
Params: - x – the specified X coordinate
- y – the specified Y coordinate
/**
* Adds a point to the path by drawing a straight line from the
* current coordinates to the new coordinates.
*
* @param x the specified X coordinate
* @param y the specified Y coordinate
*/
public final void lineTo(float x, float y) {
needRoom(true, 2);
pointTypes[numTypes++] = SEG_LINETO;
floatCoords[numCoords++] = prevX = currX = x;
floatCoords[numCoords++] = prevY = currY = y;
}
Adds a point to the path by drawing a straight line from the
current coordinates to the new coordinates relative to the
current point.
Params: - relx – the specified relative X coordinate
- rely – the specified relative Y coordinate
See Also:
/**
* Adds a point to the path by drawing a straight line from the
* current coordinates to the new coordinates relative to the
* current point.
*
* @param relx the specified relative X coordinate
* @param rely the specified relative Y coordinate
* @see Path2D#lineTo
*/
public final void lineToRel(float relx, float rely) {
needRoom(true, 2);
pointTypes[numTypes++] = SEG_LINETO;
floatCoords[numCoords++] = prevX = (currX += relx);
floatCoords[numCoords++] = prevY = (currY += rely);
}
Adds a curved segment to the path, defined by two new points, by drawing a Quadratic curve that intersects both the current coordinates and the specified coordinates (x2,y2)
, using the specified point (x1,y1)
as a quadratic parametric control point. Params: - x1 – the X coordinate of the quadratic control point
- y1 – the Y coordinate of the quadratic control point
- x2 – the X coordinate of the final end point
- y2 – the Y coordinate of the final end point
/**
* Adds a curved segment to the path, defined by two new points, 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.
*
* @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 final void quadTo(float x1, float y1,
float x2, float y2)
{
needRoom(true, 4);
pointTypes[numTypes++] = SEG_QUADTO;
floatCoords[numCoords++] = prevX = x1;
floatCoords[numCoords++] = prevY = y1;
floatCoords[numCoords++] = currX = x2;
floatCoords[numCoords++] = currY = y2;
}
Adds a curved segment to the path, defined by two new points relative to the current point, by drawing a Quadratic curve that intersects both the current coordinates and the specified relative coordinates (rx2,ry2)
, using the specified relative point (rx1,ry1)
as a quadratic parametric control point. This is equivalent to: quadTo(getCurrentX() + rx1, getCurrentY() + ry1,
getCurrentX() + rx2, getCurrentY() + ry2);
Params: - relx1 – the relative X coordinate of the quadratic control point
- rely1 – the relative Y coordinate of the quadratic control point
- relx2 – the relative X coordinate of the final end point
- rely2 – the relative Y coordinate of the final end point
See Also:
/**
* Adds a curved segment to the path, defined by two new points
* relative to the current point, by
* drawing a Quadratic curve that intersects both the current
* coordinates and the specified relative coordinates {@code (rx2,ry2)},
* using the specified relative point {@code (rx1,ry1)} as a quadratic
* parametric control point.
* This is equivalent to:
* <pre>
* quadTo(getCurrentX() + rx1, getCurrentY() + ry1,
* getCurrentX() + rx2, getCurrentY() + ry2);
* </pre>
*
* @param relx1 the relative X coordinate of the quadratic control point
* @param rely1 the relative Y coordinate of the quadratic control point
* @param relx2 the relative X coordinate of the final end point
* @param rely2 the relative Y coordinate of the final end point
* @see Path2D#quadTo
*/
public final void quadToRel(float relx1, float rely1,
float relx2, float rely2)
{
needRoom(true, 4);
pointTypes[numTypes++] = SEG_QUADTO;
floatCoords[numCoords++] = prevX = currX + relx1;
floatCoords[numCoords++] = prevY = currY + rely1;
floatCoords[numCoords++] = (currX += relx2);
floatCoords[numCoords++] = (currY += rely2);
}
Adds a curved segment to the path, defined by a new point, by drawing a Quadratic curve that intersects both the current coordinates and the specified coordinates (x,y)
, using a quadratic parametric control point that is positioned symmetrically across the current point from the previous curve control point. If the previous path segment is not a curve, then the control point will be positioned at the current point. This is equivalent to: quadTo(getCurrentX() * 2 - ,
getCurrentY() * 2 - ,
x, y);
Params: - x2 – the X coordinate of the final end point
- y2 – the Y coordinate of the final end point
See Also:
/**
* Adds a curved segment to the path, defined by a new point, by
* drawing a Quadratic curve that intersects both the current
* coordinates and the specified coordinates {@code (x,y)},
* using a quadratic parametric control point that is positioned
* symmetrically across the current point from the previous curve
* control point.
* If the previous path segment is not a curve, then the control
* point will be positioned at the current point. This is
* equivalent to:
* <pre>
* quadTo(getCurrentX() * 2 - <previousControlX>,
* getCurrentY() * 2 - <previousControlY>,
* x, y);
* </pre>
*
* @param x2 the X coordinate of the final end point
* @param y2 the Y coordinate of the final end point
* @see Path2D#quadTo
*/
public final void quadToSmooth(float x2, float y2) {
needRoom(true, 4);
pointTypes[numTypes++] = SEG_QUADTO;
floatCoords[numCoords++] = prevX = (currX * 2.0f - prevX);
floatCoords[numCoords++] = prevY = (currY * 2.0f - prevY);
floatCoords[numCoords++] = currX = x2;
floatCoords[numCoords++] = currY = y2;
}
Adds a curved segment to the path, defined by a new point relative to the current point, by drawing a Quadratic curve that intersects both the current coordinates and the specified relative coordinates (x,y)
, using a quadratic parametric control point that is positioned symmetrically across the current point from the previous curve control point. If the previous path segment is not a curve, then the control point will be positioned at the current point. This is equivalent to: quadTo(getCurrentX() * 2 - ,
getCurrentY() * 2 - ,
getCurrentX() + x, getCurrentY() + y);
Params: - relx2 – the relative X coordinate of the final end point
- rely2 – the relative Y coordinate of the final end point
See Also:
/**
* Adds a curved segment to the path, defined by a new point
* relative to the current point, by
* drawing a Quadratic curve that intersects both the current
* coordinates and the specified relative coordinates {@code (x,y)},
* using a quadratic parametric control point that is positioned
* symmetrically across the current point from the previous curve
* control point.
* If the previous path segment is not a curve, then the control
* point will be positioned at the current point. This is
* equivalent to:
* <pre>
* quadTo(getCurrentX() * 2 - <previousControlX>,
* getCurrentY() * 2 - <previousControlY>,
* getCurrentX() + x, getCurrentY() + y);
* </pre>
*
* @param relx2 the relative X coordinate of the final end point
* @param rely2 the relative Y coordinate of the final end point
* @see Path2D#quadTo
*/
public final void quadToSmoothRel(float relx2, float rely2) {
needRoom(true, 4);
pointTypes[numTypes++] = SEG_QUADTO;
floatCoords[numCoords++] = prevX = (currX * 2.0f - prevX);
floatCoords[numCoords++] = prevY = (currY * 2.0f - prevY);
floatCoords[numCoords++] = (currX += relx2);
floatCoords[numCoords++] = (currY += rely2);
}
Adds a curved segment to the path, defined by three new points, by drawing a Bézier curve that intersects both the current coordinates and the specified coordinates (x3,y3)
, using the specified points (x1,y1)
and (x2,y2)
as Bézier control points. Params: - x1 – the X coordinate of the first Bézier control point
- y1 – the Y coordinate of the first Bézier control point
- x2 – the X coordinate of the second Bézier control point
- y2 – the Y coordinate of the second Bézier control point
- x3 – the X coordinate of the final end point
- y3 – the Y coordinate of the final end point
See Also:
/**
* Adds a curved segment to the path, defined by three new points, 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.
*
* @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
* @see Path2D#curveTo
*/
public final void curveTo(float x1, float y1,
float x2, float y2,
float x3, float y3)
{
needRoom(true, 6);
pointTypes[numTypes++] = SEG_CUBICTO;
floatCoords[numCoords++] = x1;
floatCoords[numCoords++] = y1;
floatCoords[numCoords++] = prevX = x2;
floatCoords[numCoords++] = prevY = y2;
floatCoords[numCoords++] = currX = x3;
floatCoords[numCoords++] = currY = y3;
}
Adds a curved segment to the path, defined by three new points relative to the current point, by drawing a Bézier curve that intersects both the current coordinates and the specified coordinates (x3,y3)
, using the specified points (x1,y1)
and (x2,y2)
as Bézier control points. This is equivalent to: curveTo(getCurrentX() + rx1, getCurrentY() + ry1,
getCurrentX() + rx2, getCurrentY() + ry2,
getCurrentX() + rx3, getCurrentY() + ry3)
Params: - relx1 – the relative X coordinate of the first Bézier control point
- rely1 – the relative Y coordinate of the first Bézier control point
- relx2 – the relative X coordinate of the second Bézier control point
- rely2 – the relative Y coordinate of the second Bézier control point
- relx3 – the relative X coordinate of the final end point
- rely3 – the relative Y coordinate of the final end point
See Also:
/**
* Adds a curved segment to the path, defined by three new points
* relative to the current point, 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.
* This is equivalent to:
* <pre>
* curveTo(getCurrentX() + rx1, getCurrentY() + ry1,
* getCurrentX() + rx2, getCurrentY() + ry2,
* getCurrentX() + rx3, getCurrentY() + ry3)
* </pre>
*
* @param relx1 the relative X coordinate of the first Bézier control point
* @param rely1 the relative Y coordinate of the first Bézier control point
* @param relx2 the relative X coordinate of the second Bézier control point
* @param rely2 the relative Y coordinate of the second Bézier control point
* @param relx3 the relative X coordinate of the final end point
* @param rely3 the relative Y coordinate of the final end point
* @see Path2D#curveTo
*/
public final void curveToRel(float relx1, float rely1,
float relx2, float rely2,
float relx3, float rely3)
{
needRoom(true, 6);
pointTypes[numTypes++] = SEG_CUBICTO;
floatCoords[numCoords++] = currX + relx1;
floatCoords[numCoords++] = currY + rely1;
floatCoords[numCoords++] = prevX = currX + relx2;
floatCoords[numCoords++] = prevY = currY + rely2;
floatCoords[numCoords++] = (currX += relx3);
floatCoords[numCoords++] = (currY += rely3);
}
Adds a curved segment to the path, defined by two new points and a third point inferred from the previous curve, by drawing a Bézier curve that intersects both the current coordinates and the specified coordinates (x3,y3)
, using the specified point (x2,y2)
as the second Bézier control point and a first Bézier control point that is positioned symmetrically across the current point from the previous curve control point. This is equivalent to: curveTo(getCurrentX() * 2.0f - ,
getCurrentY() * 2.0f - ,
x2, y2, x3, y3);
Params: - x2 – the X coordinate of the second Bézier control point
- y2 – the Y coordinate of the second Bézier control point
- x3 – the X coordinate of the final end point
- y3 – the Y coordinate of the final end point
See Also:
/**
* Adds a curved segment to the path, defined by two new points and
* a third point inferred from the previous curve, by
* drawing a Bézier curve that intersects both the current
* coordinates and the specified coordinates {@code (x3,y3)},
* using the specified point {@code (x2,y2)} as the second
* Bézier control point and a first Bézier control
* point that is positioned
* symmetrically across the current point from the previous curve
* control point.
* This is equivalent to:
* <pre>
* curveTo(getCurrentX() * 2.0f - <previousControlX>,
* getCurrentY() * 2.0f - <previousControlY>,
* x2, y2, x3, y3);
* </pre>
*
* @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
* @see Path2D#curveTo
*/
public final void curveToSmooth(float x2, float y2,
float x3, float y3)
{
needRoom(true, 6);
pointTypes[numTypes++] = SEG_CUBICTO;
floatCoords[numCoords++] = currX * 2.0f - prevX;
floatCoords[numCoords++] = currY * 2.0f - prevY;
floatCoords[numCoords++] = prevX = x2;
floatCoords[numCoords++] = prevY = y2;
floatCoords[numCoords++] = currX = x3;
floatCoords[numCoords++] = currY = y3;
}
Adds a curved segment to the path, defined by two new points relative to the current point and a third point inferred from the previous curve, by drawing a Bézier curve that intersects both the current coordinates and the specified relative coordinates (rx3,ry3)
, using the specified relative point (rx2,ry2)
as the second Bézier control point and a first Bézier control point that is positioned symmetrically across the current point from the previous curve control point. This is equivalent to: curveTo(getCurrentX() * 2.0f - ,
getCurrentY() * 2.0f - ,
getCurrentX() + x2, getCurrentY() + y2,
getCurrentX() + x3, getCurrentY() + y3);
Params: - relx2 – the relative X coordinate of the second Bézier control point
- rely2 – the relative Y coordinate of the second Bézier control point
- relx3 – the relative X coordinate of the final end point
- rely3 – the relative Y coordinate of the final end point
See Also:
/**
* Adds a curved segment to the path, defined by two new points relative
* to the current point and
* a third point inferred from the previous curve, by
* drawing a Bézier curve that intersects both the current
* coordinates and the specified relative coordinates {@code (rx3,ry3)},
* using the specified relative point {@code (rx2,ry2)} as the second
* Bézier control point and a first Bézier control
* point that is positioned
* symmetrically across the current point from the previous curve
* control point.
* This is equivalent to:
* <pre>
* curveTo(getCurrentX() * 2.0f - <previousControlX>,
* getCurrentY() * 2.0f - <previousControlY>,
* getCurrentX() + x2, getCurrentY() + y2,
* getCurrentX() + x3, getCurrentY() + y3);
* </pre>
*
* @param relx2 the relative X coordinate of the second Bézier control point
* @param rely2 the relative Y coordinate of the second Bézier control point
* @param relx3 the relative X coordinate of the final end point
* @param rely3 the relative Y coordinate of the final end point
* @see Path2D#curveTo
*/
public final void curveToSmoothRel(float relx2, float rely2,
float relx3, float rely3)
{
needRoom(true, 6);
pointTypes[numTypes++] = SEG_CUBICTO;
floatCoords[numCoords++] = currX * 2.0f - prevX;
floatCoords[numCoords++] = currY * 2.0f - prevY;
floatCoords[numCoords++] = prevX = currX + relx2;
floatCoords[numCoords++] = prevY = currY + rely2;
floatCoords[numCoords++] = (currX += relx3);
floatCoords[numCoords++] = (currY += rely3);
}
Append a section of a quadrant of an oval to the current path, relative to the current point. See appendOvalQuadrant
for a precise definition of the path segments to be added, considering that this method uses the current point of the path as the first pair of coordinates and a hard-coded prefix of CORNER_ONLY
. This method is equivalent to (and only slightly faster than): appendOvalQuadrant(getCurrentX(), getCurrentY(),
cx, cy, ex, ey, tfrom, tto,
CornerPrefix.CORNER_ONLY);
Note that you could define a circle inscribed in the rectangular bounding box from (x0, y0)
to (x1, y1)
with the following 4 calls to this method: Path2D path = new Path2D();
float cx = (x0 + x1) * 0.5f; // center X coordinate of top and bottom
float cy = (y0 + y1) * 0.5f; // center Y coordinate of left and right
path.moveTo(cx, y0);
path.ovalQuadrantTo(x1, y0, x1, cy, 0f, 1f);
path.ovalQuadrantTo(x1, y1, cx, y1, 0f, 1f);
path.ovalQuadrantTo(x0, y1, x0, cy, 0f, 1f);
path.ovalQuadrantTo(x0, y0, cx, y0, 0f, 1f);
path.closePath();
You could also define a rounded rectangle inscribed in the rectangular bounding box from (x0, y0)
to (x1, y1)
with a corner arc radius r
less than half the width and the height with the following 4 calls to this method: Path2D path = new Path2D();
float lx = x0 + r;
float rx = x1 - r;
float ty = y0 + r;
float by = y1 - r;
path.moveTo(rx, y0);
path.ovalQuadrantTo(x1, y0, x1, ty, 0f, 1f);
path.lineTo(x1, by);
path.ovalQuadrantTo(x1, y1, rx, y1, 0f, 1f);
path.lineTo(lx, y1);
path.ovalQuadrantTo(x0, y1, x0, by, 0f, 1f);
path.lineTo(x0, by);
path.ovalQuadrantTo(x0, y0, lx, y0, 0f, 1f);
path.closePath();
Params: - cx – the X coordinate of the corner
- cy – the Y coordinate of the corner
- ex – the X coordinate of the midpoint of the trailing edge
interpolated by the oval
- ey – the Y coordinate of the midpoint of the trailing edge
interpolated by the oval
- tfrom – the fraction of the oval section where the curve should start
- tto – the fraction of the oval section where the curve should end
Throws: - IllegalPathStateException –
if there is no current point in the path
- IllegalArgumentException – if the
tfrom
and tto
values do not satisfy the required relationship (0 <= tfrom <= tto <= 1).
/**
* Append a section of a quadrant of an oval to the current path,
* relative to the current point.
* See {@link appendOvalQuadrant} for a precise definition of the
* path segments to be added, considering that this method uses the
* current point of the path as the first pair of coordinates and
* a hard-coded prefix of {@link CornerPrefix.CORNER_ONLY CORNER_ONLY}.
* This method is equivalent to (and only slightly faster than):
* <pre>
* appendOvalQuadrant(getCurrentX(), getCurrentY(),
* cx, cy, ex, ey, tfrom, tto,
* CornerPrefix.CORNER_ONLY);
* </pre>
* Note that you could define a circle inscribed in the rectangular
* bounding box from {@code (x0, y0)} to {@code (x1, y1)} with the
* following 4 calls to this method:
* <pre>
* Path2D path = new Path2D();
* float cx = (x0 + x1) * 0.5f; // center X coordinate of top and bottom
* float cy = (y0 + y1) * 0.5f; // center Y coordinate of left and right
* path.moveTo(cx, y0);
* path.ovalQuadrantTo(x1, y0, x1, cy, 0f, 1f);
* path.ovalQuadrantTo(x1, y1, cx, y1, 0f, 1f);
* path.ovalQuadrantTo(x0, y1, x0, cy, 0f, 1f);
* path.ovalQuadrantTo(x0, y0, cx, y0, 0f, 1f);
* path.closePath();
* </pre>
* You could also define a rounded rectangle inscribed in the rectangular
* bounding box from {@code (x0, y0)} to {@code (x1, y1)} with a corner
* arc radius {@code r} less than half the width and the height with the
* following 4 calls to this method:
* <pre>
* Path2D path = new Path2D();
* float lx = x0 + r;
* float rx = x1 - r;
* float ty = y0 + r;
* float by = y1 - r;
* path.moveTo(rx, y0);
* path.ovalQuadrantTo(x1, y0, x1, ty, 0f, 1f);
* path.lineTo(x1, by);
* path.ovalQuadrantTo(x1, y1, rx, y1, 0f, 1f);
* path.lineTo(lx, y1);
* path.ovalQuadrantTo(x0, y1, x0, by, 0f, 1f);
* path.lineTo(x0, by);
* path.ovalQuadrantTo(x0, y0, lx, y0, 0f, 1f);
* path.closePath();
* </pre>
*
* @param cx the X coordinate of the corner
* @param cy the Y coordinate of the corner
* @param ex the X coordinate of the midpoint of the trailing edge
* interpolated by the oval
* @param ey the Y coordinate of the midpoint of the trailing edge
* interpolated by the oval
* @param tfrom the fraction of the oval section where the curve should start
* @param tto the fraction of the oval section where the curve should end
* @throws IllegalPathStateException
* if there is no current point in the path
* @throws IllegalArgumentException
* if the {@code tfrom} and {@code tto} values do not satisfy the
* required relationship {@code (0 <= tfrom <= tto <= 1).
*/
public final void ovalQuadrantTo(float cx, float cy,
float ex, float ey,
float tfrom, float tto)
{
if (numTypes < 1) {
throw new IllegalPathStateException("missing initial moveto "+
"in path definition");
}
appendOvalQuadrant(currX, currY,
cx, cy, ex, ey, tfrom, tto, CornerPrefix.CORNER_ONLY);
}
Append a section of a quadrant of an oval to the current path. The oval from which a quadrant is taken is the oval that would be inscribed in a parallelogram defined by 3 points, (sx, sy)
which is considered to be the midpoint of the edge leading into the corner of the oval where the oval grazes it, (cx, cy)
which is considered to be the location of the corner of the parallelogram in which the oval is inscribed, and (ex, ey)
which is considered to be the midpoint of the edge leading away from the corner of the oval where the oval grazes it. A typical case involves the two segments being equal in length and at right angles to each other in which case the oval is a quarter of a circle. Only the portion of the oval from tfrom
to tto
will be included where 0f
represents the point where the oval grazes the leading edge, 1f
represents the point where the oval grazes the trailing edge, and 0.5f
represents the point on the oval closest to the corner (i.e. the "45 degree" point). The two values must satisfy the relation (0 <= tfrom <= tto <= 1)
. If tfrom
is not 0f
then the caller would most likely want to use one of the prefix
values that inserts a segment leading to the initial point (see below).
An initial moveTo
or lineTo
can be added to direct the path to the starting point of the oval section if MOVE_THEN_CORNER
or LINE_THEN_CORNER
are specified by the prefix argument. The lineTo
path segment will only be added if the current point is not already at the indicated location to avoid spurious empty line segments. The prefix can be specified as CORNER_ONLY
if the current point on the path is known to be at the starting point of the oval section, but could otherwise produce odd results if the current point is not appropriate.
Note that you could define a circle inscribed in the rectangular bounding box from (x0, y0)
to (x1, y1)
with the following 4 calls to this method:
Path2D path = new Path2D();
float cx = (x0 + x1) * 0.5f; // center X coordinate of top and bottom
float cy = (y0 + y1) * 0.5f; // center Y coordinate of left and right
path.appendOvalQuadrant(cx, y0, x1, y0, x1, cy, 0f, 1f, MOVE_THEN_CORNER);
path.appendOvalQuadrant(x1, cy, x1, y1, cx, y1, 0f, 1f, CORNER_ONLY);
path.appendOvalQuadrant(cx, y1, x0, y1, x0, cy, 0f, 1f, CORNER_ONLY);
path.appendOvalQuadrant(x0, cy, x0, y0, cx, y0, 0f, 1f, CORNER_ONLY);
path.closePath();
You could also define a rounded rectangle inscribed in the rectangular bounding box from (x0, y0)
to (x1, y1)
with a corner arc radius r
less than half the width and the height with the following 4 calls to this method: Path2D path = new Path2D();
float lx = x0 + r;
float rx = x1 - r;
float ty = y0 + r;
float by = y1 - r;
path.appendOvalQuadrant(rx, y0, x1, y0, x1, ty, 0f, 1f, MOVE_THEN_CORNER);
path.appendOvalQuadrant(x1, by, x1, y1, rx, y1, 0f, 1f, LINE_THEN_CORNER);
path.appendOvalQuadrant(lx, y1, x0, y1, x0, by, 0f, 1f, LINE_THEN_CORNER);
path.appendOvalQuadrant(x0, by, x0, y0, lx, y0, 0f, 1f, LINE_THEN_CORNER);
path.closePath();
Params: - sx – the X coordinate of the midpoint of the leading edge
interpolated by the oval
- sy – the Y coordinate of the midpoint of the leading edge
interpolated by the oval
- cx – the X coordinate of the corner
- cy – the Y coordinate of the corner
- ex – the X coordinate of the midpoint of the trailing edge
interpolated by the oval
- ey – the Y coordinate of the midpoint of the trailing edge
interpolated by the oval
- tfrom – the fraction of the oval section where the curve should start
- tto – the fraction of the oval section where the curve should end
- prefix – the specification of what additional path segments should
be appended to lead the current path to the starting point
Throws: - IllegalPathStateException – if there is no current point in the path and the prefix is not
CornerPrevix.MOVE_THEN_CORNER MOVE_THEN_CORNER
. - IllegalArgumentException – if the
tfrom
and tto
values do not satisfy the required relationship (0 <= tfrom <= tto <= 1).
/**
* Append a section of a quadrant of an oval to the current path.
* The oval from which a quadrant is taken is the oval that would be
* inscribed in a parallelogram defined by 3 points,
* {@code (sx, sy)} which is considered to be the midpoint of the edge
* leading into the corner of the oval where the oval grazes it,
* {@code (cx, cy)} which is considered to be the location of the
* corner of the parallelogram in which the oval is inscribed,
* and {@code (ex, ey)} which is considered to be the midpoint of the
* edge leading away from the corner of the oval where the oval grazes it.
* A typical case involves the two segments being equal in length and
* at right angles to each other in which case the oval is a quarter of
* a circle.
* <p>
* Only the portion of the oval from {@code tfrom} to {@code tto}
* will be included where {@code 0f} represents the point where the
* oval grazes the leading edge, {@code 1f} represents the point where
* the oval grazes the trailing edge, and {@code 0.5f} represents the
* point on the oval closest to the corner (i.e. the "45 degree" point).
* The two values must satisfy the relation
* {@code (0 <= tfrom <= tto <= 1)}.
* If {@code tfrom} is not {@code 0f} then the caller would most likely
* want to use one of the {@code prefix} values that inserts a segment
* leading to the initial point (see below).
* <p>
* An initial {@link moveTo} or {@link lineTo} can be added to direct
* the path to the starting point of the oval section if
* {@link CornerPrefix.MOVE_THEN_CORNER MOVE_THEN_CORNER} or
* {@link CornerPrefix.LINE_THEN_CORNER LINE_THEN_CORNER} are
* specified by the prefix argument.
* The {@code lineTo} path segment will only be added if the current point
* is not already at the indicated location to avoid spurious empty line
* segments.
* The prefix can be specified as
* {@link CornerPrefix.CORNER_ONLY CORNER_ONLY} if the current point
* on the path is known to be at the starting point of the oval section,
* but could otherwise produce odd results if the current point is not
* appropriate.
* <p>
* Note that you could define a circle inscribed in the rectangular
* bounding box from {@code (x0, y0)} to {@code (x1, y1)} with the
* following 4 calls to this method:
* <pre>
* Path2D path = new Path2D();
* float cx = (x0 + x1) * 0.5f; // center X coordinate of top and bottom
* float cy = (y0 + y1) * 0.5f; // center Y coordinate of left and right
* path.appendOvalQuadrant(cx, y0, x1, y0, x1, cy, 0f, 1f, MOVE_THEN_CORNER);
* path.appendOvalQuadrant(x1, cy, x1, y1, cx, y1, 0f, 1f, CORNER_ONLY);
* path.appendOvalQuadrant(cx, y1, x0, y1, x0, cy, 0f, 1f, CORNER_ONLY);
* path.appendOvalQuadrant(x0, cy, x0, y0, cx, y0, 0f, 1f, CORNER_ONLY);
* path.closePath();
* </pre>
* You could also define a rounded rectangle inscribed in the rectangular
* bounding box from {@code (x0, y0)} to {@code (x1, y1)} with a corner
* arc radius {@code r} less than half the width and the height with the
* following 4 calls to this method:
* <pre>
* Path2D path = new Path2D();
* float lx = x0 + r;
* float rx = x1 - r;
* float ty = y0 + r;
* float by = y1 - r;
* path.appendOvalQuadrant(rx, y0, x1, y0, x1, ty, 0f, 1f, MOVE_THEN_CORNER);
* path.appendOvalQuadrant(x1, by, x1, y1, rx, y1, 0f, 1f, LINE_THEN_CORNER);
* path.appendOvalQuadrant(lx, y1, x0, y1, x0, by, 0f, 1f, LINE_THEN_CORNER);
* path.appendOvalQuadrant(x0, by, x0, y0, lx, y0, 0f, 1f, LINE_THEN_CORNER);
* path.closePath();
* </pre>
*
* @param sx the X coordinate of the midpoint of the leading edge
* interpolated by the oval
* @param sy the Y coordinate of the midpoint of the leading edge
* interpolated by the oval
* @param cx the X coordinate of the corner
* @param cy the Y coordinate of the corner
* @param ex the X coordinate of the midpoint of the trailing edge
* interpolated by the oval
* @param ey the Y coordinate of the midpoint of the trailing edge
* interpolated by the oval
* @param tfrom the fraction of the oval section where the curve should start
* @param tto the fraction of the oval section where the curve should end
* @param prefix the specification of what additional path segments should
* be appended to lead the current path to the starting point
* @throws IllegalPathStateException
* if there is no current point in the path and the prefix is
* not {@code CornerPrevix.MOVE_THEN_CORNER MOVE_THEN_CORNER}.
* @throws IllegalArgumentException
* if the {@code tfrom} and {@code tto} values do not satisfy the
* required relationship {@code (0 <= tfrom <= tto <= 1).
*/
public final void appendOvalQuadrant(float sx, float sy,
float cx, float cy,
float ex, float ey,
float tfrom, float tto,
CornerPrefix prefix)
{
if (!(tfrom >= 0f && tfrom <= tto && tto <= 1f)) {
throw new IllegalArgumentException("0 <= tfrom <= tto <= 1 required");
}
float cx0 = (float) (sx + (cx - sx) * EllipseIterator.CtrlVal);
float cy0 = (float) (sy + (cy - sy) * EllipseIterator.CtrlVal);
float cx1 = (float) (ex + (cx - ex) * EllipseIterator.CtrlVal);
float cy1 = (float) (ey + (cy - ey) * EllipseIterator.CtrlVal);
if (tto < 1f) {
float t = 1f - tto;
ex += (cx1 - ex) * t;
ey += (cy1 - ey) * t;
cx1 += (cx0 - cx1) * t;
cy1 += (cy0 - cy1) * t;
cx0 += (sx - cx0) * t;
cy0 += (sy - cy0) * t;
ex += (cx1 - ex) * t;
ey += (cy1 - ey) * t;
cx1 += (cx0 - cx1) * t;
cy1 += (cy0 - cy1) * t;
ex += (cx1 - ex) * t;
ey += (cy1 - ey) * t;
}
if (tfrom > 0f) {
if (tto < 1f) {
tfrom = tfrom / tto;
}
sx += (cx0 - sx) * tfrom;
sy += (cy0 - sy) * tfrom;
cx0 += (cx1 - cx0) * tfrom;
cy0 += (cy1 - cy0) * tfrom;
cx1 += (ex - cx1) * tfrom;
cy1 += (ey - cy1) * tfrom;
sx += (cx0 - sx) * tfrom;
sy += (cy0 - sy) * tfrom;
cx0 += (cx1 - cx0) * tfrom;
cy0 += (cy1 - cy0) * tfrom;
sx += (cx0 - sx) * tfrom;
sy += (cy0 - sy) * tfrom;
}
if (prefix == CornerPrefix.MOVE_THEN_CORNER) {
// Always execute moveTo so we break the path...
moveTo(sx, sy);
} else if (prefix == CornerPrefix.LINE_THEN_CORNER) {
if (numTypes == 1 ||
sx != currX ||
sy != currY)
{
lineTo(sx, sy);
}
}
if (tfrom == tto ||
(sx == cx0 && cx0 == cx1 && cx1 == ex &&
sy == cy0 && cy0 == cy1 && cy1 == ey))
{
if (prefix != CornerPrefix.LINE_THEN_CORNER) {
lineTo(ex, ey);
}
} else {
curveTo(cx0, cy0, cx1, cy1, ex, ey);
}
}
Append a portion of an ellipse to the path.
The ellipse from which the portions are extracted follows the rules:
- The ellipse will have its X axis tilted from horizontal by the angle
xAxisRotation
specified in radians. - The ellipse will have the X and Y radii (viewed from its tilted coordinate system) specified by
radiusx
and radiusy
unless that ellipse is too small to bridge the gap from the current point to the specified destination point in which case a larger ellipse with the same ratio of dimensions will be substituted instead. - The ellipse may slide perpendicular to the direction from the
current point to the specified destination point so that it just
touches the two points.
The direction it slides (to the "left" or to the "right") will be
chosen to meet the criteria specified by the two boolean flags as
described below.
Only one direction will allow the method to meet both criteria.
- If the
largeArcFlag
is true, then the ellipse will sweep the longer way around the ellipse that meets these criteria. - If the
sweepFlag
is true, then the ellipse will sweep clockwise around the ellipse that meets these criteria.
The method will do nothing if the destination point is the same as
the current point.
The method will draw a simple line segment to the destination point
if either of the two radii are zero.
Note: This method adheres to the definition of an elliptical arc path
segment from the SVG spec:
http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
Params: - radiusx – the X radius of the tilted ellipse
- radiusy – the Y radius of the tilted ellipse
- xAxisRotation – the angle of tilt of the ellipse
- largeArcFlag – true iff the path will sweep the long way around
the ellipse
- sweepFlag – true iff the path will sweep clockwise around
the ellipse
- x – the destination X coordinate
- y – the destination Y coordinate
Throws: - IllegalPathStateException –
if there is no current point in the path
/**
* Append a portion of an ellipse to the path.
* The ellipse from which the portions are extracted follows the rules:
* <ul>
* <li>The ellipse will have its X axis tilted from horizontal by the
* angle {@code xAxisRotation} specified in radians.
* <li>The ellipse will have the X and Y radii (viewed from its tilted
* coordinate system) specified by {@code radiusx} and {@code radiusy}
* unless that ellipse is too small to bridge the gap from the current
* point to the specified destination point in which case a larger
* ellipse with the same ratio of dimensions will be substituted instead.
* <li>The ellipse may slide perpendicular to the direction from the
* current point to the specified destination point so that it just
* touches the two points.
* The direction it slides (to the "left" or to the "right") will be
* chosen to meet the criteria specified by the two boolean flags as
* described below.
* Only one direction will allow the method to meet both criteria.
* <li>If the {@code largeArcFlag} is true, then the ellipse will sweep
* the longer way around the ellipse that meets these criteria.
* <li>If the {@code sweepFlag} is true, then the ellipse will sweep
* clockwise around the ellipse that meets these criteria.
* </ul>
* The method will do nothing if the destination point is the same as
* the current point.
* The method will draw a simple line segment to the destination point
* if either of the two radii are zero.
* <p>
* Note: This method adheres to the definition of an elliptical arc path
* segment from the SVG spec:
* <pre>
* http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
* </pre>
*
* @param radiusx the X radius of the tilted ellipse
* @param radiusy the Y radius of the tilted ellipse
* @param xAxisRotation the angle of tilt of the ellipse
* @param largeArcFlag true iff the path will sweep the long way around
* the ellipse
* @param sweepFlag true iff the path will sweep clockwise around
* the ellipse
* @param x the destination X coordinate
* @param y the destination Y coordinate
* @throws IllegalPathStateException
* if there is no current point in the path
*/
public void arcTo(float radiusx, float radiusy, float xAxisRotation,
boolean largeArcFlag, boolean sweepFlag,
float x, float y)
{
// First ensure preceding moveto
if (numTypes < 1) {
throw new IllegalPathStateException("missing initial moveto "+
"in path definition");
}
// Reference equations are provided for implementation assistance:
// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
// We use the following modifications:
// They use a secondary coordinate system which is based on
// - translating to the midpoint between the endpoints
// - rotating so that the xAxis is "horizontal"
// You can see that most of their math then has their secondary
// coordinates being divided by rx and ry everywhere so we scale
// by 1/rx and 1/ry so that we are working on a unit circle:
// [ x' ] [ +cos/rx +sin/rx ] [ x - mx ]
// [ ] = [ ] * [ ]
// [ y' ] [ -sin/ry +cos/ry ] [ y - my ]
// and reversing back to user space coordinates:
// [ x ] [ +cos -sin ] [ x' * rx ] [ mx ]
// [ ] = [ ] * [ ] + [ ]
// [ y ] [ +sin +cos ] [ y' * ry ] [ my ]
double rx = Math.abs(radiusx);
double ry = Math.abs(radiusy);
if (rx == 0 || ry == 0) {
lineTo(x, y);
return;
}
double x1 = currX;
double y1 = currY;
double x2 = x;
double y2 = y;
if (x1 == x2 && y1 == y2) {
return;
}
double cosphi, sinphi;
if (xAxisRotation == 0.0) {
cosphi = 1.0;
sinphi = 0.0;
} else {
cosphi = Math.cos(xAxisRotation);
sinphi = Math.sin(xAxisRotation);
}
double mx = (x1 + x2) / 2.0;
double my = (y1 + y2) / 2.0;
double relx1 = x1 - mx;
double rely1 = y1 - my;
double x1p = (cosphi * relx1 + sinphi * rely1) / rx;
double y1p = (cosphi * rely1 - sinphi * relx1) / ry;
// The documentation for the SVG arc operator recommends computing
// a "scale" value and then scaling the radii appropriately if the
// scale is greater than 1. Technically, they are computing the
// ratio of the distance to the endpoints compared to the distance
// across the indicated section of the ellipse centered at the midpoint.
// If the ratio is greater than 1 then the endpoints are outside the
// ellipse and so the ellipse is not large enough to bridge the gap
// without growing. If they are inside, then we slide the ellipse
// in the appropriate direction as specified by the 2 flags so that
// the transformed relative points are on the edge. If they
// are outside, then we note that we simply have a (distorted) half
// circle to render in that case since the endpoints will be on
// opposite sides of a stretched version of the unmoved ellipse.
double lenpsq = x1p * x1p + y1p * y1p;
if (lenpsq >= 1.0) {
// Unlike the reference equations, we do not need to scale the
// radii here since we will work directly from the transformed
// relative vectors which already have the proper distance from
// the midpoint. (They are already on the "stretched" ellipse.)
// Produce 2 quadrant circles from:
// x1p,y1p => xqp,yqp => x2p,y2p
// where x2p,y2p = -x1p,-y1p
// and xqp,yqp = either y1p,-x1p or -y1p,x1p depending on sweepFlag
// the corners of the quadrants are at:
// x1p+xqp,y1p+yqp and x2p+xqp,y2p+yqp
// or consequently at:
// x1+(xq-mx),y1+(yq-my) and x2+(xq-mx),y2+(yq-my)
double xqpr = y1p * rx;
double yqpr = x1p * ry;
if (sweepFlag) { xqpr = -xqpr; } else { yqpr = -yqpr; }
double relxq = cosphi * xqpr - sinphi * yqpr;
double relyq = cosphi * yqpr + sinphi * xqpr;
double xq = mx + relxq;
double yq = my + relyq;
double xc = x1 + relxq;
double yc = y1 + relyq;
appendOvalQuadrant((float) x1, (float) y1,
(float) xc, (float) yc,
(float) xq, (float) yq,
0f, 1f, CornerPrefix.CORNER_ONLY);
xc = x2 + relxq;
yc = y2 + relyq;
appendOvalQuadrant((float) xq, (float) yq,
(float) xc, (float) yc,
(float) x2, (float) y2,
0f, 1f, CornerPrefix.CORNER_ONLY);
return;
}
// We now need to displace the circle perpendicularly to the line
// between the end points so that the new center is at a unit distance
// to either end point. One component of the new distance will be
// the distance from the midpoint to either end point (the square
// of which is already computed in "den" above). The other component
// of the new distances will be how far we displace the center:
// lenpsq + displen^2 = 1.0
// displen^2 = 1.0 - lenpsq
// displen = sqrt(1 - lenpsq)
// The vector we displace along is the perpendicular of the x1p,y1p
// vector whose length is sqrt(lenpsq) so we need to divide that vector
// by that length to turn it into a unit vector:
// cxp = +/-y1p / sqrt(lenpsq) * displen
// cyp = +/-x1p / sqrt(lenpsq) * displen
// To simplify, we combine the "/sqrt(lenpsq)" factor into displen to
// share the one sqrt() calculation:
// scalef = displen / sqrt(lenpsq) = sqrt((1-lenpsq)/lenpsq)
double scalef = Math.sqrt((1.0 - lenpsq) / lenpsq);
// cxp,cyp is displaced perpendicularly to the relative vector x1p,y1p
// by the scalef value. The perpendicular is either -y1p,x1p or
// y1p,-x1p depending on the values of the flags.
double cxp = scalef * y1p;
double cyp = scalef * x1p;
// The direction of the perpendicular (which component is negated)
// depends on both flags.
if (largeArcFlag == sweepFlag) { cxp = -cxp; } else { cyp = -cyp; }
mx += (cosphi * cxp * rx - sinphi * cyp * ry);
my += (cosphi * cyp * ry + sinphi * cxp * rx);
// Now we sweep by quadrants in the direction specified until we
// reach the angle to the destination point and possibly perform
// one last partial-quadrant arc segment.
// First we need to reexpress our vectors relative to the new center.
double ux = x1p - cxp;
double uy = y1p - cyp;
// x2p = -x1p; x2p-cxp = -x1p-cxp = -(x1p+cxp)
// y2p = -y1p; y2p-cyp = -y1p-cyp = -(y1p+cyp)
double vx = -(x1p + cxp);
double vy = -(y1p + cyp);
// px and py are the factors that produce the perpendicular for ux,uy
// in the direction specified by sweepFlag.
boolean done = false; // set to true when we detect "last quadrant"
float quadlen = 1.0f; // 1.0 yields a full 90 degree arc at a time
boolean wasclose = false; // overshoot prevention
do {
// Compute the next circle quadrant endpoint, cw or ccw
double xqp = uy;
double yqp = ux;
if (sweepFlag) { xqp = -xqp; } else { yqp = -yqp; }
// qp.v > 0 tells us if sweep towards v is < 180
if (xqp * vx + yqp * vy > 0) {
// u.v >= 0 now tells us if sweep towards v is <= 90
// (It is also true for >270, but we already checked for <180)
double dot = ux * vx + uy * vy;
if (dot >= 0) {
// u.v is the cosine of the angle we have left since both
// u and v are unit vectors. We now need to express how
// much we want to shorten this last arc segment in terms
// of 0.0=>1.0 meaning 0=>90 degrees.
quadlen = (float) (Math.acos(dot) / (Math.PI / 2.0));
done = true;
}
// Remember that we were once within 180 degrees so we
// do not accidentally overshoot due to fp rounding error.
wasclose = true;
} else if (wasclose) {
// At some point we were in the <180 case above, but now we
// are back at the >180 case having never gone into the <90
// case where done would have been set to true. This should
// not happen, but since we are computing the perpendiculars
// and then expecting that they will have predictable results
// in the dot product equations, there is a theoretical chance
// of a tiny round-off error that would cause us to overshoot
// from just barely >90 left to suddenly past the 0 point.
// If that ever happens, we will end up in here and we can just
// break out of the loop since that last quadrant we rendered
// should have landed us right on top of the vx,vy location.
break;
}
double relxq = (cosphi * xqp * rx - sinphi * yqp * ry);
double relyq = (cosphi * yqp * ry + sinphi * xqp * rx);
double xq = mx + relxq;
double yq = my + relyq;
double xc = x1 + relxq;
double yc = y1 + relyq;
appendOvalQuadrant((float) x1, (float) y1,
(float) xc, (float) yc,
(float) xq, (float) yq,
0f, quadlen, CornerPrefix.CORNER_ONLY);
x1 = xq;
y1 = yq;
ux = xqp;
uy = yqp;
} while (!done);
}
Append a portion of an ellipse to the path using relative coordinates.
This method is identical to calling:
arcTo(radiusX, radiusY, xAxisRotation,
largeArcFlag, sweepFlag,
getCurrentX() + rx, getCurrentY() + ry);
Params: - radiusx – the X radius of the tilted ellipse
- radiusy – the Y radius of the tilted ellipse
- xAxisRotation – the angle of tilt of the ellipse
- largeArcFlag – true iff the path will sweep the long way around
the ellipse
- sweepFlag – true iff the path will sweep clockwise around
the ellipse
- relx – the relative destination relative X coordinate
- rely – the relative destination relative Y coordinate
Throws: - IllegalPathStateException –
if there is no current point in the path
See Also:
/**
* Append a portion of an ellipse to the path using relative coordinates.
* This method is identical to calling:
* <pre>
* arcTo(radiusX, radiusY, xAxisRotation,
* largeArcFlag, sweepFlag,
* getCurrentX() + rx, getCurrentY() + ry);
* </pre>
*
* @param radiusx the X radius of the tilted ellipse
* @param radiusy the Y radius of the tilted ellipse
* @param xAxisRotation the angle of tilt of the ellipse
* @param largeArcFlag true iff the path will sweep the long way around
* the ellipse
* @param sweepFlag true iff the path will sweep clockwise around
* the ellipse
* @param relx the relative destination relative X coordinate
* @param rely the relative destination relative Y coordinate
* @throws IllegalPathStateException
* if there is no current point in the path
* @see Path2D#arcTo
*/
public void arcToRel(float radiusx, float radiusy, float xAxisRotation,
boolean largeArcFlag, boolean sweepFlag,
float relx, float rely)
{
arcTo(radiusx, radiusy, xAxisRotation,
largeArcFlag, sweepFlag,
currX + relx, currY + rely);
}
int pointCrossings(float px, float py) {
float movx, movy, curx, cury, endx, endy;
float coords[] = floatCoords;
curx = movx = coords[0];
cury = movy = coords[1];
int crossings = 0;
int ci = 2;
for (int i = 1; i < numTypes; i++) {
switch (pointTypes[i]) {
case PathIterator.SEG_MOVETO:
if (cury != movy) {
crossings +=
Shape.pointCrossingsForLine(px, py,
curx, cury,
movx, movy);
}
movx = curx = coords[ci++];
movy = cury = coords[ci++];
break;
case PathIterator.SEG_LINETO:
crossings +=
Shape.pointCrossingsForLine(px, py,
curx, cury,
endx = coords[ci++],
endy = coords[ci++]);
curx = endx;
cury = endy;
break;
case PathIterator.SEG_QUADTO:
crossings +=
Shape.pointCrossingsForQuad(px, py,
curx, cury,
coords[ci++],
coords[ci++],
endx = coords[ci++],
endy = coords[ci++],
0);
curx = endx;
cury = endy;
break;
case PathIterator.SEG_CUBICTO:
crossings +=
Shape.pointCrossingsForCubic(px, py,
curx, cury,
coords[ci++],
coords[ci++],
coords[ci++],
coords[ci++],
endx = coords[ci++],
endy = coords[ci++],
0);
curx = endx;
cury = endy;
break;
case PathIterator.SEG_CLOSE:
if (cury != movy) {
crossings +=
Shape.pointCrossingsForLine(px, py,
curx, cury,
movx, movy);
}
curx = movx;
cury = movy;
break;
}
}
if (cury != movy) {
crossings +=
Shape.pointCrossingsForLine(px, py,
curx, cury,
movx, movy);
}
return crossings;
}
int rectCrossings(float rxmin, float rymin,
float rxmax, float rymax)
{
float coords[] = floatCoords;
float curx, cury, movx, movy, endx, endy;
curx = movx = coords[0];
cury = movy = coords[1];
int crossings = 0;
int ci = 2;
for (int i = 1;
crossings != Shape.RECT_INTERSECTS && i < numTypes;
i++)
{
switch (pointTypes[i]) {
case PathIterator.SEG_MOVETO:
if (curx != movx || cury != movy) {
crossings =
Shape.rectCrossingsForLine(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
movx, movy);
}
// Count should always be a multiple of 2 here.
// assert((crossings & 1) != 0);
movx = curx = coords[ci++];
movy = cury = coords[ci++];
break;
case PathIterator.SEG_LINETO:
crossings =
Shape.rectCrossingsForLine(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
endx = coords[ci++],
endy = coords[ci++]);
curx = endx;
cury = endy;
break;
case PathIterator.SEG_QUADTO:
crossings =
Shape.rectCrossingsForQuad(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
coords[ci++],
coords[ci++],
endx = coords[ci++],
endy = coords[ci++],
0);
curx = endx;
cury = endy;
break;
case PathIterator.SEG_CUBICTO:
crossings =
Shape.rectCrossingsForCubic(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
coords[ci++],
coords[ci++],
coords[ci++],
coords[ci++],
endx = coords[ci++],
endy = coords[ci++],
0);
curx = endx;
cury = endy;
break;
case PathIterator.SEG_CLOSE:
if (curx != movx || cury != movy) {
crossings =
Shape.rectCrossingsForLine(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
movx, movy);
}
curx = movx;
cury = movy;
// Count should always be a multiple of 2 here.
// assert((crossings & 1) != 0);
break;
}
}
if (crossings != Shape.RECT_INTERSECTS &&
(curx != movx || cury != movy))
{
crossings =
Shape.rectCrossingsForLine(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
movx, movy);
}
// Count should always be a multiple of 2 here.
// assert((crossings & 1) != 0);
return crossings;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public final 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 < 1) {
moveTo(coords[0], coords[1]);
break;
}
if (pointTypes[numTypes - 1] != SEG_CLOSE &&
floatCoords[numCoords-2] == coords[0] &&
floatCoords[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;
}
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public final void transform(BaseTransform tx) {
if (numCoords == 0) return;
needRoom(false, 6);
floatCoords[numCoords + 0] = moveX;
floatCoords[numCoords + 1] = moveY;
floatCoords[numCoords + 2] = prevX;
floatCoords[numCoords + 3] = prevY;
floatCoords[numCoords + 4] = currX;
floatCoords[numCoords + 5] = currY;
tx.transform(floatCoords, 0, floatCoords, 0, numCoords / 2 + 3);
moveX = floatCoords[numCoords + 0];
moveY = floatCoords[numCoords + 1];
prevX = floatCoords[numCoords + 2];
prevY = floatCoords[numCoords + 3];
currX = floatCoords[numCoords + 4];
currY = floatCoords[numCoords + 5];
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public final RectBounds getBounds() {
float x1, y1, x2, y2;
int i = numCoords;
if (i > 0) {
y1 = y2 = floatCoords[--i];
x1 = x2 = floatCoords[--i];
while (i > 0) {
float y = floatCoords[--i];
float x = floatCoords[--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 RectBounds(x1, y1, x2, y2);
}
// The following three methods are used only by Prism to access
// internal structures; not intended for general use!
public final int getNumCommands() {
return numTypes;
}
public final byte[] getCommandsNoClone() {
return pointTypes;
}
public final float[] getFloatCoordsNoClone() {
return floatCoords;
}
{@inheritDoc}
The iterator for this class is not multi-threaded safe, which means that the Path2D
class does not guarantee that modifications to the geometry of this Path2D
object do not affect any iterations of that geometry that are already in process.
/**
* {@inheritDoc}
* <p>
* The iterator for this class is not multi-threaded safe,
* which means that the {@code Path2D} class does not
* guarantee that modifications to the geometry of this
* {@code Path2D} object do not affect any iterations of
* that geometry that are already in process.
*/
public PathIterator getPathIterator(BaseTransform tx) {
if (tx == null) {
return new CopyIterator(this);
} else {
return new TxIterator(this, tx);
}
}
static class CopyIterator extends Path2D.Iterator {
float floatCoords[];
CopyIterator(Path2D p2df) {
super(p2df);
this.floatCoords = p2df.floatCoords;
}
public int currentSegment(float[] coords) {
int type = path.pointTypes[typeIdx];
int numCoords = curvecoords[type];
if (numCoords > 0) {
System.arraycopy(floatCoords, pointIdx,
coords, 0, numCoords);
}
return type;
}
public int currentSegment(double[] coords) {
int type = path.pointTypes[typeIdx];
int numCoords = curvecoords[type];
if (numCoords > 0) {
for (int i = 0; i < numCoords; i++) {
coords[i] = floatCoords[pointIdx + i];
}
}
return type;
}
}
static class TxIterator extends Path2D.Iterator {
float floatCoords[];
BaseTransform transform;
TxIterator(Path2D p2df, BaseTransform tx) {
super(p2df);
this.floatCoords = p2df.floatCoords;
this.transform = tx;
}
public int currentSegment(float[] coords) {
int type = path.pointTypes[typeIdx];
int numCoords = curvecoords[type];
if (numCoords > 0) {
transform.transform(floatCoords, pointIdx,
coords, 0, numCoords / 2);
}
return type;
}
public int currentSegment(double[] coords) {
int type = path.pointTypes[typeIdx];
int numCoords = curvecoords[type];
if (numCoords > 0) {
transform.transform(floatCoords, pointIdx,
coords, 0, numCoords / 2);
}
return type;
}
}
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. /**
* Closes the current subpath by drawing a straight line back to
* the coordinates of the last {@code moveTo}. If the path is already
* closed then this method has no effect.
*/
public final void closePath() {
if (numTypes == 0 || pointTypes[numTypes - 1] != SEG_CLOSE) {
needRoom(true, 0);
pointTypes[numTypes++] = SEG_CLOSE;
prevX = currX = moveX;
prevY = currY = moveY;
}
}
public void pathDone() {
}
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. Params: - s – the
Shape
whose geometry is appended to this path - 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
/**
* Appends the geometry of the specified {@code Shape} object to the
* path, possibly connecting the new geometry to the existing path
* segments with a line segment.
* If the {@code connect} parameter is {@code true} and the
* path is not empty then any initial {@code moveTo} in the
* geometry of the appended {@code Shape}
* is turned into a {@code lineTo} segment.
* If the destination coordinates of such a connecting {@code lineTo}
* segment match the ending coordinates of a currently open
* subpath then the segment is omitted as superfluous.
* The winding rule of the specified {@code Shape} is ignored
* and the appended geometry is governed by the winding
* rule specified for this path.
*
* @param s the {@code Shape} whose geometry is appended
* to this path
* @param connect a boolean to control whether or not to turn an initial
* {@code moveTo} segment into a {@code lineTo} segment
* to connect the new geometry to the existing path
*/
public final void append(Shape s, boolean connect) {
append(s.getPathIterator(null), connect);
}
static class SVGParser {
final String svgpath;
final int len;
int pos;
boolean allowcomma;
public SVGParser(String svgpath) {
this.svgpath = svgpath;
this.len = svgpath.length();
}
public boolean isDone() {
return (toNextNonWsp() >= len);
}
public char getChar() {
return svgpath.charAt(pos++);
}
public boolean nextIsNumber() {
if (toNextNonWsp() < len) {
switch (svgpath.charAt(pos)) {
case '-':
case '+':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '.':
return true;
}
}
return false;
}
public float f() {
return getFloat();
}
public float a() {
return (float) Math.toRadians(getFloat());
}
public float getFloat() {
int start = toNextNonWsp();
this.allowcomma = true;
int end = toNumberEnd();
if (start < end) {
String flstr = svgpath.substring(start, end);
try {
return Float.parseFloat(flstr);
} catch (NumberFormatException e) {
}
throw new IllegalArgumentException("invalid float ("+flstr+
") in path at pos="+start);
}
throw new IllegalArgumentException("end of path looking for float");
}
public boolean b() {
toNextNonWsp();
this.allowcomma = true;
if (pos < len) {
char flag = svgpath.charAt(pos);
switch (flag) {
case '0': pos++; return false;
case '1': pos++; return true;
}
throw new IllegalArgumentException("invalid boolean flag ("+flag+
") in path at pos="+pos);
}
throw new IllegalArgumentException("end of path looking for boolean");
}
private int toNextNonWsp() {
boolean canbecomma = this.allowcomma;
while (pos < len) {
switch (svgpath.charAt(pos)) {
case ',':
if (!canbecomma) {
return pos;
}
canbecomma = false;
break;
case ' ':
case '\t':
case '\r':
case '\n':
break;
default:
return pos;
}
pos++;
}
return pos;
}
private int toNumberEnd() {
boolean allowsign = true;
boolean hasexp = false;
boolean hasdecimal = false;
while (pos < len) {
switch (svgpath.charAt(pos)) {
case '-':
case '+':
if (!allowsign) return pos;
allowsign = false;
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
allowsign = false;
break;
case 'E': case 'e':
if (hasexp) return pos;
hasexp = allowsign = true;
break;
case '.':
if (hasexp || hasdecimal) return pos;
hasdecimal = true;
allowsign = false;
break;
default:
return pos;
}
pos++;
}
return pos;
}
}
Appends the geometry of the path in the specified String
argument in the format of an SVG path. The specification of the grammar of the language for an SVG path is specified on the W3C web page: http://www.w3.org/TR/SVG/paths.html#PathDataBNF
and the interpretation of the various elements in the format is
specified on the W3C web page:
http://www.w3.org/TR/SVG/paths.html#PathData
Params: - svgpath – the
String
object containing the SVG style definition of the geometry to be apppended
Throws: - IllegalArgumentException – if
svgpath
does not match the indicated SVG path grammar - IllegalPathStateException –
if there is no current point in the path
/**
* Appends the geometry of the path in the specified {@code String}
* argument in the format of an SVG path.
* The specification of the grammar of the language for an SVG path
* is specified on the W3C web page:
* <pre>
* http://www.w3.org/TR/SVG/paths.html#PathDataBNF
* </pre>
* and the interpretation of the various elements in the format is
* specified on the W3C web page:
* <pre>
* http://www.w3.org/TR/SVG/paths.html#PathData
* </pre>
*
* @param svgpath the {@code String} object containing the SVG style
* definition of the geometry to be apppended
* @throws IllegalArgumentException
* if {@code svgpath} does not match the indicated SVG path grammar
* @throws IllegalPathStateException
* if there is no current point in the path
*/
public final void appendSVGPath(String svgpath) {
SVGParser p = new SVGParser(svgpath);
p.allowcomma = false;
while (!p.isDone()) {
p.allowcomma = false;
char cmd = p.getChar();
switch (cmd) {
case 'M':
moveTo(p.f(), p.f());
while (p.nextIsNumber()) {
lineTo(p.f(), p.f());
}
break;
case 'm':
if (numTypes > 0) {
moveToRel(p.f(), p.f());
} else {
moveTo(p.f(), p.f());
}
while (p.nextIsNumber()) {
lineToRel(p.f(), p.f());
}
break;
case 'L':
do {
lineTo(p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'l':
do {
lineToRel(p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'H':
do {
lineTo(p.f(), currY);
} while (p.nextIsNumber());
break;
case 'h':
do {
lineToRel(p.f(), 0);
} while (p.nextIsNumber());
break;
case 'V':
do {
lineTo(currX, p.f());
} while (p.nextIsNumber());
break;
case 'v':
do {
lineToRel(0, p.f());
} while (p.nextIsNumber());
break;
case 'Q':
do {
quadTo(p.f(), p.f(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'q':
do {
quadToRel(p.f(), p.f(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'T':
do {
quadToSmooth(p.f(), p.f());
} while (p.nextIsNumber());
break;
case 't':
do {
quadToSmoothRel(p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'C':
do {
curveTo(p.f(), p.f(), p.f(), p.f(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'c':
do {
curveToRel(p.f(), p.f(), p.f(), p.f(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'S':
do {
curveToSmooth(p.f(), p.f(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 's':
do {
curveToSmoothRel(p.f(), p.f(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'A':
do {
arcTo(p.f(), p.f(), p.a(), p.b(), p.b(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'a':
do {
arcToRel(p.f(), p.f(), p.a(), p.b(), p.b(), p.f(), p.f());
} while (p.nextIsNumber());
break;
case 'Z': case 'z': closePath(); break;
default:
throw new IllegalArgumentException("invalid command ("+cmd+
") in SVG path at pos="+p.pos);
}
p.allowcomma = false;
}
}
Returns the fill style winding rule.
See Also: Returns: an integer representing the current winding rule.
/**
* 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 final int getWindingRule() {
return windingRule;
}
Sets the winding rule for this path to the specified value.
Params: - rule – an integer representing the specified
winding rule
Throws: - IllegalArgumentException – if
rule
is not either WIND_EVEN_ODD
or WIND_NON_ZERO
See Also:
/**
* Sets the winding rule for this path to the specified value.
*
* @param rule an integer representing the specified
* winding rule
* @exception IllegalArgumentException if
* {@code rule} is not either
* {@link #WIND_EVEN_ODD} or
* {@link #WIND_NON_ZERO}
* @see #getWindingRule
*/
public final 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 Point2D
object. Returns: a Point2D
object containing the ending coordinates of the path or null
if there are no points in the path.
/**
* Returns the coordinates most recently added to the end of the path
* as a {@link Point2D} object.
*
* @return a {@code Point2D} object containing the ending coordinates of
* the path or {@code null} if there are no points in the path.
*/
public final Point2D getCurrentPoint() {
if (numTypes < 1) {
return null;
}
return new Point2D(currX, currY);
}
public final float getCurrentX() {
if (numTypes < 1) {
throw new IllegalPathStateException("no current point in empty path");
}
return currX;
}
public final float getCurrentY() {
if (numTypes < 1) {
throw new IllegalPathStateException("no current point in empty path");
}
return currY;
}
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.
/**
* 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 final void reset() {
numTypes = numCoords = 0;
moveX = moveY = prevX = prevY = currX = currY = 0;
}
Returns a new Shape
representing a transformed version of this Path2D
. Note that the exact type and coordinate precision of the return value is not specified for this method. The method will return a Shape that contains no less precision for the transformed geometry than this Path2D
currently maintains, but it may contain no more precision either. If the tradeoff of precision vs. storage size in the result is important then the convenience constructors in the Path2D(Shape, BaseTransform) Path2D
Params: - tx – the
BaseTransform
used to transform a new Shape
.
Returns: a new Shape
, transformed with the specified BaseTransform
.
/**
* Returns a new {@code Shape} representing a transformed version
* of this {@code Path2D}.
* Note that the exact type and coordinate precision of the return
* value is not specified for this method.
* The method will return a Shape that contains no less precision
* for the transformed geometry than this {@code Path2D} currently
* maintains, but it may contain no more precision either.
* If the tradeoff of precision vs. storage size in the result is
* important then the convenience constructors in the
* {@link Path2D(Shape, BaseTransform) Path2D}
*
* @param tx the {@code BaseTransform} used to transform a
* new {@code Shape}.
* @return a new {@code Shape}, transformed with the specified
* {@code BaseTransform}.
*/
public final Shape createTransformedShape(BaseTransform tx) {
return new Path2D(this, tx);
}
@Override
public Path2D copy() {
return new Path2D(this);
}
{@inheritDoc} Note that this method may return false when the geometry of the given Path2D
is identical to the geometry of this object but is expressed in a different way. This method will only return true when the internal representation of this object is exactly the same as that of the given object. /**
* {@inheritDoc}
*
* Note that this method may return false when the geometry of the
* given {@code Path2D} is identical to the geometry of this object
* but is expressed in a different way. This method will only return
* true when the internal representation of this object is exactly the
* same as that of the given object.
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Path2D) {
Path2D p = (Path2D)obj;
if (p.numTypes == this.numTypes &&
p.numCoords == this.numCoords &&
p.windingRule == this.windingRule)
{
for (int i = 0; i < numTypes; i++) {
if (p.pointTypes[i] != this.pointTypes[i]) {
return false;
}
}
for (int i = 0; i < numCoords; i++) {
if (p.floatCoords[i] != this.floatCoords[i]) {
return false;
}
}
return true;
}
}
return false;
}
@Override
public int hashCode() {
int hash = 7;
hash = 11 * hash + numTypes;
hash = 11 * hash + numCoords;
hash = 11 * hash + windingRule;
for (int i = 0; i < numTypes; i++) {
hash = 11 * hash + pointTypes[i];
}
for (int i = 0; i < numCoords; i++) {
hash = 11 * hash + Float.floatToIntBits(floatCoords[i]);
}
return hash;
}
Tests if the specified coordinates are inside the closed boundary of the specified PathIterator
. This method provides a basic facility for implementors of the Shape
interface to implement support for the Shape.contains(float, float)
method.
Params: - pi – the specified
PathIterator
- x – the specified X coordinate
- y – the specified Y coordinate
Returns: true
if the specified coordinates are inside the specified PathIterator
; false
otherwise
/**
* Tests if the specified coordinates are inside the closed
* boundary of the specified {@link PathIterator}.
* <p>
* This method provides a basic facility for implementors of
* the {@link Shape} interface to implement support for the
* {@link Shape#contains(double, double)} method.
*
* @param pi the specified {@code PathIterator}
* @param x the specified X coordinate
* @param y the specified Y coordinate
* @return {@code true} if the specified coordinates are inside the
* specified {@code PathIterator}; {@code false} otherwise
*/
public static boolean contains(PathIterator pi, float x, float y) {
if (x * 0f + y * 0f == 0f) {
/* N * 0.0 is 0.0 only if N is finite.
* Here we know that both x and y are finite.
*/
int mask = (pi.getWindingRule() == WIND_NON_ZERO ? -1 : 1);
int cross = Shape.pointCrossingsForPath(pi, x, y);
return ((cross & mask) != 0);
} else {
/* Either x or y was infinite or NaN.
* A NaN always produces a negative response to any test
* and Infinity values cannot be "inside" any path so
* they should return false as well.
*/
return false;
}
}
Tests if the specified Point2D
is inside the closed boundary of the specified PathIterator
. This method provides a basic facility for implementors of the Shape
interface to implement support for the Shape.contains(Point2D)
method.
Params: - pi – the specified
PathIterator
- p – the specified
Point2D
Returns: true
if the specified coordinates are inside the specified PathIterator
; false
otherwise
/**
* Tests if the specified {@link Point2D} is inside the closed
* boundary of the specified {@link PathIterator}.
* <p>
* This method provides a basic facility for implementors of
* the {@link Shape} interface to implement support for the
* {@link Shape#contains(Point2D)} method.
*
* @param pi the specified {@code PathIterator}
* @param p the specified {@code Point2D}
* @return {@code true} if the specified coordinates are inside the
* specified {@code PathIterator}; {@code false} otherwise
*/
public static boolean contains(PathIterator pi, Point2D p) {
return contains(pi, p.x, p.y);
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public final boolean contains(float x, float y) {
if (x * 0f + y * 0f == 0f) {
/* N * 0.0 is 0.0 only if N is finite.
* Here we know that both x and y are finite.
*/
if (numTypes < 2) {
return false;
}
int mask = (windingRule == WIND_NON_ZERO ? -1 : 1);
return ((pointCrossings(x, y) & mask) != 0);
} else {
/* Either x or y was infinite or NaN.
* A NaN always produces a negative response to any test
* and Infinity values cannot be "inside" any path so
* they should return false as well.
*/
return false;
}
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
@Override
public final boolean contains(Point2D p) {
return contains(p.x, p.y);
}
Tests if the specified rectangular area is entirely inside the closed boundary of the specified PathIterator
. This method provides a basic facility for implementors of the Shape
interface to implement support for the Shape.contains(float, float, float, float)
method.
This method object may conservatively return false 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 segments could lie entirely within the interior of the path if they are part of a path with a WIND_NON_ZERO
winding rule or if the segments are retraced in the reverse direction such that the two sets of segments cancel each other out without any exterior area falling 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.
Params: - pi – the specified
PathIterator
- x – the specified X coordinate
- y – the specified Y coordinate
- w – the width of the specified rectangular area
- h – the height of the specified rectangular area
Returns: true
if the specified PathIterator
contains the specified rectangluar area; false
otherwise.
/**
* Tests if the specified rectangular area is entirely inside the
* closed boundary of the specified {@link PathIterator}.
* <p>
* This method provides a basic facility for implementors of
* the {@link Shape} interface to implement support for the
* {@link Shape#contains(double, double, double, double)} method.
* <p>
* This method object may conservatively return false 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 segments could lie entirely within the interior of the
* path if they are part of a path with a {@link #WIND_NON_ZERO}
* winding rule or if the segments are retraced in the reverse
* direction such that the two sets of segments cancel each
* other out without any exterior area falling 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 area
* @param h the height of the specified rectangular area
* @return {@code true} if the specified {@code PathIterator} contains
* the specified rectangluar area; {@code false} otherwise.
*/
public static boolean contains(PathIterator pi,
float x, float y, float w, float h)
{
if (java.lang.Float.isNaN(x+w) || java.lang.Float.isNaN(y+h)) {
/* [xy]+[wh] is NaN if any of those values are NaN,
* or if adding the two together would produce NaN
* by virtue of adding opposing Infinte values.
* Since we need to add them below, their sum must
* not be NaN.
* We return false because NaN always produces a
* negative response to tests
*/
return false;
}
if (w <= 0 || h <= 0) {
return false;
}
int mask = (pi.getWindingRule() == WIND_NON_ZERO ? -1 : 2);
int crossings = Shape.rectCrossingsForPath(pi, x, y, x+w, y+h);
return (crossings != Shape.RECT_INTERSECTS &&
(crossings & mask) != 0);
}
{@inheritDoc}
This method object may conservatively return false 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 segments could lie entirely within the interior of the path if they are part of a path with a WIND_NON_ZERO
winding rule or if the segments are retraced in the reverse direction such that the two sets of segments cancel each other out without any exterior area falling 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.
/**
* {@inheritDoc}
* <p>
* This method object may conservatively return false 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 segments could lie entirely within the interior of the
* path if they are part of a path with a {@link #WIND_NON_ZERO}
* winding rule or if the segments are retraced in the reverse
* direction such that the two sets of segments cancel each
* other out without any exterior area falling 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.
*/
public final boolean contains(float x, float y, float w, float h) {
if (java.lang.Float.isNaN(x+w) || java.lang.Float.isNaN(y+h)) {
/* [xy]+[wh] is NaN if any of those values are NaN,
* or if adding the two together would produce NaN
* by virtue of adding opposing Infinte values.
* Since we need to add them below, their sum must
* not be NaN.
* We return false because NaN always produces a
* negative response to tests
*/
return false;
}
if (w <= 0 || h <= 0) {
return false;
}
int mask = (windingRule == WIND_NON_ZERO ? -1 : 2);
int crossings = rectCrossings(x, y, x+w, y+h);
return (crossings != Shape.RECT_INTERSECTS &&
(crossings & mask) != 0);
}
Tests if the interior of the specified PathIterator
intersects the interior of a specified set of rectangular coordinates. This method provides a basic facility for implementors of the Shape
interface to implement support for the Shape.intersects(float, float, float, float)
method.
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.
Params: - pi – the specified
PathIterator
- x – the specified X coordinate
- y – the specified Y coordinate
- w – the width of the specified rectangular coordinates
- h – the height of the specified rectangular coordinates
Returns: true
if the specified PathIterator
and the interior of the specified set of rectangular coordinates intersect each other; false
otherwise.
/**
* Tests if the interior of the specified {@link PathIterator}
* intersects the interior of a specified set of rectangular
* coordinates.
* <p>
* This method provides a basic facility for implementors of
* the {@link Shape} interface to implement support for the
* {@link Shape#intersects(double, double, double, double)} 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(PathIterator pi,
float x, float y, float w, float h)
{
if (java.lang.Float.isNaN(x+w) || java.lang.Float.isNaN(y+h)) {
/* [xy]+[wh] is NaN if any of those values are NaN,
* or if adding the two together would produce NaN
* by virtue of adding opposing Infinte values.
* Since we need to add them below, their sum must
* not be NaN.
* We return false because NaN always produces a
* negative response to tests
*/
return false;
}
if (w <= 0 || h <= 0) {
return false;
}
int mask = (pi.getWindingRule() == WIND_NON_ZERO ? -1 : 2);
int crossings = Shape.rectCrossingsForPath(pi, x, y, x+w, y+h);
return (crossings == Shape.RECT_INTERSECTS ||
(crossings & mask) != 0);
}
{@inheritDoc}
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.
/**
* {@inheritDoc}
* <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.
*/
public final boolean intersects(float x, float y, float w, float h) {
if (java.lang.Float.isNaN(x+w) || java.lang.Float.isNaN(y+h)) {
/* [xy]+[wh] is NaN if any of those values are NaN,
* or if adding the two together would produce NaN
* by virtue of adding opposing Infinte values.
* Since we need to add them below, their sum must
* not be NaN.
* We return false because NaN always produces a
* negative response to tests
*/
return false;
}
if (w <= 0 || h <= 0) {
return false;
}
int mask = (windingRule == WIND_NON_ZERO ? -1 : 2);
int crossings = rectCrossings(x, y, x+w, y+h);
return (crossings == Shape.RECT_INTERSECTS ||
(crossings & mask) != 0);
}
{@inheritDoc}
The iterator for this class is not multi-threaded safe, which means that this Path2D
class does not guarantee that modifications to the geometry of this Path2D
object do not affect any iterations of that geometry that are already in process.
/**
* {@inheritDoc}
* <p>
* The iterator for this class is not multi-threaded safe,
* which means that this {@code Path2D} class does not
* guarantee that modifications to the geometry of this
* {@code Path2D} object do not affect any iterations of
* that geometry that are already in process.
*/
public PathIterator getPathIterator(BaseTransform tx,
float flatness)
{
return new FlatteningPathIterator(getPathIterator(tx), flatness);
}
static abstract class Iterator implements PathIterator {
int typeIdx;
int pointIdx;
Path2D path;
Iterator(Path2D path) {
this.path = path;
}
public int getWindingRule() {
return path.getWindingRule();
}
public boolean isDone() {
return (typeIdx >= path.numTypes);
}
public void next() {
int type = path.pointTypes[typeIdx++];
pointIdx += curvecoords[type];
}
}
public void setTo(Path2D otherPath) {
numTypes = otherPath.numTypes;
numCoords = otherPath.numCoords;
if (numTypes > pointTypes.length) {
pointTypes = new byte[numTypes];
}
System.arraycopy(otherPath.pointTypes, 0, pointTypes, 0, numTypes);
if (numCoords > floatCoords.length) {
floatCoords = new float[numCoords];
}
System.arraycopy(otherPath.floatCoords, 0, floatCoords, 0, numCoords);
windingRule = otherPath.windingRule;
moveX = otherPath.moveX;
moveY = otherPath.moveY;
prevX = otherPath.prevX;
prevY = otherPath.prevY;
currX = otherPath.currX;
currY = otherPath.currY;
}
}