Java/2D Graphics GUI/Path
Версия от 18:01, 31 мая 2010; (обсуждение)
Содержание
- 1 A class representing a cubic path segment
- 2 A class representing a linear path segment.
- 3 A class representing a quadratic path segment
- 4 A geometric path constructed from straight lines, quadratic and cubic (Bezier) curves and elliptical arc.
- 5 Describe a path
- 6 Fill GeneralPath
- 7 GeneralPath
- 8 GeneralPath Demo
- 9 Open GeneralPath Demo
- 10 Utilitiy class for length calculations of paths.
- 11 Yet another GeneralPath demo
A class representing a cubic path segment
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
/**
* A class representing a cubic path segment.
*
* @version $Id: Cubic.java 478249 2006-11-22 17:29:37Z dvholten $
*/
public class Cubic extends AbstractSegment {
public Point2D.Double p1, p2, p3, p4;
public Cubic() {
p1 = new Point2D.Double();
p2 = new Point2D.Double();
p3 = new Point2D.Double();
p4 = new Point2D.Double();
}
public Cubic(double x1, double y1, double x2, double y2,
double x3, double y3, double x4, double y4) {
p1 = new Point2D.Double(x1, y1);
p2 = new Point2D.Double(x2, y2);
p3 = new Point2D.Double(x3, y3);
p4 = new Point2D.Double(x4, y4);
}
public Cubic(Point2D.Double p1, Point2D.Double p2,
Point2D.Double p3, Point2D.Double p4) {
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
this.p4 = p4;
}
public Object clone() {
return new Cubic(new Point2D.Double(p1.x, p1.y),
new Point2D.Double(p2.x, p2.y),
new Point2D.Double(p3.x, p3.y),
new Point2D.Double(p4.x, p4.y));
}
public Segment reverse() {
return new Cubic(new Point2D.Double(p4.x, p4.y),
new Point2D.Double(p3.x, p3.y),
new Point2D.Double(p2.x, p2.y),
new Point2D.Double(p1.x, p1.y));
}
private void getMinMax(double p1, double p2,
double p3, double p4,
double [] minMax) {
if (p4 > p1){
minMax[0] = p1; minMax[1] = p4;
} else {
minMax[0] = p4; minMax[1] = p1;
}
double c0 = 3*(p2-p1);
double c1 = 6*(p3-p2);
double c2 = 3*(p4-p3);
double [] eqn = { c0, c1-2*c0, c2-c1+c0 };
int roots = QuadCurve2D.solveQuadratic(eqn);
for (int r=0; r<roots; r++) {
double tv = eqn[r];
if ((tv <= 0) || (tv >= 1)) continue;
tv = ((1-tv)*(1-tv)*(1-tv)*p1 +
3*tv*(1-tv)*(1-tv)*p2 +
3*tv*tv*(1-tv)*p3 +
tv*tv*tv*p4);
if (tv < minMax[0]) minMax[0] = tv;
else if (tv > minMax[1]) minMax[1] = tv;
}
}
public double minX() {
double [] minMax = {0, 0};
getMinMax(p1.x, p2.x, p3.x, p4.x, minMax);
return minMax[0];
}
public double maxX() {
double [] minMax = {0, 0};
getMinMax(p1.x, p2.x, p3.x, p4.x, minMax);
return minMax[1];
}
public double minY() {
double [] minMax = {0, 0};
getMinMax(p1.y, p2.y, p3.y, p4.y, minMax);
return minMax[0];
}
public double maxY() {
double [] minMax = {0, 0};
getMinMax(p1.y, p2.y, p3.y, p4.y, minMax);
return minMax[1];
}
public Rectangle2D getBounds2D() {
double [] minMaxX = {0, 0};
getMinMax(p1.x, p2.x, p3.x, p4.x, minMaxX);
double [] minMaxY = {0, 0};
getMinMax(p1.y, p2.y, p3.y, p4.y, minMaxY);
return new Rectangle2D.Double
(minMaxX[0], minMaxY[0],
minMaxX[1]-minMaxX[0], minMaxY[1]-minMaxY[0]);
}
protected int findRoots(double y, double [] roots) {
double [] eqn = { p1.y-y, 3*(p2.y-p1.y), 3*(p1.y-2*p2.y+p3.y),
3*p2.y-p1.y+p4.y-3*p3.y };
return CubicCurve2D.solveCubic(eqn, roots);
// return solveCubic(eqn[3], eqn[2], eqn[1], eqn[0], roots);
}
public Point2D.Double evalDt(double t) {
double x = 3*( (p2.x-p1.x)*(1-t)*(1-t) +
2*(p3.x-p2.x)*(1-t)*t +
(p4.x-p3.x)*t*t);
double y = 3*( (p2.y-p1.y)*(1-t)*(1-t) +
2*(p3.y-p2.y)*(1-t)*t +
(p4.y-p3.y)*t*t);
return new Point2D.Double(x, y);
}
public Point2D.Double eval(double t) {
double x = ((1-t)*(1-t)*(1-t)*p1.x +
3*(t* (1-t)*(1-t)*p2.x +
t* t* (1-t)*p3.x) +
t*t*t *p4.x);
double y = ((1-t)*(1-t)*(1-t)*p1.y +
3*(t* (1-t)*(1-t)*p2.y +
t* t* (1-t)*p3.y) +
t*t*t *p4.y);
return new Point2D.Double(x, y);
}
/**
* Subdivides this Cubic curve into two curves at t = 0.5.
* can be done with getSegment but this is more efficent.
* @param s0 if non-null contains portion of curve from 0->.5
* @param s1 if non-null contains portion of curve from .5->1
*/
public void subdivide(Segment s0, Segment s1) {
Cubic c0=null, c1=null;
if (s0 instanceof Cubic) c0 = (Cubic)s0;
if (s1 instanceof Cubic) c1 = (Cubic)s1;
subdivide(c0, c1);
}
/**
* Subdivides this Cubic curve into two curves at given t.
* @param s0 if non-null contains portion of curve from 0->t.
* @param s1 if non-null contains portion of curve from t->1.
*/
public void subdivide(double t, Segment s0, Segment s1) {
Cubic c0=null, c1=null;
if (s0 instanceof Cubic) c0 = (Cubic)s0;
if (s1 instanceof Cubic) c1 = (Cubic)s1;
subdivide(t, c0, c1);
}
/**
* Subdivides this Cubic curve into two curves at t = 0.5.
* can be done with getSegment but this is more efficent.
* @param c0 if non-null contains portion of curve from 0->.5
* @param c1 if non-null contains portion of curve from .5->1
*/
public void subdivide(Cubic c0, Cubic c1) {
if ((c0 == null) && (c1 == null)) return;
double npX = (p1.x+3*(p2.x+p3.x)+p4.x)*0.125;
double npY = (p1.y+3*(p2.y+p3.y)+p4.y)*0.125;
double npdx = ((p2.x-p1.x)+2*(p3.x-p2.x)+(p4.x-p3.x))*0.125;
double npdy = ((p2.y-p1.y)+2*(p3.y-p2.y)+(p4.y-p3.y))*0.125;
if (c0 != null) {
c0.p1.x = p1.x;
c0.p1.y = p1.y;
c0.p2.x = (p2.x+p1.x)*0.5;
c0.p2.y = (p2.y+p1.y)*0.5;
c0.p3.x = npX-npdx;
c0.p3.y = npY-npdy;
c0.p4.x = npX;
c0.p4.y = npY;
}
if (c1 != null) {
c1.p1.x = npX;
c1.p1.y = npY;
c1.p2.x = npX+npdx;
c1.p2.y = npY+npdy;
c1.p3.x = (p4.x+p3.x)*0.5;
c1.p3.y = (p4.y+p3.y)*0.5;
c1.p4.x = p4.x;
c1.p4.y = p4.y;
}
}
/**
* Subdivides this Cubic curve into two curves at given t.
* @param c0 if non-null contains portion of curve from 0->t.
* @param c1 if non-null contains portion of curve from t->1.
*/
public void subdivide(double t, Cubic c0, Cubic c1) {
if ((c0 == null) && (c1 == null)) return;
Point2D.Double np = eval(t);
Point2D.Double npd = evalDt(t);
if (c0 != null) {
c0.p1.x = p1.x;
c0.p1.y = p1.y;
c0.p2.x = (p2.x+p1.x)*t;
c0.p2.y = (p2.y+p1.y)*t;
c0.p3.x = np.x-(npd.x*t/3);
c0.p3.y = np.y-(npd.y*t/3);
c0.p4.x = np.x;
c0.p4.y = np.y;
}
if (c1 != null) {
c1.p1.x = np.x;
c1.p1.y = np.y;
c1.p2.x = np.x+(npd.x*(1-t)/3);
c1.p2.y = np.y+(npd.y*(1-t)/3);
c1.p3.x = (p4.x+p3.x)*(1-t);
c1.p3.y = (p4.y+p3.y)*(1-t);
c1.p4.x = p4.x;
c1.p4.y = p4.y;
}
}
public Segment getSegment(double t0, double t1) {
double dt = t1-t0;
Point2D.Double np1 = eval(t0);
Point2D.Double dp1 = evalDt(t0);
Point2D.Double np2 = new Point2D.Double(np1.x+dt*dp1.x/3,
np1.y+dt*dp1.y/3);
Point2D.Double np4 = eval(t1);
Point2D.Double dp4 = evalDt(t1);
Point2D.Double np3 = new Point2D.Double(np4.x-dt*dp4.x/3,
np4.y-dt*dp4.y/3);
return new Cubic(np1, np2, np3, np4);
}
private static int count = 0;
protected double subLength(double leftLegLen, double rightLegLen,
double maxErr) {
count++;
double cldx, cldy, cdx, cdy;
cldx = p3.x-p2.x;
cldy = p3.y-p2.y;
double crossLegLen = Math.sqrt(cldx*cldx+cldy*cldy);
cdx = p4.x-p1.x;
cdy = p4.y-p1.y;
double cordLen = Math.sqrt(cdx*cdx+cdy*cdy);
double hullLen = leftLegLen+rightLegLen+crossLegLen;
if (hullLen < maxErr) return (hullLen+cordLen)/2;
double err = (hullLen-cordLen);
if (err < maxErr)
return (hullLen+cordLen)/2;
Cubic c = new Cubic();
double npX = (p1.x+3*(p2.x+p3.x)+p4.x)*0.125;
double npY = (p1.y+3*(p2.y+p3.y)+p4.y)*0.125;
double npdx = (cldx + cdx)*.125;
double npdy = (cldy + cdy)*.125;
c.p1.x = p1.x;
c.p1.y = p1.y;
c.p2.x = (p2.x+p1.x)*.5;
c.p2.y = (p2.y+p1.y)*.5;
c.p3.x = npX-npdx;
c.p3.y = npY-npdy;
c.p4.x = npX;
c.p4.y = npY;
double midLen = Math.sqrt(npdx*npdx+npdy*npdy);
double len = c.subLength(leftLegLen/2, midLen, maxErr/2);
c.p1.x = npX;
c.p1.y = npY;
c.p2.x = npX+npdx;
c.p2.y = npY+npdy;
c.p3.x = (p4.x+p3.x)*.5;
c.p3.y = (p4.y+p3.y)*.5;
c.p4.x = p4.x;
c.p4.y = p4.y;
len += c.subLength(midLen, rightLegLen/2, maxErr/2);
return len;
}
public double getLength() {
return getLength(0.000001);
}
public double getLength(double maxErr) {
double dx, dy;
dx = p2.x-p1.x;
dy = p2.y-p1.y;
double leftLegLen = Math.sqrt(dx*dx+dy*dy);
dx = p4.x-p3.x;
dy = p4.y-p3.y;
double rightLegLen = Math.sqrt(dx*dx+dy*dy);
dx = p3.x-p2.x;
dy = p3.y-p2.y;
double crossLegLen = Math.sqrt(dx*dx+dy*dy);
double eps = maxErr*(leftLegLen+rightLegLen+crossLegLen);
return subLength(leftLegLen, rightLegLen, eps);
}
public String toString() {
return "M" + p1.x + "," + p1.y +
"C" + p2.x + "," + p2.y + " " +
p3.x + "," + p3.y + " " +
p4.x + "," + p4.y;
}
/*
public static boolean epsEq(double a, double b) {
final double eps = 0.000001;
return (((a + eps) > b) && ((a-eps) < b));
}
public static void sub(Cubic orig, Cubic curr,
double t, double inc, int lev) {
Cubic left=new Cubic();
Cubic right=new Cubic();
curr.subdivide(left, right);
Point2D.Double ptl = left.eval(.5);
Point2D.Double ptr = right.eval(.5);
Point2D.Double pt1 = orig.eval(t-inc);
Point2D.Double pt2 = orig.eval(t+inc);
int steps = 100;
Point2D.Double l, r, o;
for (int i=0; i<=steps; i++) {
l = left.eval(i/(double)steps);
o = orig.eval(t-(2*inc)*(1-i/(double)steps));
if (!epsEq(l.x, o.x) || !epsEq(l.y, o.y))
System.err.println("Lf Pt: [" + l.x + "," + l.y +
"] Orig: [" + o.x + "," + o.y +"]");
r = right.eval(i/(double)steps);
o = orig.eval(t+(2*inc*i/(double)steps));
if (!epsEq(r.x, o.x) || !epsEq(r.y, o.y))
System.err.println("Rt Pt: [" + r.x + "," + r.y +
"] Orig: [" + o.x + "," + o.y +"]");
}
if (lev != 0) {
sub(orig, left, t-inc, inc/2, lev-1);
sub(orig, right, t+inc, inc/2, lev-1);
}
}
public static void evalCubic(Cubic c) {
int steps = 1000000;
Point2D.Double oldP = c.eval(0);
Point2D.Double newP;
double len = 0;
for (int i=1; i<=steps; i++) {
newP = c.eval(i/(double)steps);
double dx = newP.x-oldP.x;
double dy = newP.y-oldP.y;
len += Math.sqrt(dx*dx + dy*dy);
oldP = newP;
}
System.err.println("Length(.1): " + c.getLength(.001) +
" x " + count); count = 0;
System.err.println("Length : " + c.getLength() +
" x " + count); count = 0;
System.err.println("D Len : " + len);
}
public static void main(String args[]) {
Cubic c;
c = new Cubic(0,0, 10,10, 20,-10, 30,0);
sub(c, c, .5, .25, 3);
evalCubic(c);
c = new Cubic(0,0, 1,0, 2,-1, 3,0);
sub(c, c, .5, .25, 3);
evalCubic(c);
}
*/
}
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* An interface that path segments must implement.
*
* @version $Id: Segment.java 478249 2006-11-22 17:29:37Z dvholten $
*/
interface Segment extends Cloneable {
double minX();
double maxX();
double minY();
double maxY();
Rectangle2D getBounds2D();
Point2D.Double evalDt(double t);
Point2D.Double eval(double t);
Segment getSegment(double t0, double t1);
Segment splitBefore(double t);
Segment splitAfter(double t);
void subdivide(Segment s0, Segment s1);
void subdivide(double t, Segment s0, Segment s1);
double getLength();
double getLength(double maxErr);
SplitResults split(double y);
class SplitResults {
Segment [] above;
Segment [] below;
SplitResults(Segment []below, Segment []above) {
this.below = below;
this.above = above;
}
Segment [] getBelow() {
return below;
}
Segment [] getAbove() {
return above;
}
}
}
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* An abstract class for path segments.
*
* @version $Id: AbstractSegment.java 478249 2006-11-22 17:29:37Z dvholten $
*/
abstract class AbstractSegment implements Segment {
protected abstract int findRoots(double y, double [] roots);
public Segment.SplitResults split(double y) {
double [] roots = { 0, 0, 0 };
int numSol = findRoots(y, roots);
if (numSol == 0) return null; // No split
Arrays.sort(roots, 0, numSol);
double [] segs = new double[numSol+2];
int numSegments=0;
segs[numSegments++] = 0;
for (int i=0; i<numSol; i++) {
double r = roots[i];
if (r <= 0.0) continue;
if (r >= 1.0) break;
if (segs[numSegments-1] != r)
segs[numSegments++] = r;
}
segs[numSegments++] = 1.0;
if (numSegments == 2) return null;
// System.err.println("Y: " + y + "#Seg: " + numSegments +
// " Seg: " + this);
Segment [] parts = new Segment[numSegments];
double pT = 0.0;
int pIdx = 0;
boolean firstAbove=false, prevAbove=false;
for (int i=1; i<numSegments; i++) {
// System.err.println("Segs: " + segs[i-1]+", "+segs[i]);
parts[pIdx] = getSegment(segs[i-1], segs[i]);
Point2D.Double pt = parts[pIdx].eval(0.5);
// System.err.println("Pt: " + pt);
if (pIdx == 0) {
pIdx++;
firstAbove = prevAbove = (pt.y < y);
continue;
}
boolean above = (pt.y < y);
if (prevAbove == above) {
// Merge segments
parts[pIdx-1] = getSegment(pT, segs[i]);
} else {
pIdx++;
pT=segs[i-1];
prevAbove = above;
}
}
if (pIdx == 1) return null;
Segment [] below, above;
if (firstAbove) {
above = new Segment[(pIdx+1)/2];
below = new Segment[pIdx/2];
} else {
above = new Segment[pIdx/2];
below = new Segment[(pIdx+1)/2];
}
int ai=0, bi=0;
for (int i=0; i<pIdx; i++) {
if (firstAbove) above[ai++] = parts[i];
else below[bi++] = parts[i];
firstAbove = !firstAbove;
}
return new SplitResults(below, above);
}
public Segment splitBefore(double t) {
return getSegment(0.0, t);
}
public Segment splitAfter(double t) {
return getSegment(t, 1.0);
}
// Doubles have 48bit precision
static final double eps = 1/(double)(1L<<48);
static final double tol = 4.0*eps;
public static int solveLine(double a, double b,
double [] roots) {
if (a == 0) {
if (b != 0)
// No intersection.
return 0;
// All pts intersect just return 0.
roots[0] = 0;
return 1;
}
roots[0] = -b/a;
return 1;
}
public static int solveQuad(double a, double b, double c,
double [] roots) {
// System.err.println("Quad: " + a +"t^2 + " + b +"t + " + c);
if (a == 0) {
// no square term.
return solveLine(b, c, roots);
}
double det = b*b-4*a*c;
// System.err.println("Det: " + det);
if (Math.abs(det) <= tol*b*b) {
// one real root (det doesn"t contain any useful info)
roots[0] = -b/(2*a);
return 1;
}
if (det < 0)
return 0; // No real roots
// Two real roots
det = Math.sqrt(det);
double w = -(b + matchSign(det, b));
roots[0] = (2*c)/w;
roots[1] = w/(2*a);
return 2;
}
public static double matchSign(double a, double b) {
if (b < 0) return (a < 0)?a:-a;
return (a > 0)?a:-a;
}
public static int solveCubic(double a3, double a2,
double a1, double a0,
double [] roots) {
// System.err.println("Cubic: " + a3 + "t^3 + " +
// a2 +"t^2 + " +
// a1 +"t + " + a0);
double [] dRoots = { 0, 0};
int dCnt = solveQuad(3*a3, 2*a2, a1, dRoots);
double [] yVals = {0, 0, 0, 0};
double [] tVals = {0, 0, 0, 0};
int yCnt=0;
yVals[yCnt] = a0;
tVals[yCnt++] = 0;
double r;
switch (dCnt) {
case 1:
r = dRoots[0];
if ((r > 0) && (r < 1)) {
yVals[yCnt] = ((a3*r+a2)*r+a1)*r+a0;
tVals[yCnt++] = r;
}
break;
case 2:
if (dRoots[0] > dRoots[1]) {
double t = dRoots[0];
dRoots[0] = dRoots[1];
dRoots[1] = t;
}
r = dRoots[0];
if ((r > 0) && (r < 1)) {
yVals[yCnt] = ((a3*r+a2)*r+a1)*r+a0;
tVals[yCnt++] = r;
}
r = dRoots[1];
if ((r > 0) && (r < 1)) {
yVals[yCnt] = ((a3*r+a2)*r+a1)*r+a0;
tVals[yCnt++] = r;
}
break;
default: break;
}
yVals[yCnt] = a3+a2+a1+a0;
tVals[yCnt++] = 1.0;
int ret=0;
for (int i=0; i<yCnt-1; i++) {
double y0 = yVals[i], t0 = tVals[i];
double y1 = yVals[i+1], t1 = tVals[i+1];
if ((y0 < 0) && (y1 < 0)) continue;
if ((y0 > 0) && (y1 > 0)) continue;
if (y0 > y1) { // swap so y0 < 0 and y1 > 0
double t;
t = y0; y0=y1; y1=t;
t = t0; t0=t1; t1=t;
}
if (-y0 < tol*y1) { roots[ret++] = t0; continue; }
if (y1 < -tol*y0) { roots[ret++] = t1; i++; continue; }
double epsZero = tol*(y1-y0);
int cnt;
for (cnt=0; cnt<20; cnt++) {
double dt = t1-t0;
double dy = y1-y0;
// double t = (t0+t1)/2;
// double t= t0+Math.abs(y0/dy)*dt;
// This tends to make sure that we come up
// a little short each time this generaly allows
// you to eliminate as much of the range as possible
// without overshooting (in which case you may eliminate
// almost nothing).
double t= t0+(Math.abs(y0/dy)*99+.5)*dt/100;
double v = ((a3*t+a2)*t+a1)*t+a0;
if (Math.abs(v) < epsZero) {
roots[ret++] = t; break;
}
if (v < 0) { t0 = t; y0=v;}
else { t1 = t; y1=v;}
}
if (cnt == 20)
roots[ret++] = (t0+t1)/2;
}
return ret;
}
/*
public static void check(Segment seg, float y, PrintStream ps) {
ps.println("<path fill=\"none\" stroke=\"black\" " +
" stroke-width=\"3\" d=\"" + seg + "\"/>");
ps.println("<line x1=\"-1000\" y1=\""+y+
"\" x2=\"1000\" y2=\""+y+"\" fill=\"none\" stroke=\"orange\"/>\n");
SplitResults sr = seg.split(y);
if (sr == null) return;
Segment [] above = sr.getAbove();
Segment [] below = sr.getBelow();
for (int i=0; i<above.length; i++) {
ps.println("<path fill=\"none\" stroke=\"blue\" " +
" stroke-width=\"2.5\" " +
" d=\"" + above[i] + "\"/>");
}
for (int i=0; i<below.length; i++) {
ps.println("<path fill=\"none\" stroke=\"red\" " +
" stroke-width=\"2\" " +
"d=\"" + below[i] + "\"/>");
}
}
public static void main(String [] args) {
PrintStream ps;
double [] roots = { 0, 0, 0 };
int n = solveCubic (-0.10000991821289062, 9.600013732910156,
-35.70000457763672, 58.0, roots);
for (int i=0; i<n; i++)
System.err.println("Root: " + roots[i]);
Cubic c;
c = new Cubic(new Point2D.Double(153.6999969482422,5.099999904632568),
new Point2D.Double(156.6999969482422,4.099999904632568),
new Point2D.Double(160.39999389648438,2.3999998569488525),
new Point2D.Double(164.6999969482422,0.0));
c.split(0);
c = new Cubic(new Point2D.Double(24.899999618530273,23.10000228881836),
new Point2D.Double(41.5,8.399999618530273),
new Point2D.Double(64.69999694824219,1.0),
new Point2D.Double(94.5999984741211,1.0));
c.split(0);
try {
ps = new PrintStream(new FileOutputStream(args[0]));
} catch(java.io.IOException ioe) {
ioe.printStackTrace();
return;
}
ps.println("<?xml version=\"1.0\" standalone=\"no\"?>\n" +
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n" +
"\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n" +
"<svg width=\"450\" height=\"500\"\n" +
" viewBox=\"-100 -100 450 500\"\n" +
" xmlns=\"http://www.w3.org/2000/svg\"\n" +
" xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
check(new Cubic(new Point2D.Double(0, 0),
new Point2D.Double(100, 100),
new Point2D.Double(-50, 100),
new Point2D.Double(50, 0)), 40, ps);
check(new Cubic(new Point2D.Double(100, 0),
new Point2D.Double(200, 100),
new Point2D.Double(50, -50),
new Point2D.Double(150, 30)), 20, ps);
check(new Cubic(new Point2D.Double(200, 0),
new Point2D.Double(300, 100),
new Point2D.Double(150, 100),
new Point2D.Double(250, 0)), 75, ps);
check(new Quadradic(new Point2D.Double(0, 100),
new Point2D.Double(50,150),
new Point2D.Double(10,100)), 115, ps);
check(new Linear(new Point2D.Double(100, 100),
new Point2D.Double(150,150)), 115, ps);
ps.println("</svg>");
}
*/
}
A class representing a linear path segment.
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
/**
* A class representing a linear path segment.
*
* @version $Id: Linear.java 478249 2006-11-22 17:29:37Z dvholten $
*/
public class Linear implements Segment {
public Point2D.Double p1, p2;
public Linear() {
p1 = new Point2D.Double();
p2 = new Point2D.Double();
}
public Linear(double x1, double y1,
double x2, double y2) {
p1 = new Point2D.Double(x1, y1);
p2 = new Point2D.Double(x2, y2);
}
public Linear(Point2D.Double p1, Point2D.Double p2) {
this.p1 = p1;
this.p2 = p2;
}
public Object clone() {
return new Linear(new Point2D.Double(p1.x, p1.y),
new Point2D.Double(p2.x, p2.y));
}
public Segment reverse() {
return new Linear(new Point2D.Double(p2.x, p2.y),
new Point2D.Double(p1.x, p1.y));
}
public double minX() {
if (p1.x < p2.x) return p1.x;
return p2.x;
}
public double maxX() {
if (p1.x > p2.x) return p1.x;
return p2.x;
}
public double minY() {
if (p1.y < p2.y) return p1.y;
return p2.y;
}
public double maxY() {
if (p1.y > p2.y) return p2.y;
return p1.y;
}
public Rectangle2D getBounds2D() {
double x, y, w, h;
if (p1.x < p2.x) {
x = p1.x; w = p2.x-p1.x;
} else {
x = p2.x; w = p1.x-p2.x;
}
if (p1.y < p2.y) {
y = p1.y; h = p2.y-p1.y;
} else {
y = p2.y; h = p1.y-p2.y;
}
return new Rectangle2D.Double(x, y, w, h);
}
public Point2D.Double evalDt(double t) {
double x = (p2.x-p1.x);
double y = (p2.y-p1.y);
return new Point2D.Double(x, y);
}
public Point2D.Double eval(double t) {
double x = p1.x + t*(p2.x-p1.x);
double y = p1.y + t*(p2.y-p1.y);
return new Point2D.Double(x, y);
}
public Segment.SplitResults split(double y) {
if ((y == p1.y) || (y == p2.y)) return null;
if ((y <= p1.y) && (y <= p2.y)) return null;
if ((y >= p1.y) && (y >= p2.y)) return null;
// This should be checked for numerical stability. So you
// need to ensure that p2.y-p1.y retains enough bits to be
// useful.
double t = (y-p1.y)/(p2.y-p1.y);
Segment [] t0 = {getSegment(0,t)};
Segment [] t1 = {getSegment(t,1)};
if (p2.y < y)
return new Segment.SplitResults(t0, t1);
return new Segment.SplitResults(t1, t0);
}
public Segment getSegment(double t0, double t1) {
Point2D.Double np1 = eval(t0);
Point2D.Double np2 = eval(t1);
return new Linear(np1, np2);
}
public Segment splitBefore(double t) {
return new Linear(p1, eval(t));
}
public Segment splitAfter(double t) {
return new Linear(eval(t), p2);
}
/**
* Subdivides this Linear segment into two segments at t = 0.5.
* can be done with getSegment but this is more efficent.
* @param s0 if non-null contains portion of curve from 0->.5
* @param s1 if non-null contains portion of curve from .5->1
*/
public void subdivide(Segment s0, Segment s1) {
Linear l0=null, l1=null;
if (s0 instanceof Linear) l0 = (Linear)s0;
if (s1 instanceof Linear) l1 = (Linear)s1;
subdivide(l0, l1);
}
/**
* Subdivides this Linear segment into two segments at given t.
* @param s0 if non-null contains portion of curve from 0->t.
* @param s1 if non-null contains portion of curve from t->1.
*/
public void subdivide(double t, Segment s0, Segment s1) {
Linear l0=null, l1=null;
if (s0 instanceof Linear) l0 = (Linear)s0;
if (s1 instanceof Linear) l1 = (Linear)s1;
subdivide(t, l0, l1);
}
/**
* Subdivides this Cubic curve into two curves at t = 0.5.
* Can be done with getSegment but this is more efficent.
* @param l0 if non-null contains portion of curve from 0->.5
* @param l1 if non-null contains portion of curve from .5->1
*/
public void subdivide(Linear l0, Linear l1) {
if ((l0 == null) && (l1 == null)) return;
double x = (p1.x+p2.x)*.5;
double y = (p1.y+p2.y)*.5;
if (l0 != null) {
l0.p1.x = p1.x;
l0.p1.y = p1.y;
l0.p2.x = x;
l0.p2.y = y;
}
if (l1 != null) {
l1.p1.x = x;
l1.p1.y = y;
l1.p2.x = p2.x;
l1.p2.y = p2.y;
}
}
/**
* Subdivides this Cubic curve into two curves.
* Can be done with getSegment but this is more efficent.
* @param t position to split the curve
* @param l0 if non-null contains portion of curve from 0->t
* @param l1 if non-null contains portion of curve from t->1
*/
public void subdivide(double t, Linear l0, Linear l1) {
if ((l0 == null) && (l1 == null)) return;
double x = p1.x+t*(p2.x-p1.x);
double y = p1.y+t*(p2.y-p1.y);
if (l0 != null) {
l0.p1.x = p1.x;
l0.p1.y = p1.y;
l0.p2.x = x;
l0.p2.y = y;
}
if (l1 != null) {
l1.p1.x = x;
l1.p1.y = y;
l1.p2.x = p2.x;
l1.p2.y = p2.y;
}
}
public double getLength() {
double dx = p2.x-p1.x;
double dy = p2.y-p1.y;
return Math.sqrt(dx*dx+dy*dy);
}
public double getLength(double maxErr) {
return getLength();
}
public String toString() {
return "M" + p1.x + "," + p1.y +
"L" + p2.x + "," + p2.y;
}
/*
public static boolean epsEq(double a, double b) {
final double eps = 0.000001;
return (((a + eps) > b) && ((a-eps) < b));
}
public static void sub(Linear orig, Linear curr,
double t, double inc, int lev) {
Linear left=new Linear();
Linear right=new Linear();
curr.subdivide(left, right);
Point2D.Double ptl = left.eval(.5);
Point2D.Double ptr = right.eval(.5);
Point2D.Double pt1 = orig.eval(t-inc);
Point2D.Double pt2 = orig.eval(t+inc);
int steps = 100;
Point2D.Double l, r, o;
for (int i=0; i<=steps; i++) {
l = left.eval(i/(double)steps);
o = orig.eval(t-(2*inc)*(1-i/(double)steps));
if (!epsEq(l.x, o.x) || !epsEq(l.y, o.y))
System.err.println("Lf Pt: [" + l.x + "," + l.y +
"] Orig: [" + o.x + "," + o.y +"]");
r = right.eval(i/(double)steps);
o = orig.eval(t+(2*inc*i/(double)steps));
if (!epsEq(r.x, o.x) || !epsEq(r.y, o.y))
System.err.println("Rt Pt: [" + r.x + "," + r.y +
"] Orig: [" + o.x + "," + o.y +"]");
}
if (lev != 0) {
sub(orig, left, t-inc, inc/2, lev-1);
sub(orig, right, t+inc, inc/2, lev-1);
}
}
public static void eval(Linear l) {
System.err.println("Length : " + l.getLength());
}
public static void main(String args[]) {
Linear l;
l = new Linear(0,0, 30,0);
sub(l, l, .5, .25, 3);
eval(l);
l = new Linear(0,0, 0,30);
sub(l, l, .5, .25, 3);
eval(l);
l = new Linear(0,0, 20,30);
sub(l, l, .5, .25, 3);
eval(l);
}
*/
}
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* An interface that path segments must implement.
*
* @version $Id: Segment.java 478249 2006-11-22 17:29:37Z dvholten $
*/
interface Segment extends Cloneable {
double minX();
double maxX();
double minY();
double maxY();
Rectangle2D getBounds2D();
Point2D.Double evalDt(double t);
Point2D.Double eval(double t);
Segment getSegment(double t0, double t1);
Segment splitBefore(double t);
Segment splitAfter(double t);
void subdivide(Segment s0, Segment s1);
void subdivide(double t, Segment s0, Segment s1);
double getLength();
double getLength(double maxErr);
SplitResults split(double y);
class SplitResults {
Segment [] above;
Segment [] below;
SplitResults(Segment []below, Segment []above) {
this.below = below;
this.above = above;
}
Segment [] getBelow() {
return below;
}
Segment [] getAbove() {
return above;
}
}
}
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* An abstract class for path segments.
*
* @version $Id: AbstractSegment.java 478249 2006-11-22 17:29:37Z dvholten $
*/
abstract class AbstractSegment implements Segment {
protected abstract int findRoots(double y, double [] roots);
public Segment.SplitResults split(double y) {
double [] roots = { 0, 0, 0 };
int numSol = findRoots(y, roots);
if (numSol == 0) return null; // No split
Arrays.sort(roots, 0, numSol);
double [] segs = new double[numSol+2];
int numSegments=0;
segs[numSegments++] = 0;
for (int i=0; i<numSol; i++) {
double r = roots[i];
if (r <= 0.0) continue;
if (r >= 1.0) break;
if (segs[numSegments-1] != r)
segs[numSegments++] = r;
}
segs[numSegments++] = 1.0;
if (numSegments == 2) return null;
// System.err.println("Y: " + y + "#Seg: " + numSegments +
// " Seg: " + this);
Segment [] parts = new Segment[numSegments];
double pT = 0.0;
int pIdx = 0;
boolean firstAbove=false, prevAbove=false;
for (int i=1; i<numSegments; i++) {
// System.err.println("Segs: " + segs[i-1]+", "+segs[i]);
parts[pIdx] = getSegment(segs[i-1], segs[i]);
Point2D.Double pt = parts[pIdx].eval(0.5);
// System.err.println("Pt: " + pt);
if (pIdx == 0) {
pIdx++;
firstAbove = prevAbove = (pt.y < y);
continue;
}
boolean above = (pt.y < y);
if (prevAbove == above) {
// Merge segments
parts[pIdx-1] = getSegment(pT, segs[i]);
} else {
pIdx++;
pT=segs[i-1];
prevAbove = above;
}
}
if (pIdx == 1) return null;
Segment [] below, above;
if (firstAbove) {
above = new Segment[(pIdx+1)/2];
below = new Segment[pIdx/2];
} else {
above = new Segment[pIdx/2];
below = new Segment[(pIdx+1)/2];
}
int ai=0, bi=0;
for (int i=0; i<pIdx; i++) {
if (firstAbove) above[ai++] = parts[i];
else below[bi++] = parts[i];
firstAbove = !firstAbove;
}
return new SplitResults(below, above);
}
public Segment splitBefore(double t) {
return getSegment(0.0, t);
}
public Segment splitAfter(double t) {
return getSegment(t, 1.0);
}
// Doubles have 48bit precision
static final double eps = 1/(double)(1L<<48);
static final double tol = 4.0*eps;
public static int solveLine(double a, double b,
double [] roots) {
if (a == 0) {
if (b != 0)
// No intersection.
return 0;
// All pts intersect just return 0.
roots[0] = 0;
return 1;
}
roots[0] = -b/a;
return 1;
}
public static int solveQuad(double a, double b, double c,
double [] roots) {
// System.err.println("Quad: " + a +"t^2 + " + b +"t + " + c);
if (a == 0) {
// no square term.
return solveLine(b, c, roots);
}
double det = b*b-4*a*c;
// System.err.println("Det: " + det);
if (Math.abs(det) <= tol*b*b) {
// one real root (det doesn"t contain any useful info)
roots[0] = -b/(2*a);
return 1;
}
if (det < 0)
return 0; // No real roots
// Two real roots
det = Math.sqrt(det);
double w = -(b + matchSign(det, b));
roots[0] = (2*c)/w;
roots[1] = w/(2*a);
return 2;
}
public static double matchSign(double a, double b) {
if (b < 0) return (a < 0)?a:-a;
return (a > 0)?a:-a;
}
public static int solveCubic(double a3, double a2,
double a1, double a0,
double [] roots) {
// System.err.println("Cubic: " + a3 + "t^3 + " +
// a2 +"t^2 + " +
// a1 +"t + " + a0);
double [] dRoots = { 0, 0};
int dCnt = solveQuad(3*a3, 2*a2, a1, dRoots);
double [] yVals = {0, 0, 0, 0};
double [] tVals = {0, 0, 0, 0};
int yCnt=0;
yVals[yCnt] = a0;
tVals[yCnt++] = 0;
double r;
switch (dCnt) {
case 1:
r = dRoots[0];
if ((r > 0) && (r < 1)) {
yVals[yCnt] = ((a3*r+a2)*r+a1)*r+a0;
tVals[yCnt++] = r;
}
break;
case 2:
if (dRoots[0] > dRoots[1]) {
double t = dRoots[0];
dRoots[0] = dRoots[1];
dRoots[1] = t;
}
r = dRoots[0];
if ((r > 0) && (r < 1)) {
yVals[yCnt] = ((a3*r+a2)*r+a1)*r+a0;
tVals[yCnt++] = r;
}
r = dRoots[1];
if ((r > 0) && (r < 1)) {
yVals[yCnt] = ((a3*r+a2)*r+a1)*r+a0;
tVals[yCnt++] = r;
}
break;
default: break;
}
yVals[yCnt] = a3+a2+a1+a0;
tVals[yCnt++] = 1.0;
int ret=0;
for (int i=0; i<yCnt-1; i++) {
double y0 = yVals[i], t0 = tVals[i];
double y1 = yVals[i+1], t1 = tVals[i+1];
if ((y0 < 0) && (y1 < 0)) continue;
if ((y0 > 0) && (y1 > 0)) continue;
if (y0 > y1) { // swap so y0 < 0 and y1 > 0
double t;
t = y0; y0=y1; y1=t;
t = t0; t0=t1; t1=t;
}
if (-y0 < tol*y1) { roots[ret++] = t0; continue; }
if (y1 < -tol*y0) { roots[ret++] = t1; i++; continue; }
double epsZero = tol*(y1-y0);
int cnt;
for (cnt=0; cnt<20; cnt++) {
double dt = t1-t0;
double dy = y1-y0;
// double t = (t0+t1)/2;
// double t= t0+Math.abs(y0/dy)*dt;
// This tends to make sure that we come up
// a little short each time this generaly allows
// you to eliminate as much of the range as possible
// without overshooting (in which case you may eliminate
// almost nothing).
double t= t0+(Math.abs(y0/dy)*99+.5)*dt/100;
double v = ((a3*t+a2)*t+a1)*t+a0;
if (Math.abs(v) < epsZero) {
roots[ret++] = t; break;
}
if (v < 0) { t0 = t; y0=v;}
else { t1 = t; y1=v;}
}
if (cnt == 20)
roots[ret++] = (t0+t1)/2;
}
return ret;
}
/*
public static void check(Segment seg, float y, PrintStream ps) {
ps.println("<path fill=\"none\" stroke=\"black\" " +
" stroke-width=\"3\" d=\"" + seg + "\"/>");
ps.println("<line x1=\"-1000\" y1=\""+y+
"\" x2=\"1000\" y2=\""+y+"\" fill=\"none\" stroke=\"orange\"/>\n");
SplitResults sr = seg.split(y);
if (sr == null) return;
Segment [] above = sr.getAbove();
Segment [] below = sr.getBelow();
for (int i=0; i<above.length; i++) {
ps.println("<path fill=\"none\" stroke=\"blue\" " +
" stroke-width=\"2.5\" " +
" d=\"" + above[i] + "\"/>");
}
for (int i=0; i<below.length; i++) {
ps.println("<path fill=\"none\" stroke=\"red\" " +
" stroke-width=\"2\" " +
"d=\"" + below[i] + "\"/>");
}
}
public static void main(String [] args) {
PrintStream ps;
double [] roots = { 0, 0, 0 };
int n = solveCubic (-0.10000991821289062, 9.600013732910156,
-35.70000457763672, 58.0, roots);
for (int i=0; i<n; i++)
System.err.println("Root: " + roots[i]);
Cubic c;
c = new Cubic(new Point2D.Double(153.6999969482422,5.099999904632568),
new Point2D.Double(156.6999969482422,4.099999904632568),
new Point2D.Double(160.39999389648438,2.3999998569488525),
new Point2D.Double(164.6999969482422,0.0));
c.split(0);
c = new Cubic(new Point2D.Double(24.899999618530273,23.10000228881836),
new Point2D.Double(41.5,8.399999618530273),
new Point2D.Double(64.69999694824219,1.0),
new Point2D.Double(94.5999984741211,1.0));
c.split(0);
try {
ps = new PrintStream(new FileOutputStream(args[0]));
} catch(java.io.IOException ioe) {
ioe.printStackTrace();
return;
}
ps.println("<?xml version=\"1.0\" standalone=\"no\"?>\n" +
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n" +
"\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n" +
"<svg width=\"450\" height=\"500\"\n" +
" viewBox=\"-100 -100 450 500\"\n" +
" xmlns=\"http://www.w3.org/2000/svg\"\n" +
" xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
check(new Cubic(new Point2D.Double(0, 0),
new Point2D.Double(100, 100),
new Point2D.Double(-50, 100),
new Point2D.Double(50, 0)), 40, ps);
check(new Cubic(new Point2D.Double(100, 0),
new Point2D.Double(200, 100),
new Point2D.Double(50, -50),
new Point2D.Double(150, 30)), 20, ps);
check(new Cubic(new Point2D.Double(200, 0),
new Point2D.Double(300, 100),
new Point2D.Double(150, 100),
new Point2D.Double(250, 0)), 75, ps);
check(new Quadradic(new Point2D.Double(0, 100),
new Point2D.Double(50,150),
new Point2D.Double(10,100)), 115, ps);
check(new Linear(new Point2D.Double(100, 100),
new Point2D.Double(150,150)), 115, ps);
ps.println("</svg>");
}
*/
}
A class representing a quadratic path segment
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
/**
* A class representing a quadratic path segment.
*
* @version $Id: Quadradic.java 478249 2006-11-22 17:29:37Z dvholten $
*/
public class Quadradic extends AbstractSegment {
public Point2D.Double p1, p2, p3;
public Quadradic() {
p1 = new Point2D.Double();
p2 = new Point2D.Double();
p3 = new Point2D.Double();
}
public Quadradic(double x1, double y1,
double x2, double y2,
double x3, double y3) {
p1 = new Point2D.Double(x1, y1);
p2 = new Point2D.Double(x2, y2);
p3 = new Point2D.Double(x3, y3);
}
public Quadradic(Point2D.Double p1,
Point2D.Double p2,
Point2D.Double p3) {
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
}
public Object clone() {
return new Quadradic(new Point2D.Double(p1.x, p1.y),
new Point2D.Double(p2.x, p2.y),
new Point2D.Double(p3.x, p3.y));
}
public Segment reverse() {
return new Quadradic(new Point2D.Double(p3.x, p3.y),
new Point2D.Double(p2.x, p2.y),
new Point2D.Double(p1.x, p1.y));
}
private void getMinMax(double p1, double p2,
double p3, double [] minMax) {
if (p3 > p1){
minMax[0] = p1; minMax[1] = p3;
} else {
minMax[0] = p3; minMax[1] = p1;
}
double a = (p1-2*p2+p3);
double b = (p2-p1);
if (a == 0) return;
double tv = b/a;
if ((tv <= 0) || (tv >= 1)) return;
tv = ((p1-2*p2+p3)*tv+2*(p2-p1))*tv + p1;
if (tv < minMax[0]) minMax[0] = tv;
else if (tv > minMax[1]) minMax[1] = tv;
}
public double minX() {
double [] minMax = {0, 0};
getMinMax(p1.x, p2.x, p3.x, minMax);
return minMax[0];
}
public double maxX() {
double [] minMax = {0, 0};
getMinMax(p1.x, p2.x, p3.x, minMax);
return minMax[1];
}
public double minY() {
double [] minMax = {0, 0};
getMinMax(p1.y, p2.y, p3.y, minMax);
return minMax[0];
}
public double maxY() {
double [] minMax = {0, 0};
getMinMax(p1.y, p2.y, p3.y, minMax);
return minMax[1];
}
public Rectangle2D getBounds2D() {
double [] minMaxX = {0, 0};
getMinMax(p1.x, p2.x, p3.x, minMaxX);
double [] minMaxY = {0, 0};
getMinMax(p1.y, p2.y, p3.y, minMaxY);
return new Rectangle2D.Double
(minMaxX[0], minMaxY[0],
minMaxX[1]-minMaxX[0], minMaxY[1]-minMaxY[0]);
}
protected int findRoots(double y, double [] roots) {
double [] eqn = { p1.y-y, 2*(p2.y-p1.y), p1.y-2*p2.y+p3.y };
return QuadCurve2D.solveQuadratic(eqn, roots);
// return solveQuad(eqn[2], eqn[1], eqn[0], roots);
}
public Point2D.Double evalDt(double t) {
double x = 2*(p1.x-2*p2.x+p3.x)*t + 2*(p2.x-p1.x);
double y = 2*(p1.y-2*p2.y+p3.y)*t + 2*(p2.y-p1.y);
return new Point2D.Double(x, y);
}
public Point2D.Double eval(double t) {
double x = ((p1.x-2*p2.x+p3.x)*t+2*(p2.x-p1.x))*t + p1.x;
double y = ((p1.y-2*p2.y+p3.y)*t+2*(p2.y-p1.y))*t + p1.y;
return new Point2D.Double(x, y);
}
public Segment getSegment(double t0, double t1) {
double dt = t1-t0;
Point2D.Double np1 = eval(t0);
Point2D.Double dp1 = evalDt(t0);
Point2D.Double np2 = new Point2D.Double
(np1.x+.5*dt*dp1.x, np1.y+.5*dt*dp1.y);
Point2D.Double np3 = eval(t1);
return new Quadradic(np1, np2, np3);
}
/**
* Subdivides this Quadradic curve into two curves at t = 0.5.
* can be done with getSegment but this is more efficent.
* @param q0 if non-null contains portion of curve from 0->.5
* @param q1 if non-null contains portion of curve from .5->1
*/
public void subdivide(Quadradic q0, Quadradic q1) {
if ((q0 == null) && (q1 == null)) return;
double x = (p1.x-2*p2.x+p3.x)*.25+(p2.x-p1.x) + p1.x;
double y = (p1.y-2*p2.y+p3.y)*.25+(p2.y-p1.y) + p1.y;
double dx = (p1.x-2*p2.x+p3.x)*.25 + (p2.x-p1.x)*.5;
double dy = (p1.y-2*p2.y+p3.y)*.25 + (p2.y-p1.y)*.5;
if (q0 != null) {
q0.p1.x = p1.x;
q0.p1.y = p1.y;
q0.p2.x = x-dx;
q0.p2.y = y-dy;
q0.p3.x = x;
q0.p3.y = y;
}
if (q1 != null) {
q1.p1.x = x;
q1.p1.y = y;
q1.p2.x = x+dx;
q1.p2.y = y+dy;
q1.p3.x = p3.x;
q1.p3.y = p3.y;
}
}
/**
* Subdivides this Quadradic curve into two curves at given t.
* @param q0 if non-null contains portion of curve from 0->t.
* @param q1 if non-null contains portion of curve from t->1.
*/
public void subdivide(double t, Quadradic q0, Quadradic q1) {
Point2D.Double np = eval(t);
Point2D.Double npd = evalDt(t);
if (q0 != null) {
q0.p1.x = p1.x;
q0.p1.y = p1.y;
q0.p2.x = np.x-(npd.x*t*.5);
q0.p2.y = np.y-(npd.y*t*.5);
q0.p3.x = np.x;
q0.p3.y = np.y;
}
if (q1 != null) {
q1.p1.x = np.x;
q1.p1.y = np.y;
q1.p2.x = np.x+(npd.x*(1-t)*.5);
q1.p2.y = np.y+(npd.y*(1-t)*.5);
q1.p3.x = p3.x;
q1.p3.y = p3.y;
}
}
/**
* Subdivides this Quadradic curve into two curves at t = 0.5.
* can be done with getSegment but this is more efficent.
* @param s0 if non-null contains portion of curve from 0->.5
* @param s1 if non-null contains portion of curve from .5->1
*/
public void subdivide(Segment s0, Segment s1) {
Quadradic q0=null, q1=null;
if (s0 instanceof Quadradic) q0 = (Quadradic)s0;
if (s1 instanceof Quadradic) q1 = (Quadradic)s1;
subdivide(q0, q1);
}
/**
* Subdivides this Quadradic curve into two curves at t.
* can be done with getSegment but this is more efficent.
* @param s0 if non-null contains portion of curve from 0->.5
* @param s1 if non-null contains portion of curve from .5->1
*/
public void subdivide(double t, Segment s0, Segment s1) {
Quadradic q0=null, q1=null;
if (s0 instanceof Quadradic) q0 = (Quadradic)s0;
if (s1 instanceof Quadradic) q1 = (Quadradic)s1;
subdivide(t, q0, q1);
}
static int count = 0;
protected double subLength(double leftLegLen, double rightLegLen,
double maxErr) {
count++;
double dx, dy;
dx = p3.x-p1.x;
dy = p3.y-p1.y;
double cordLen = Math.sqrt(dx*dx+dy*dy);
double hullLen = leftLegLen+rightLegLen;
if (hullLen < maxErr) return (hullLen+cordLen)*.5;
double err = (hullLen-cordLen);
if (err < maxErr)
return (hullLen+cordLen)*.5;
Quadradic q = new Quadradic();
double x = (p1.x+2*p2.x+p3.x)*.25;
double y = (p1.y+2*p2.y+p3.y)*.25;
dx = .25*dx;
dy = .25*dy;
q.p1.x = p1.x;
q.p1.y = p1.y;
q.p2.x = x-dx;
q.p2.y = y-dy;
q.p3.x = x;
q.p3.y = y;
double midLen = .25*cordLen;
double len = q.subLength(leftLegLen*.5, midLen, maxErr*.5);
q.p1.x = x;
q.p1.y = y;
q.p2.x = x+dx;
q.p2.y = y+dy;
q.p3.x = p3.x;
q.p3.y = p3.y;
len += q.subLength(midLen, rightLegLen*.5, maxErr*.5);
return len;
}
public double getLength() {
return getLength(0.000001);
}
public double getLength(double maxErr) {
double dx, dy;
dx = p2.x-p1.x;
dy = p2.y-p1.y;
double leftLegLen = Math.sqrt(dx*dx+dy*dy);
dx = p3.x-p2.x;
dy = p3.y-p2.y;
double rightLegLen = Math.sqrt(dx*dx+dy*dy);
double eps = maxErr*(leftLegLen+rightLegLen);
return subLength(leftLegLen, rightLegLen, eps);
}
public String toString() {
return "M" + p1.x + "," + p1.y +
"Q" + p2.x + "," + p2.y + " " +
p3.x + "," + p3.y;
}
/*
public static boolean epsEq(double a, double b) {
final double eps = 0.00001;
return (((a + eps) > b) && ((a-eps) < b));
}
public static void sub(Quadradic orig, Quadradic curr,
double t, double inc, int lev) {
Quadradic left=new Quadradic();
Quadradic right=new Quadradic();
curr.subdivide(left, right);
Point2D.Double ptl = left.eval(.5);
Point2D.Double ptr = right.eval(.5);
Point2D.Double pt1 = orig.eval(t-inc);
Point2D.Double pt2 = orig.eval(t+inc);
int steps = 100;
Point2D.Double l, r, o;
for (int i=0; i<=steps; i++) {
l = left.eval(i/(double)steps);
o = orig.eval(t-(2*inc)*(1-i/(double)steps));
if (!epsEq(l.x, o.x) || !epsEq(l.y, o.y))
System.err.println("Lf Pt: [" + l.x + "," + l.y +
"] Orig: [" + o.x + "," + o.y +"]");
r = right.eval(i/(double)steps);
o = orig.eval(t+(2*inc*i/(double)steps));
if (!epsEq(r.x, o.x) || !epsEq(r.y, o.y))
System.err.println("Rt Pt: [" + r.x + "," + r.y +
"] Orig: [" + o.x + "," + o.y +"]");
}
if (lev != 0) {
sub(orig, left, t-inc, inc/2, lev-1);
sub(orig, right, t+inc, inc/2, lev-1);
}
}
public static void evalQuad(Quadradic q) {
int steps = 1000000;
Point2D.Double oldP = q.eval(0);
Point2D.Double newP;
double len = 0;
for (int i=1; i<=steps; i++) {
newP = q.eval(i/(double)steps);
double dx = newP.x-oldP.x;
double dy = newP.y-oldP.y;
len += Math.sqrt(dx*dx + dy*dy);
oldP = newP;
}
System.err.println("Length(.1): " + q.getLength(.001) +
" x " + count); count = 0;
System.err.println("Length : " + q.getLength() +
" x " + count); count = 0;
System.err.println("D Len : " + len);
}
public static void main(String args[]) {
Quadradic q;
q = new Quadradic(0,0, 10,10, 30,0);
sub(q, q, .5, .25, 3);
evalQuad(q);
q = new Quadradic(0,0, 1,2, 3,0);
sub(q, q, .5, .25, 3);
evalQuad(q);
}
*/
}
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* An interface that path segments must implement.
*
* @version $Id: Segment.java 478249 2006-11-22 17:29:37Z dvholten $
*/
interface Segment extends Cloneable {
double minX();
double maxX();
double minY();
double maxY();
Rectangle2D getBounds2D();
Point2D.Double evalDt(double t);
Point2D.Double eval(double t);
Segment getSegment(double t0, double t1);
Segment splitBefore(double t);
Segment splitAfter(double t);
void subdivide(Segment s0, Segment s1);
void subdivide(double t, Segment s0, Segment s1);
double getLength();
double getLength(double maxErr);
SplitResults split(double y);
class SplitResults {
Segment [] above;
Segment [] below;
SplitResults(Segment []below, Segment []above) {
this.below = below;
this.above = above;
}
Segment [] getBelow() {
return below;
}
Segment [] getAbove() {
return above;
}
}
}
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* An abstract class for path segments.
*
* @version $Id: AbstractSegment.java 478249 2006-11-22 17:29:37Z dvholten $
*/
abstract class AbstractSegment implements Segment {
protected abstract int findRoots(double y, double [] roots);
public Segment.SplitResults split(double y) {
double [] roots = { 0, 0, 0 };
int numSol = findRoots(y, roots);
if (numSol == 0) return null; // No split
Arrays.sort(roots, 0, numSol);
double [] segs = new double[numSol+2];
int numSegments=0;
segs[numSegments++] = 0;
for (int i=0; i<numSol; i++) {
double r = roots[i];
if (r <= 0.0) continue;
if (r >= 1.0) break;
if (segs[numSegments-1] != r)
segs[numSegments++] = r;
}
segs[numSegments++] = 1.0;
if (numSegments == 2) return null;
// System.err.println("Y: " + y + "#Seg: " + numSegments +
// " Seg: " + this);
Segment [] parts = new Segment[numSegments];
double pT = 0.0;
int pIdx = 0;
boolean firstAbove=false, prevAbove=false;
for (int i=1; i<numSegments; i++) {
// System.err.println("Segs: " + segs[i-1]+", "+segs[i]);
parts[pIdx] = getSegment(segs[i-1], segs[i]);
Point2D.Double pt = parts[pIdx].eval(0.5);
// System.err.println("Pt: " + pt);
if (pIdx == 0) {
pIdx++;
firstAbove = prevAbove = (pt.y < y);
continue;
}
boolean above = (pt.y < y);
if (prevAbove == above) {
// Merge segments
parts[pIdx-1] = getSegment(pT, segs[i]);
} else {
pIdx++;
pT=segs[i-1];
prevAbove = above;
}
}
if (pIdx == 1) return null;
Segment [] below, above;
if (firstAbove) {
above = new Segment[(pIdx+1)/2];
below = new Segment[pIdx/2];
} else {
above = new Segment[pIdx/2];
below = new Segment[(pIdx+1)/2];
}
int ai=0, bi=0;
for (int i=0; i<pIdx; i++) {
if (firstAbove) above[ai++] = parts[i];
else below[bi++] = parts[i];
firstAbove = !firstAbove;
}
return new SplitResults(below, above);
}
public Segment splitBefore(double t) {
return getSegment(0.0, t);
}
public Segment splitAfter(double t) {
return getSegment(t, 1.0);
}
// Doubles have 48bit precision
static final double eps = 1/(double)(1L<<48);
static final double tol = 4.0*eps;
public static int solveLine(double a, double b,
double [] roots) {
if (a == 0) {
if (b != 0)
// No intersection.
return 0;
// All pts intersect just return 0.
roots[0] = 0;
return 1;
}
roots[0] = -b/a;
return 1;
}
public static int solveQuad(double a, double b, double c,
double [] roots) {
// System.err.println("Quad: " + a +"t^2 + " + b +"t + " + c);
if (a == 0) {
// no square term.
return solveLine(b, c, roots);
}
double det = b*b-4*a*c;
// System.err.println("Det: " + det);
if (Math.abs(det) <= tol*b*b) {
// one real root (det doesn"t contain any useful info)
roots[0] = -b/(2*a);
return 1;
}
if (det < 0)
return 0; // No real roots
// Two real roots
det = Math.sqrt(det);
double w = -(b + matchSign(det, b));
roots[0] = (2*c)/w;
roots[1] = w/(2*a);
return 2;
}
public static double matchSign(double a, double b) {
if (b < 0) return (a < 0)?a:-a;
return (a > 0)?a:-a;
}
public static int solveCubic(double a3, double a2,
double a1, double a0,
double [] roots) {
// System.err.println("Cubic: " + a3 + "t^3 + " +
// a2 +"t^2 + " +
// a1 +"t + " + a0);
double [] dRoots = { 0, 0};
int dCnt = solveQuad(3*a3, 2*a2, a1, dRoots);
double [] yVals = {0, 0, 0, 0};
double [] tVals = {0, 0, 0, 0};
int yCnt=0;
yVals[yCnt] = a0;
tVals[yCnt++] = 0;
double r;
switch (dCnt) {
case 1:
r = dRoots[0];
if ((r > 0) && (r < 1)) {
yVals[yCnt] = ((a3*r+a2)*r+a1)*r+a0;
tVals[yCnt++] = r;
}
break;
case 2:
if (dRoots[0] > dRoots[1]) {
double t = dRoots[0];
dRoots[0] = dRoots[1];
dRoots[1] = t;
}
r = dRoots[0];
if ((r > 0) && (r < 1)) {
yVals[yCnt] = ((a3*r+a2)*r+a1)*r+a0;
tVals[yCnt++] = r;
}
r = dRoots[1];
if ((r > 0) && (r < 1)) {
yVals[yCnt] = ((a3*r+a2)*r+a1)*r+a0;
tVals[yCnt++] = r;
}
break;
default: break;
}
yVals[yCnt] = a3+a2+a1+a0;
tVals[yCnt++] = 1.0;
int ret=0;
for (int i=0; i<yCnt-1; i++) {
double y0 = yVals[i], t0 = tVals[i];
double y1 = yVals[i+1], t1 = tVals[i+1];
if ((y0 < 0) && (y1 < 0)) continue;
if ((y0 > 0) && (y1 > 0)) continue;
if (y0 > y1) { // swap so y0 < 0 and y1 > 0
double t;
t = y0; y0=y1; y1=t;
t = t0; t0=t1; t1=t;
}
if (-y0 < tol*y1) { roots[ret++] = t0; continue; }
if (y1 < -tol*y0) { roots[ret++] = t1; i++; continue; }
double epsZero = tol*(y1-y0);
int cnt;
for (cnt=0; cnt<20; cnt++) {
double dt = t1-t0;
double dy = y1-y0;
// double t = (t0+t1)/2;
// double t= t0+Math.abs(y0/dy)*dt;
// This tends to make sure that we come up
// a little short each time this generaly allows
// you to eliminate as much of the range as possible
// without overshooting (in which case you may eliminate
// almost nothing).
double t= t0+(Math.abs(y0/dy)*99+.5)*dt/100;
double v = ((a3*t+a2)*t+a1)*t+a0;
if (Math.abs(v) < epsZero) {
roots[ret++] = t; break;
}
if (v < 0) { t0 = t; y0=v;}
else { t1 = t; y1=v;}
}
if (cnt == 20)
roots[ret++] = (t0+t1)/2;
}
return ret;
}
/*
public static void check(Segment seg, float y, PrintStream ps) {
ps.println("<path fill=\"none\" stroke=\"black\" " +
" stroke-width=\"3\" d=\"" + seg + "\"/>");
ps.println("<line x1=\"-1000\" y1=\""+y+
"\" x2=\"1000\" y2=\""+y+"\" fill=\"none\" stroke=\"orange\"/>\n");
SplitResults sr = seg.split(y);
if (sr == null) return;
Segment [] above = sr.getAbove();
Segment [] below = sr.getBelow();
for (int i=0; i<above.length; i++) {
ps.println("<path fill=\"none\" stroke=\"blue\" " +
" stroke-width=\"2.5\" " +
" d=\"" + above[i] + "\"/>");
}
for (int i=0; i<below.length; i++) {
ps.println("<path fill=\"none\" stroke=\"red\" " +
" stroke-width=\"2\" " +
"d=\"" + below[i] + "\"/>");
}
}
public static void main(String [] args) {
PrintStream ps;
double [] roots = { 0, 0, 0 };
int n = solveCubic (-0.10000991821289062, 9.600013732910156,
-35.70000457763672, 58.0, roots);
for (int i=0; i<n; i++)
System.err.println("Root: " + roots[i]);
Cubic c;
c = new Cubic(new Point2D.Double(153.6999969482422,5.099999904632568),
new Point2D.Double(156.6999969482422,4.099999904632568),
new Point2D.Double(160.39999389648438,2.3999998569488525),
new Point2D.Double(164.6999969482422,0.0));
c.split(0);
c = new Cubic(new Point2D.Double(24.899999618530273,23.10000228881836),
new Point2D.Double(41.5,8.399999618530273),
new Point2D.Double(64.69999694824219,1.0),
new Point2D.Double(94.5999984741211,1.0));
c.split(0);
try {
ps = new PrintStream(new FileOutputStream(args[0]));
} catch(java.io.IOException ioe) {
ioe.printStackTrace();
return;
}
ps.println("<?xml version=\"1.0\" standalone=\"no\"?>\n" +
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n" +
"\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n" +
"<svg width=\"450\" height=\"500\"\n" +
" viewBox=\"-100 -100 450 500\"\n" +
" xmlns=\"http://www.w3.org/2000/svg\"\n" +
" xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
check(new Cubic(new Point2D.Double(0, 0),
new Point2D.Double(100, 100),
new Point2D.Double(-50, 100),
new Point2D.Double(50, 0)), 40, ps);
check(new Cubic(new Point2D.Double(100, 0),
new Point2D.Double(200, 100),
new Point2D.Double(50, -50),
new Point2D.Double(150, 30)), 20, ps);
check(new Cubic(new Point2D.Double(200, 0),
new Point2D.Double(300, 100),
new Point2D.Double(150, 100),
new Point2D.Double(250, 0)), 75, ps);
check(new Quadradic(new Point2D.Double(0, 100),
new Point2D.Double(50,150),
new Point2D.Double(10,100)), 115, ps);
check(new Linear(new Point2D.Double(100, 100),
new Point2D.Double(150,150)), 115, ps);
ps.println("</svg>");
}
*/
}
A geometric path constructed from straight lines, quadratic and cubic (Bezier) curves and elliptical arc.
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import java.awt.Shape;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
/**
* The <code>ExtendedGeneralPath</code> class represents a geometric
* path constructed from straight lines, quadratic and cubic (Bezier)
* curves and elliptical arc. This class delegates lines and curves to
* an enclosed <code>GeneralPath</code>. Elliptical arc is implemented
* using an <code>Arc2D</code> in float precision.
*
* <p><b>Warning</b> : An elliptical arc may be composed of several
* path segments. For futher details, see the SVG Appendix F.6
*
* @author
* @version $Id: ExtendedPathIterator.java 475477 2006-11-15 22:44:28Z cam $
*/
interface ExtendedPathIterator {
/**
* The segment type constant that specifies that the preceding
* subpath should be closed by appending a line segment back to
* the point corresponding to the most recent SEG_MOVETO.
*/
int SEG_CLOSE = PathIterator.SEG_CLOSE;
/**
* The segment type constant for a point that specifies the end
* point of a line to be drawn from the most recently specified
* point. */
int SEG_MOVETO = PathIterator.SEG_MOVETO;
/**
* The segment type constant for a point that specifies the end
* point of a line to be drawn from the most recently specified
* point.
*/
int SEG_LINETO = PathIterator.SEG_LINETO;
/**
* The segment type constant for the pair of points that specify a
* quadratic parametric curve to be drawn from the most recently
* specified point. The curve is interpolated by solving the
* parametric control equation in the range (t=[0..1]) using the
* most recently specified (current) point (CP), the first control
* point (P1), and the final interpolated control point (P2).
*/
int SEG_QUADTO = PathIterator.SEG_QUADTO;
/**
* The segment type constant for the set of 3 points that specify
* a cubic parametric curve to be drawn from the most recently
* specified point. The curve is interpolated by solving the
* parametric control equation in the range (t=[0..1]) using the
* most recently specified (current) point (CP), the first control
* point (P1), the second control point (P2), and the final
* interpolated control point (P3).
*/
int SEG_CUBICTO = PathIterator.SEG_CUBICTO;
/** The segment type constant for an elliptical arc. This consists of
* Seven values [rx, ry, angle, largeArcFlag, sweepFlag, x, y].
* rx, ry are the radious of the ellipse.
* angle is angle of the x axis of the ellipse.
* largeArcFlag is zero if the smaller of the two arcs are to be used.
* sweepFlag is zero if the "left" branch is taken one otherwise.
* x and y are the destination for the ellipse. */
int SEG_ARCTO = 4321;
/** The winding rule constant for specifying an even-odd rule for
* determining the interior of a path. The even-odd rule specifies
* that a point lies inside the path if a ray drawn in any
* direction from that point to infinity is crossed by path
* segments an odd number of times.
*/
int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD;
/**
* The winding rule constant for specifying a non-zero rule for
* determining the interior of a path. The non-zero rule specifies
* that a point lies inside the path if a ray drawn in any
* direction from that point to infinity is crossed by path
* segments a different number of times in the counter-clockwise
* direction than the clockwise direction.
*/
int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO;
int currentSegment();
int currentSegment(double[] coords);
int currentSegment(float[] coords);
int getWindingRule();
boolean isDone();
void next();
}
Describe a path
import java.awt.Shape;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
public class DescribePath {
public static void describePath(Shape s) {
PathIterator pi = s.getPathIterator(null);
while (pi.isDone() == false) {
describeCurrentSegment(pi);
pi.next();
}
}
public static void describeCurrentSegment(PathIterator pi) {
double[] coordinates = new double[6];
int type = pi.currentSegment(coordinates);
switch (type) {
case PathIterator.SEG_MOVETO:
System.out.println("move to " + coordinates[0] + ", "
+ coordinates[1]);
break;
case PathIterator.SEG_LINETO:
System.out.println("line to " + coordinates[0] + ", "
+ coordinates[1]);
break;
case PathIterator.SEG_QUADTO:
System.out.println("quadratic to " + coordinates[0] + ", "
+ coordinates[1] + ", " + coordinates[2] + ", "
+ coordinates[3]);
break;
case PathIterator.SEG_CUBICTO:
System.out.println("cubic to " + coordinates[0] + ", "
+ coordinates[1] + ", " + coordinates[2] + ", "
+ coordinates[3] + ", " + coordinates[4] + ", "
+ coordinates[5]);
break;
case PathIterator.SEG_CLOSE:
System.out.println("close");
break;
default:
break;
}
}
public static void main(String[] args) {
describePath(new Rectangle2D.Double(0, 0, 72, 72));
}
}
Fill GeneralPath
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import javax.swing.JApplet;
import javax.swing.JFrame;
public class FilledGeneralPath extends JApplet {
public void init() {
setBackground(Color.white);
setForeground(Color.white);
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int x = 5;
int y = 7;
// fill and stroke GeneralPath
int xPoints[] = { x, 200, x, 200 };
int yPoints[] = { y, 200, 200, y };
GeneralPath filledPolygon = new GeneralPath(GeneralPath.WIND_EVEN_ODD,
xPoints.length);
filledPolygon.moveTo(xPoints[0], yPoints[0]);
for (int index = 1; index < xPoints.length; index++) {
filledPolygon.lineTo(xPoints[index], yPoints[index]);
}
filledPolygon.closePath();
g2.setPaint(Color.red);
g2.fill(filledPolygon);
g2.setPaint(Color.black);
g2.draw(filledPolygon);
g2.drawString("Filled and Stroked GeneralPath", x, 250);
}
public static void main(String s[]) {
JFrame f = new JFrame("");
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
JApplet applet = new FilledGeneralPath();
f.getContentPane().add("Center", applet);
applet.init();
f.pack();
f.setSize(new Dimension(300, 300));
f.show();
}
}
GeneralPath
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.util.Vector;
import javax.swing.JApplet;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ManyGeneralPath extends JApplet {
DrawingCanvas canvas;
public static void main(String[] a) {
JFrame f = new JFrame();
ManyGeneralPath path = new ManyGeneralPath();
path.init();
f.getContentPane().add(path);
f.setDefaultCloseOperation(1);
f.setSize(650, 250);
f.setVisible(true);
}
public void init() {
Container container = getContentPane();
JPanel panel = new JPanel();
canvas = new DrawingCanvas();
container.add(canvas);
}
class DrawingCanvas extends Canvas {
Vector generalPaths;
GeneralPath selectedGPath = null;
Rectangle2D boundingRec = null;
int selectedRule = GeneralPath.WIND_NON_ZERO;
boolean drawNoFill = false;
public DrawingCanvas() {
setBackground(Color.white);
setSize(400, 200);
generalPaths = new Vector();
GeneralPath gp1, gp2, gp3, gp4, gp5, gp6, gp7, gp8;
gp1 = new GeneralPath();
gp1.moveTo(50, 10);
gp1.lineTo(70, 80);
gp1.lineTo(90, 40);
gp1.lineTo(10, 40);
gp1.lineTo(50, 80);
gp1.closePath();
generalPaths.addElement(gp1);
gp2 = new GeneralPath();
gp2.moveTo(120, 20);
gp2.lineTo(180, 20);
gp2.lineTo(120, 80);
gp2.lineTo(180, 80);
gp2.closePath();
generalPaths.addElement(gp2);
gp3 = new GeneralPath();
gp3.moveTo(220, 20);
gp3.lineTo(280, 20);
gp3.lineTo(280, 60);
gp3.lineTo(240, 60);
gp3.lineTo(240, 40);
gp3.lineTo(260, 40);
gp3.lineTo(260, 80);
gp3.lineTo(220, 80);
gp3.closePath();
generalPaths.addElement(gp3);
gp4 = new GeneralPath();
gp4.moveTo(310, 20);
gp4.lineTo(380, 20);
gp4.lineTo(380, 80);
gp4.lineTo(320, 80);
gp4.lineTo(320, 10);
gp4.lineTo(340, 10);
gp4.lineTo(340, 60);
gp4.lineTo(360, 60);
gp4.lineTo(360, 40);
gp4.lineTo(310, 40);
gp4.closePath();
generalPaths.addElement(gp4);
gp5 = new GeneralPath();
gp5.moveTo(50, 120);
gp5.lineTo(70, 180);
gp5.lineTo(20, 140);
gp5.lineTo(80, 140);
gp5.lineTo(30, 180);
gp5.closePath();
generalPaths.addElement(gp5);
gp6 = new GeneralPath();
gp6.moveTo(120, 180);
gp6.quadTo(150, 120, 180, 180);
gp6.closePath();
generalPaths.addElement(gp6);
gp7 = new GeneralPath();
gp7.moveTo(220, 150);
gp7.curveTo(240, 130, 280, 160, 300, 140);
gp7.lineTo(300, 180);
gp7.quadTo(260, 160, 220, 180);
gp7.closePath();
generalPaths.addElement(gp7);
gp8 = new GeneralPath();
gp8.moveTo(360, 100);
gp8.lineTo(360, 200);
gp8.lineTo(400, 140);
gp8.lineTo(320, 120);
gp8.lineTo(400, 180);
gp8.lineTo(320, 180);
gp8.closePath();
generalPaths.addElement(gp8);
}
public void paint(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
for (int i = 0; i < generalPaths.size(); i++) {
if (drawNoFill) {
g2D.draw((GeneralPath) generalPaths.elementAt(i));
} else {
g2D.fill((GeneralPath) generalPaths.elementAt(i));
}
}
}
}
}
GeneralPath Demo
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import javax.swing.JApplet;
import javax.swing.JFrame;
public class GeneralPathDemo2D extends JApplet {
public void init() {
setBackground(Color.white);
setForeground(Color.white);
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(Color.gray);
int x = 5;
int y = 7;
// draw GeneralPath (polygon)
int xPoints[] = { x, 200, x, 200 };
int yPoints[] = { y, 200, 200, y };
GeneralPath polygon = new GeneralPath(GeneralPath.WIND_EVEN_ODD,
xPoints.length);
polygon.moveTo(xPoints[0], yPoints[0]);
for (int index = 1; index < xPoints.length; index++) {
polygon.lineTo(xPoints[index], yPoints[index]);
}
polygon.closePath();
g2.draw(polygon);
g2.drawString("GeneralPath", x, 250);
}
public static void main(String s[]) {
JFrame f = new JFrame("");
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
JApplet applet = new GeneralPathDemo2D();
f.getContentPane().add("Center", applet);
applet.init();
f.pack();
f.setSize(new Dimension(300, 300));
f.show();
}
}
Open GeneralPath Demo
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import javax.swing.JApplet;
import javax.swing.JFrame;
public class GeneralPathOpenDemo2D extends JApplet {
public void init() {
setBackground(Color.white);
setForeground(Color.white);
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(Color.gray);
int x = 5;
int y = 7;
// draw GeneralPath (polyline)
int xPoints[] = { x, 200, x, 200 };
int yPoints[] = { y, 200, 200, y };
GeneralPath polyline = new GeneralPath(GeneralPath.WIND_EVEN_ODD,
xPoints.length);
polyline.moveTo(xPoints[0], yPoints[0]);
for (int index = 1; index < xPoints.length; index++) {
polyline.lineTo(xPoints[index], yPoints[index]);
}
g2.draw(polyline);
g2.drawString("GeneralPath (open)", x, 250);
}
public static void main(String s[]) {
JFrame f = new JFrame("");
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
JApplet applet = new GeneralPathOpenDemo2D();
f.getContentPane().add("Center", applet);
applet.init();
f.pack();
f.setSize(new Dimension(300, 300));
f.show();
}
}
Utilitiy class for length calculations of paths.
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Utilitiy class for length calculations of paths.
* <p>
* PathLength is a utility class for calculating the length
* of a path, the location of a point at a particular length
* along the path, and the angle of the tangent to the path
* at a given length.
* </p>
* <p>
* It uses a FlatteningPathIterator to create a flattened version
* of the Path. This means the values returned are not always
* exact (in fact, they rarely are), but in most cases they
* are reasonably accurate.
* </p>
*
* @author
* @version $Id: PathLength.java 489226 2006-12-21 00:05:36Z cam $
*/
public class PathLength {
/**
* The path to use for calculations.
*/
protected Shape path;
/**
* The list of flattened path segments.
*/
protected List segments;
/**
* Array where the index is the index of the original path segment
* and the value is the index of the first of the flattened segments
* in {@link #segments} that corresponds to that original path segment.
*/
protected int[] segmentIndexes;
/**
* Cached copy of the path length.
*/
protected float pathLength;
/**
* Whether this path been flattened yet.
*/
protected boolean initialised;
/**
* Creates a new PathLength object for the specified {@link Shape}.
* @param path The Path (or Shape) to use.
*/
public PathLength(Shape path) {
setPath(path);
}
/**
* Returns the path to use for calculations.
* @return Path used in calculations.
*/
public Shape getPath() {
return path;
}
/**
* Sets the path to use for calculations.
* @param v Path to be used in calculations.
*/
public void setPath(Shape v) {
this.path = v;
initialised = false;
}
/**
* Returns the length of the path used by this PathLength object.
* @return The length of the path.
*/
public float lengthOfPath() {
if (!initialised) {
initialise();
}
return pathLength;
}
/**
* Flattens the path and determines the path length.
*/
protected void initialise() {
pathLength = 0f;
PathIterator pi = path.getPathIterator(new AffineTransform());
SingleSegmentPathIterator sspi = new SingleSegmentPathIterator();
segments = new ArrayList(20);
List indexes = new ArrayList(20);
int index = 0;
int origIndex = -1;
float lastMoveX = 0f;
float lastMoveY = 0f;
float currentX = 0f;
float currentY = 0f;
float[] seg = new float[6];
int segType;
segments.add(new PathSegment(PathIterator.SEG_MOVETO, 0f, 0f, 0f,
origIndex));
while (!pi.isDone()) {
origIndex++;
indexes.add(new Integer(index));
segType = pi.currentSegment(seg);
switch (segType) {
case PathIterator.SEG_MOVETO:
segments.add(new PathSegment(segType, seg[0], seg[1],
pathLength, origIndex));
currentX = seg[0];
currentY = seg[1];
lastMoveX = currentX;
lastMoveY = currentY;
index++;
pi.next();
break;
case PathIterator.SEG_LINETO:
pathLength += Point2D.distance(currentX, currentY, seg[0],
seg[1]);
segments.add(new PathSegment(segType, seg[0], seg[1],
pathLength, origIndex));
currentX = seg[0];
currentY = seg[1];
index++;
pi.next();
break;
case PathIterator.SEG_CLOSE:
pathLength += Point2D.distance(currentX, currentY,
lastMoveX, lastMoveY);
segments.add(new PathSegment(PathIterator.SEG_LINETO,
lastMoveX, lastMoveY,
pathLength, origIndex));
currentX = lastMoveX;
currentY = lastMoveY;
index++;
pi.next();
break;
default:
sspi.setPathIterator(pi, currentX, currentY);
FlatteningPathIterator fpi =
new FlatteningPathIterator(sspi, 0.01f);
while (!fpi.isDone()) {
segType = fpi.currentSegment(seg);
if (segType == PathIterator.SEG_LINETO) {
pathLength += Point2D.distance(currentX, currentY,
seg[0], seg[1]);
segments.add(new PathSegment(segType, seg[0],
seg[1], pathLength,
origIndex));
currentX = seg[0];
currentY = seg[1];
index++;
}
fpi.next();
}
}
}
segmentIndexes = new int[indexes.size()];
for (int i = 0; i < segmentIndexes.length; i++) {
segmentIndexes[i] = ((Integer) indexes.get(i)).intValue();
}
initialised = true;
}
/**
* Returns the number of segments in the path.
*/
public int getNumberOfSegments() {
if (!initialised) {
initialise();
}
return segmentIndexes.length;
}
/**
* Returns the length at the start of the segment given by the specified
* index.
*/
public float getLengthAtSegment(int index) {
if (!initialised) {
initialise();
}
if (index <= 0) {
return 0;
}
if (index >= segmentIndexes.length) {
return pathLength;
}
PathSegment seg = (PathSegment) segments.get(segmentIndexes[index]);
return seg.getLength();
}
/**
* Returns the index of the segment at the given distance along the path.
*/
public int segmentAtLength(float length) {
int upperIndex = findUpperIndex(length);
if (upperIndex == -1) {
// Length is off the end of the path.
return -1;
}
if (upperIndex == 0) {
// Length was probably zero, so return the upper segment.
PathSegment upper = (PathSegment) segments.get(upperIndex);
return upper.getIndex();
}
PathSegment lower = (PathSegment) segments.get(upperIndex - 1);
return lower.getIndex();
}
/**
* Returns the point that is the given proportion along the path segment
* given by the specified index.
*/
public Point2D pointAtLength(int index, float proportion) {
if (!initialised) {
initialise();
}
if (index < 0 || index >= segmentIndexes.length) {
return null;
}
PathSegment seg = (PathSegment) segments.get(segmentIndexes[index]);
float start = seg.getLength();
float end;
if (index == segmentIndexes.length - 1) {
end = pathLength;
} else {
seg = (PathSegment) segments.get(segmentIndexes[index + 1]);
end = seg.getLength();
}
return pointAtLength(start + (end - start) * proportion);
}
/**
* Returns the point that is at the given length along the path.
* @param length The length along the path
* @return The point at the given length
*/
public Point2D pointAtLength(float length) {
int upperIndex = findUpperIndex(length);
if (upperIndex == -1) {
// Length is off the end of the path.
return null;
}
PathSegment upper = (PathSegment) segments.get(upperIndex);
if (upperIndex == 0) {
// Length was probably zero, so return the upper point.
return new Point2D.Float(upper.getX(), upper.getY());
}
PathSegment lower = (PathSegment) segments.get(upperIndex - 1);
// Now work out where along the line would be the length.
float offset = length - lower.getLength();
// Compute the slope.
double theta = Math.atan2(upper.getY() - lower.getY(),
upper.getX() - lower.getX());
float xPoint = (float) (lower.getX() + offset * Math.cos(theta));
float yPoint = (float) (lower.getY() + offset * Math.sin(theta));
return new Point2D.Float(xPoint, yPoint);
}
/**
* Returns the slope of the path at the specified length.
* @param index The segment number
* @param proportion The proportion along the given segment
* @return the angle in radians, in the range [-{@link Math#PI},
* {@link Math#PI}].
*/
public float angleAtLength(int index, float proportion) {
if (!initialised) {
initialise();
}
if (index < 0 || index >= segmentIndexes.length) {
return 0f;
}
PathSegment seg = (PathSegment) segments.get(segmentIndexes[index]);
float start = seg.getLength();
float end;
if (index == segmentIndexes.length - 1) {
end = pathLength;
} else {
seg = (PathSegment) segments.get(segmentIndexes[index + 1]);
end = seg.getLength();
}
return angleAtLength(start + (end - start) * proportion);
}
/**
* Returns the slope of the path at the specified length.
* @param length The length along the path
* @return the angle in radians, in the range [-{@link Math#PI},
* {@link Math#PI}].
*/
public float angleAtLength(float length) {
int upperIndex = findUpperIndex(length);
if (upperIndex == -1) {
// Length is off the end of the path.
return 0f;
}
PathSegment upper = (PathSegment) segments.get(upperIndex);
if (upperIndex == 0) {
// Length was probably zero, so return the angle between the first
// and second segments.
upperIndex = 1;
}
PathSegment lower = (PathSegment) segments.get(upperIndex - 1);
// Compute the slope.
return (float) Math.atan2(upper.getY() - lower.getY(),
upper.getX() - lower.getX());
}
/**
* Returns the index of the path segment that bounds the specified
* length along the path.
* @param length The length along the path
* @return The path segment index, or -1 if there is not such segment
*/
public int findUpperIndex(float length) {
if (!initialised) {
initialise();
}
if (length < 0 || length > pathLength) {
// Length is outside the path, so return -1.
return -1;
}
// Find the two segments that are each side of the length.
int lb = 0;
int ub = segments.size() - 1;
while (lb != ub) {
int curr = (lb + ub) >> 1;
PathSegment ps = (PathSegment) segments.get(curr);
if (ps.getLength() >= length) {
ub = curr;
} else {
lb = curr + 1;
}
}
for (;;) {
PathSegment ps = (PathSegment) segments.get(ub);
if (ps.getSegType() != PathIterator.SEG_MOVETO
|| ub == segments.size() - 1) {
break;
}
ub++;
}
int upperIndex = -1;
int currentIndex = 0;
int numSegments = segments.size();
while (upperIndex <= 0 && currentIndex < numSegments) {
PathSegment ps = (PathSegment) segments.get(currentIndex);
if (ps.getLength() >= length
&& ps.getSegType() != PathIterator.SEG_MOVETO) {
upperIndex = currentIndex;
}
currentIndex++;
}
return upperIndex;
}
/**
* A {@link PathIterator} that returns only the next path segment from
* another {@link PathIterator}.
*/
protected static class SingleSegmentPathIterator implements PathIterator {
/**
* The path iterator being wrapped.
*/
protected PathIterator it;
/**
* Whether the single segment has been passed.
*/
protected boolean done;
/**
* Whether the generated move command has been returned.
*/
protected boolean moveDone;
/**
* The x coordinate of the next move command.
*/
protected double x;
/**
* The y coordinate of the next move command.
*/
protected double y;
/**
* Sets the path iterator to use and the initial SEG_MOVETO command
* to return before it.
*/
public void setPathIterator(PathIterator it, double x, double y) {
this.it = it;
this.x = x;
this.y = y;
done = false;
moveDone = false;
}
public int currentSegment(double[] coords) {
int type = it.currentSegment(coords);
if (!moveDone) {
coords[0] = x;
coords[1] = y;
return SEG_MOVETO;
}
return type;
}
public int currentSegment(float[] coords) {
int type = it.currentSegment(coords);
if (!moveDone) {
coords[0] = (float) x;
coords[1] = (float) y;
return SEG_MOVETO;
}
return type;
}
public int getWindingRule() {
return it.getWindingRule();
}
public boolean isDone() {
return done || it.isDone();
}
public void next() {
if (!done) {
if (!moveDone) {
moveDone = true;
} else {
it.next();
done = true;
}
}
}
}
/**
* A single path segment in the flattened version of the path.
* This is a local helper class. PathSegment-objects are stored in
* the {@link PathLength#segments} - list.
* This is used as an immutable value-object.
*/
protected static class PathSegment {
/**
* The path segment type.
*/
protected final int segType;
/**
* The x coordinate of the path segment.
*/
protected float x;
/**
* The y coordinate of the path segment.
*/
protected float y;
/**
* The length of the path segment, accumulated from the start.
*/
protected float length;
/**
* The index of the original path segment this flattened segment is a
* part of.
*/
protected int index;
/**
* Creates a new PathSegment with the specified parameters.
* @param segType The segment type
* @param x The x coordinate
* @param y The y coordinate
* @param len The segment length
* @param idx The index of the original path segment this flattened
* segment is a part of
*/
PathSegment(int segType, float x, float y, float len, int idx) {
this.segType = segType;
this.x = x;
this.y = y;
this.length = len;
this.index = idx;
}
/**
* Returns the segment type.
*/
public int getSegType() {
return segType;
}
/**
* Returns the x coordinate of the path segment.
*/
public float getX() {
return x;
}
/**
* Sets the x coordinate of the path segment.
*/
public void setX(float v) {
x = v;
}
/**
* Returns the y coordinate of the path segment.
*/
public float getY() {
return y;
}
/**
* Sets the y coordinate of the path segment.
*/
public void setY(float v) {
y = v;
}
/**
* Returns the length of the path segment.
*/
public float getLength() {
return length;
}
/**
* Sets the length of the path segment.
*/
public void setLength(float v) {
length = v;
}
/**
* Returns the segment index.
*/
public int getIndex() {
return index;
}
/**
* Sets the segment index.
*/
public void setIndex(int v) {
index = v;
}
}
}
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* An interface that path segments must implement.
*
* @version $Id: Segment.java 478249 2006-11-22 17:29:37Z dvholten $
*/
interface Segment extends Cloneable {
double minX();
double maxX();
double minY();
double maxY();
Rectangle2D getBounds2D();
Point2D.Double evalDt(double t);
Point2D.Double eval(double t);
Segment getSegment(double t0, double t1);
Segment splitBefore(double t);
Segment splitAfter(double t);
void subdivide(Segment s0, Segment s1);
void subdivide(double t, Segment s0, Segment s1);
double getLength();
double getLength(double maxErr);
SplitResults split(double y);
class SplitResults {
Segment [] above;
Segment [] below;
SplitResults(Segment []below, Segment []above) {
this.below = below;
this.above = above;
}
Segment [] getBelow() {
return below;
}
Segment [] getAbove() {
return above;
}
}
}
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* An abstract class for path segments.
*
* @version $Id: AbstractSegment.java 478249 2006-11-22 17:29:37Z dvholten $
*/
abstract class AbstractSegment implements Segment {
protected abstract int findRoots(double y, double [] roots);
public Segment.SplitResults split(double y) {
double [] roots = { 0, 0, 0 };
int numSol = findRoots(y, roots);
if (numSol == 0) return null; // No split
Arrays.sort(roots, 0, numSol);
double [] segs = new double[numSol+2];
int numSegments=0;
segs[numSegments++] = 0;
for (int i=0; i<numSol; i++) {
double r = roots[i];
if (r <= 0.0) continue;
if (r >= 1.0) break;
if (segs[numSegments-1] != r)
segs[numSegments++] = r;
}
segs[numSegments++] = 1.0;
if (numSegments == 2) return null;
// System.err.println("Y: " + y + "#Seg: " + numSegments +
// " Seg: " + this);
Segment [] parts = new Segment[numSegments];
double pT = 0.0;
int pIdx = 0;
boolean firstAbove=false, prevAbove=false;
for (int i=1; i<numSegments; i++) {
// System.err.println("Segs: " + segs[i-1]+", "+segs[i]);
parts[pIdx] = getSegment(segs[i-1], segs[i]);
Point2D.Double pt = parts[pIdx].eval(0.5);
// System.err.println("Pt: " + pt);
if (pIdx == 0) {
pIdx++;
firstAbove = prevAbove = (pt.y < y);
continue;
}
boolean above = (pt.y < y);
if (prevAbove == above) {
// Merge segments
parts[pIdx-1] = getSegment(pT, segs[i]);
} else {
pIdx++;
pT=segs[i-1];
prevAbove = above;
}
}
if (pIdx == 1) return null;
Segment [] below, above;
if (firstAbove) {
above = new Segment[(pIdx+1)/2];
below = new Segment[pIdx/2];
} else {
above = new Segment[pIdx/2];
below = new Segment[(pIdx+1)/2];
}
int ai=0, bi=0;
for (int i=0; i<pIdx; i++) {
if (firstAbove) above[ai++] = parts[i];
else below[bi++] = parts[i];
firstAbove = !firstAbove;
}
return new SplitResults(below, above);
}
public Segment splitBefore(double t) {
return getSegment(0.0, t);
}
public Segment splitAfter(double t) {
return getSegment(t, 1.0);
}
// Doubles have 48bit precision
static final double eps = 1/(double)(1L<<48);
static final double tol = 4.0*eps;
public static int solveLine(double a, double b,
double [] roots) {
if (a == 0) {
if (b != 0)
// No intersection.
return 0;
// All pts intersect just return 0.
roots[0] = 0;
return 1;
}
roots[0] = -b/a;
return 1;
}
public static int solveQuad(double a, double b, double c,
double [] roots) {
// System.err.println("Quad: " + a +"t^2 + " + b +"t + " + c);
if (a == 0) {
// no square term.
return solveLine(b, c, roots);
}
double det = b*b-4*a*c;
// System.err.println("Det: " + det);
if (Math.abs(det) <= tol*b*b) {
// one real root (det doesn"t contain any useful info)
roots[0] = -b/(2*a);
return 1;
}
if (det < 0)
return 0; // No real roots
// Two real roots
det = Math.sqrt(det);
double w = -(b + matchSign(det, b));
roots[0] = (2*c)/w;
roots[1] = w/(2*a);
return 2;
}
public static double matchSign(double a, double b) {
if (b < 0) return (a < 0)?a:-a;
return (a > 0)?a:-a;
}
public static int solveCubic(double a3, double a2,
double a1, double a0,
double [] roots) {
// System.err.println("Cubic: " + a3 + "t^3 + " +
// a2 +"t^2 + " +
// a1 +"t + " + a0);
double [] dRoots = { 0, 0};
int dCnt = solveQuad(3*a3, 2*a2, a1, dRoots);
double [] yVals = {0, 0, 0, 0};
double [] tVals = {0, 0, 0, 0};
int yCnt=0;
yVals[yCnt] = a0;
tVals[yCnt++] = 0;
double r;
switch (dCnt) {
case 1:
r = dRoots[0];
if ((r > 0) && (r < 1)) {
yVals[yCnt] = ((a3*r+a2)*r+a1)*r+a0;
tVals[yCnt++] = r;
}
break;
case 2:
if (dRoots[0] > dRoots[1]) {
double t = dRoots[0];
dRoots[0] = dRoots[1];
dRoots[1] = t;
}
r = dRoots[0];
if ((r > 0) && (r < 1)) {
yVals[yCnt] = ((a3*r+a2)*r+a1)*r+a0;
tVals[yCnt++] = r;
}
r = dRoots[1];
if ((r > 0) && (r < 1)) {
yVals[yCnt] = ((a3*r+a2)*r+a1)*r+a0;
tVals[yCnt++] = r;
}
break;
default: break;
}
yVals[yCnt] = a3+a2+a1+a0;
tVals[yCnt++] = 1.0;
int ret=0;
for (int i=0; i<yCnt-1; i++) {
double y0 = yVals[i], t0 = tVals[i];
double y1 = yVals[i+1], t1 = tVals[i+1];
if ((y0 < 0) && (y1 < 0)) continue;
if ((y0 > 0) && (y1 > 0)) continue;
if (y0 > y1) { // swap so y0 < 0 and y1 > 0
double t;
t = y0; y0=y1; y1=t;
t = t0; t0=t1; t1=t;
}
if (-y0 < tol*y1) { roots[ret++] = t0; continue; }
if (y1 < -tol*y0) { roots[ret++] = t1; i++; continue; }
double epsZero = tol*(y1-y0);
int cnt;
for (cnt=0; cnt<20; cnt++) {
double dt = t1-t0;
double dy = y1-y0;
// double t = (t0+t1)/2;
// double t= t0+Math.abs(y0/dy)*dt;
// This tends to make sure that we come up
// a little short each time this generaly allows
// you to eliminate as much of the range as possible
// without overshooting (in which case you may eliminate
// almost nothing).
double t= t0+(Math.abs(y0/dy)*99+.5)*dt/100;
double v = ((a3*t+a2)*t+a1)*t+a0;
if (Math.abs(v) < epsZero) {
roots[ret++] = t; break;
}
if (v < 0) { t0 = t; y0=v;}
else { t1 = t; y1=v;}
}
if (cnt == 20)
roots[ret++] = (t0+t1)/2;
}
return ret;
}
/*
public static void check(Segment seg, float y, PrintStream ps) {
ps.println("<path fill=\"none\" stroke=\"black\" " +
" stroke-width=\"3\" d=\"" + seg + "\"/>");
ps.println("<line x1=\"-1000\" y1=\""+y+
"\" x2=\"1000\" y2=\""+y+"\" fill=\"none\" stroke=\"orange\"/>\n");
SplitResults sr = seg.split(y);
if (sr == null) return;
Segment [] above = sr.getAbove();
Segment [] below = sr.getBelow();
for (int i=0; i<above.length; i++) {
ps.println("<path fill=\"none\" stroke=\"blue\" " +
" stroke-width=\"2.5\" " +
" d=\"" + above[i] + "\"/>");
}
for (int i=0; i<below.length; i++) {
ps.println("<path fill=\"none\" stroke=\"red\" " +
" stroke-width=\"2\" " +
"d=\"" + below[i] + "\"/>");
}
}
public static void main(String [] args) {
PrintStream ps;
double [] roots = { 0, 0, 0 };
int n = solveCubic (-0.10000991821289062, 9.600013732910156,
-35.70000457763672, 58.0, roots);
for (int i=0; i<n; i++)
System.err.println("Root: " + roots[i]);
Cubic c;
c = new Cubic(new Point2D.Double(153.6999969482422,5.099999904632568),
new Point2D.Double(156.6999969482422,4.099999904632568),
new Point2D.Double(160.39999389648438,2.3999998569488525),
new Point2D.Double(164.6999969482422,0.0));
c.split(0);
c = new Cubic(new Point2D.Double(24.899999618530273,23.10000228881836),
new Point2D.Double(41.5,8.399999618530273),
new Point2D.Double(64.69999694824219,1.0),
new Point2D.Double(94.5999984741211,1.0));
c.split(0);
try {
ps = new PrintStream(new FileOutputStream(args[0]));
} catch(java.io.IOException ioe) {
ioe.printStackTrace();
return;
}
ps.println("<?xml version=\"1.0\" standalone=\"no\"?>\n" +
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n" +
"\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n" +
"<svg width=\"450\" height=\"500\"\n" +
" viewBox=\"-100 -100 450 500\"\n" +
" xmlns=\"http://www.w3.org/2000/svg\"\n" +
" xmlns:xlink=\"http://www.w3.org/1999/xlink\">");
check(new Cubic(new Point2D.Double(0, 0),
new Point2D.Double(100, 100),
new Point2D.Double(-50, 100),
new Point2D.Double(50, 0)), 40, ps);
check(new Cubic(new Point2D.Double(100, 0),
new Point2D.Double(200, 100),
new Point2D.Double(50, -50),
new Point2D.Double(150, 30)), 20, ps);
check(new Cubic(new Point2D.Double(200, 0),
new Point2D.Double(300, 100),
new Point2D.Double(150, 100),
new Point2D.Double(250, 0)), 75, ps);
check(new Quadradic(new Point2D.Double(0, 100),
new Point2D.Double(50,150),
new Point2D.Double(10,100)), 115, ps);
check(new Linear(new Point2D.Double(100, 100),
new Point2D.Double(150,150)), 115, ps);
ps.println("</svg>");
}
*/
}
Yet another GeneralPath demo
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GeneralPathDemo extends JPanel {
int x, y, x1, y1, x2, y2;
GeneralPath oddShape;
BufferedImage bi;
Graphics2D big;
boolean firstTime;
Rectangle area;
public GeneralPathDemo() {
oddShape = new GeneralPath();
firstTime = true;
area = new Rectangle();
}
public GeneralPath createPath() {
oddShape.moveTo(20, 30);
oddShape.lineTo(30, 40);
oddShape.lineTo(50, 10);
oddShape.lineTo(70, 20);
oddShape.curveTo(10, 90, 100, 50, 34, 99);
return oddShape;
}
public void paintComponent(Graphics g) {
// super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if (firstTime) {
Dimension dim = getSize();
int w = dim.width;
int h = dim.height;
oddShape = createPath();
area = new Rectangle(w, h);
bi = (BufferedImage) createImage(w, h);
big = bi.createGraphics();
big.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
firstTime = false;
}
// Clears the shape that was previously drawn.
big.setColor(Color.white);
big.fillRect(0, 0, area.width, area.height);
big.setColor(Color.magenta);
big.setStroke(new BasicStroke(3.0f));
big.draw(oddShape);
// Draws the buffered image to the screen.
g2.drawImage(bi, 0, 0, this);
}
public static void main(String s[]) {
JFrame f = new JFrame("Odd Shape");
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.getContentPane().add(new GeneralPathDemo());
f.setSize(new Dimension(350, 200));
f.setVisible(true);
}
}