package ij.gui;
import ij.*;
import ij.process.*;
import ij.measure.*;
import ij.plugin.Colors;
import ij.plugin.PointToolOptions;
import ij.plugin.filter.Analyzer;
import ij.plugin.frame.Recorder;
import ij.util.Java2; 
import java.awt.*;
import java.awt.image.*;
import java.awt.event.KeyEvent;
import java.util.*;
import java.awt.geom.*;

/** This class represents a collection of points. */
public class PointRoi extends PolygonRoi {
	public static final String[] sizes = {"Tiny", "Small", "Medium", "Large", "Extra Large"};
	public static final String[] types = {"Hybrid", "Crosshair", "Dot", "Circle"};
	private static final String TYPE_KEY = "point.type";
	private static final String SIZE_KEY = "point.size";
	private static final String CROSS_COLOR_KEY = "point.cross.color";
	private static final int TINY=1, SMALL=3, MEDIUM=5, LARGE=7, EXTRA_LARGE=11;
	private static final int HYBRID=0, CROSSHAIR=1, DOT=2, CIRCLE=3;
	private static final BasicStroke twoPixelsWide = new BasicStroke(2);
	private static final BasicStroke threePixelsWide = new BasicStroke(3);
	private static int defaultType = HYBRID;
	private static int defaultSize = SMALL;
	private static Font font;
	private static Color defaultCrossColor = Color.white;
	private static int fontSize = 9;
	public static final int MAX_COUNTERS = 100;
	private static String[] counterChoices;
	private static Color[] colors;
	private boolean showLabels;
	private int type = HYBRID;
	private int size = SMALL;
	private static int defaultCounter;
	private int counter;
	private int nCounters = 1;
	private short[] counters;
	private short[] positions;
	private int[] counts = new int[MAX_COUNTERS];
	private ResultsTable rt;
	private long lastPointTime;
	private int[] counterInfo;
	
	static {
		setDefaultType((int)Prefs.get(TYPE_KEY, HYBRID));
		setDefaultSize((int)Prefs.get(SIZE_KEY, 1));
	}
	
	/** Creates a new PointRoi using the specified int arrays of offscreen coordinates. */
	public PointRoi(int[] ox, int[] oy, int points) {
		super(itof(ox), itof(oy), points, POINT);
		width+=1; height+=1;
	}

	/** Creates a new PointRoi using the specified float arrays of offscreen coordinates. */
	public PointRoi(float[] ox, float[] oy, int points) {
		super(ox, oy, points, POINT);
		width+=1; height+=1;
	}

	/** Creates a new PointRoi using the specified float arrays of offscreen coordinates. */
	public PointRoi(float[] ox, float[] oy) {
		this(ox, oy, ox.length);
	}

	/** Creates a new PointRoi from a FloatPolygon. */
	public PointRoi(FloatPolygon poly) {
		this(poly.xpoints, poly.ypoints, poly.npoints);
	}

	/** Creates a new PointRoi from a Polygon. */
	public PointRoi(Polygon poly) {
		this(itof(poly.xpoints), itof(poly.ypoints), poly.npoints);
	}

	/** Creates a new PointRoi using the specified offscreen int coordinates. */
	public PointRoi(int ox, int oy) {
		super(makeXArray(ox, null), makeYArray(oy, null), 1, POINT);
		width=1; height=1;
		incrementCounter(null);
	}

	/** Creates a new PointRoi using the specified offscreen double coordinates. */
	public PointRoi(double ox, double oy) {
		super(makeXArray(ox, null), makeYArray(oy, null), 1, POINT);
		width=1; height=1;
		incrementCounter(null);
	}

	/** Creates a new PointRoi using the specified screen coordinates. */
	public PointRoi(int sx, int sy, ImagePlus imp) {
		super(makeXArray(sx, imp), makeYArray(sy, imp), 1, POINT);
		defaultCounter = 0;
		setImage(imp);
		width=1; height=1;
		type = defaultType;
		size = defaultSize;
		showLabels = !Prefs.noPointLabels;
		if (imp!=null) {
			int r = 10;
			double mag = ic!=null?ic.getMagnification():1;
			if (mag<1)
				r = (int)(r/mag);
			imp.draw(x-r, y-r, 2*r, 2*r);
		}
		setCounter(Toolbar.getMultiPointMode()?defaultCounter:0);
		incrementCounter(imp);
		enlargeArrays(50);
		if (Recorder.record && !Recorder.scriptMode()) 
			Recorder.record("makePoint", x, y);
	}
	
