| Line |

    package ij.gui;
    import java.awt.*;
    import java.awt.image.*;
    import ij.*;
    import ij.process.*;
    import ij.measure.*;
    import java.awt.event.KeyEvent;

    /** This class represents a straight line selection. */
    public class Line extends Roi {

        public int x1, y1, x2, y2;  // the line
        public double x1d, y1d, x2d, y2d;   // the line using sub-pixel coordinates
        private double x1R, y1R, x2R, y2R;  // the line, relative to base of bounding rect
        private static int lineWidth = 1;
        private double xHandleOffset, yHandleOffset;
        private double startxd, startyd;

        /** Creates a new straight line selection using the specified
            starting and ending screen coordinates. */
        public Line(int ox1, int oy1, int ox2, int oy2) {
            //this(ox1, oy1, null);
            super(ox1, oy1, null);
            type = LINE;
            startxd = startX; startyd = startY;
            grow(ox2, oy2);
            x1d=x+x1R; y1d=y+y1R; x2d=x+x2R; y2d=y+y2R;
            x1=(int)x1d; y1=(int)y1d; x2=(int)x2d; y2=(int)y2d;
            state = NORMAL;
        }

        /** Starts the process of creating a new user-generated straight line
            selection. 'sx' and 'sy' are screen coordinates that specify
            the start of the line. The user will determine the end of the line
            interactively using rubber banding. */
        public Line(int sx, int sy, ImagePlus imp) {
            super(sx, sy, imp);
            startxd = ic.offScreenXD(sx);
            startyd = ic.offScreenYD(sy);
            x1R = x2R = startxd - startX;
            y1R = y2R = startyd - startY;
            type = LINE;
        }

        /** Obsolete */
        public Line(int ox1, int oy1, int ox2, int oy2, ImagePlus imp) {
            this(ox1, oy1, ox2, oy2);
            setImage(imp);
        }

        protected void grow(int sx, int sy) {
            double xend = ic!=null?ic.offScreenXD(sx):sx;
            double yend = ic!=null?ic.offScreenYD(sy):sy;
            if (xend<0.0) xend=0.0; if (yend<0.0) yend=0.0;
            if (xend>xMax) xend=xMax; if (yend>yMax) yend=yMax;
            double xstart=x+x1R, ystart=y+y1R;
            if (constrain) {
                double dx = Math.abs(xend-xstart);
                double dy = Math.abs(yend-ystart);
                if (dx>=dy)
                    yend = ystart;
                else
                    xend = xstart;
            }
            x=(int)Math.min(x+x1R,xend); y=(int)Math.min(y+y1R,yend);
            x1R=xstart-x; y1R=ystart-y;
            x2R=xend-x; y2R=yend-y;
            width=(int)Math.abs(x2R-x1R); height=(int)Math.abs(y2R-y1R);
            if (width<1) width=1; if (height<1) height=1;
            updateClipRect();
            if (imp!=null) {
                if (lineWidth==1)
                    imp.draw(clipX, clipY, clipWidth, clipHeight);
                else
                    imp.draw();
            }
            oldX=x; oldY=y;
            oldWidth=width; oldHeight=height;
        }

        void move(int sx, int sy) {
            int xNew = ic.offScreenX(sx);
            int yNew = ic.offScreenY(sy);
            x += xNew - startxd;
            y += yNew - startyd;
            clipboard=null;
            startxd = xNew;
            startyd = yNew;
            if (lineWidth==1) {
                updateClipRect();
                imp.draw(clipX, clipY, clipWidth, clipHeight);
            } else
                imp.draw();
            oldX = x;
            oldY = y;
            oldWidth = width;
            oldHeight=height;
        }

        protected void moveHandle(int sx, int sy) {
            double ox = ic.offScreenXD(sx);
            double oy = ic.offScreenYD(sy);
            x1d=x+x1R; y1d=y+y1R; x2d=x+x2R; y2d=y+y2R;
            switch (activeHandle) {
                case 0: x1d=ox; y1d=oy; break;
                case 1: x2d=ox; y2d=oy; break;
                case 2:
                    double dx = ox-(x1d+(x2d-x1d)/2);
                    double dy = oy-(y1d+(y2d-y1d)/2);
                    x1d+=dx; y1d+=dy; x2d+=dx; y2d+=dy;
                    if (lineWidth>1) {
                        x1d+=xHandleOffset; y1d+=yHandleOffset;
                        x2d+=xHandleOffset; y2d+=yHandleOffset;
                    }
                    break;
            }
            if (constrain) {
                double dx = Math.abs(x1d-x2d);
                double dy = Math.abs(y1d-y2d);
                if (activeHandle==0) {
                    if (dx>=dy) y1d = y2d; else x1d = x2d;
                } else if (activeHandle==1) {
                    if (dx>=dy) y2d= y1d; else x2d = x1d;
                }
            }
            x=(int)Math.min(x1d,x2d); y=(int)Math.min(y1d,y2d);
            x1R=x1d-x; y1R=y1d-y;
            x2R=x2d-x; y2R=y2d-y;
            width=(int)Math.abs(x2R-x1R); height=(int)Math.abs(y2R-y1R);
            updateClipRect();
            if (lineWidth==1)
                imp.draw(clipX, clipY, clipWidth, clipHeight);
            else
                imp.draw();
            oldX = x;
            oldY = y;
            oldWidth = width;
            oldHeight = height;
        }

        protected void mouseDownInHandle(int handle, int sx, int sy) {
            state = MOVING_HANDLE;
            activeHandle = handle;
            if (lineWidth<=3)
                ic.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
        }

        /** Draws this line in the image. */
        public void draw(Graphics g) {
            g.setColor(instanceColor!=null?instanceColor:ROIColor);
            x1d=x+x1R; y1d=y+y1R; x2d=x+x2R; y2d=y+y2R;
            x1=(int)x1d; y1=(int)y1d; x2=(int)x2d; y2=(int)y2d;
            int sx1 = ic.screenXD(x1d);
            int sy1 = ic.screenYD(y1d);
            int sx2 = ic.screenXD(x2d);
            int sy2 = ic.screenYD(y2d);
            int sx3 = sx1 + (sx2-sx1)/2;
            int sy3 = sy1 + (sy2-sy1)/2;
            if (lineWidth==1)
                g.drawLine(sx1, sy1, sx2, sy2);
            else {
                Polygon p = getPolygon();
                g.drawLine(ic.screenX(p.xpoints[0]), ic.screenY(p.ypoints[0]), ic.screenX(p.xpoints[1]), ic.screenY(p.ypoints[1]));
                g.drawLine(ic.screenX(p.xpoints[1]), ic.screenY(p.ypoints[1]), ic.screenX(p.xpoints[2]), ic.screenY(p.ypoints[2]));
                g.drawLine(ic.screenX(p.xpoints[2]), ic.screenY(p.ypoints[2]), ic.screenX(p.xpoints[3]), ic.screenY(p.ypoints[3]));
                g.drawLine(ic.screenX(p.xpoints[3]), ic.screenY(p.ypoints[3]), ic.screenX(p.xpoints[0]), ic.screenY(p.ypoints[0]));
                //updateFullWindow = true;
            }
            if (state!=CONSTRUCTING) {
                int size2 = HANDLE_SIZE/2;
                handleColor=instanceColor!=null?instanceColor:ROIColor; drawHandle(g, sx1-size2, sy1-size2); handleColor=Color.white;
                drawHandle(g, sx2-size2, sy2-size2);
                drawHandle(g, sx3-size2, sy3-size2);
           }
            if (state!=NORMAL)
                IJ.showStatus(imp.getLocationAsString(x2,y2)+", angle=" + IJ.d2s(getAngle(x1,y1,x2,y2)) + ", length=" + IJ.d2s(getLength()));
            if (updateFullWindow)
                {updateFullWindow = false; imp.draw();}
        }

        /** Returns the length of this line. */
        public double getLength() {
            if (imp==null || IJ.altKeyDown())
                return getRawLength();
            else {
                Calibration cal = imp.getCalibration();
                return Math.sqrt((x2d-x1d)*cal.pixelWidth*(x2d-x1d)*cal.pixelWidth
                    + (y2d-y1d)*cal.pixelHeight*(y2d-y1d)*cal.pixelHeight);
            }
        }

        /** Returns the length of this line in pixels. */
        public double getRawLength() {
            return Math.sqrt((x2d-x1d)*(x2d-x1d)+(y2d-y1d)*(y2d-y1d));
        }

        /** Returns the pixel values along this line. */
        public double[] getPixels() {
                double[] profile;
                ImageProcessor ip = imp.getProcessor();
                if (lineWidth==1)
                    profile = ip.getLine(x1d, y1d, x2d, y2d);
                else {
                    ImageProcessor ip2 = rotateWideLine(ip);
                    int width = ip2.getWidth();
                    int height = ip2.getHeight();
                    profile = new double[width];
                    double[] aLine;
                    ip2.setInterpolate(false);
                    for (int y=0; y<height; y++) {
                        aLine = ip2.getLine(0, y, width-1, y);
                        for (int i=0; i<width; i++)
                            profile[i] += aLine[i];
                    }
                    for (int i=0; i<width; i++)
                        profile[i] /= height;
                }
                return profile;
        }

        ImageProcessor rotateWideLine(ImageProcessor ip) {
            int width = (int)Math.round(getRawLength());
            int height = lineWidth;
            ImageProcessor ip2 = new FloatProcessor(width, height);
            double angle = Math.atan2(y1d-y2d, x2d-x1d);
            double srcWidth = (double)ip.getWidth();
            double srcHeight = (double)ip.getHeight();
            Polygon p = getPolygon();
            int sxbase = p.xpoints[1];
            int sybase = p.ypoints[1];
            double r, theta, sx, sy;
            for (int dy=0; dy<height; dy++) {
                for (int dx=0; dx<width; dx++) {
                    r = Math.sqrt(dx*dx+dy*dy);
                    theta = Math.atan2(dy, dx);
                    theta += angle;
                    sx = sxbase + r*Math.cos(theta);
                    sy = sybase - r*Math.sin(theta);
                    //if (dy==height/2 && dx==width/2) IJ.log(""+angle+"  "+dx+"  "+dy+"  "+sx+"  "+sy+"  "+r +"  "+"  "+theta+"  "+sy);
                    if (sx>srcWidth || sy>srcHeight || sy<0.0 || sx<0.0 )
                        ip2.putPixelValue(dx, dy, 0.0);
                    else
                        ip2.putPixelValue(dx, dy, ip.getInterpolatedValue(sx, sy));
                }
            }
            if (IJ.altKeyDown()) {
                ip2.resetMinAndMax();
                new ImagePlus("Rotated Line", ip2).show();
            }
            return ip2;
        }

        public Polygon getPolygon() {
            Polygon p = new Polygon();
            if (lineWidth==1) {
                p.addPoint(x1, y1);
                p.addPoint(x2, y2);
            } else {
                double angle = Math.atan2(y1-y2, x2-x1);
                double width2 = lineWidth/2.0;
                double p1x = x1 + Math.cos(angle+Math.PI/2d)*width2;
                double p1y = y1 - Math.sin(angle+Math.PI/2d)*width2;
                double p2x = x1 + Math.cos(angle-Math.PI/2d)*width2;
                double p2y = y1 - Math.sin(angle-Math.PI/2d)*width2;
                double p3x = x2 + Math.cos(angle-Math.PI/2d)*width2;
                double p3y = y2 - Math.sin(angle-Math.PI/2d)*width2;
                double p4x = x2 + Math.cos(angle+Math.PI/2d)*width2;
                double p4y = y2 - Math.sin(angle+Math.PI/2d)*width2;
                p.addPoint((int)Math.round(p1x), (int)Math.round(p1y));
                p.addPoint((int)Math.round(p2x), (int)Math.round(p2y));
                p.addPoint((int)Math.round(p3x), (int)Math.round(p3y));
                p.addPoint((int)Math.round(p4x), (int)Math.round(p4y));
            }
            return p;
        }

        public void drawPixels(ImageProcessor ip) {
            ip.setLineWidth(1);
            if (lineWidth==1) {
                ip.moveTo(x1, y1);
                ip.lineTo(x2, y2);
            } else {
                ip.drawPolygon(getPolygon());
                updateFullWindow = true;
            }
        }

        public boolean contains(int x, int y) {
            if (lineWidth>1)
                return getPolygon().contains(x, y);
            else
                return false;
        }

        /** Returns a handle number if the specified screen coordinates are
            inside or near a handle, otherwise returns -1. */
        public int isHandle(int sx, int sy) {
            int size = HANDLE_SIZE+5;
            if (lineWidth>1) size += (int)Math.log(lineWidth);
            int halfSize = size/2;
            int sx1 = ic.screenXD(x+x1R) - halfSize;
            int sy1 = ic.screenYD(y+y1R) - halfSize;
            int sx2 = ic.screenXD(x+x2R) - halfSize;
            int sy2 = ic.screenYD(y+y2R) - halfSize;
            int sx3 = sx1 + (sx2-sx1)/2-1;
            int sy3 = sy1 + (sy2-sy1)/2-1;
            if (sx>=sx1&&sx<=sx1+size&&sy>=sy1&&sy<=sy1+size) return 0;
            if (sx>=sx2&&sx<=sx2+size&&sy>=sy2&&sy<=sy2+size) return 1;
            if (sx>=sx3&&sx<=sx3+size+2&&sy>=sy3&&sy<=sy3+size+2) return 2;
            return -1;
        }

        public static int getWidth() {
            return lineWidth;
        }

        public static void setWidth(int w) {
            if (w<1) w = 1;
            if (w>200) w = 200;
            lineWidth = w;
        }

        /** Return the bounding rectangle of this line. */
        public Rectangle getBounds() {
            int xmin = (int)Math.round(Math.min(x1d, x2d));
            int ymin = (int)Math.round(Math.min(y1d, y2d));
            int w = (int)Math.round(Math.abs(x2d - x1d));
            int h = (int)Math.round(Math.abs(y2d - y1d));
            return new Rectangle(xmin, ymin, w, h);
        }

        /** Nudge end point of line by one pixel. */
        public void nudgeCorner(int key) {
            if (ic==null) return;
            double inc = 1.0/ic.getMagnification();
            switch(key) {
                case KeyEvent.VK_UP: y2R-=inc; break;
                case KeyEvent.VK_DOWN: y2R+=inc; break;
                case KeyEvent.VK_LEFT: x2R-=inc; break;
                case KeyEvent.VK_RIGHT: x2R+=inc; break;
            }
            grow(ic.screenXD(x+x2R), ic.screenYD(y+y2R));
        }

    }

| Line |