package ij.gui;

import java.awt.*;
import java.awt.image.*;
import java.awt.event.KeyEvent;
import ij.*;
import ij.process.*;
import ij.measure.*;
import ij.plugin.frame.Recorder;
import ij.plugin.filter.Analyzer;
import ij.macro.Interpreter;

/** A rectangular region of interest and superclass for the other ROI classes. */
public class Roi extends Object implements Cloneable, java.io.Serializable {

    public static final int CONSTRUCTING=0, MOVING=1, RESIZING=2, NORMAL=3, MOVING_HANDLE=4; // States
    public static final int RECTANGLE=0, OVAL=1, POLYGON=2, FREEROI=3, TRACED_ROI=4, LINE=5, 
        POLYLINE=6, FREELINE=7, ANGLE=8, COMPOSITE=9, POINT=10; // Types
    public static final int HANDLE_SIZE = 5; 
    public static final int NOT_PASTING = -1; 
    
    static final int NO_MODS=0, ADD_TO_ROI=1, SUBTRACT_FROM_ROI=2; // modification states
        
    int startX, startY, x, y, width, height;
    int activeHandle;
    int state;
    int modState = NO_MODS;
    
    public static Roi previousRoi;
    protected static Color ROIColor = Prefs.getColor(Prefs.ROICOLOR,Color.yellow);
    protected static int pasteMode = Blitter.COPY;
    
    protected int type;
    protected int xMax, yMax;
    protected ImagePlus imp;
    protected ImageCanvas ic;
    protected int oldX, oldY, oldWidth, oldHeight;
    protected int clipX, clipY, clipWidth, clipHeight;
    protected ImagePlus clipboard;
    protected boolean constrain; // to be square
    protected boolean center;
    protected boolean updateFullWindow;
    protected double mag = 1.0;
    protected String name;
    protected ImageProcessor cachedMask;

    /** Creates a new rectangular Roi. */
    public Roi(int x, int y, int width, int height) {
        setImage(null);
        if (width<1) width = 1;
        if (height<1) height = 1;
        if (width>xMax) width = xMax;
        if (height>yMax) height = yMax;
        //setLocation(x, y);
        this.x = x;
        this.y = y;
        startX = x; startY = y;
        oldX = x; oldY = y; oldWidth=0; oldHeight=0;
        this.width = width;
        this.height = height;
        oldWidth=width;
        oldHeight=height;
        clipX = x;
        clipY = y;
        clipWidth = width;
        clipHeight = height;
        state = NORMAL;
        type = RECTANGLE;
        if (ic!=null) {
            Graphics g = ic.getGraphics();
            draw(g);
            g.dispose();
        }
    }
    
    /** Creates a new rectangular Roi. */
    public Roi(Rectangle r) {
        this(r.x, r.y, r.width, r.height);
    }

    /** Starts the process of creating a user-defined rectangular Roi,
        where sx and sy are the starting screen coordinates. */
    public Roi(int sx, int sy, ImagePlus imp) {
        setImage(imp);
        int ox=sx, oy=sy;
        if (ic!=null) {
            ox = ic.offScreenX(sx);
            oy = ic.offScreenY(sy);
        }
        setLocation(ox, oy);
        width = 0;
        height = 0;
        state = CONSTRUCTING;
        type = RECTANGLE;
    }
    
    /** Obsolete */
    public Roi(int x, int y, int width, int height, ImagePlus imp) {
        this(x, y, width, height);
        setImage(imp);
    }

    public void setLocation(int x, int y) {
        //if (x<0) x = 0;
        //if (y<0) y = 0;
        //if ((x+width)>xMax) x = xMax-width;
        //if ((y+height)>yMax) y = yMax-height;
        //IJ.write(imp.getTitle() + ": Roi.setlocation(" + x + "," + y + ")");
        this.x = x;
        this.y = y;
        startX = x; startY = y;
        oldX = x; oldY = y; oldWidth=0; oldHeight=0;
    }
    
    public void setImage(ImagePlus imp) {
        this.imp = imp;
        cachedMask = null;
        if (imp==null) {
            ic = null;
            clipboard = null;
            xMax = 99999;
            yMax = 99999;
        } else {
            ImageWindow win = imp.getWindow();
            if (win!=null)
                ic = win.getCanvas();
            xMax = imp.getWidth();
            yMax = imp.getHeight();
        }
    }
    