	static float[] itof(int[] arr) {
		if (arr==null)
			return null;
		int n = arr.length;
		float[] temp = new float[n];
		for (int i=0; i1) {
			fontSize = 8;
			fontSize += convertSizeToIndex(size);
			if (fontSize>18)
				fontSize = 18;
			fontSize = (int)Math.round(fontSize);
			font = new Font("SansSerif", Font.PLAIN, fontSize);
			g.setFont(font);
			if (fontSize>9)
				Java2.setAntialiasedText(g, true);
		}
		int slice = imp!=null&&positions!=null&&imp.getStackSize()>1?imp.getCurrentSlice():0;
		if (Prefs.showAllPoints)
			slice = 0;
		for (int i=0; i1.0) {
			saveXform = g2d.getTransform();
			g2d.translate(x, y);
			g2d.scale(flattenScale, flattenScale);
			x = y = 0;
		}
		Color color = strokeColor!=null?strokeColor:ROIColor;
		if (!overlay && isActiveOverlayRoi()) {
			if (color==Color.cyan)
				color = Color.magenta;
			else
				color = Color.cyan;
		}
		if (nCounters>1 && counters!=null && n<=counters.length)
			color = getColor(counters[n-1]);
		if (type==HYBRID || type==CROSSHAIR) {
			if (type==HYBRID)
				g.setColor(Color.white);
			else {
				g.setColor(color);
				colorSet = true;
			}
			if (size>LARGE)
				g2d.setStroke(threePixelsWide);
			g.drawLine(x-(size+2), y, x+size+2, y);
			g.drawLine(x, y-(size+2), x, y+size+2);
		}
		if (type!=CROSSHAIR && size>SMALL)
			g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		if (type==HYBRID || type==DOT) { 
			if (!colorSet) {
				g.setColor(color);
				colorSet = true;
			}
			if (size>LARGE)
				g2d.setStroke(onePixelWide);
			if (size>LARGE && type==DOT)
				g.fillOval(x-size2, y-size2, size, size);
			else if (size>LARGE && type==HYBRID)
				g.fillRect(x-(size2-2), y-(size2-2), size-4, size-4);
			else if (size>SMALL && type==HYBRID)
				g.fillRect(x-(size2-1), y-(size2-1), size-2, size-2);
			else
				g.fillRect(x-size2, y-size2, size, size);
		}
		if (showLabels && nPoints>1) {
			int offset = (int)Math.round(0.4);
			if (offset<1) offset=1;
			offset++;
			if (nCounters==1) {
				if (!colorSet)
					g.setColor(color);
				g.drawString(""+n, x+offset, y+offset+fontSize);
			} else if (counters!=null) {
				g.setColor(getColor(counters[n-1]));
				g.drawString(""+counters[n-1], x+offset, y+offset+fontSize);
			}
		}
		if ((size>TINY||type==DOT) && (type==HYBRID||type==DOT)) {
			g.setColor(Color.black);
			if (size>LARGE && type==HYBRID)
				g.drawOval(x-(size2-1), y-(size2-1), size-3, size-3);
			else if (size>SMALL && type==HYBRID)
				g.drawOval(x-size2, y-size2, size-1, size-1);
			else
				g.drawOval(x-(size2+1), y-(size2+1), size+1, size+1);
		}
		if (type==CIRCLE) {
			int scaledSize = (int)Math.round(size+1);
			g.setColor(color);
			if (size>LARGE)
				g2d.setStroke(twoPixelsWide);
			g.drawOval(x-scaledSize/2, y-scaledSize/2, scaledSize, scaledSize);
		}
		if (saveXform!=null)
			g2d.setTransform(saveXform);
	}
	
