//revised from prefuse display;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.logging.Logger;
/**
* Represents a clipping rectangle in a prefuse <code>Display</code>.
*
* @author <a href="http://jheer.org">jeffrey heer</a>
*/
public class Clip {
private static final byte EMPTY = 0;
private static final byte INUSE = 1;
private static final byte INVALID = 2;
private double[] clip = new double[8];
private byte status = INVALID;
/**
* Reset the clip to an empty status.
*/
public void reset() {
status = EMPTY;
}
/**
* Invalidate the clip. In this state, the clip contents have no meaning.
*/
public void invalidate() {
status = INVALID;
}
/**
* Set the clip contents, and set the status to valid and in use.
* @param c the clip whose contents should be copied
*/
public void setClip(Clip c) {
status = INUSE;
System.arraycopy(c.clip, 0, clip, 0, clip.length);
}
/**
* Set the clip contents, and set the status to valid and in use.
* @param r the clip contents to copy
*/
public void setClip(Rectangle2D r) {
setClip(r.getX(),r.getY(),r.getWidth(),r.getHeight());
}
/**
* Set the clip contents, and set the status to valid and in use.
* @param x the minimum x-coordinate
* @param y the minimum y-coorindate
* @param w the clip width
* @param h the clip height
*/
public void setClip(double x, double y, double w, double h) {
status = INUSE;
clip[0] = x;
clip[1] = y;
clip[6] = x+w;
clip[7] = y+h;
}
/**
* Transform the clip contents. A new clip region will be created
* which is the bounding box of the transformed region.
* @param at the affine transform
*/
public void transform(AffineTransform at) {
// make the extra corner points valid
clip[2] = clip[0]; clip[3] = clip[7];
clip[4] = clip[6]; clip[5] = clip[1];
// transform the points
at.transform(clip,0,clip,0,4);
// make safe against rotation
double xmin = clip[0], ymin = clip[1];
double xmax = clip[6], ymax = clip[7];
for ( int i=0; i<7; i+=2 ) {
if ( clip[i] < xmin )
xmin = clip[i];
if ( clip[i] > xmax )
xmax = clip[i];
if ( clip[i+1] < ymin )
ymin = clip[i+1];
if ( clip[i+1] > ymax )
ymax = clip[i+1];
}
clip[0] = xmin; clip[1] = ymin;
clip[6] = xmax; clip[7] = ymax;
}
/**
* Limit the clip such that it fits within the specified region.
* @param x1 the minimum x-coordinate
* @param y1 the minimum y-coorindate
* @param x2 the maximum x-coordinate
* @param y2 the maximum y-coorindate
*/
public void limit(double x1, double y1, double x2, double y2) {
clip[0] = Math.max(clip[0],x1);
clip[1] = Math.max(clip[1],y1);
clip[6] = Math.min(clip[6],x2);
clip[7] = Math.min(clip[7],y2);
}
/**
* Indicates if this Clip intersects the given rectangle expanded
* by the additional margin pace.
* @param r the rectangle to test for intersect
* @param margin additional margin "bleed" to include in the intersection
* @return true if the clip intersects the expanded region, false otherwise
*/
public boolean intersects(Rectangle2D r, double margin) {
double tw = clip[6]-clip[0];
double th = clip[7]-clip[1];
double rw = r.getWidth();
double rh = r.getHeight();
if (rw < 0 || rh < 0 || tw < 0 || th < 0) {
return false;
}
double tx = clip[0];
double ty = clip[1];
double rx = r.getX()-margin;
double ry = r.getY()-margin;
rw += rx+2*margin;
rh += ry+2*margin;
tw += tx;
th += ty;
// overflow || intersect
return ((rw < rx || rw > tx) &&
(rh < ry || rh > ty) &&
(tw < tx || tw > rx) &&
(th < ty || th > ry));
}
/**
* Union this clip with another clip. As a result, this clip
* will become a bounding box around the two original clips.
* @param c the clip to union with
*/
public void union(Clip c) {
if ( status == INVALID )
return;
if ( status == EMPTY ) {
setClip(c);
status = INUSE;
return;
}
clip[0] = Math.min(clip[0], c.clip[0]);
clip[1] = Math.min(clip[1], c.clip[1]);
clip[6] = Math.max(clip[6], c.clip[6]);
clip[7] = Math.max(clip[7], c.clip[7]);
}
/**
* Union this clip with another region. As a result, this clip
* will become a bounding box around the two original regions.
* @param r the rectangle to union with
*/
public void union(Rectangle2D r) {
if ( status == INVALID )
return;
double minx = r.getMinX();
double miny = r.getMinY();
double maxx = r.getMaxX();
double maxy = r.getMaxY();
if ( Double.isNaN(minx) || Double.isNaN(miny) ||
Double.isNaN(maxx) || Double.isNaN(maxy) ) {
Logger.getLogger(getClass().getName()).warning(
"Union with invalid clip region: "+r);
return;
}
if ( status == EMPTY ) {
setClip(r);
status = INUSE;
return;
}
clip[0] = Math.min(clip[0], minx);
clip[1] = Math.min(clip[1], miny);
clip[6] = Math.max(clip[6], maxx);
clip[7] = Math.max(clip[7], maxy);
}
/**
* Union this clip with another region. As a result, this clip
* will become a bounding box around the two original regions.
* @param x the x-coordinate of the region to union with
* @param y the y-coordinate of the region to union with
* @param w the width of the region to union with
* @param h the height of the region to union with
*/
public void union(double x, double y, double w, double h) {
if ( status == INVALID )
return;
if ( status == EMPTY ) {
setClip(x,y,w,h);
status = INUSE;
return;
}
clip[0] = Math.min(clip[0], x);
clip[1] = Math.min(clip[1], y);
clip[6] = Math.max(clip[6], x+w);
clip[7] = Math.max(clip[7], y+h);
}
/**
* Intersect this clip with another region. As a result, this
* clip will become the intersecting area of the two regions.
* @param c the clip to intersect with
*/
public void intersection(Clip c) {
if ( status == INVALID )
return;
if ( status == EMPTY ) {
setClip(c);
status = INUSE;
return;
}
clip[0] = Math.max(clip[0], c.clip[0]);
clip[1] = Math.max(clip[1], c.clip[1]);
clip[6] = Math.min(clip[6], c.clip[6]);
clip[7] = Math.min(clip[7], c.clip[7]);
}
/**
* Intersect this clip with another region. As a result, this
* clip will become the intersecting area of the two regions.
* @param r the rectangle to intersect with
*/
public void intersection(Rectangle2D r) {
if ( status == INVALID )
return;
if ( status == EMPTY ) {
setClip(r);
status = INUSE;
return;
}
clip[0] = Math.max(clip[0], r.getMinX());
clip[1] = Math.max(clip[1], r.getMinY());
clip[6] = Math.min(clip[6], r.getMaxX());
clip[7] = Math.min(clip[7], r.getMaxY());
}
/**
* Intersect this clip with another region. As a result, this
* clip will become the intersecting area of the two regions.
* @param x the x-coordinate of the region to intersect with
* @param y the y-coordinate of the region to intersect with
* @param w the width of the region to intersect with
* @param h the height of the region to intersect with
*/
public void intersection(double x, double y, double w, double h) {
if ( status == INVALID )
return;
if ( status == EMPTY ) {
setClip(x,y,w,h);
status = INUSE;
return;
}
clip[0] = Math.max(clip[0], x);
clip[1] = Math.max(clip[1], y);
clip[6] = Math.min(clip[6], x+w);
clip[7] = Math.min(clip[7], y+h);
}
/**
* Minimally expand the clip such that each coordinate is an integer.
*/
public void expandToIntegerLimits() {
clip[0] = Math.floor(clip[0]);
clip[1] = Math.floor(clip[1]);
clip[6] = Math.ceil(clip[6]);
clip[7] = Math.ceil(clip[7]);
}
/**
* Expand the clip in all directions by the given value.
* @param b the value to expand by
*/
public void expand(double b) {
clip[0] -= b; clip[1] -= b;
clip[6] += b; clip[7] += b;
}
/**
* Grow the clip width and height by the given value. The minimum
* coordinates will be unchanged.
* @param b the value to grow the width and height by
*/
public void grow(double b) {
clip[6] += b; clip[7] += b;
}
/**
* Get the minimum x-coordinate.
* @return the minimum x-coordinate
*/
public double getMinX() {
return clip[0];
}
/**
* Get the minimum y-coordinate.
* @return the minimum y-coordinate
*/
public double getMinY() {
return clip[1];
}
/**
* Get the maximum x-coordinate.
* @return the maximum x-coordinate
*/
public double getMaxX() {
return clip[6];
}
/**
* Get the maximum y-coordinate.
* @return the maximum y-coordinate
*/
public double getMaxY() {
return clip[7];
}
/**
* Get the clip's width
* @return the clip width
*/
public double getWidth() {
return clip[6]-clip[0];
}
/**
* Get the clip's height
* @return the clip height
*/
public double getHeight() {
return clip[7]-clip[1];
}
/**
* Indicates if the clip is set to an empty status.
* @return true if the clip is set to empty, false otherwise
*/
public boolean isEmpty() {
return status==EMPTY;
}
/**
* Indicates if the clip is set to an invalid status.
* @return true if the clip is set to invalid, false otherwise
*/
public boolean isInvalid() {
return status==INVALID;
}
// ------------------------------------------------------------------------
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object o) {
if ( o instanceof Rectangle2D ) {
Rectangle2D r = (Rectangle2D)o;
return ( r.getMinX()==clip[0] && r.getMinY()==clip[1] &&
r.getMaxX()==clip[6] && r.getMaxY()==clip[7] );
} else if ( o instanceof Clip ) {
Clip r = (Clip)o;
if ( r.status == status ) {
if ( status == Clip.INUSE )
return ( r.clip[0]==clip[0] && r.clip[1]==clip[1] &&
r.clip[6]==clip[6] && r.clip[7]==clip[7] );
else
return true;
} else {
return false;
}
} else {
return false;
}
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
StringBuffer sb = new StringBuffer(20);
sb.append("Clip[");
switch (status) {
case INVALID:
sb.append("invalid");
break;
case EMPTY:
sb.append("empty");
break;
default:
sb.append(clip[0]).append(",");
sb.append(clip[1]).append(",");
sb.append(clip[6]).append(",");
sb.append(clip[7]);
}
sb.append("]");
return sb.toString();
}
} // end of class Clip
|