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