    public int getType() {
        return type;
    }
    
    public int getState() {
        return state;
    }
    
    /** Returns the perimeter length. */
    public double getLength() {
        double pw=1.0, ph=1.0;
        if (imp!=null) {
            Calibration cal = imp.getCalibration();
            pw = cal.pixelWidth;
            ph = cal.pixelHeight;
        }
        return 2.0*width*pw+2.0*height*ph;
    }
    
    /** Returns Feret's diameter, the greatest distance between 
        any two points along the ROI boundary. */
    public double getFeretsDiameter() {
        double pw=1.0, ph=1.0;
        if (imp!=null) {
            Calibration cal = imp.getCalibration();
            pw = cal.pixelWidth;
            ph = cal.pixelHeight;
        }
        return Math.sqrt(width*width*pw*pw+height*height*ph*ph);
    }

    /** Return this selection's bounding rectangle. */
    public Rectangle getBounds() {
        return new Rectangle(x, y, width, height);
    }
    
    /** This obsolete method has been replaced by getBounds(). */
    public Rectangle getBoundingRect() {
        return getBounds();
    }

    /** Returns the outline of this selection as a Polygon, or 
        null if this is a straight line selection. 
        @see ij.process.ImageProcessor#setRoi
        @see ij.process.ImageProcessor#drawPolygon
        @see ij.process.ImageProcessor#fillPolygon
    */
    public Polygon getPolygon() {
        int[] xpoints = new int[4];
        int[] ypoints = new int[4];
        xpoints[0] = x;
        ypoints[0] = y;
        xpoints[1] = x+width;
        ypoints[1] = y;
        xpoints[2] = x+width;
        ypoints[2] = y+height;
        xpoints[3] = x;
        ypoints[3] = y+height;
        return new Polygon(xpoints, ypoints, 4);
    }

    /** Returns a copy of this roi. See Thinking is Java by Bruce Eckel
        (www.eckelobjects.com) for a good description of object cloning. */
    public synchronized Object clone() {
        try { 
            Roi r = (Roi)super.clone();
            r.setImage(null);
            return r;
        }
        catch (CloneNotSupportedException e) {return null;}
    }
    
    protected void grow(int sx, int sy) {
        if (clipboard!=null) return;
        int xNew = ic.offScreenX(sx);
        int yNew = ic.offScreenY(sy);
        if (type==RECTANGLE) {
            if (xNew < 0) xNew = 0;
            if (yNew < 0) yNew = 0;
        }
        if (constrain) {
            // constrain selection to be square
            int dx, dy, d;
            dx = xNew - x;
            dy = yNew - y;
            if (dx<dy)
                d = dx;
            else
                d = dy;
            xNew = x + d;
            yNew = y + d;
        }
        
        if (center) {
            width = Math.abs(xNew - startX)*2;
            height = Math.abs(yNew - startY)*2;
            x = startX - width/2;
            y = startY - height/2;
        } else {
            width = Math.abs(xNew - startX);
            height = Math.abs(yNew - startY);
            x = (xNew>=startX)?startX:startX - width;
            y = (yNew>=startY)?startY:startY - height;
            if (type==RECTANGLE) {
                if ((x+width) > xMax) width = xMax-x;
                if ((y+height) > yMax) height = yMax-y;
            }
        }
        updateClipRect();
        imp.draw(clipX, clipY, clipWidth, clipHeight);
        oldX = x;
        oldY = y;
        oldWidth = width;
        oldHeight = height;
    }

    protected void moveHandle(int sx, int sy) {
        if (clipboard!=null) return;
        int ox = ic.offScreenX(sx);
        int oy = ic.offScreenY(sy);
        if (ox<0) ox=0; if (oy<0) oy=0;
        if (ox>xMax) ox=xMax; if (oy>yMax) oy=yMax;
        //IJ.log("moveHandle: "+activeHandle+" "+ox+" "+oy);
        int x1=x, y1=y, x2=x1+width, y2=y+height;
        switch (activeHandle) {
            case 0: x=ox; y=oy; break;
            case 1: y=oy; break;
            case 2: x2=ox; y=oy; break;
            case 3: x2=ox; break;           
            case 4: x2=ox; y2=oy; break;
            case 5: y2=oy; break;
            case 6: x=ox; y2=oy; break;
            case 7: x=ox; break;
        }
        if (x<x2)
           width=x2-x;
        else
          {width=1; x=x2;}
        if (y<y2)
           height = y2-y;
        else
           {height=1; y=y2;}
        if (constrain)
            height = width;
        updateClipRect();
        imp.draw(clipX, clipY, clipWidth, clipHeight);
        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 - startX;
        y += yNew - startY;
        if (clipboard==null && type==RECTANGLE) {
            if (x<0) x=0; if (y<0) y=0;
            if ((x+width)>xMax) x = xMax-width;
            if ((y+height)>yMax) y = yMax-height;
        }
        startX = xNew;
        startY = yNew;
        updateClipRect();
        imp.draw(clipX, clipY, clipWidth, clipHeight);
        oldX = x;
        oldY = y;
        oldWidth = width;
        oldHeight=height;
    }

