/* * @(#)RectangleReader.java * * $Date: 2012-03-16 00:55:19 -0500 (Fri, 16 Mar 2012) $ * * 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: * http://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.Rectangle; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.PathIterator; import java.awt.geom.Rectangle2D; import com.bric.math.MathG; /** This can identify if a shape is a Rectangle, Rectangle2D or other. * <P>If a shape is a rectangle, then certain operations can be * optimized. * <P>Also there is a bug when clipping shapes using Quartz on * Mac: a GeneralPath encapsulating exactly the same area * as a Rectangle2D may not clip correctly. If this abstract * shape is instead converted to a Rectangle2D: the bug * goes away! */ public class RectangleReader { /** Returns true if a shape is a rectangle. */ public static boolean isRectangle(Shape s) { return convert(s)!=null; } /** Returns true if a shape is a rectangle when the transform is applied. */ public static boolean isRectangle(Shape s,AffineTransform tx) { return convert(s, tx)!=null; } /** This studies a shape and determines if it is * a <code>Rectangle</code>, a <code>Rectangle2D</code>, * or neither. * * @param shape the shape to study * @return a <code>Rectangle</code>, <code>Rectangle2D</code>, * or <code>null</code>. */ public static final Rectangle2D convert(Shape shape) { return convert(shape, null); } /** This studies a shape and determines if it is * a <code>Rectangle</code>, a <code>Rectangle2D</code>, * or neither. * * @param shape the shape to study * @param transform the optional transform to apply to the shape. * @return a <code>Rectangle</code>, <code>Rectangle2D</code>, * or <code>null</code>. */ public static final Rectangle2D convert(Shape shape,AffineTransform transform) { if(shape==null) return null; if(transform!=null && transform.isIdentity()) transform = null; if(shape instanceof Rectangle && transform==null) return (Rectangle)shape; if(shape instanceof Rectangle2D && transform==null) { Rectangle2D rect = (Rectangle2D)shape; return getRectangle( rect ); } /* Lots of ways we could approach this... * This is a straight-forward logical approach that * could probably stand to be improved performance- * wise: * 1. Get the bounds of the shape. * 2. Iterate over the shape a second time, and see if * all points are collinear with the bounds. * */ double[] data = new double[6]; int k; double lastX = 0; double lastY = 0; PathIterator i = shape.getPathIterator(transform); double left = 0; double right = 0; double top = 0; double bottom = 0; boolean defined = false; double moveX = 0; double moveY = 0; while(i.isDone()==false) { k = i.currentSegment(data); k = SimplifiedPathIterator.simplify(k, lastX, lastY, data); if(k==PathIterator.SEG_CLOSE) { k = PathIterator.SEG_LINETO; data[0] = moveX; data[1] = moveY; } if(k==PathIterator.SEG_MOVETO) { moveX = data[0]; moveY = data[1]; lastX = data[0]; lastY = data[1]; //multiple paths are a deal-breaker if(defined) return null; } else if(k==PathIterator.SEG_CLOSE) { //do nothing } else if(k==PathIterator.SEG_LINETO) { if(defined==false) { left = right = lastX; top = bottom = lastY; defined = true; } else { if(lastX<left) left = lastX; if(lastY<top) top = lastY; if(lastX>right) right = lastX; if(lastY>bottom) bottom = lastY; //either X or Y needs to be the same //in a rectangle: if(lastX!=data[0] && lastY!=data[1]) return null; } if(data[0]<left) left = data[0]; if(data[1]<top) top = data[1]; if(data[0]>right) right = data[0]; if(data[1]>bottom) bottom = data[1]; lastX = data[0]; lastY = data[1]; } else { return null; } i.next(); } if(defined==false) return null; if(lastX!=moveX && lastY!=moveY) return null; i = shape.getPathIterator(transform); while(i.isDone()==false) { k = i.currentSegment(data); k = SimplifiedPathIterator.simplify(k, lastX, lastY, data); if(k==PathIterator.SEG_MOVETO) { lastX = data[0]; lastY = data[1]; } else if(k==PathIterator.SEG_LINETO) { double midX = (data[0]+lastX)/2; double midY = (data[1]+lastY)/2; if(data[1]==top) { if(SimplifiedPathIterator.collinear(left, top, right, top, data[0], data[1])==false) { return null; } } else if(data[1]==bottom) { if(SimplifiedPathIterator.collinear(left, bottom, right, bottom, data[0], data[1])==false) { return null; } } else if(data[0]==left) { if(SimplifiedPathIterator.collinear(left, top, left, bottom, data[0], data[1])==false) { return null; } } else if(data[0]==right) { if(SimplifiedPathIterator.collinear(right, top, right, bottom, data[0], data[1])==false) { return null; } } else { return null; } if(midY==top) { if(SimplifiedPathIterator.collinear(left, top, right, top, midX, midY)==false) { return null; } } else if(midY==bottom) { if(SimplifiedPathIterator.collinear(left, bottom, right, bottom, midX, midY)==false) { return null; } } else if(midX==left) { if(SimplifiedPathIterator.collinear(left, top, left, bottom, midX, midY)==false) { return null; } } else if(midX==right) { if(SimplifiedPathIterator.collinear(right, top, right, bottom, midX, midY)==false) { return null; } } else { return null; } lastX = data[0]; lastY = data[1]; } i.next(); } Rectangle intRect = getRectangle(left,top,right-left,bottom-top); if(intRect!=null) return intRect; return new Rectangle2D.Double(left, top, right-left, bottom-top); } private static final double TOL = .000000000001; /** This checks to see if a Rectangle2D can be expressed as * a int-based Rectangle. * @param r * @return a new <code>Rectangle</code> if possible, or * the original argument if not. */ private static final Rectangle2D getRectangle(Rectangle2D r) { double x = r.getX(); double y = r.getY(); double w = r.getWidth(); double h = r.getHeight(); Rectangle newRect = getRectangle(x,y,w,h); if(newRect!=null) return newRect; return r; } /** This checks to see if a Rectangle2D can be expressed as * a int-based Rectangle. * @param r * @return a new <code>Rectangle</code> if possible, or * the null if not. */ private static final Rectangle getRectangle(double x,double y,double w,double h) { if(w<0) { x = x+w; w = -w; } if(h<0) { y = y+w; h = -h; } int iw = MathG.roundInt(w); int ih = MathG.roundInt(h); if(Math.abs(iw-w)>TOL) return null; if(Math.abs(ih-h)>TOL) return null; int ix = MathG.roundInt(x); int iy = MathG.roundInt(y); if(Math.abs(ix-x)>TOL) return null; if(Math.abs(iy-y)>TOL) return null; return new Rectangle(ix,iy,iw,ih); } }