/*
* @(#)ShapeUtils.java
*
* $Date: 2014-03-26 21:14:52 +0100 (Sze, 26 márc. 2014) $
*
* Copyright (c) 2011 by Jeremy Wood.
* All rights reserved.
*
* The copyright of this software is owned by Jeremy Wood.
* You may not use, copy or modify this software, except in
* accordance with the license agreement you entered into with
* Jeremy Wood. For details see accompanying license terms.
*
* This software is probably, but not necessarily, discussed here:
* https://javagraphics.java.net/
*
* That site should also contain the most recent official version
* of this software. (See the SVN repository for more details.)
*/
package com.bric.geom;
import java.awt.Shape;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
/** This is a collection of static methods relating to java.awt.Shapes.
*/
public class ShapeUtils {
/** Return false if any coordinates in this shape contain NaN or Infinite values. */
public static boolean isValid(Shape shape) {
PathIterator i = shape.getPathIterator(null);
float[] coords = new float[6];
while(!i.isDone()) {
int k = i.currentSegment(coords);
int s = 0;
if(k==PathIterator.SEG_MOVETO || k==PathIterator.SEG_LINETO) {
s = 2;
} else if(k==PathIterator.SEG_QUADTO) {
s = 4;
} else if(k==PathIterator.SEG_CUBICTO) {
s = 6;
}
for(int a = 0; a<s; a++) {
if( Float.isNaN(coords[a]) )
return false;
if( Float.isInfinite(coords[a]) )
return false;
}
i.next();
}
return true;
}
/** This traces the shape provided. This can be used to create
* a "drawing" effect.
* <P>This assumes every segment is equally important/long, which is
* not always the case.
*
* @param shape
* @param progress a float from [0,1], indicating what fraction
* of the shape provided should be traced.
* @return a portion of the shape provided
*/
public static GeneralPath traceShape(Shape shape,float progress) {
if(progress<0 || progress>1) {
//be a little forgiving; sometimes progress may be
//"1.000001" due to rounding errors...
if(progress<-.01) {
throw new IllegalArgumentException("progress cannot be less than zero ("+progress+")");
} else if(progress>1.01) {
throw new IllegalArgumentException("progress cannot be greater than one ("+progress+")");
}
if(progress<0)
progress = 0;
if(progress>1)
progress = 1;
}
float[] f = new float[6];
PathIterator i = shape.getPathIterator(null);
float ctr = 0;
int k;
while(i.isDone()==false) {
k = i.currentSegment(f);
if(k!=PathIterator.SEG_MOVETO && k!=PathIterator.SEG_CLOSE)
ctr++;
i.next();
}
GeneralPath path = new GeneralPath(i.getWindingRule());
i = shape.getPathIterator(null);
float lastX = 0;
float lastY = 0;
float ctr2 = 0;
while(i.isDone()==false) {
k = i.currentSegment(f);
float t = (progress-ctr2/ctr)*ctr;
if(t<=0) return path;
if(t>=1) t = 1;
if(k==PathIterator.SEG_MOVETO) {
path.moveTo(f[0], f[1]);
} else if(k==PathIterator.SEG_LINETO) {
path.lineTo(lastX*(1-t)+t*f[0],lastY*(1-t)+t*f[1]);
} else if(k==PathIterator.SEG_QUADTO) {
if(t>.999999) {
path.quadTo(f[0], f[1], f[2], f[3]);
} else {
double t0 = 0;
double t1 = t;
double ay = lastY-2*f[1]+f[3];
double by = -2*lastY+2*f[1];
double cy = lastY;
double ax = lastX-2*f[0]+f[2];
double bx = -2*lastX+2*f[0];
double cx = lastX;
double tZ = (t0+t1)/2.0;
double f0 = ay*t0*t0+by*t0+cy;
double f1 = ay*tZ*tZ+by*tZ+cy;
double f2 = ay*t1*t1+by*t1+cy;
double ay2 = 2*f2-4*f1+2*f0;
double cy2 = f0;
double by2 = f2-cy2-ay2;
f0 = ax*t0*t0+bx*t0+cx;
f1 = ax*tZ*tZ+bx*tZ+cx;
f2 = ax*t1*t1+bx*t1+cx;
double ax2 = 2*f2-4*f1+2*f0;
double cx2 = f0;
double bx2 = f2-cx2-ax2;
double ctrlY = (2*cy2+by2)/2;
double y1 = ay2-cy2+2*ctrlY;
double ctrlX = (2*cx2+bx2)/2;
double x1 = ax2-cx2+2*ctrlX;
path.quadTo((float)ctrlX, (float)ctrlY,
(float)x1, (float)y1);
}
} else if(k==PathIterator.SEG_CUBICTO) {
if(t>.999999) {
path.curveTo( f[0], f[1], f[2], f[3], f[4], f[5]);
} else {
double t0 = 0;
double t1 = t;
double ay = -lastY+3*f[1]-3*f[3]+f[5];
double by = 3*lastY-6*f[1]+3*f[3];
double cy = -3*lastY+3*f[1];
double dy = lastY;
double ax = -lastX+3*f[0]-3*f[2]+f[4];
double bx = 3*lastX-6*f[0]+3*f[2];
double cx = -3*lastX+3*f[0];
double dx = lastX;
double tW = 2.0*t0/3.0+t1/3.0;
double tZ = t0/3.0+2.0*t1/3.0;
double f0 = ay*t0*t0*t0+by*t0*t0+cy*t0+dy;
double f1 = ay*tW*tW*tW+by*tW*tW+cy*tW+dy;
double f2 = ay*tZ*tZ*tZ+by*tZ*tZ+cy*tZ+dy;
double f3 = ay*t1*t1*t1+by*t1*t1+cy*t1+dy;
double dy2 = f0;
double cy2 = (-11*f0+18*f1-9*f2+2*f3)/2.0;
double by2 = (-19*f0+27*f2-8*f3-10*cy2)/4;
double ay2 = f3-by2-cy2-f0;
f0 = ax*t0*t0*t0+bx*t0*t0+cx*t0+dx;
f1 = ax*tW*tW*tW+bx*tW*tW+cx*tW+dx;
f2 = ax*tZ*tZ*tZ+bx*tZ*tZ+cx*tZ+dx;
f3 = ax*t1*t1*t1+bx*t1*t1+cx*t1+dx;
double dx2 = f0;
double cx2 = (-11*f0+18*f1-9*f2+2*f3)/2.0;
double bx2 = (-19*f0+27*f2-8*f3-10*cx2)/4;
double ax2 = f3-bx2-cx2-f0;
double cy0 = (3*dy2+cy2)/3;
double cy1 = (by2-3*dy2+6*cy0)/3;
double y1 = ay2+dy2-3*cy0+3*cy1;
double cx0 = (3*dx2+cx2)/3;
double cx1 = (bx2-3*dx2+6*cx0)/3;
double x1 = ax2+dx2-3*cx0+3*cx1;
path.curveTo((float)cx0, (float)cy0,
(float)cx1, (float)cy1,
(float)x1, (float)y1);
}
}
if(k!=PathIterator.SEG_MOVETO && k!=PathIterator.SEG_CLOSE)
ctr2++;
i.next();
if(k==PathIterator.SEG_MOVETO || k==PathIterator.SEG_LINETO) {
lastX = f[0];
lastY = f[1];
} else if(k==PathIterator.SEG_QUADTO) {
lastX = f[2];
lastY = f[3];
} else if(k==PathIterator.SEG_CUBICTO) {
lastX = f[4];
lastY = f[5];
}
}
return path;
}
/** TSimilar to tracing, this progresses a dot from the beginning
* to the end of this path.
*
* @param shape
* @param progress a float from [0,1]
*/
public static Point2D getPoint(Shape shape,float progress) {
if(progress<0 || progress>1) {
//be a little forgiving; sometimes progress may be
//"1.000001" due to rounding errors...
if(progress<-.01) {
throw new IllegalArgumentException("progress cannot be less than zero ("+progress+")");
} else if(progress>1.01) {
throw new IllegalArgumentException("progress cannot be greater than one ("+progress+")");
}
if(progress<0)
progress = 0;
if(progress>1)
progress = 1;
}
float[] f = new float[6];
PathIterator i = shape.getPathIterator(null);
float ctr = 0;
int k;
while(i.isDone()==false) {
k = i.currentSegment(f);
if(k!=PathIterator.SEG_MOVETO && k!=PathIterator.SEG_CLOSE)
ctr++;
i.next();
}
i = shape.getPathIterator(null);
float lastX = 0;
float lastY = 0;
float ctr2 = 0;
while(i.isDone()==false) {
k = i.currentSegment(f);
float t = (progress-ctr2/ctr)*ctr;
if(t<=0) return new Point2D.Double(lastX,lastY);
if(t>=1) t = 1;
if(k==PathIterator.SEG_MOVETO) {
lastX = f[0];
lastY = f[1];
} else if(k==PathIterator.SEG_LINETO) {
lastX = lastX*(1-t)+t*f[0];
lastY = lastY*(1-t)+t*f[1];
} else if(k==PathIterator.SEG_QUADTO) {
if(t>.999999) {
lastX = f[2];
lastY = f[3];
} else {
double ay = lastY-2*f[1]+f[3];
double by = -2*lastY+2*f[1];
double cy = lastY;
double ax = lastX-2*f[0]+f[2];
double bx = -2*lastX+2*f[0];
double cx = lastX;
lastX = (float)(ax*t*t+bx*t+cx);
lastY = (float)(ay*t*t+by*t+cy);
}
} else if(k==PathIterator.SEG_CUBICTO) {
if(t>.999999) {
lastX = f[4];
lastY = f[5];
} else {
double ay = -lastY+3*f[1]-3*f[3]+f[5];
double by = 3*lastY-6*f[1]+3*f[3];
double cy = -3*lastY+3*f[1];
double dy = lastY;
double ax = -lastX+3*f[0]-3*f[2]+f[4];
double bx = 3*lastX-6*f[0]+3*f[2];
double cx = -3*lastX+3*f[0];
double dx = lastX;
lastX = (float)(ax*t*t*t+bx*t*t+cx*t+dx);
lastY = (float)(ay*t*t*t+by*t*t+cy*t+dy);
}
}
if(k!=PathIterator.SEG_MOVETO && k!=PathIterator.SEG_CLOSE)
ctr2++;
i.next();
}
return new Point2D.Double(lastX,lastY);
}
/** Returns the number of separate paths in the shape provided. */
public static int getSubPathCount(Shape s) {
PathIterator i = s.getPathIterator(null);
int ctr = 0;
float[] coords = new float[6];
while(i.isDone()==false) {
if(i.currentSegment(coords)==PathIterator.SEG_MOVETO) {
ctr++;
}
i.next();
}
return ctr++;
}
/** Returns each path in s as a separate GeneralPath */
public static GeneralPath[] getSubPaths(Shape s) {
String s2 = ShapeStringUtils.toString(s);
int ctr = 0;
int i = 0;
while(i<s2.length()) {
int k = s2.indexOf('m',i);
if(k==-1) {
i = s2.length();
} else {
ctr++;
i = k+1;
}
}
int[] indices = new int[ctr];
ctr = 0;
i = 0;
while(i<s2.length()) {
int k = s2.indexOf('m',i);
if(k==-1) {
i = s2.length();
} else {
indices[ctr++] = k;
i = k+1;
}
}
GeneralPath[] p = new GeneralPath[ctr];
for(i = 0; i<indices.length; i++) {
String text;
if(i<indices.length-1) {
text = s2.substring(indices[i],indices[i+1]-1);
} else {
text = s2.substring(indices[i]);
}
p[i] = ShapeStringUtils.createGeneralPath(text);
}
return p;
}
/** Return true if two shapes are equal. */
public static boolean equals(Shape shape, Shape shape2) {
PathIterator iter1 = shape.getPathIterator(null);
PathIterator iter2 = shape.getPathIterator(null);
double[] coords1 = new double[6];
double[] coords2 = new double[6];
while((!iter1.isDone()) && (!iter2.isDone())) {
int k1 = iter1.currentSegment(coords1);
int k2 = iter2.currentSegment(coords2);
if(k1!=k2)
return false;
if(k1==PathIterator.SEG_MOVETO || k1==PathIterator.SEG_LINETO) {
if(!equals(coords1, coords2, 2))
return false;
} else if(k1==PathIterator.SEG_QUADTO) {
if(!equals(coords1, coords2, 4))
return false;
} else if(k1==PathIterator.SEG_CUBICTO) {
if(!equals(coords1, coords2, 6))
return false;
} else if(k1==PathIterator.SEG_CLOSE) {
//do nothing
} else {
throw new RuntimeException("unrecognized segment "+k1);
}
iter1.next();
iter2.next();
}
return iter1.isDone() && iter2.isDone();
}
private static boolean equals(double[] array1,double[] array2,int length) {
for(int a = 0; a<length; a++) {
if(array1[a]!=array2[a])
return false;
}
return true;
}
}