    /** Nudge ROI one pixel on arrow key press. */
    public void nudge(int key) {
        switch(key) {
            case KeyEvent.VK_UP:
                y--;
                if (y<0 && (type!=RECTANGLE||clipboard==null))
                    y = 0;
                break;
            case KeyEvent.VK_DOWN:
                y++;
                if ((y+height)>=yMax && (type!=RECTANGLE||clipboard==null))
                    y = yMax-height;
                break;
            case KeyEvent.VK_LEFT:
                x--;
                if (x<0 && (type!=RECTANGLE||clipboard==null))
                    x = 0;
                break;
            case KeyEvent.VK_RIGHT:
                x++;
                if ((x+width)>=xMax && (type!=RECTANGLE||clipboard==null))
                    x = xMax-width;
                break;
        }
        updateClipRect();
        imp.draw(clipX, clipY, clipWidth, clipHeight);
        oldX = x; oldY = y;
        showStatus();
    }
    
    /** Nudge lower right corner of rectangular and oval ROIs by
        one pixel based on arrow key press. */
    public void nudgeCorner(int key) {
        if (type>OVAL || clipboard!=null)
            return;
        switch(key) {
            case KeyEvent.VK_UP:
                height--;
                if (height<1) height = 1;
                break;
            case KeyEvent.VK_DOWN:
                height++;
                if ((y+height) > yMax) height = yMax-y;
                break;
            case KeyEvent.VK_LEFT:
                width--;
                if (width<1) width = 1;
                break;
            case KeyEvent.VK_RIGHT:
                width++;
                if ((x+width) > xMax) width = xMax-x;
                break;
        }
        updateClipRect();
        imp.draw(clipX, clipY, clipWidth, clipHeight);
        oldX = x; oldY = y;
        cachedMask = null;
        showStatus();
    }
    
    protected void updateClipRect() {
    // Finds the union of current and previous roi
        clipX = (x<=oldX)?x:oldX;
        clipY = (y<=oldY)?y:oldY;
        clipWidth = ((x+width>=oldX+oldWidth)?x+width:oldX+oldWidth) - clipX + 1;
        clipHeight = ((y+height>=oldY+oldHeight)?y+height:oldY+oldHeight) - clipY + 1;
        int m = 3;
        if (ic!=null) {
            double mag = ic.getMagnification();
            if (mag<1.0)
                m = (int)(3/mag);
        }
        clipX-=m; clipY-=m;
        clipWidth+=m*2; clipHeight+=m*2;
     }
        
    protected void handleMouseDrag(int sx, int sy, int flags) {
        if (ic==null) return;
        constrain = (flags&Event.SHIFT_MASK)!=0;
        center = (flags&Event.CTRL_MASK)!=0 || (IJ.isMacintosh()&&(flags&Event.META_MASK)!=0);
        switch(state) {
            case CONSTRUCTING:
                grow(sx, sy);
                break;
            case MOVING:
                move(sx, sy);
                break;
            case MOVING_HANDLE:
                moveHandle(sx, sy);
                break;
            default:
                break;
        }
    }

    int getHandleSize() {
        double mag = ic!=null?ic.getMagnification():1.0;
        double size = HANDLE_SIZE/mag;
        return (int)(size*mag);
    }
    