	public void drawPixels(ImageProcessor ip) {
		ip.setLineWidth(Analyzer.markWidth);
		for (int i=0; i=0 && index<=nPoints && counters!=null) {
			counts[counters[index]]--;
			for (int i=index; i1;
		if (counter!=0 || isStack || counters!=null) {
			if (counters==null) {
				counters = new short[nPoints*2];
				positions = new short[nPoints*2];
			}
			counters[nPoints-1] = (short)counter;
			if (imp!=null)
					positions[nPoints-1] = imp.getStackSize()>1?(short)imp.getCurrentSlice():0;
			//if (positions[nPoints-1]==0 || positions[nPoints-1]==1 || counters[nPoints-1]==0)
			//	IJ.log("incrementCounter: "+nPoints+" "+" "+positions[nPoints-1]+" "+counters[nPoints-1]+" "+imp);
			if (nPoints+1==counters.length) {
				short[] temp = new short[counters.length*2];
				System.arraycopy(counters, 0, temp, 0, counters.length);
				counters = temp;
				temp = new short[counters.length*2];
				System.arraycopy(positions, 0, temp, 0, positions.length);
				positions = temp;
			}
		}
		if (rt!=null && WindowManager.getFrame(getCountsTitle())!=null)
			displayCounts();
	}
	
	public void resetCounters() {
		for (int i=0; i=0 && type=0 && type=0 && index=0 && size1)
			return ("Roi[Points, count="+nPoints+"]");
		else
			return ("Roi[Point, x="+x+", y="+y+"]");
	}
	
	public void setCounter(int counter) {
		this.counter = counter;
		if (counter>nCounters-1 && nCounters0) n++;
		}
		return n;
	}
	
	public boolean counting() {
		return getNCounters()>1 || counts[0]>1;
	}

	public static void setDefaultCounter(int counter) {
		defaultCounter = counter;
	}

	public int getCount(int counter) {
		if (counter==0 && counters==null)
			return nPoints;
		else
			return counts[counter];
	}
	
	/** Returns the counter assocated with the specified point. */
	public int getCounter(int index) {
		if (counters==null || index>=counters.length)
			return 0;
		else
			return counters[index];
	}

	public int[] getCounters() {
		if (nPoints>65535)
			return null;
		int[] temp = new int[nPoints];
		if (counters!=null) {
			for (int i=0; i>8)&0xffff;
				this.counters[i] = (short)counter;
				this.positions[i] = (short)position;
				if (counternCounters-1)
						nCounters = counter + 1;
				}
			}
			IJ.setTool("multi-point");
		}
	}
	
	public int getPointPosition(int index) {
		if (positions!=null && index1 && positions!=null) {
			int nChannels = 1;
			int nSlices = 1;
			int nFrames = 1;
			boolean isHyperstack = false;
			if (imp.isComposite() || imp.isHyperStack()) {
				isHyperstack = true;
				nChannels = imp.getNChannels();
				nSlices = imp.getNSlices();
				nFrames = imp.getNFrames();
				int nDimensions = 2;
				if (nChannels>1) nDimensions++;
				if (nSlices>1) nDimensions++;
				if (nFrames>1) nDimensions++;
				if (nDimensions==3) {
					isHyperstack = false;
					if (nChannels>1)
						firstColumnHdr = "Channel";
				} else
					firstColumnHdr = "Image";
			}
			int firstSlice = Integer.MAX_VALUE;
			for (int i=0; i0 && positions[i]0) {
				for (int i=0; ilastSlice)
						lastSlice = positions[i];
				}
			}
			if (firstSlice>0) {
				for (int slice=firstSlice; slice<=lastSlice; slice++) {
					rt.setValue(firstColumnHdr, row, slice);
					if (isHyperstack) {
						int[] position = imp.convertIndexToPosition(slice);
						if (nChannels>1)
							rt.setValue("Channel", row, position[0]);
						if (nSlices>1)
							rt.setValue("Slice", row, position[1]);
						if (nFrames>1)
							rt.setValue("Frame", row, position[2]);
					}
					for (int counter=0; counter1?imp.getCurrentSlice():0;
		for (int i=0; i=sx2 && sx<=sx2+size && sy>=sy2 && sy<=sy2+size) {
				handle = i;
				break;
			}
		}
		return handle;
	}
	
	/** Returns the points as an array of Points. 
	 * Wilhelm Burger: modified to use FloatPolygon for correct point positions.
	*/
	public Point[] getContainedPoints() {
		FloatPolygon p = getFloatPolygon();
		Point[] points = new Point[p.npoints];
		for (int i=0; i iterator() {	
		return new Iterator() {
			final Point[] pnts = getContainedPoints();
			final int n = pnts.length;
			int next = (n == 0) ? 1 : 0;
			@Override
			public boolean hasNext() {
				return next < n;
			}
			@Override
			public Point next() {
				if (next >= n) {
					throw new NoSuchElementException();
				}
				Point pnt = pnts[next];
				next = next + 1;
				return pnt;
			}
			@Override
			public void remove() {
				throw new UnsupportedOperationException();
			}
		};
	}
	
	@Override
	protected int getClosestPoint(double x, double y, FloatPolygon points) {
		int index = -1;
		double distance = Double.MAX_VALUE;
		int slice = imp!=null&&positions!=null&&imp.getStackSize()>1?imp.getCurrentSlice():0;
		if (Prefs.showAllPoints)
			slice = 0;
		for (int i=0; i 

Ads help maintain this website.