package prefuse.util;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RectangularShape;
import java.awt.geom.RoundRectangle2D;
import prefuse.render.AbstractShapeRenderer;
import prefuse.visual.VisualItem;
/**
* Library of useful computer graphics routines such as geometry routines
* for computing the intersection of different shapes and rendering methods
* for computing bounds and performing optimized drawing.
*
* @author <a href="http://jheer.org">jeffrey heer</a>
*/
public class GraphicsLib {
/** Indicates no intersection between shapes */
public static final int NO_INTERSECTION = 0;
/** Indicates intersection between shapes */
public static final int COINCIDENT = -1;
/** Indicates two lines are parallel */
public static final int PARALLEL = -2;
/**
* Compute the intersection of two line segments.
* @param a the first line segment
* @param b the second line segment
* @param intersect a Point in which to store the intersection point
* @return the intersection code. One of {@link #NO_INTERSECTION},
* {@link #COINCIDENT}, or {@link #PARALLEL}.
*/
public static int intersectLineLine(Line2D a, Line2D b, Point2D intersect) {
double a1x = a.getX1(), a1y = a.getY1();
double a2x = a.getX2(), a2y = a.getY2();
double b1x = b.getX1(), b1y = b.getY1();
double b2x = b.getX2(), b2y = b.getY2();
return intersectLineLine(a1x,a1y,a2x,a2y,b1x,b1y,b2x,b2y,intersect);
}
/**
* Compute the intersection of two line segments.
* @param a1x the x-coordinate of the first endpoint of the first line
* @param a1y the y-coordinate of the first endpoint of the first line
* @param a2x the x-coordinate of the second endpoint of the first line
* @param a2y the y-coordinate of the second endpoint of the first line
* @param b1x the x-coordinate of the first endpoint of the second line
* @param b1y the y-coordinate of the first endpoint of the second line
* @param b2x the x-coordinate of the second endpoint of the second line
* @param b2y the y-coordinate of the second endpoint of the second line
* @param intersect a Point in which to store the intersection point
* @return the intersection code. One of {@link #NO_INTERSECTION},
* {@link #COINCIDENT}, or {@link #PARALLEL}.
*/
public static int intersectLineLine(double a1x, double a1y, double a2x,
double a2y, double b1x, double b1y, double b2x, double b2y,
Point2D intersect)
{
double ua_t = (b2x-b1x)*(a1y-b1y)-(b2y-b1y)*(a1x-b1x);
double ub_t = (a2x-a1x)*(a1y-b1y)-(a2y-a1y)*(a1x-b1x);
double u_b = (b2y-b1y)*(a2x-a1x)-(b2x-b1x)*(a2y-a1y);
if ( u_b != 0 ) {
double ua = ua_t / u_b;
double ub = ub_t / u_b;
if ( 0 <= ua && ua <= 1 && 0 <= ub && ub <= 1 ) {
intersect.setLocation(a1x+ua*(a2x-a1x), a1y+ua*(a2y-a1y));
return 1;
} else {
return NO_INTERSECTION;
}
} else {
return ( ua_t == 0 || ub_t == 0 ? COINCIDENT : PARALLEL );
}
}
/**
* Compute the intersection of a line and a rectangle.
* @param a1 the first endpoint of the line
* @param a2 the second endpoint of the line
* @param r the rectangle
* @param pts a length 2 or greater array of points in which to store
* the results
* @return the intersection code. One of {@link #NO_INTERSECTION},
* {@link #COINCIDENT}, or {@link #PARALLEL}.
*/
public static int intersectLineRectangle(Point2D a1, Point2D a2, Rectangle2D r, Point2D[] pts) {
double a1x = a1.getX(), a1y = a1.getY();
double a2x = a2.getX(), a2y = a2.getY();
double mxx = r.getMaxX(), mxy = r.getMaxY();
double mnx = r.getMinX(), mny = r.getMinY();
if ( pts[0] == null ) pts[0] = new Point2D.Double();
if ( pts[1] == null ) pts[1] = new Point2D.Double();
int i = 0;
if ( intersectLineLine(mnx,mny,mxx,mny,a1x,a1y,a2x,a2y,pts[i]) > 0 ) i++;
if ( intersectLineLine(mxx,mny,mxx,mxy,a1x,a1y,a2x,a2y,pts[i]) > 0 ) i++;
if ( i == 2 ) return i;
if ( intersectLineLine(mxx,mxy,mnx,mxy,a1x,a1y,a2x,a2y,pts[i]) > 0 ) i++;
if ( i == 2 ) return i;
if ( intersectLineLine(mnx,mxy,mnx,mny,a1x,a1y,a2x,a2y,pts[i]) > 0 ) i++;
return i;
}
/**
* Compute the intersection of a line and a rectangle.
* @param l the line
* @param r the rectangle
* @param pts a length 2 or greater array of points in which to store
* the results
* @return the intersection code. One of {@link #NO_INTERSECTION},
* {@link #COINCIDENT}, or {@link #PARALLEL}.
*/
public static int intersectLineRectangle(Line2D l, Rectangle2D r, Point2D[] pts) {
double a1x = l.getX1(), a1y = l.getY1();
double a2x = l.getX2(), a2y = l.getY2();
double mxx = r.getMaxX(), mxy = r.getMaxY();
double mnx = r.getMinX(), mny = r.getMinY();
if ( pts[0] == null ) pts[0] = new Point2D.Double();
if ( pts[1] == null ) pts[1] = new Point2D.Double();
int i = 0;
if ( intersectLineLine(mnx,mny,mxx,mny,a1x,a1y,a2x,a2y,pts[i]) > 0 ) i++;
if ( intersectLineLine(mxx,mny,mxx,mxy,a1x,a1y,a2x,a2y,pts[i]) > 0 ) i++;
if ( i == 2 ) return i;
if ( intersectLineLine(mxx,mxy,mnx,mxy,a1x,a1y,a2x,a2y,pts[i]) > 0 ) i++;
if ( i == 2 ) return i;
if ( intersectLineLine(mnx,mxy,mnx,mny,a1x,a1y,a2x,a2y,pts[i]) > 0 ) i++;
return i;
}
/**
* Computes the 2D convex hull of a set of points using Graham's
* scanning algorithm. The algorithm has been implemented as described
* in Cormen, Leiserson, and Rivest's Introduction to Algorithms.
*
* The running time of this algorithm is O(n log n), where n is
* the number of input points.
*
* @param pts the input points in [x0,y0,x1,y1,...] order
* @param len the length of the pts array to consider (2 * #points)
* @return the convex hull of the input points
*/
public static double[] convexHull(double[] pts, int len) {
if (len < 6) {
throw new IllegalArgumentException(
"Input must have at least 3 points");
}
int plen = len/2-1;
float[] angles = new float[plen];
int[] idx = new int[plen];
int[] stack = new int[len/2];
return convexHull(pts, len, angles, idx, stack);
}
/**
* Computes the 2D convex hull of a set of points using Graham's
* scanning algorithm. The algorithm has been implemented as described
* in Cormen, Leiserson, and Rivest's Introduction to Algorithms.
*
* The running time of this algorithm is O(n log n), where n is
* the number of input points.
*
* @param pts
* @return the convex hull of the input points
*/
public static double[] convexHull(double[] pts, int len,
float[] angles, int[] idx, int[] stack)
{
// check arguments
int plen = len/2 - 1;
if (len < 6) {
throw new IllegalArgumentException(
"Input must have at least 3 points");
}
if (angles.length < plen || idx.length < plen || stack.length < len/2) {
throw new IllegalArgumentException(
"Pre-allocated data structure too small");
}
int i0 = 0;
// find the starting ref point: leftmost point with the minimum y coord
for ( int i=2; i < len; i += 2 ) {
if ( pts[i+1] < pts[i0+1] ) {
i0 = i;
} else if ( pts[i+1] == pts[i0+1] ) {
i0 = (pts[i] < pts[i0] ? i : i0);
}
}
// calculate polar angles from ref point and sort
for ( int i=0, j=0; i < len; i+=2 ) {
if ( i == i0 ) continue;
angles[j] = (float)Math.atan2(pts[i+1]-pts[i0+1], pts[i]-pts[i0]);
idx[j++] = i;
}
ArrayLib.sort(angles,idx,plen);
// toss out duplicated angles
float angle = angles[0];
int ti = 0, tj = idx[0];
for ( int i=1; i<plen; i++ ) {
int j = idx[i];
if ( angle == angles[i] ) {
// keep whichever angle corresponds to the most distant
// point from the reference point
double x1 = pts[tj] - pts[i0];
double y1 = pts[tj+1] - pts[i0+1];
double x2 = pts[j] - pts[i0];
double y2 = pts[j+1] - pts[i0+1];
double d1 = x1*x1 + y1*y1;
double d2 = x2*x2 + y2*y2;
if ( d1 >= d2 ) {
idx[i] = -1;
} else {
idx[ti] = -1;
angle = angles[i];
ti = i;
tj = j;
}
} else {
angle = angles[i];
ti = i;
tj = j;
}
}
// initialize our stack
int sp = 0;
stack[sp++] = i0;
int j = 0;
for ( int k=0; k<2; j++ ) {
if ( idx[j] != -1 ) {
stack[sp++] = idx[j];
k++;
}
}
// do graham's scan
for ( ; j < plen; j++ ) {
if ( idx[j] == -1 ) continue; // skip tossed out points
while ( isNonLeft(i0, stack[sp-2], stack[sp-1], idx[j], pts) ) {
sp--;
}
stack[sp++] = idx[j];
}
// construct the hull
double[] hull = new double[2*sp];
for ( int i=0; i<sp; i++ ) {
hull[2*i] = pts[stack[i]];
hull[2*i+1] = pts[stack[i]+1];
}
return hull;
}
/**
* Convex hull helper method for detecting a non left turn about 3 points
*/
private static boolean isNonLeft(int i0, int i1, int i2, int i3, double[] pts) {
double l1, l2, l4, l5, l6, angle1, angle2, angle;
l1 = Math.sqrt(Math.pow(pts[i2+1]-pts[i1+1],2) + Math.pow(pts[i2]-pts[i1],2));
l2 = Math.sqrt(Math.pow(pts[i3+1]-pts[i2+1],2) + Math.pow(pts[i3]-pts[i2],2));
l4 = Math.sqrt(Math.pow(pts[i3+1]-pts[i0+1],2) + Math.pow(pts[i3]-pts[i0],2));
l5 = Math.sqrt(Math.pow(pts[i1+1]-pts[i0+1],2) + Math.pow(pts[i1]-pts[i0],2));
l6 = Math.sqrt(Math.pow(pts[i2+1]-pts[i0+1],2) + Math.pow(pts[i2]-pts[i0],2));
angle1 = Math.acos( ( (l2*l2)+(l6*l6)-(l4*l4) ) / (2*l2*l6) );
angle2 = Math.acos( ( (l6*l6)+(l1*l1)-(l5*l5) ) / (2*l6*l1) );
angle = (Math.PI - angle1) - angle2;
if (angle <= 0.0) {
return(true);
} else {
return(false);
}
}
/**
* Computes the mean, or centroid, of a set of points
* @param pts the points array, in x1, y1, x2, y2, ... arrangement.
* @param len the length of the array to consider
* @return the centroid as a length-2 float array
*/
public static float[] centroid(float pts[], int len) {
float[] c = new float[] {0, 0};
for ( int i=0; i < len; i+=2 ) {
c[0] += pts[i];
c[1] += pts[i+1];
}
c[0] /= len/2;
c[1] /= len/2;
return c;
}
/**
* Expand a polygon by adding the given distance along the line from
* the centroid of the polyong.
* @param pts the polygon to expand, a set of points in a float array
* @param len the length of the range of the array to consider
* @param amt the amount by which to expand the polygon, each point
* will be moved this distance along the line from the centroid of the
* polygon to the given point.
*/
public static void growPolygon(float pts[], int len, float amt) {
float[] c = centroid(pts, len);
for ( int i=0; i < len; i+=2 ) {
float vx = pts[i]-c[0];
float vy = pts[i+1]-c[1];
float norm = (float)Math.sqrt(vx*vx+vy*vy);
pts[i] += amt*vx/norm;
pts[i+1] += amt*vy/norm;
}
}
/**
* Compute a cardinal spline, a series of cubic Bezier splines smoothly
* connecting a set of points. Cardinal splines maintain C(1)
* continuity, ensuring the connected spline segments form a differentiable
* curve, ensuring at least a minimum level of smoothness.
* @param pts the points to interpolate with a cardinal spline
* @param slack a parameter controlling the "tightness" of the spline to
* the control points, 0.10 is a typically suitable value
* @param closed true if the cardinal spline should be closed (i.e. return
* to the starting point), false for an open curve
* @return the cardinal spline as a Java2D {@link java.awt.geom.GeneralPath}
* instance.
*/
public static GeneralPath cardinalSpline(float pts[], float slack, boolean closed) {
GeneralPath path = new GeneralPath();
path.moveTo(pts[0], pts[1]);
return cardinalSpline(path, pts, slack, closed, 0f, 0f);
}
/**
* Compute a cardinal spline, a series of cubic Bezier splines smoothly
* connecting a set of points. Cardinal splines maintain C(1)
* continuity, ensuring the connected spline segments form a differentiable
* curve, ensuring at least a minimum level of smoothness.
* @param pts the points to interpolate with a cardinal spline
* @param start the starting index from which to read points
* @param npoints the number of points to consider
* @param slack a parameter controlling the "tightness" of the spline to
* the control points, 0.10 is a typically suitable value
* @param closed true if the cardinal spline should be closed (i.e. return
* to the starting point), false for an open curve
* @return the cardinal spline as a Java2D {@link java.awt.geom.GeneralPath}
* instance.
*/
public static GeneralPath cardinalSpline(float pts[], int start, int npoints,
float slack, boolean closed)
{
GeneralPath path = new GeneralPath();
path.moveTo(pts[start], pts[start+1]);
return cardinalSpline(path, pts, start, npoints, slack, closed, 0f, 0f);
}
/**
* Compute a cardinal spline, a series of cubic Bezier splines smoothly
* connecting a set of points. Cardinal splines maintain C(1)
* continuity, ensuring the connected spline segments form a differentiable
* curve, ensuring at least a minimum level of smoothness.
* @param p the GeneralPath instance to use to store the result
* @param pts the points to interpolate with a cardinal spline
* @param slack a parameter controlling the "tightness" of the spline to
* the control points, 0.10 is a typically suitable value
* @param closed true if the cardinal spline should be closed (i.e. return
* to the starting point), false for an open curve
* @param tx a value by which to translate the curve along the x-dimension
* @param ty a value by which to translate the curve along the y-dimension
* @return the cardinal spline as a Java2D {@link java.awt.geom.GeneralPath}
* instance.
*/
public static GeneralPath cardinalSpline(GeneralPath p,
float pts[], float slack, boolean closed, float tx, float ty)
{
int npoints = 0;
for ( ; npoints<pts.length; ++npoints )
if ( Float.isNaN(pts[npoints]) ) break;
return cardinalSpline(p, pts, 0, npoints/2, slack, closed, tx, ty);
}
/**
* Compute a cardinal spline, a series of cubic Bezier splines smoothly
* connecting a set of points. Cardinal splines maintain C(1)
* continuity, ensuring the connected spline segments form a differentiable
* curve, ensuring at least a minimum level of smoothness.
* @param p the GeneralPath instance to use to store the result
* @param pts the points to interpolate with a cardinal spline
* @param start the starting index from which to read points
* @param npoints the number of points to consider
* @param slack a parameter controlling the "tightness" of the spline to
* the control points, 0.10 is a typically suitable value
* @param closed true if the cardinal spline should be closed (i.e. return
* to the starting point), false for an open curve
* @param tx a value by which to translate the curve along the x-dimension
* @param ty a value by which to translate the curve along the y-dimension
* @return the cardinal spline as a Java2D {@link java.awt.geom.GeneralPath}
* instance.
*/
public static GeneralPath cardinalSpline(GeneralPath p,
float pts[], int start, int npoints,
float slack, boolean closed, float tx, float ty)
{
// compute the size of the path
int len = 2*npoints;
int end = start+len;
if ( len < 6 ) {
throw new IllegalArgumentException(
"To create spline requires at least 3 points");
}
float dx1, dy1, dx2, dy2;
// compute first control point
if ( closed ) {
dx2 = pts[start+2]-pts[end-2];
dy2 = pts[start+3]-pts[end-1];
} else {
dx2 = pts[start+4]-pts[start];
dy2 = pts[start+5]-pts[start+1];
}
// repeatedly compute next control point and append curve
int i;
for ( i=start+2; i<end-2; i+=2 ) {
dx1 = dx2; dy1 = dy2;
dx2 = pts[i+2]-pts[i-2];
dy2 = pts[i+3]-pts[i-1];
p.curveTo(tx+pts[i-2]+slack*dx1, ty+pts[i-1]+slack*dy1,
tx+pts[i] -slack*dx2, ty+pts[i+1]-slack*dy2,
tx+pts[i], ty+pts[i+1]);
}
// compute last control point
if ( closed ) {
dx1 = dx2; dy1 = dy2;
dx2 = pts[start]-pts[i-2];
dy2 = pts[start+1]-pts[i-1];
p.curveTo(tx+pts[i-2]+slack*dx1, ty+pts[i-1]+slack*dy1,
tx+pts[i] -slack*dx2, ty+pts[i+1]-slack*dy2,
tx+pts[i], ty+pts[i+1]);
dx1 = dx2; dy1 = dy2;
dx2 = pts[start+2]-pts[end-2];
dy2 = pts[start+3]-pts[end-1];
p.curveTo(tx+pts[end-2]+slack*dx1, ty+pts[end-1]+slack*dy1,
tx+pts[0] -slack*dx2, ty+pts[1] -slack*dy2,
tx+pts[0], ty+pts[1]);
p.closePath();
} else {
p.curveTo(tx+pts[i-2]+slack*dx2, ty+pts[i-1]+slack*dy2,
tx+pts[i] -slack*dx2, ty+pts[i+1]-slack*dy2,
tx+pts[i], ty+pts[i+1]);
}
return p;
}
/**
* Computes a set of curves using the cardinal spline approach, but
* using straight lines for completely horizontal or vertical segments.
* @param p the GeneralPath instance to use to store the result
* @param pts the points to interpolate with the spline
* @param epsilon threshold value under which to treat the difference
* between two values to be zero. Used to determine which segments to
* treat as lines rather than curves.
* @param slack a parameter controlling the "tightness" of the spline to
* the control points, 0.10 is a typically suitable value
* @param closed true if the spline should be closed (i.e. return
* to the starting point), false for an open curve
* @param tx a value by which to translate the curve along the x-dimension
* @param ty a value by which to translate the curve along the y-dimension
* @return the stack spline as a Java2D {@link java.awt.geom.GeneralPath}
* instance.
*/
public static GeneralPath stackSpline(GeneralPath p, float[] pts,
float epsilon, float slack, boolean closed, float tx, float ty)
{
int npoints = 0;
for ( ; npoints<pts.length; ++npoints )
if ( Float.isNaN(pts[npoints]) ) break;
return stackSpline(p,pts,0,npoints/2,epsilon,slack,closed,tx,ty);
}
/**
* Computes a set of curves using the cardinal spline approach, but
* using straight lines for completely horizontal or vertical segments.
* @param p the GeneralPath instance to use to store the result
* @param pts the points to interpolate with the spline
* @param start the starting index from which to read points
* @param npoints the number of points to consider
* @param epsilon threshold value under which to treat the difference
* between two values to be zero. Used to determine which segments to
* treat as lines rather than curves.
* @param slack a parameter controlling the "tightness" of the spline to
* the control points, 0.10 is a typically suitable value
* @param closed true if the spline should be closed (i.e. return
* to the starting point), false for an open curve
* @param tx a value by which to translate the curve along the x-dimension
* @param ty a value by which to translate the curve along the y-dimension
* @return the stack spline as a Java2D {@link java.awt.geom.GeneralPath}
* instance.
*/
public static GeneralPath stackSpline(GeneralPath p,
float pts[], int start, int npoints, float epsilon,
float slack, boolean closed, float tx, float ty)
{
// compute the size of the path
int len = 2*npoints;
int end = start+len;
if ( len < 6 ) {
throw new IllegalArgumentException(
"To create spline requires at least 3 points");
}
float dx1, dy1, dx2, dy2;
// compute first control point
if ( closed ) {
dx2 = pts[start+2]-pts[end-2];
dy2 = pts[start+3]-pts[end-1];
} else {
dx2 = pts[start+4]-pts[start];
dy2 = pts[start+5]-pts[start+1];
}
// repeatedly compute next control point and append curve
int i;
for ( i=start+2; i<end-2; i+=2 ) {
dx1 = dx2; dy1 = dy2;
dx2 = pts[i+2]-pts[i-2];
dy2 = pts[i+3]-pts[i-1];
if ( Math.abs(pts[i] -pts[i-2]) < epsilon ||
Math.abs(pts[i+1]-pts[i-1]) < epsilon )
{
p.lineTo(tx+pts[i], ty+pts[i+1]);
} else {
p.curveTo(tx+pts[i-2]+slack*dx1, ty+pts[i-1]+slack*dy1,
tx+pts[i] -slack*dx2, ty+pts[i+1]-slack*dy2,
tx+pts[i], ty+pts[i+1]);
}
}
// compute last control point
dx1 = dx2; dy1 = dy2;
dx2 = pts[start]-pts[i-2];
dy2 = pts[start+1]-pts[i-1];
if ( Math.abs(pts[i] -pts[i-2]) < epsilon ||
Math.abs(pts[i+1]-pts[i-1]) < epsilon )
{
p.lineTo(tx+pts[i], ty+pts[i+1]);
} else {
p.curveTo(tx+pts[i-2]+slack*dx1, ty+pts[i-1]+slack*dy1,
tx+pts[i] -slack*dx2, ty+pts[i+1]-slack*dy2,
tx+pts[i], ty+pts[i+1]);
}
// close the curve if requested
if ( closed ) {
if ( Math.abs(pts[end-2]-pts[0]) < epsilon ||
Math.abs(pts[end-1]-pts[1]) < epsilon )
{
p.lineTo(tx+pts[0], ty+pts[1]);
} else {
dx1 = dx2; dy1 = dy2;
dx2 = pts[start+2]-pts[end-2];
dy2 = pts[start+3]-pts[end-1];
p.curveTo(tx+pts[end-2]+slack*dx1, ty+pts[end-1]+slack*dy1,
tx+pts[0] -slack*dx2, ty+pts[1] -slack*dy2,
tx+pts[0], ty+pts[1]);
}
p.closePath();
}
return p;
}
/**
* Expand a rectangle by the given amount.
* @param r the rectangle to expand
* @param amount the amount by which to expand the rectangle
*/
public static void expand(Rectangle2D r, double amount) {
r.setRect(r.getX()-amount, r.getY()-amount,
r.getWidth()+2*amount, r.getHeight()+2*amount);
}
// ------------------------------------------------------------------------
/**
* Sets a VisualItem's bounds based on its shape and stroke type. This
* method is optimized to avoid calling .getBounds2D where it can, thus
* avoiding object initialization and reducing object churn.
* @param item the VisualItem whose bounds are to be set
* @param shape a Shape from which to determine the item bounds
* @param stroke the stroke type that will be used for drawing the object,
* and may affect the final bounds. A null value indicates the
* default (line width = 1) stroke is used.
*/
public static void setBounds(VisualItem item,
Shape shape, BasicStroke stroke)
{
double x, y, w, h, lw, lw2;
if ( shape instanceof RectangularShape ) {
// this covers rectangle, rounded rectangle, ellipse, and arcs
RectangularShape r = (RectangularShape)shape;
x = r.getX();
y = r.getY();
w = r.getWidth();
h = r.getHeight();
} else if ( shape instanceof Line2D ) {
// this covers straight lines
Line2D l = (Line2D)shape;
x = l.getX1();
y = l.getY1();
w = l.getX2();
h = l.getY2();
if ( w < x ) {
lw = x;
x = w;
w = lw-x;
} else {
w = w-x;
}
if ( h < y ) {
lw = y;
y = h;
h = lw-y;
} else {
h = h-y;
}
} else {
// this covers any other arbitrary shapes, but
// takes a small object allocation / garbage collection hit
Rectangle2D r = shape.getBounds2D();
x = r.getX();
y = r.getY();
w = r.getWidth();
h = r.getHeight();
}
// adjust boundary for stoke length as necessary
if ( stroke != null && (lw=stroke.getLineWidth()) > 1 ) {
lw2 = lw/2.0;
x -= lw2; y -= lw2; w += lw; h += lw;
}
item.setBounds(x, y, w, h);
}
/**
* Render a shape associated with a VisualItem into a graphics context. This
* method uses the {@link java.awt.Graphics} interface methods when it can,
* as opposed to the {@link java.awt.Graphics2D} methods such as
* {@link java.awt.Graphics2D#draw(java.awt.Shape)} and
* {@link java.awt.Graphics2D#fill(java.awt.Shape)}, resulting in a
* significant performance increase on the Windows platform, particularly
* for rectangle and line drawing calls.
* @param g the graphics context to render to
* @param item the item being represented by the shape, this instance is
* used to get the correct color values for the drawing
* @param shape the shape to render
* @param stroke the stroke type to use for drawing the object.
* @param type the rendering type indicating if the shape should be drawn,
* filled, or both. One of
* {@link prefuse.render.AbstractShapeRenderer#RENDER_TYPE_DRAW},
* {@link prefuse.render.AbstractShapeRenderer#RENDER_TYPE_FILL},
* {@link prefuse.render.AbstractShapeRenderer#RENDER_TYPE_DRAW_AND_FILL}, or
* {@link prefuse.render.AbstractShapeRenderer#RENDER_TYPE_NONE}.
*/
public static void paint(Graphics2D g, VisualItem item,
Shape shape, BasicStroke stroke, int type)
{
// if render type is NONE, then there is nothing to do
if ( type == AbstractShapeRenderer.RENDER_TYPE_NONE )
return;
// set up colors
Color strokeColor = ColorLib.getColor(item.getStrokeColor());
Color fillColor = ColorLib.getColor(item.getFillColor());
boolean sdraw = (type == AbstractShapeRenderer.RENDER_TYPE_DRAW ||
type == AbstractShapeRenderer.RENDER_TYPE_DRAW_AND_FILL) &&
strokeColor.getAlpha() != 0;
boolean fdraw = (type == AbstractShapeRenderer.RENDER_TYPE_FILL ||
type == AbstractShapeRenderer.RENDER_TYPE_DRAW_AND_FILL) &&
fillColor.getAlpha() != 0;
if ( !(sdraw || fdraw) ) return;
Stroke origStroke = null;
if ( sdraw ) {
origStroke = g.getStroke();
g.setStroke(stroke);
}
int x, y, w, h, aw, ah;
double xx, yy, ww, hh;
// see if an optimized (non-shape) rendering call is available for us
// these can speed things up significantly on the windows JRE
// it is stupid we have to do this, but we do what we must
// if we are zoomed in, we have no choice but to use
// full precision rendering methods.
AffineTransform at = g.getTransform();
double scale = Math.max(at.getScaleX(), at.getScaleY());
if ( scale > 1.5 ) {
if (fdraw) { g.setPaint(fillColor); g.fill(shape); }
if (sdraw) { g.setPaint(strokeColor); g.draw(shape); }
}
else if ( shape instanceof RectangularShape )
{
RectangularShape r = (RectangularShape)shape;
xx = r.getX(); ww = r.getWidth();
yy = r.getY(); hh = r.getHeight();
x = (int)xx;
y = (int)yy;
w = (int)(ww+xx-x);
h = (int)(hh+yy-y);
if ( shape instanceof Rectangle2D ) {
if (fdraw) {
g.setPaint(fillColor);
g.fillRect(x, y, w, h);
}
if (sdraw) {
g.setPaint(strokeColor);
g.drawRect(x, y, w, h);
}
} else if ( shape instanceof RoundRectangle2D ) {
RoundRectangle2D rr = (RoundRectangle2D)shape;
aw = (int)rr.getArcWidth();
ah = (int)rr.getArcHeight();
if (fdraw) {
g.setPaint(fillColor);
g.fillRoundRect(x, y, w, h, aw, ah);
}
if (sdraw) {
g.setPaint(strokeColor);
g.drawRoundRect(x, y, w, h, aw, ah);
}
} else if ( shape instanceof Ellipse2D ) {
if (fdraw) {
g.setPaint(fillColor);
g.fillOval(x, y, w, h);
}
if (sdraw) {
g.setPaint(strokeColor);
g.drawOval(x, y, w, h);
}
} else {
if (fdraw) { g.setPaint(fillColor); g.fill(shape); }
if (sdraw) { g.setPaint(strokeColor); g.draw(shape); }
}
} else if ( shape instanceof Line2D ) {
if (sdraw) {
Line2D l = (Line2D)shape;
x = (int)(l.getX1()+0.5);
y = (int)(l.getY1()+0.5);
w = (int)(l.getX2()+0.5);
h = (int)(l.getY2()+0.5);
g.setPaint(strokeColor);
g.drawLine(x, y, w, h);
}
} else {
if (fdraw) { g.setPaint(fillColor); g.fill(shape); }
if (sdraw) { g.setPaint(strokeColor); g.draw(shape); }
}
if ( sdraw ) {
g.setStroke(origStroke);
}
}
} // end of class GraphicsLib