    public void draw(Graphics g) {
        if (ic==null) return;
        g.setColor(ROIColor);
        mag = ic.getMagnification();
        int sw = (int)(width*mag);
        int sh = (int)(height*mag);
        //if (x+width==imp.getWidth()) sw -= 1;
        //if (y+height==imp.getHeight()) sh -= 1;
        int sx1 = ic.screenX(x);
        int sy1 = ic.screenY(y);
        int sx2 = sx1+sw/2;
        int sy2 = sy1+sh/2;
        int sx3 = sx1+sw;
        int sy3 = sy1+sh;
        g.drawRect(sx1, sy1, sw, sh);
        if (state!=CONSTRUCTING && clipboard==null) {
            int size2 = HANDLE_SIZE/2;
            drawHandle(g, sx1-size2, sy1-size2);
            drawHandle(g, sx2-size2, sy1-size2);
            drawHandle(g, sx3-size2, sy1-size2);
            drawHandle(g, sx3-size2, sy2-size2);
            drawHandle(g, sx3-size2, sy3-size2);
            drawHandle(g, sx2-size2, sy3-size2);
            drawHandle(g, sx1-size2, sy3-size2);
            drawHandle(g, sx1-size2, sy2-size2);
        }
        drawPreviousRoi(g);
        if (state!=NORMAL) showStatus();
        if (updateFullWindow)
            {updateFullWindow = false; imp.draw();}
    }
    
    void drawPreviousRoi(Graphics g) {
        if (previousRoi!=null && previousRoi!=this && previousRoi.modState!=NO_MODS) {
            if (type!=POINT && previousRoi.getType()==POINT && previousRoi.modState!=SUBTRACT_FROM_ROI)
                return;
            previousRoi.setImage(imp);
            previousRoi.draw(g);
        }       
    }
    
    void drawHandle(Graphics g, int x, int y) {
        double size = (width*height)*mag;
        if (type==LINE) {
            size = Math.sqrt(width*width+height*height);
            size *= size*mag;
        }
        if (size>6000.0) {
            g.setColor(Color.black);
            g.fillRect(x,y,5,5);
            g.setColor(Color.white);
            g.fillRect(x+1,y+1,3,3);
        } else if (size>1500.0) {
            g.setColor(Color.black);
            g.fillRect(x+1,y+1,4,4);
            g.setColor(Color.white);
            g.fillRect(x+2,y+2,2,2);
        } else {            
            g.setColor(Color.black);
            g.fillRect(x+1,y+1,3,3);
            g.setColor(Color.white);
            g.fillRect(x+2,y+2,1,1);
        }
    }

    public void drawPixels() {
        if (imp!=null)
            drawPixels(imp.getProcessor()); 
    }

    public void drawPixels(ImageProcessor ip) {
        endPaste();
        ip.drawRect(x, y, width, height);
        if (Line.getWidth()>1)
            updateFullWindow = true;
    }

    public boolean contains(int x, int y) {
        Rectangle r = new Rectangle(this.x, this.y, width, height);
        return r.contains(x, y);
    }
        
