// Charles A. Loomis, Jr., and University of California, Santa Cruz, // Copyright (c) 2000 package org.freehep.swing.graphics; import java.awt.Color; import java.awt.Cursor; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.util.Arrays; import org.freehep.swing.images.FreeHepImage; /** * A panel which selects a rectangular region on the screen which can * be arbitrarily rotated. * * @author Charles Loomis * @author Mark Donszelmann * @version $Id: RotatedRectangleSelectionPanel.java 8584 2006-08-10 23:06:37Z duns $ */ public class RotatedRectangleSelectionPanel extends AbstractRegionSelectionPanel { /** * The initial starting width of the first and last sides. */ final private static int STARTING_WIDTH = 25; /** * Creates a RotatedRectangleSelectionPanel. */ public RotatedRectangleSelectionPanel() { super(); } /** * The number of control points is 6---the four corners and the * centerpoints of the first and last sides. * * @return 6 the number of control points */ public int getNumberOfControlPoints() { return 6; } public Cursor getControlPointCursor(int index) { int k; String type; switch(index) { case 0: case 3: k = 4; type = "Resize"; break; case 1: case 2: k = 5; type = "Resize"; break; case 4: k = 5; type = "Rotation"; break; case 5: k = 4; type = "Rotation"; break; default: return FreeHepImage.getCursor("RotatedRectangleCursor"); } return compassCursor(type, xCtrlPts[index] - xCtrlPts[k], yCtrlPts[index] - yCtrlPts[k], 8, true); } /** * Initialize the control points given the starting point (x,y). * * @param x x-coordinate of the starting point * @param y y-coordinate of the starting point */ public void initializeControlPoints(int x, int y) { // Set the fifth control point to be the active one and // initialize all of the coordinates. activeCtrlPt = 5; Arrays.fill(yCtrlPts,y); xCtrlPts[0] = x-STARTING_WIDTH; xCtrlPts[1] = x-STARTING_WIDTH; xCtrlPts[2] = x+STARTING_WIDTH; xCtrlPts[3] = x+STARTING_WIDTH; xCtrlPts[4] = x; xCtrlPts[5] = x; } /** * Move the active control point to the point (x,y). * * @param x x-coordinate of the new point * @param y y-coordinate of the new point */ public void updateActiveControlPoint(int x, int y) { // Bring the location within bounds. x = forceXCoordinateWithinBounds(x); y = forceYCoordinateWithinBounds(y); // Change what is done depending on which control point is // active. int dx; int dy; int deltax; int deltay; double angle; double radius; switch (activeCtrlPt) { case 0: // Determine the radius of the corners from the middle of // the control sides. radius = getRadius(x,y,4); // Get the angle of the rotated rectangle. angle = getAngle(); deltax = x - xCtrlPts[4]; deltay = y - yCtrlPts[4]; dx = (int) -Math.round(radius*Math.sin(angle)); dy = (int) Math.round(radius*Math.cos(angle)); if (deltax*dx+deltay*dy < 0) { dx = -dx; dy = -dy; } // Update the other control points. xCtrlPts[0] = xCtrlPts[4]+dx; yCtrlPts[0] = yCtrlPts[4]+dy; xCtrlPts[3] = xCtrlPts[4]-dx; yCtrlPts[3] = yCtrlPts[4]-dy; xCtrlPts[1] = xCtrlPts[5]+dx; yCtrlPts[1] = yCtrlPts[5]+dy; xCtrlPts[2] = xCtrlPts[5]-dx; yCtrlPts[2] = yCtrlPts[5]-dy; break; case 1: // Determine the radius of the corners from the middle of // the control sides. radius = getRadius(x,y,5); // Get the angle of the rotated rectangle. angle = getAngle(); deltax = x - xCtrlPts[5]; deltay = y - yCtrlPts[5]; dx = (int) -Math.round(radius*Math.sin(angle)); dy = (int) Math.round(radius*Math.cos(angle)); if (deltax*dx+deltay*dy < 0) { dx = -dx; dy = -dy; } // Update the other control points. xCtrlPts[0] = xCtrlPts[4]+dx; yCtrlPts[0] = yCtrlPts[4]+dy; xCtrlPts[3] = xCtrlPts[4]-dx; yCtrlPts[3] = yCtrlPts[4]-dy; xCtrlPts[1] = xCtrlPts[5]+dx; yCtrlPts[1] = yCtrlPts[5]+dy; xCtrlPts[2] = xCtrlPts[5]-dx; yCtrlPts[2] = yCtrlPts[5]-dy; break; case 2: // Determine the radius of the corners from the middle of // the control sides. radius = getRadius(x,y,5); // Get the angle of the rotated rectangle. angle = getAngle(); deltax = x - xCtrlPts[5]; deltay = y - yCtrlPts[5]; dx = (int) -Math.round(radius*Math.sin(angle)); dy = (int) Math.round(radius*Math.cos(angle)); if (deltax*dx+deltay*dy < 0) { dx = -dx; dy = -dy; } // Update the other control points. xCtrlPts[0] = xCtrlPts[4]-dx; yCtrlPts[0] = yCtrlPts[4]-dy; xCtrlPts[3] = xCtrlPts[4]+dx; yCtrlPts[3] = yCtrlPts[4]+dy; xCtrlPts[1] = xCtrlPts[5]-dx; yCtrlPts[1] = yCtrlPts[5]-dy; xCtrlPts[2] = xCtrlPts[5]+dx; yCtrlPts[2] = yCtrlPts[5]+dy; break; case 3: // Determine the radius of the corners from the middle of // the control sides. radius = getRadius(x,y,4); // Get the angle of the rotated rectangle. angle = getAngle(); deltax = x - xCtrlPts[4]; deltay = y - yCtrlPts[4]; dx = (int) -Math.round(radius*Math.sin(angle)); dy = (int) Math.round(radius*Math.cos(angle)); if (deltax*dx+deltay*dy < 0) { dx = -dx; dy = -dy; } // Update the other control points. xCtrlPts[0] = xCtrlPts[4]-dx; yCtrlPts[0] = yCtrlPts[4]-dy; xCtrlPts[3] = xCtrlPts[4]+dx; yCtrlPts[3] = yCtrlPts[4]+dy; xCtrlPts[1] = xCtrlPts[5]-dx; yCtrlPts[1] = yCtrlPts[5]-dy; xCtrlPts[2] = xCtrlPts[5]+dx; yCtrlPts[2] = yCtrlPts[5]+dy; break; case 4: /* Fall through! */ case 5: // Determine the radius of the corners from the middle of // the control sides. radius = getRadius(xCtrlPts[0], yCtrlPts[0], 4); // Update the active control point. xCtrlPts[activeCtrlPt] = x; yCtrlPts[activeCtrlPt] = y; // Get the angle of the rotated rectangle. angle = getAngle(); dx = (int) -Math.round(radius*Math.sin(angle)); dy = (int) Math.round(radius*Math.cos(angle)); // Update the other control points. xCtrlPts[0] = xCtrlPts[4]+dx; yCtrlPts[0] = yCtrlPts[4]+dy; xCtrlPts[3] = xCtrlPts[4]-dx; yCtrlPts[3] = yCtrlPts[4]-dy; xCtrlPts[1] = xCtrlPts[5]+dx; yCtrlPts[1] = yCtrlPts[5]+dy; xCtrlPts[2] = xCtrlPts[5]-dx; yCtrlPts[2] = yCtrlPts[5]-dy; break; default: break; } repaintPanel(); } /** * A utility routine to get the radius from one of the control * points. */ private double getRadius(int x, int y, int ctrlPt) { int dx = x - xCtrlPts[ctrlPt]; int dy = y - yCtrlPts[ctrlPt]; return Math.sqrt((double) (dx*dx+dy*dy)); } /** * A utility routine to get the angle of the rotated rectangle. */ private double getAngle() { // Get the angle of the rotated rectangle. double deltax = xCtrlPts[5] - xCtrlPts[4]; double deltay = yCtrlPts[5] - yCtrlPts[4]; if (deltax!=0 || deltay!=0) { return Math.atan2(deltay,deltax); } else { return 0.; } } public void paintComponent(Graphics g) { // Allow parent to draw any custom painting. super.paintComponent(g); // If the selection region is visible, paint it. if (visible) { // Make a 2D graphics context. Graphics2D g2d = (Graphics2D) g; // Draw a rectangle on top the the image. g2d.setStroke(thickStroke); g.setColor(Color.black); g.drawPolygon(xCtrlPts, yCtrlPts, 4); if (visibleGuides) { g.drawLine(xCtrlPts[4], yCtrlPts[4], xCtrlPts[5], yCtrlPts[5]); } g2d.setStroke(thinStroke); g.setColor(Color.white); g.drawPolygon(xCtrlPts, yCtrlPts, 4); if (visibleGuides) { g.drawLine(xCtrlPts[4], yCtrlPts[4], xCtrlPts[5], yCtrlPts[5]); } if (activeCtrlPt >= 0) { // Draw the active control point. g.setColor(Color.black); g.fillRect(xCtrlPts[activeCtrlPt]-ctrlPtSize-1, yCtrlPts[activeCtrlPt]-ctrlPtSize-1, 2*ctrlPtSize+3, 2*ctrlPtSize+3); g.setColor(Color.white); g.fillRect(xCtrlPts[activeCtrlPt]-ctrlPtSize, yCtrlPts[activeCtrlPt]-ctrlPtSize, 2*ctrlPtSize+1, 2*ctrlPtSize+1); } } } /** * Make the affine transform which corresponds to this rectangular * selection. * * @return AffineTransform which describes the selected region */ public AffineTransform makeAffineTransform() { // Find first the upper, left-hand point. int first = 0; int savedValue = xCtrlPts[0]*xCtrlPts[0]+yCtrlPts[0]*yCtrlPts[0]; for (int i=1; i<4; i++) { int value = xCtrlPts[i]*xCtrlPts[i]+yCtrlPts[i]*yCtrlPts[i]; if (value<savedValue) { savedValue = value; first = i; } } // Calculate the index of the opposite corner. int third = (first+2)%4; // Now use the cross-product to determine which of the // remaining points is the one which keep the path going // clockwise. int second = (first+1)%4; int dx0 = xCtrlPts[third]-xCtrlPts[first]; int dy0 = yCtrlPts[third]-yCtrlPts[first]; int dx1 = xCtrlPts[second]-xCtrlPts[first]; int dy1 = yCtrlPts[second]-yCtrlPts[first]; if (dx0*dy1-dy0*dx1>0) second = (first+3)%4; // Get the appropriate radius. int centerIndex = (first==0 || first==3) ? 4 : 5; double radius = getRadius(xCtrlPts[first], yCtrlPts[first], centerIndex); double angle = getAngle(); // Calculate the delta-x and delta-y for the points. double dx = Math.abs(radius*Math.sin(angle)); double dy = Math.abs(radius*Math.cos(angle)); // Get the sign of the offsets from the control points. double sdx; double sdy; // The point closest to the origin. centerIndex = (first==0 || first==3) ? 4 : 5; sdx = ((xCtrlPts[first]-xCtrlPts[centerIndex])>0) ? 1. : -1.; sdy = ((yCtrlPts[first]-yCtrlPts[centerIndex])>0) ? 1. : -1.; double x0 = xCtrlPts[centerIndex]+sdx*dx; double y0 = yCtrlPts[centerIndex]+sdy*dy; // The next point clockwise. centerIndex = (second==0 || second==3) ? 4 : 5; sdx = ((xCtrlPts[second]-xCtrlPts[centerIndex])>0) ? 1. : -1.; sdy = ((yCtrlPts[second]-yCtrlPts[centerIndex])>0) ? 1. : -1.; double x1 = xCtrlPts[centerIndex]+sdx*dx; double y1 = yCtrlPts[centerIndex]+sdy*dy; // The next point clockwise. centerIndex = (third==0 || third==3) ? 4 : 5; sdx = ((xCtrlPts[third]-xCtrlPts[centerIndex])>0) ? 1. : -1.; sdy = ((yCtrlPts[third]-yCtrlPts[centerIndex])>0) ? 1. : -1.; double x2 = xCtrlPts[centerIndex]+sdx*dx; double y2 = yCtrlPts[centerIndex]+sdy*dy; // The control points are in the correct order, so we can just // call the utility function of the parent. return makeTransform(x0,y0,x1,y1,x2,y2); } /** * Check that the area of the selection is non-zero. * * @return flag indicating whether the selection is valid */ public boolean isValidSelection() { return (visible) && (xCtrlPts[4]!=xCtrlPts[5] || yCtrlPts[4]!=yCtrlPts[5]) && (xCtrlPts[0]!=xCtrlPts[3] || yCtrlPts[0]!=yCtrlPts[3]); } }