package ij.gui;
import ij.*;
import ij.process.*;
import java.awt.*;
import java.awt.geom.*;
public class Arrow extends Line {
public static final String STYLE_KEY = "arrow.style";
public static final String WIDTH_KEY = "arrow.width";
public static final String SIZE_KEY = "arrow.size";
public static final String DOUBLE_HEADED_KEY = "arrow.double";
public static final String OUTLINE_KEY = "arrow.outline";
public static final int FILLED=0, NOTCHED=1, OPEN=2, HEADLESS=3, BAR=4;
public static final String[] styles = {"Filled", "Notched", "Open", "Headless", "Bar"};
private static int defaultStyle = (int)Prefs.get(STYLE_KEY, FILLED);
private static float defaultWidth = (float)Prefs.get(WIDTH_KEY, 2);
private static double defaultHeadSize = (int)Prefs.get(SIZE_KEY, 10); private static boolean defaultDoubleHeaded = Prefs.get(DOUBLE_HEADED_KEY, false);
private static boolean defaultOutline = Prefs.get(OUTLINE_KEY, false);
private int style;
private double headSize = 10; private boolean doubleHeaded;
private boolean outline;
private float[] points = new float[2*5];
private GeneralPath path = new GeneralPath();
private static Stroke defaultStroke = new BasicStroke();
double headShaftRatio;
static {
if (defaultStyle<FILLED || defaultStyle>HEADLESS)
defaultStyle = FILLED;
}
public Arrow(double ox1, double oy1, double ox2, double oy2) {
super(ox1, oy1, ox2, oy2);
setStrokeWidth(2);
}
public Arrow(int sx, int sy, ImagePlus imp) {
super(sx, sy, imp);
setStrokeWidth(defaultWidth);
style = defaultStyle;
headSize = defaultHeadSize;
doubleHeaded = defaultDoubleHeaded;
outline = defaultOutline;
setStrokeColor(Toolbar.getForegroundColor());
}
public void draw(Graphics g) {
Shape shape2 = null;
if (doubleHeaded) {
flipEnds();
shape2 = getShape();
flipEnds();
}
Shape shape = getShape();
Color color = strokeColor!=null? strokeColor:ROIColor;
if (fillColor!=null) color = fillColor;
g.setColor(color);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
AffineTransform at = g2.getDeviceConfiguration().getDefaultTransform();
double mag = getMagnification();
int xbase=0, ybase=0;
if (ic!=null) {
Rectangle r = ic.getSrcRect();
xbase = r.x; ybase = r.y;
}
at.setTransform(mag, 0.0, 0.0, mag, -xbase*mag, -ybase*mag);
if (outline) {
float lineWidth = (float)(getOutlineWidth()*mag);
g2.setStroke(new BasicStroke(lineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
g2.draw(at.createTransformedShape(shape));
if (doubleHeaded) g2.draw(at.createTransformedShape(shape2));
g2.setStroke(defaultStroke);
} else {
g2.fill(at.createTransformedShape(shape));
if (doubleHeaded) g2.fill(at.createTransformedShape(shape2));
}
if (state!=CONSTRUCTING && !overlay) {
int size2 = HANDLE_SIZE/2;
handleColor=Color.white;
drawHandle(g, screenXD(x1d)-size2, screenYD(y1d)-size2);
drawHandle(g, screenXD(x2d)-size2, screenYD(y2d)-size2);
drawHandle(g, screenXD(x1d+(x2d-x1d)/2.0)-size2, screenYD(y1d+(y2d-y1d)/2.0)-size2);
}
if (state!=NORMAL && imp!=null && imp.getRoi()!=null)
showStatus();
if (updateFullWindow)
{updateFullWindow = false; imp.draw();}
}
private void flipEnds() {
double tmp = x1R;
x1R=x2R;
x2R=tmp;
tmp=y1R;
y1R=y2R;
y2R=tmp;
}
private Shape getPath() {
path.reset();
path = new GeneralPath();
calculatePoints();
float tailx = points[0];
float taily = points[1];
float headbackx = points[2*1];
float headbacky = points[2*1+1];
float headtipx = points[2*3];
float headtipy = points[2*3+1];
if (outline) {
double dx = headtipx - tailx;
double dy = headtipy - taily;
double shaftLength = Math.sqrt(dx*dx+dy*dy);
dx = headtipx - headbackx;
dy = headtipy- headbacky;
double headLength = Math.sqrt(dx*dx+dy*dy);
headShaftRatio = headLength/shaftLength;
if (headShaftRatio>1.0)
headShaftRatio = 1.0;
}
path.moveTo(tailx, taily); path.lineTo(headbackx, headbacky); path.moveTo(headbackx, headbacky); if (style==OPEN)
path.moveTo(points[2 * 2], points[2 * 2 + 1]);
else
path.lineTo(points[2 * 2], points[2 * 2 + 1]); path.lineTo(headtipx, headtipy); path.lineTo(points[2 * 4], points[2 * 4 + 1]); path.lineTo(headbackx, headbacky); return path;
}
private void calculatePoints() {
double tip = 0.0;
double base;
double shaftWidth = getStrokeWidth();
double length = 8+10*shaftWidth*0.5;
length = length*(headSize/10.0);
length -= shaftWidth*1.42;
if (style==NOTCHED) length*=0.74;
if (style==OPEN) length*=1.32;
if (length<0.0 || style==HEADLESS) length=0.0;
double x = getXBase();
double y = getYBase();
x1d=x+x1R; y1d=y+y1R; x2d=x+x2R; y2d=y+y2R;
x1=(int)x1d; y1=(int)y1d; x2=(int)x2d; y2=(int)y2d;
double dx=x2d-x1d, dy=y2d-y1d;
double arrowLength = Math.sqrt(dx*dx+dy*dy);
dx=dx/arrowLength; dy=dy/arrowLength;
if (doubleHeaded && style!=HEADLESS) {
points[0] = (float)(x1d+dx*shaftWidth*2.0);
points[1] = (float)(y1d+dy*shaftWidth*2.0);
} else {
points[0] = (float)x1d;
points[1] = (float)y1d;
}
if (length>0) {
double factor = style==OPEN?1.3:1.42;
points[2*3] = (float)(x2d-dx*shaftWidth*factor);
points[2*3+1] = (float)(y2d-dy*shaftWidth*factor);
if (style==BAR) {
points[2*3] = (float)(x2d-dx*shaftWidth*0.5);
points[2*3+1] = (float)(y2d-dy*shaftWidth*0.5);
}
} else {
points[2*3] = (float)x2d;
points[2*3+1] = (float)y2d;
}
final double alpha = Math.atan2(points[2*3+1]-points[1], points[2*3]-points[0]);
double SL = 0.0;
switch (style) {
case FILLED: case HEADLESS:
tip = Math.toRadians(20.0);
base = Math.toRadians(90.0);
points[1*2] = (float) (points[2*3] - length*Math.cos(alpha));
points[1*2+1] = (float) (points[2*3+1] - length*Math.sin(alpha));
SL = length*Math.sin(base)/Math.sin(base+tip);;
break;
case NOTCHED:
tip = Math.toRadians(20);
base = Math.toRadians(120);
points[1*2] = (float) (points[2*3] - length*Math.cos(alpha));
points[1*2+1] = (float) (points[2*3+1] - length*Math.sin(alpha));
SL = length*Math.sin(base)/Math.sin(base+tip);;
break;
case OPEN:
tip = Math.toRadians(25); points[1*2] = points[2*3];
points[1*2+1] = points[2*3+1];
SL = length;
break;
case BAR:
tip = Math.toRadians(90); points[1*2] = points[2*3];
points[1*2+1] = points[2*3+1];
SL = length;
updateFullWindow = true;
break;
}
points[2*2] = (float) (points[2*3] - SL*Math.cos(alpha+tip));
points[2*2+1] = (float) (points[2*3+1] - SL*Math.sin(alpha+tip));
points[2*4] = (float) (points[2*3] - SL*Math.cos(alpha-tip));
points[2*4+1] = (float) (points[2*3+1] - SL*Math.sin(alpha-tip));
}
private Shape getShape() {
Shape arrow = getPath();
BasicStroke stroke = new BasicStroke((float)getStrokeWidth(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
Shape outlineShape = stroke.createStrokedShape(arrow);
Area a1 = new Area(arrow);
Area a2 = new Area(outlineShape);
try {a1.add(a2);} catch(Exception e) {};
return a1;
}
private ShapeRoi getShapeRoi() {
Shape arrow = getPath();
BasicStroke stroke = new BasicStroke(getStrokeWidth(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
ShapeRoi sroi = new ShapeRoi(arrow);
Shape outlineShape = stroke.createStrokedShape(arrow);
sroi.or(new ShapeRoi(outlineShape));
return sroi;
}
public ImageProcessor getMask() {
if (width==0 && height==0)
return null;
else
return getShapeRoi().getMask();
}
private double getOutlineWidth() {
double width = getStrokeWidth()/8.0;
double head = headSize/7.0;
double lineWidth = width + head + headShaftRatio;
if (lineWidth<1.0) lineWidth = 1.0;
return lineWidth;
}
public void drawPixels(ImageProcessor ip) {
ShapeRoi shapeRoi = getShapeRoi();
ShapeRoi shapeRoi2 = null;
if (doubleHeaded) {
flipEnds();
shapeRoi2 = getShapeRoi();
flipEnds();
}
if (outline) {
int lineWidth = ip.getLineWidth();
ip.setLineWidth((int)Math.round(getOutlineWidth()));
shapeRoi.drawPixels(ip);
if (doubleHeaded) shapeRoi2.drawPixels(ip);
ip.setLineWidth(lineWidth);
} else {
ip.fill(shapeRoi);
if (doubleHeaded) ip.fill(shapeRoi2);
}
}
public boolean contains(int x, int y) {
return getShapeRoi().contains(x, y);
}
public Rectangle getBounds() {
return getShapeRoi().getBounds();
}
protected void handleMouseDown(int sx, int sy) {
super.handleMouseDown(sx, sy);
startxd = ic!=null?ic.offScreenXD(sx):sx;
startyd = ic!=null?ic.offScreenYD(sy):sy;
}
protected int clipRectMargin() {
double mag = getMagnification();
double arrowWidth = getStrokeWidth();
double size = 8+10*arrowWidth*mag*0.5;
return (int)Math.max(size*2.0, headSize);
}
public boolean isDrawingTool() {
return true;
}
public static void setDefaultWidth(double width) {
defaultWidth = (float)width;
}
public static double getDefaultWidth() {
return defaultWidth;
}
public void setStyle(int style) {
this.style = style;
}
public void setStyle(String style) {
style = style.toLowerCase();
int newStyle = Arrow.FILLED;
if (style.contains("notched"))
newStyle = Arrow.NOTCHED;
else if (style.contains("open"))
newStyle = Arrow.OPEN;
else if (style.contains("headless"))
newStyle = Arrow.HEADLESS;
else if (style.contains("bar"))
newStyle = Arrow.BAR;
setStyle(newStyle);
setOutline(style.contains("outline"));
setDoubleHeaded(style.contains("double"));
if (style.contains("small"))
setHeadSize(5);
else if (style.contains("large"))
setHeadSize(15);
}
public int getStyle() {
return style;
}
public static void setDefaultStyle(int style) {
defaultStyle = style;
}
public static int getDefaultStyle() {
return defaultStyle;
}
public void setHeadSize(double headSize) {
this.headSize = headSize;
}
public double getHeadSize() {
return headSize;
}
public static void setDefaultHeadSize(double size) {
defaultHeadSize = size;
}
public static double getDefaultHeadSize() {
return defaultHeadSize;
}
public void setDoubleHeaded(boolean b) {
doubleHeaded = b;
}
public boolean getDoubleHeaded() {
return doubleHeaded;
}
public static void setDefaultDoubleHeaded(boolean b) {
defaultDoubleHeaded = b;
}
public static boolean getDefaultDoubleHeaded() {
return defaultDoubleHeaded;
}
public void setOutline(boolean b) {
outline = b;
}
public boolean getOutline() {
return outline;
}
public static void setDefaultOutline(boolean b) {
defaultOutline = b;
}
public static boolean getDefaultOutline() {
return defaultOutline;
}
}