    /** 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) {
        if (clipboard!=null || ic==null) return -1;
        double mag = ic.getMagnification();
        int size = HANDLE_SIZE+3;
        int halfSize = size/2;
        int sx1 = ic.screenX(x) - halfSize;
        int sy1 = ic.screenY(y) - halfSize;
        int sx3 = ic.screenX(x+width) - halfSize;
        int sy3 = ic.screenY(y+height) - halfSize;
        int sx2 = sx1 + (sx3 - sx1)/2;
        int sy2 = sy1 + (sy3 - sy1)/2;
        if (sx>=sx1&&sx<=sx1+size&&sy>=sy1&&sy<=sy1+size) return 0;
        if (sx>=sx2&&sx<=sx2+size&&sy>=sy1&&sy<=sy1+size) return 1;
        if (sx>=sx3&&sx<=sx3+size&&sy>=sy1&&sy<=sy1+size) return 2;
        if (sx>=sx3&&sx<=sx3+size&&sy>=sy2&&sy<=sy2+size) return 3;
        if (sx>=sx3&&sx<=sx3+size&&sy>=sy3&&sy<=sy3+size) return 4;
        if (sx>=sx2&&sx<=sx2+size&&sy>=sy3&&sy<=sy3+size) return 5;
        if (sx>=sx1&&sx<=sx1+size&&sy>=sy3&&sy<=sy3+size) return 6;
        if (sx>=sx1&&sx<=sx1+size&&sy>=sy2&&sy<=sy2+size) return 7;
        return -1;
    }
    
    protected void mouseDownInHandle(int handle, int sx, int sy) {
        state = MOVING_HANDLE;
        activeHandle = handle;
    }

    protected void handleMouseDown(int sx, int sy) {
        if (state==NORMAL && ic!=null) {
            state = MOVING;
            startX = ic.offScreenX(sx);
            startY = ic.offScreenY(sy);
            showStatus();
        }
    }
        
    protected void handleMouseUp(int screenX, int screenY) {
        state = NORMAL;
        imp.draw(clipX-5, clipY-5, clipWidth+10, clipHeight+10);
        if (Recorder.record) {
            String method;
            if (type==LINE) {
                if (imp==null) return;
                Line line = (Line)imp.getRoi();
                Recorder.record("makeLine", line.x1, line.y1, line.x2, line.y2);
            } else if (type==OVAL)
                Recorder.record("makeOval", x, y, width, height);
            else
                Recorder.record("makeRectangle", x, y, width, height);
        }
        modifyRoi();
    }

    void modifyRoi() {
        if (previousRoi==null || previousRoi.modState==NO_MODS)
            return;
        //IJ.log("modifyRoi: "+ type+"  "+modState+" "+previousRoi.type+"  "+previousRoi.modState);
        if (type==POINT || previousRoi.getType()==POINT) {
            if (type==POINT && previousRoi.getType()==POINT)
                addPoint();
            else if (isArea() && previousRoi.getType()==POINT && previousRoi.modState==SUBTRACT_FROM_ROI)
                subtractPoints();
            return;
        }
        ShapeRoi s1  = null;
        ShapeRoi s2 = null;
        if (previousRoi instanceof ShapeRoi)
            s1 = (ShapeRoi)previousRoi;
        else
            s1 = new ShapeRoi(previousRoi);
        if (this instanceof ShapeRoi)
            s2 = (ShapeRoi)this;
        else
            s2 = new ShapeRoi(this);
        if (previousRoi.modState==ADD_TO_ROI)
            s1.or(s2);
        else
            s1.not(s2);
        previousRoi.modState = NO_MODS;
        Roi[] rois = s1.getRois();
        if (rois.length==0) return;
        int type2 = rois[0].getType();
        //IJ.log(rois.length+" "+type2);
        if (rois.length==1 && (type2==POLYGON||type2==FREEROI))
            imp.setRoi(rois[0]);
        else
            imp.setRoi(s1);
    }
    
    void addPoint() {
        if (!(type==POINT && previousRoi.getType()==POINT)) {
            modState = NO_MODS;
            imp.draw();
            return;
        }
        previousRoi.modState = NO_MODS;
        PointRoi p1 = (PointRoi)previousRoi;
        Rectangle r = getBounds();
        imp.setRoi(p1.addPoint(r.x, r.y));
    }
    
    void subtractPoints() {
        previousRoi.modState = NO_MODS;
        PointRoi p1 = (PointRoi)previousRoi;
        PointRoi p2 = p1.subtractPoints(this);
        if (p2!=null)
            imp.setRoi(p1.subtractPoints(this));
        else
            imp.killRoi();
    }

    /** Called by IJ.doWand(), IJ.makeRectangle(), IJ.makeOval() and the
        makeSelection() macro function when the shift or alt key is down to
        to add to or subtract from an existing selection. */
    public void addOrSubtract() {
        if (!IJ.isJava2() || previousRoi==null) return;
        if (IJ.shiftKeyDown())
            previousRoi.modState = ADD_TO_ROI;
        else if (IJ.altKeyDown())
            previousRoi.modState = SUBTRACT_FROM_ROI;
        else
            previousRoi.modState = NO_MODS;
        modifyRoi();
    }

    protected void showStatus() {
        String value;
        if (state!=CONSTRUCTING && (type==RECTANGLE||type==POINT) && width<=25 && height<=25) {
            ImageProcessor ip = imp.getProcessor();
            double v = ip.getPixelValue(x,y);
            int digits = (imp.getType()==ImagePlus.GRAY8||imp.getType()==ImagePlus.GRAY16)?0:2;
            value = ", value="+IJ.d2s(v,digits);
        } else
            value = "";
        Calibration cal = imp.getCalibration();
        String size;
        if (cal.scaled() && !IJ.altKeyDown())
            size = ", w="+IJ.d2s(width*cal.pixelWidth)+", h="+IJ.d2s(height*cal.pixelHeight);
        else
            size = ", w="+width+", h="+height;
        IJ.showStatus(imp.getLocationAsString(x,y)+size+value);
    }
        
