/* Copyright (C) 2005-2011 Fabio Riccardi */
package com.lightcrafts.ui.crop;
import com.lightcrafts.mediax.jai.Interpolation;
import com.lightcrafts.mediax.jai.RenderedOp;
import com.lightcrafts.mediax.jai.operator.RotateDescriptor;
import com.lightcrafts.model.CropBounds;
import com.lightcrafts.platform.Platform;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.MouseInputListener;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.LinkedList;
import java.util.List;
// This is a translucent white overlay in the shape of the complement of
// a rectangle that can be dragged, turned, and resized with the mouse.
//
// It works in screen coordinates (no AffineTransform).
class CropOverlay extends JComponent implements MouseInputListener {
private final static Stroke RectStroke = new BasicStroke(20f);
private final static Color RectColor = new Color(0, 0, 0, 128);
private final static Color GridColor = new Color(100, 100, 100, 128);
private final static Color RectBorderColor = Color.white;
// Basic crop cursor:
private final static Cursor CropCursor;
// Drag-the-crop cursors:
private final static Cursor MoveCursor;
private final static Cursor MovingCursor;
// Rotate cursor:
private final static Cursor RotateCursor;
private final static Cursor RotatingCursor;
// Hot point coordinates for all the cursors:
private final static ResourceBundle HotPointResources =
ResourceBundle.getBundle("com/lightcrafts/ui/crop/resources/HotPoints");
static {
CropCursor = getCursor("crop_cursor");
MoveCursor = getCursor("move_curve"); // Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR); // getCursor("move_curve");
MovingCursor = getCursor("moving_curve");
RotateCursor = getCursor("rotate");
RotatingCursor = getCursor("rotating");
}
private final static int MinDragDistance = 10;
private CropBounds crop; // the crop bounds, in screen coordinates
private Rectangle2D underlayRect; // underlay bounds, in screen coordinates
private AspectConstraint constraint; // the aspect ratio constraint
// The number of grid lines (isRotateOnly == false)
// The spacing between grid lines (isRotateOnly == true)
private static int GridCount = 3;
private static int GridSpacing = 30;
private Cursor cursor;
private Point dragStart;
private boolean isRotating;
private double rotateAngleStart;
private Point rotateMouseStart;
private double rotateWidthLimit;
private double rotateHeightLimit;
private boolean isRotateConstrained; // never moved or adjusted
private boolean isMoving;
private Point2D moveCenterStart;
private Point moveMouseStart;
private boolean adjustingNorth;
private boolean adjustingSouth;
private boolean adjustingEast;
private boolean adjustingWest;
private boolean hasHighlight;
private boolean isRotateOnly; // only allow rotations
CropOverlay(boolean isRotateOnly) {
this.isRotateOnly = isRotateOnly;
cursor = isRotateOnly ? RotateCursor : CropCursor;
setCursor(cursor);
addMouseListener(this);
addMouseMotionListener(this);
addRotateKeyListener();
}
CropBounds getCrop() {
return crop;
}
// Accept a CropBounds that may violate constraints, mutate it into a
// constrained CropBounds, and apply it.
void setCropWithConstraints(CropBounds newCrop) {
newCrop = constraint.adjust(newCrop);
newCrop = UnderlayConstraints.sizeToUnderlay(
newCrop, underlayRect, Integer.MAX_VALUE, Integer.MAX_VALUE
);
if (UnderlayConstraints.underlayContains(newCrop, underlayRect)) {
setCrop(newCrop);
}
}
void setCrop(CropBounds newCrop) {
if ((newCrop == null) || newCrop.isAngleOnly()) {
double angle = 0;
if (newCrop != null) {
angle = newCrop.getAngle();
}
newCrop = new CropBounds(underlayRect, angle);
}
crop = newCrop;
repaint();
}
void setUnderlayRect(Rectangle bounds) {
underlayRect = bounds;
}
void setAspectConstraint(AspectConstraint constraint) {
this.constraint = constraint;
if (crop != null) {
CropBounds newCrop = constraint.adjust(crop);
newCrop = UnderlayConstraints.translateToUnderlay(newCrop, underlayRect);
// The crop may be null, if the constraints could not be enforced
// within the bounds of the image.
setCrop(newCrop);
}
}
void modeEntered() {
// At mode start, take a look at the current underlay and the current
// crop, and decide whether rotate drag gestures should use the
// underlay dimensions or the initial crop dimensions as the limit
// for resizing during rotation.
updateRotateConstraints();
}
Dimension2D getRotateLimitDimensions() {
return new RotateLimitDimension(rotateWidthLimit, rotateHeightLimit);
}
void setRotateLimitDimensions(Dimension2D limit) {
rotateWidthLimit = limit.getWidth();
rotateHeightLimit = limit.getHeight();
}
// See modeEntered() and mousePressed().
private void updateRotateConstraints() {
isRotateConstrained =
UnderlayConstraints.isRotateDefinedCrop(crop, underlayRect);
rotateWidthLimit = isRotateConstrained ?
underlayRect.getWidth() : crop.getWidth();
rotateHeightLimit = isRotateConstrained ?
underlayRect.getHeight() : crop.getHeight();
}
protected void paintComponent(Graphics graphics) {
Shape shape = getCropAsShape();
if (shape != null) {
Graphics2D g = (Graphics2D) graphics;
Color oldColor = g.getColor();
Stroke oldStroke = g.getStroke();
RenderingHints oldHints = g.getRenderingHints();
g.setColor(RectColor);
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
Area complement = new Area(getBounds());
complement.subtract(new Area(shape));
g.fill(complement);
g.setColor(GridColor);
paintGrid(g);
Stroke borderStroke = new BasicStroke(hasHighlight ? 2f : 1f);
g.setColor(RectBorderColor);
g.setStroke(borderStroke);
g.draw(shape);
g.setStroke(oldStroke);
g.setColor(oldColor);
g.setRenderingHints(oldHints);
}
}
private void paintGrid(Graphics2D g) {
if (isRotateOnly || isRotating) {
paintRotateGrid(g);
}
else {
paintCropGrid(g);
}
}
private void paintCropGrid(Graphics2D g) {
Point2D ul = crop.getUpperLeft();
Point2D ur = crop.getUpperRight();
Point2D ll = crop.getLowerLeft();
Point2D lr = crop.getLowerRight();
for (int x=1; x<GridCount; x++) {
Point2D p = new Point2D.Double(
(x * ul.getX() + (GridCount - x) * lr.getX()) / GridCount,
(x * ul.getY() + (GridCount - x) * lr.getY()) / GridCount
);
Line2D hLine = new Line2D.Double(ul, ur);
hLine = getSegmentThroughPoint(hLine, p);
g.draw(hLine);
Line2D vLine = new Line2D.Double(ul, ll);
vLine = getSegmentThroughPoint(vLine, p);
g.draw(vLine);
}
}
private void paintRotateGrid(Graphics2D g) {
Point2D center = crop.getCenter();
Point2D ul = crop.getUpperLeft();
Point2D ur = crop.getUpperRight();
Point2D ll = crop.getLowerLeft();
Point2D lr = crop.getLowerRight();
Point2D midLeft = getMidPoint(ul, ll);
Point2D midTop = getMidPoint(ul, ur);
Point2D midRight = getMidPoint(ur, lr);
Point2D midBottom = getMidPoint(ll, lr);
Line2D hMidLine = new Line2D.Double(midLeft, midRight);
Line2D vMidLine = new Line2D.Double(midTop, midBottom);
List<Point2D> upPts = getPointsBetween(center, midTop, GridSpacing);
List<Point2D> downPts = getPointsBetween(center, midBottom, GridSpacing);
List<Point2D> rightPts = getPointsBetween(center, midRight, GridSpacing);
List<Point2D> leftPts = getPointsBetween(center, midLeft, GridSpacing);
// When painting all these lines, we take care not to paint the center
// lines twice, which would make them appear darker than the other
// lines because of the compositing.
for (Point2D p : upPts) {
Line2D line = getSegmentThroughPoint(hMidLine, p);
g.draw(line);
}
boolean firstDown = true;
for (Point2D p : downPts) {
if (firstDown) {
firstDown = false;
continue;
}
Line2D line = getSegmentThroughPoint(hMidLine, p);
g.draw(line);
}
for (Point2D p : rightPts) {
Line2D line = getSegmentThroughPoint(vMidLine, p);
g.draw(line);
}
boolean firstLeft = true;
for (Point2D p : leftPts) {
if (firstLeft) {
firstLeft = false;
continue;
}
Line2D line = getSegmentThroughPoint(vMidLine, p);
g.draw(line);
}
}
public void mouseClicked(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
return;
}
Point p = e.getPoint();
if (isRotateOnly) {
if (crop == null) {
setCrop(new CropBounds(underlayRect));
}
isRotating = true;
rotateAngleStart = crop.getAngle();
rotateMouseStart = p;
}
else {
if (isOnRect(p)) {
if (isOnNorth(p)) {
adjustingNorth = true;
}
if (isOnSouth(p)) {
adjustingSouth = true;
}
if (isOnEast(p)) {
adjustingEast = true;
}
if (isOnWest(p)) {
adjustingWest = true;
}
updateRotateConstraints();
}
else if (isInRect(p)) {
if (hasRotateModifier(e)) {
isRotating = true;
rotateAngleStart = crop.getAngle();
rotateMouseStart = p;
}
else {
isMoving = true;
moveMouseStart = p;
moveCenterStart = crop.getCenter();
}
updateRotateConstraints();
}
else if (crop != null && (! crop.isAngleOnly())) {
isRotating = true;
rotateAngleStart = crop.getAngle();
rotateMouseStart = p;
}
else {
if (hasRotateModifier(e)) {
setCrop(new CropBounds(underlayRect));
isRotating = true;
rotateAngleStart = crop.getAngle();
rotateMouseStart = p;
}
else {
if (! underlayRect.contains(p)) {
// Use the closest point inside the underlay instead.
Point2D closest = getClosestUnderlayPoint(p);
p = new Point(
(int) Math.round(closest.getX()),
(int) Math.round(closest.getY())
);
}
dragStart = p;
}
}
}
updateCursor(e);
}
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
return;
}
isMoving = false;
isRotating = false;
dragStart = null;
rotateMouseStart = null;
moveMouseStart = null;
moveCenterStart = null;
adjustingNorth = false;
adjustingSouth = false;
adjustingEast = false;
adjustingWest = false;
updateCursor(e);
repaint();
}
public void mouseDragged(MouseEvent e) {
updateHighlight(e);
Point p = e.getPoint();
// Backup the current crop, so we can back out changes if it turns
// out they exceed the underlay constraint.
CropBounds oldCrop = crop;
if ((dragStart != null) &&
((Math.abs(dragStart.x - p.x) < MinDragDistance) ||
(Math.abs(dragStart.y - p.y) < MinDragDistance))) {
return;
}
if (dragStart != null) {
if (! underlayRect.contains(p)) {
return;
}
double x = Math.min(p.x, dragStart.x);
double y = Math.min(p.y, dragStart.y);
double w = Math.abs(p.x - dragStart.x);
double h = Math.abs(p.y - dragStart.y);
Rectangle2D rect = new Rectangle2D.Double(x, y, w, h);
CropBounds newCrop = new CropBounds(rect);
setCrop(newCrop);
if (isOnNorth(p)) {
adjustingNorth = true;
}
else {
adjustingSouth = true;
}
if (isOnWest(p)) {
adjustingWest = true;
}
else {
adjustingEast = true;
}
dragStart = null;
}
else if (isMoving) {
int dx = p.x - moveMouseStart.x;
int dy = p.y - moveMouseStart.y;
Point2D.Double center = new Point2D.Double(
moveCenterStart.getX() + dx,
moveCenterStart.getY() + dy
);
double width = crop.getWidth();
double height = crop.getHeight();
double angle = crop.getAngle();
CropBounds newCrop = new CropBounds(center, width, height, angle);
newCrop = UnderlayConstraints.translateToUnderlay(newCrop, underlayRect);
if (newCrop != null) {
setCrop(newCrop);
}
}
else if (isRotating) {
Point2D center = crop.getCenter();
Line2D start = new Line2D.Double(center, rotateMouseStart);
Line2D end = new Line2D.Double(center, p);
double startAngle = Math.atan2(
start.getY2() - start.getY1(), start.getX2() - start.getX1()
);
double endAngle = Math.atan2(
end.getY2() - end.getY1(), end.getX2() - end.getX1()
);
double angle = rotateAngleStart + endAngle - startAngle;
CropBounds newCrop = new CropBounds(crop, angle);
updateRotateConstraints();
newCrop = UnderlayConstraints.sizeToUnderlay(
newCrop, underlayRect, rotateWidthLimit, rotateHeightLimit
);
if (newCrop != null) {
setCrop(newCrop);
}
return;
}
else if ((crop != null) && (! crop.isAngleOnly())) {
// This MouseEvent is a regular mid-drag event.
// If an edge is adjusting, then preserve the opposite edge.
if (isEdgeAdjusting()) {
if (adjustingNorth) {
setNorth(p);
crop = constraint.adjustWidth(crop);
if (constraint.isNoConstraint()) {
crop = UnderlayConstraints.adjustNorthToUnderlay(crop, underlayRect);
}
else {
crop = UnderlayConstraints.adjustNorthWithConstraint(crop, underlayRect);
}
}
else if (adjustingSouth) {
setSouth(p);
crop = constraint.adjustWidth(crop);
if (constraint.isNoConstraint()) {
crop = UnderlayConstraints.adjustSouthToUnderlay(crop, underlayRect);
}
else {
crop = UnderlayConstraints.adjustSouthWithConstraint(crop, underlayRect);
}
}
if (adjustingEast) {
setEast(p);
crop = constraint.adjustHeight(crop);
if (constraint.isNoConstraint()) {
crop = UnderlayConstraints.adjustEastToUnderlay(crop, underlayRect);
}
else {
crop = UnderlayConstraints.adjustEastWithConstraint(crop, underlayRect);
}
}
else if (adjustingWest) {
setWest(p);
crop = constraint.adjustHeight(crop);
if (constraint.isNoConstraint()) {
crop = UnderlayConstraints.adjustWestToUnderlay(crop, underlayRect);
}
else {
crop = UnderlayConstraints.adjustWestWithConstraint(crop, underlayRect);
}
}
if (! UnderlayConstraints.underlayContains(crop, underlayRect)) {
setCrop(oldCrop);
}
updateRotateConstraints();
}
// If a corner is adjusting, then preserve the opposite corner.
else if (isCornerAdjusting()) {
p = projectOntoUnderlay(p);
Point2D center = crop.getCenter();
Point2D ul = crop.getUpperLeft();
Point2D ur = crop.getUpperRight();
Point2D ll = crop.getLowerLeft();
Point2D lr = crop.getLowerRight();
if (adjustingSouth) {
setSouth(p);
if (adjustingWest) {
setWest(p);
Line2D diag = new Line2D.Double(center, ll);
if (diag.relativeCCW(p) > 0) {
crop = constraint.adjustBottom(crop);
}
else {
crop = constraint.adjustLeft(crop);
}
crop = UnderlayConstraints.adjustSouthWestToUnderlay(
crop, underlayRect
);
}
else {
setEast(p);
Line2D diag = new Line2D.Double(center, lr);
if (diag.relativeCCW(p) > 0) {
crop = constraint.adjustBottom(crop);
}
else {
crop = constraint.adjustRight(crop);
}
crop = UnderlayConstraints.adjustSouthEastToUnderlay(
crop, underlayRect
);
}
}
else {
setNorth(p);
if (adjustingEast) {
setEast(p);
Line2D diag = new Line2D.Double(center, ur);
if (diag.relativeCCW(p) > 0) {
crop = constraint.adjustTop(crop);
}
else {
crop = constraint.adjustRight(crop);
}
crop = UnderlayConstraints.adjustNorthEastToUnderlay(
crop, underlayRect
);
}
else {
setWest(p);
Line2D diag = new Line2D.Double(center, ul);
if (diag.relativeCCW(p) > 0) {
crop = constraint.adjustTop(crop);
}
else {
crop = constraint.adjustLeft(crop);
}
crop = UnderlayConstraints.adjustNorthWestToUnderlay(
crop, underlayRect
);
}
updateRotateConstraints();
}
}
}
if (! UnderlayConstraints.underlayContains(crop, underlayRect)) {
setCrop(oldCrop);
}
}
private Point projectOntoUnderlay(Point p) {
Point q = (Point) p.clone();
Rectangle under = underlayRect.getBounds();
final int outcode = underlayRect.outcode( p );
if ( (outcode & Rectangle2D.OUT_TOP) != 0 ) {
q = new Point(q.x, under.y);
}
if ( (outcode & Rectangle2D.OUT_LEFT) != 0 ) {
q = new Point(under.x, q.y);
}
if ( (outcode & Rectangle2D.OUT_BOTTOM) != 0 ) {
q = new Point(q.x, under.y + under.height);
}
if ( (outcode & Rectangle2D.OUT_RIGHT) != 0 ) {
q = new Point(under.x + under.width, q.y);
}
return q;
}
public void mouseMoved(MouseEvent e) {
updateCursor(e);
updateHighlight(e);
}
private boolean isEdgeAdjusting() {
return (
(adjustingNorth && ! (adjustingEast || adjustingWest)) ||
(adjustingSouth && ! (adjustingEast || adjustingWest)) ||
(adjustingEast && ! (adjustingNorth || adjustingSouth)) ||
(adjustingWest && ! (adjustingNorth || adjustingSouth))
);
}
private boolean isCornerAdjusting() {
return (
(adjustingNorth && (adjustingEast || adjustingWest)) ||
(adjustingSouth && (adjustingEast || adjustingWest)) ||
(adjustingEast && (adjustingNorth || adjustingSouth)) ||
(adjustingWest && (adjustingNorth || adjustingSouth))
);
}
private void updateCursor(MouseEvent e) {
Cursor newCursor = getCursor(e);
if (cursor != newCursor) {
cursor = newCursor;
setCursor(cursor);
}
}
private void updateHighlight(MouseEvent e) {
Point p = e.getPoint();
boolean hadHighlight = hasHighlight;
hasHighlight = isAdjusting() || isInRect(p) || isOnRect(p);
if (hadHighlight != hasHighlight) {
repaint();
}
}
private Cursor getCursor(MouseEvent e) {
if ((crop == null) || crop.isAngleOnly()) {
if (hasRotateModifier(e) || isRotateOnly) {
return RotateCursor;
}
else {
return CropCursor;
}
}
Point p = e.getPoint();
if (! isRotateOnly) {
double angle = crop.getAngle();
if (isOnNorth(p)) {
if (isOnEast(p)) {
return getResizeCursor(- Math.PI / 4 + angle);
}
if (isOnWest(p)) {
return getResizeCursor(- 3 * Math.PI / 4 + angle);
}
return getResizeCursor(- Math.PI / 2 + angle);
}
if (isOnSouth(p)) {
if (isOnEast(p)) {
return getResizeCursor(+ Math.PI / 4 + angle);
}
if (isOnWest(p)) {
return getResizeCursor(+ 3 * Math.PI / 4 + angle);
}
return getResizeCursor(+ Math.PI / 2 + angle);
}
if (isOnEast(p)) {
return getResizeCursor(+ angle);
}
if (isOnWest(p)) {
return getResizeCursor(+ Math.PI + angle);
}
if (isInRect(p)) {
if (isRotating) {
return RotatingCursor;
}
else if (isMoving) {
return MovingCursor;
}
else if (
hasRotateModifier(e)
) {
return RotateCursor;
}
else {
return MoveCursor;
}
}
}
if ((crop != null) || isRotateOnly) {
if (isRotating) {
return RotatingCursor;
}
else {
return RotateCursor;
}
}
return CropCursor;
}
boolean isAdjusting() {
return adjustingNorth ||
adjustingSouth ||
adjustingEast ||
adjustingWest ||
isRotating ||
isMoving;
}
boolean isInRect(Point p) {
Shape shape = getCropAsShape();
return (shape != null) && shape.contains(p);
}
boolean isOnRect(Point p) {
Shape shape = getCropAsShape();
if (shape == null) {
return false;
}
Shape thickRect = createThickShape(shape);
return thickRect.contains(p);
}
private void setNorth(Point p) {
Line2D north = getNorthLine();
north = getSegmentThroughPoint(north, p);
Point2D center = crop.getCenter();
if (north.relativeCCW(center) != -1) {
return;
}
Point2D ul = north.getP1();
Point2D ur = north.getP2();
Point2D ll = crop.getLowerLeft();
Point2D lr = crop.getLowerRight();
CropBounds newCrop = new CropBounds(ul, ur, ll, lr);
setCrop(newCrop);
}
private void setSouth(Point p) {
Line2D south = getSouthLine();
south = getSegmentThroughPoint(south, p);
Point2D center = crop.getCenter();
if (south.relativeCCW(center) != -1) {
return;
}
Point2D ul = crop.getUpperLeft();
Point2D ur = crop.getUpperRight();
Point2D ll = south.getP2();
Point2D lr = south.getP1();
CropBounds newCrop = new CropBounds(ul, ur, ll, lr);
setCrop(newCrop);
}
private void setEast(Point p) {
Line2D east = getEastLine();
east = getSegmentThroughPoint(east, p);
Point2D center = crop.getCenter();
if (east.relativeCCW(center) != -1) {
return;
}
Point2D ul = crop.getUpperLeft();
Point2D ur = east.getP1();
Point2D ll = crop.getLowerLeft();
Point2D lr = east.getP2();
CropBounds newCrop = new CropBounds(ul, ur, ll, lr);
setCrop(newCrop);
}
private void setWest(Point p) {
Line2D west = getWestLine();
west = getSegmentThroughPoint(west, p);
Point2D center = crop.getCenter();
if (west.relativeCCW(center) != -1) {
return;
}
Point2D ul = west.getP2();
Point2D ur = crop.getUpperRight();
Point2D ll = west.getP1();
Point2D lr = crop.getLowerRight();
CropBounds newCrop = new CropBounds(ul, ur, ll, lr);
setCrop(newCrop);
}
private Line2D getNorthLine() {
return new Line2D.Double(crop.getUpperLeft(), crop.getUpperRight());
}
private Line2D getEastLine() {
return new Line2D.Double(crop.getUpperRight(), crop.getLowerRight());
}
private Line2D getSouthLine() {
return new Line2D.Double(crop.getLowerRight(), crop.getLowerLeft());
}
private Line2D getWestLine() {
return new Line2D.Double(crop.getLowerLeft(), crop.getUpperLeft());
}
private boolean isOnNorth(Point p) {
if (crop == null) {
return false;
}
return createThickShape(getNorthLine()).contains(p);
}
private boolean isOnSouth(Point p) {
if (crop == null) {
return false;
}
return createThickShape(getSouthLine()).contains(p);
}
private boolean isOnEast(Point p) {
if (crop == null) {
return false;
}
return createThickShape(getEastLine()).contains(p);
}
private boolean isOnWest(Point p) {
if (crop == null) {
return false;
}
return createThickShape(getWestLine()).contains(p);
}
private Point2D getClosestUnderlayPoint(Point2D p) {
if (underlayRect.contains(p)) {
return p;
}
if (p.getX() < underlayRect.getMinX()) {
p = new Point2D.Double(underlayRect.getMinX(), p.getY());
}
if (p.getY() < underlayRect.getMinY()) {
p = new Point2D.Double(p.getX(), underlayRect.getMinY());
}
if (p.getX() > underlayRect.getMaxX()) {
p = new Point2D.Double(underlayRect.getMaxX(), p.getY());
}
if (p.getY() > underlayRect.getMaxY()) {
p = new Point2D.Double(p.getX(), underlayRect.getMaxY());
}
return p;
}
// // Repaint this overlay when the CropBounds udpates, marking dirty as
// // little of the component as possible to minimize thrash painting of
// // the underlay.
// private void repaint(CropBounds oldCrop, CropBounds newCrop) {
// Shape oldShape = getCropAsShape(oldCrop);
// Shape newShape = getCropAsShape(newCrop);
// if ((oldShape == null) || (newShape == null)) {
// repaint();
// return;
// }
// Rectangle oldRect = oldShape.getBounds();
// Rectangle newRect = newShape.getBounds();
// if (! newRect.intersects(oldRect)) {
// repaint(oldRect);
// repaint(newRect);
// return;
// }
// Area xor = new Area(oldShape);
// xor.exclusiveOr(new Area(newShape));
//
// Set<Shape> shapes = getConnectedComponents(xor);
// for (Shape shape : shapes) {
// Rectangle rect = shape.getBounds();
// repaint(rect);
// }
// }
// Compute a line segment parallel to the given segment and translated so
// that the line constructed from the new segment by extrapolation passes
// through the given point.
private static Line2D getSegmentThroughPoint(Line2D seg, Point2D p) {
Point2D p1 = seg.getP1();
Point2D p2 = seg.getP2();
double angle = Math.atan2(p2.getY() - p1.getY(), p2.getX() - p1.getX());
angle += Math.PI / 2;
double distance = seg.ptLineDist(p);
int ccw = seg.relativeCCW(p);
distance *= - ccw;
double dx = distance * Math.cos(angle);
double dy = distance * Math.sin(angle);
p1 = new Point2D.Double(p1.getX() + dx, p1.getY() + dy);
p2 = new Point2D.Double(p2.getX() + dx, p2.getY() + dy);
return new Line2D.Double(p1, p2);
}
private static Point2D getMidPoint(Point2D p, Point2D q) {
return new Point2D.Double(
(p.getX() + q.getX()) / 2, (p.getY() + q.getY()) / 2
);
}
private static List<Point2D> getPointsBetween(
Point2D start, Point2D end, double spacing
) {
double angle = Math.atan2(
end.getY() - start.getY(), end.getX() - start.getX()
);
double dist = start.distance(end);
int nPts = (int) Math.floor(dist / spacing);
LinkedList<Point2D> points = new LinkedList<Point2D>();
Point2D p = start;
for (int n=0; n<=nPts; n++) {
points.add(p);
p = new Point2D.Double(
p.getX() + spacing * Math.cos(angle),
p.getY() + spacing * Math.sin(angle)
);
}
return points;
}
private Shape getCropAsShape() {
if ((crop == null) || crop.isAngleOnly()) {
return null;
}
Point2D ul = crop.getUpperLeft();
Point2D ur = crop.getUpperRight();
Point2D ll = crop.getLowerLeft();
Point2D lr = crop.getLowerRight();
GeneralPath path = new GeneralPath();
path.moveTo((float) ul.getX(), (float) ul.getY());
path.lineTo((float) ur.getX(), (float) ur.getY());
path.lineTo((float) lr.getX(), (float) lr.getY());
path.lineTo((float) ll.getX(), (float) ll.getY());
path.closePath();
return path;
}
private static Shape createThickShape(Shape shape) {
return RectStroke.createStrokedShape(shape);
}
// // Return a Set of Shapes, comprising the connected components of
// // a given Shape. Used for the optimized repaint() method.
// private static Set<Shape> getConnectedComponents(Shape shape) {
// PathIterator i = shape.getPathIterator(new AffineTransform());
// HashSet<Shape> components = new HashSet<Shape>();
// if (i.isDone()) {
// return components;
// }
// float[] pts = new float[6];
// GeneralPath component = new GeneralPath();
// int type = i.currentSegment(pts);
// assert type == PathIterator.SEG_MOVETO;
// component.moveTo(pts[0], pts[1]);
// i.next();
// while (! i.isDone()) {
// type = i.currentSegment(pts);
// if (type == PathIterator.SEG_MOVETO) {
// components.add(component);
// component = new GeneralPath();
// component.moveTo(pts[0], pts[1]);
// }
// else if (type == PathIterator.SEG_LINETO) {
// component.lineTo(pts[0], pts[1]);
// }
// else if (type == PathIterator.SEG_QUADTO) {
// component.quadTo(pts[0], pts[1], pts[2], pts[3]);
// }
// else if (type == PathIterator.SEG_CUBICTO) {
// component.curveTo(
// pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]
// );
// }
// else if (type == PathIterator.SEG_CLOSE) {
// component.closePath();
// }
// i.next();
// }
// components.add(component);
//
// return components;
// }
// Access one of the predefined cursors from resources.
private static Cursor getCursor(String name) {
try {
String path = "resources/" + name + ".png";
URL url = CropOverlay.class.getResource(path);
BufferedImage image = ImageIO.read(url);
int x = Integer.parseInt(HotPointResources.getString(name + "X"));
int y = Integer.parseInt(HotPointResources.getString(name + "Y"));
Point hot = new Point(x, y);
Toolkit toolkit = Toolkit.getDefaultToolkit();
Cursor cursor = toolkit.createCustomCursor(image, hot, name);
return cursor;
}
catch (IOException e) {
e.printStackTrace();
return null;
}
}
private static double RecentCursorAngle;
private static Cursor RecentCursor;
// Custom-render a new resize cursor that looks right for resizing an edge
// at the given angle.
private static Cursor getResizeCursor(double angle) {
// Don't thrash the cursor rendering: if the requested angle matches
// the most recent angle within roundoff, then send back the recent
// cursor.
if (Platform.getType() == Platform.Windows) {
int cursorType = Cursor.DEFAULT_CURSOR;
double a = Math.PI/4;
if (Math.abs(angle) < a)
cursorType = Cursor.E_RESIZE_CURSOR;
if (Math.abs(angle - Math.PI/4) < a)
cursorType = Cursor.SE_RESIZE_CURSOR;
if (Math.abs(angle - Math.PI/2) < a)
cursorType = Cursor.S_RESIZE_CURSOR;
if (Math.abs(angle - 3 * Math.PI/4) < a)
cursorType = Cursor.SW_RESIZE_CURSOR;
if (Math.abs(angle - Math.PI) < a)
cursorType = Cursor.W_RESIZE_CURSOR;
if (Math.abs(angle + 3 * Math.PI/4) < a)
cursorType = Cursor.NW_RESIZE_CURSOR;
if (Math.abs(angle + Math.PI/2) < a)
cursorType = Cursor.N_RESIZE_CURSOR;
if (Math.abs(angle + Math.PI/4) < a)
cursorType = Cursor.NE_RESIZE_CURSOR;
return Cursor.getPredefinedCursor(cursorType);
}
double diff = Math.abs(angle - RecentCursorAngle);
if ((diff < 0.001) && (RecentCursor != null)) {
return RecentCursor;
}
try {
String path = "resources/resize.png";
URL url = CropOverlay.class.getResource(path);
RenderedImage resourceImage = ImageIO.read(url);
int cx = resourceImage.getWidth() / 2;
int cy = resourceImage.getHeight() / 2;
RenderedOp rotatedImage = RotateDescriptor.create(
resourceImage,
(float) cx, (float) cy, (float) angle,
Interpolation.getInstance(Interpolation.INTERP_BICUBIC),
null, null
);
Point hot = new Point(cx, cy);
Toolkit toolkit = Toolkit.getDefaultToolkit();
BufferedImage buffer = rotatedImage.getAsBufferedImage();
Cursor cursor = toolkit.createCustomCursor(buffer, hot, "resize");
RecentCursorAngle = angle;
RecentCursor = cursor;
return cursor;
}
catch (IOException e) {
e.printStackTrace();
return null;
}
}
private static boolean hasRotateModifier(MouseEvent event) {
Platform.Type type = Platform.getType();
switch (type) {
case MacOSX:
return event.isMetaDown();
default:
return event.isControlDown();
}
}
private void addRotateKeyListener() {
KeyboardFocusManager focus =
KeyboardFocusManager.getCurrentKeyboardFocusManager();
focus.addKeyEventPostProcessor(rotateKeyProcessor);
}
public void dispose() {
KeyboardFocusManager focus =
KeyboardFocusManager.getCurrentKeyboardFocusManager();
focus.removeKeyEventPostProcessor(rotateKeyProcessor);
}
// Listen for keystrokes so we can detect when the user presses and
// releases the modifier key that allows rotation from inside the crop
// bounds, so we can update the cursor.
private KeyEventPostProcessor rotateKeyProcessor =
new KeyEventPostProcessor() {
int RotateKeyCode = Platform.getType().equals(
Platform.Type.MacOSX
) ? KeyEvent.VK_META : KeyEvent.VK_CONTROL;
int RotateModifierMask = Platform.getType().equals(
Platform.Type.MacOSX
) ? MouseEvent.META_DOWN_MASK : MouseEvent.CTRL_DOWN_MASK;
public boolean postProcessKeyEvent(KeyEvent e) {
if (e.getKeyCode() == RotateKeyCode) {
boolean isPressed = e.getID() == KeyEvent.KEY_PRESSED;
boolean isReleased = e.getID() == KeyEvent.KEY_RELEASED;
if (isPressed || isReleased) {
PointerInfo pointer = MouseInfo.getPointerInfo();
Point p = pointer.getLocation();
SwingUtilities.convertPointFromScreen(
p, CropOverlay.this
);
MouseEvent syntheticEvent = new MouseEvent(
CropOverlay.this,
0, 0,
isPressed ? RotateModifierMask : 0,
p.x, p.y, 0, false
);
updateCursor(syntheticEvent);
}
}
return false; // these key events have other interpretations
}
};
}