    public ImageProcessor getMask() {
        return null;
    }
    
    public void startPaste(ImagePlus clipboard) {
        IJ.showStatus("Pasting...");
        this.clipboard = clipboard;
        imp.getProcessor().snapshot();
        updateClipRect();
        if (IJ.debugMode) IJ.log("startPaste: "+clipX+" "+clipY+" "+clipWidth+" "+clipHeight);
        imp.draw(clipX, clipY, clipWidth, clipHeight);
    }
    
    void updatePaste() {
        if (clipboard!=null) {
            imp.getMask();
            ImageProcessor ip = imp.getProcessor();
            ip.reset();
            ip.copyBits(clipboard.getProcessor(), x, y, pasteMode);
            if (type!=RECTANGLE)
                ip.reset(ip.getMask());
            ic.setImageUpdated();
        }
    }

    public void endPaste() {
        if (clipboard!=null) {
            imp.getMask();
            ImageProcessor ip = imp.getProcessor();
            if (pasteMode!=Blitter.COPY) ip.reset();
            ip.copyBits(clipboard.getProcessor(), x, y, pasteMode);
            if (type!=RECTANGLE)
                ip.reset(ip.getMask());
            ip.snapshot();
            clipboard = null;
            imp.updateAndDraw();
            Undo.setup(Undo.FILTER, imp);
        }
    }
    
    public void abortPaste() {
        clipboard = null;
        imp.getProcessor().reset();
        imp.updateAndDraw();
    }

    /** Returns the angle in degrees between the specified line and a horizontal line. */
    public double getAngle(int x1, int y1, int x2, int y2) {
        double dx = x2-x1;
        double dy = y1-y2;
        if (imp!=null) {
            Calibration cal = imp.getCalibration();
            dx *= cal.pixelWidth;
            dy *= cal.pixelHeight;
        }
        return (180.0/Math.PI)*Math.atan2(dy, dx);
    }
    
    /** Returns the color used for drawing ROI outlines. */
    public static Color getColor() {
        return ROIColor;
    }

    /** Sets the color used for ROI outline to the specified value. */
    public static void setColor(Color c) {
        ROIColor = c;
    }
    
    /** Returns the name of this ROI, or null. */
    public String getName() {
        return name;
    }

    /** Sets the name of this ROI. */
    public void setName(String name) {
        this.name = name;
    }

    /** Sets the Paste transfer mode.
        @see ij.process.Blitter
    */
    public static void setPasteMode(int transferMode) {
        if (transferMode==pasteMode) return;
        pasteMode = transferMode;
        ImagePlus imp = WindowManager.getCurrentImage();
        if (imp!=null)
            imp.updateAndDraw();
    }
    
    /** Returns the current paste transfer mode, or NOT_PASTING (-1)
        if no paste operation is in progress.
        @see ij.process.Blitter
    */
    public int getPasteMode() {
        if (clipboard==null)
            return NOT_PASTING;
        else
            return pasteMode;
    }

    /** Returns the current paste transfer mode. */
    public static int getCurrentPasteMode() {
        return pasteMode;
    }
    
    /** Returns true if this is an area selection. */
    public boolean isArea() {
        return (type>=RECTANGLE && type<=TRACED_ROI) || type==COMPOSITE;
    }

    /** Returns true if this is a line selection. */
    public boolean isLine() {
        return type>=LINE && type<=FREELINE;
    }

    /** Convenience method that converts Roi type to a human-readable form. */
    public String getTypeAsString() {
        String s="";
        switch(type) {
            case POLYGON: s="Polygon"; break;
            case FREEROI: s="Freehand"; break;
            case TRACED_ROI: s="Traced"; break;
            case POLYLINE: s="Polyline"; break;
            case FREELINE: s="Freeline"; break;
            case ANGLE: s="Angle"; break;
            case LINE: s="Straight Line"; break;
            case OVAL: s="Oval"; break;
            case COMPOSITE: s = "Composite"; break;
            case POINT: s = "Point"; break;
            default: s="Rectangle"; break;
        }
        return s;
    }
    
    /** Returns true if this ROI is currently displayed on an image. */
    public boolean isVisible() {
        return ic!=null;
    }

    public String toString() {
        return ("Roi["+getTypeAsString()+", x="+x+", y="+y+", width="+width+", height="+height+"]");
    }

}