package ij.gui; import java.awt.*; import ij.*; import ij.process.*; /** This class implements ImageJ's wand (tracing) tool. */ public class Wand { static final int UP=0, DOWN=1, UP_OR_DOWN=2, LEFT=3, RIGHT=4, LEFT_OR_RIGHT=5, NA=6; /** The number of points in the generated outline. */ public int npoints; private int maxPoints = 1000; // will be increased if necessary /** The x-coordinates of the points in the outline. */ public int[] xpoints = new int[maxPoints]; /** The y-coordinates of the points in the outline. */ public int[] ypoints = new int[maxPoints]; private ImageProcessor ip; private byte[] bpixels; private int[] cpixels; private short[] spixels; private float[] fpixels; private int width, height; private float lowerThreshold, upperThreshold; /** Constructs a Wand object from an ImageProcessor. */ public Wand(ImageProcessor ip) { if (ip instanceof ByteProcessor) bpixels = (byte[])ip.getPixels(); else if (ip instanceof ColorProcessor) cpixels = (int[])ip.getPixels(); else if (ip instanceof ShortProcessor) spixels = (short[])ip.getPixels(); else if (ip instanceof FloatProcessor) fpixels = (float[])ip.getPixels(); width = ip.getWidth(); height = ip.getHeight(); } private float getColorPixel(int x, int y) { if (x>=0 && x=0 && y=0 && x=0 && y=0 && x=0 && y=0 && x=0 && y=lowerThreshold && value<=upperThreshold; } /* Are we tracing a one pixel wide line? */ boolean isLine(int xs, int ys) { int r = 5; int xmin=xs; int xmax=xs+2*r; if (xmax>=width) xmax=width-1; int ymin=ys-r; if (ymin<0) ymin=0; int ymax=ys+r; if (ymax>=height) ymax=height-1; int area = 0; int insideCount = 0; for (int x=xmin; (x<=xmax); x++) for (int y=ymin; y<=ymax; y++) { area++; if (inside(x,y)) insideCount++; } if (IJ.debugMode) IJ.log((((double)insideCount)/area>=0.75?"line ":"blob ")+insideCount+" "+area+" "+IJ.d2s(((double)insideCount)/area)); return ((double)insideCount)/area>=0.75; } /** Traces the boundary of an area of uniform color, where 'startX' and 'startY' are somewhere inside the area. The boundary points are stored in the public xpoints and ypoints fields. A 16 entry lookup table is used to determine the direction at each step of the tracing process. Note that the four argument version [autoOutline(x, y, lower, upper)] works more reliably. */ public void autoOutline(int startX, int startY) { int x = startX; int y = startY; int direction; lowerThreshold = upperThreshold = getPixel(startX, startY); do {x++;} while (inside(x,y)); if (isLine(x, y)) { lowerThreshold = upperThreshold = getPixel(x, y); direction = UP; } else { if (!inside(x-1,y-1)) direction = RIGHT; else if (inside(x,y-1)) direction = LEFT; else direction = DOWN; } traceEdge(x, y, direction); } /** Traces an object defined by lower and upper threshold values. The boundary points are stored in the public xpoints and ypoints fields. This verson works more reliably than autoOutline(x, y). */ public void autoOutline(int startX, int startY, double lower, double upper) { //IJ.log(startX+" "+startY+" "+lower+" "+upper); npoints = 0; int x = startX; int y = startY; int direction; lowerThreshold = (float)lower; upperThreshold = (float)upper; if (inside(x,y)) { do {x++;} while (inside(x,y)); if (!inside(x-1,y-1)) direction = RIGHT; else if (inside(x,y-1)) direction = LEFT; else direction = DOWN; } else { do {x++;} while (!inside(x,y) && x=width) return; } traceEdge(x, y, direction); } /** This is a variation of autoOutline that uses int threshold arguments. */ public void autoOutline(int startX, int startY, int lower, int upper) { autoOutline(startX, startY, (double)lower, (double)upper); } void traceEdge(int xstart, int ystart, int startingDirection) { int[] table = { // 1234, 1=upper left pixel, 2=upper right, 3=lower left, 4=lower right NA, // 0000, should never happen RIGHT, // 000X, DOWN, // 00X0 RIGHT, // 00XX UP, // 0X00 UP, // 0X0X UP_OR_DOWN, // 0XX0 Go up or down depending on current direction UP, // 0XXX LEFT, // X000 LEFT_OR_RIGHT, // X00X Go left or right depending on current direction DOWN, // X0X0 RIGHT, // X0XX LEFT, // XX00 LEFT, // XX0X DOWN, // XXX0 NA, // XXXX Should never happen }; int index; int newDirection; int x = xstart; int y = ystart; int direction = startingDirection; boolean UL = inside(x-1, y-1); // upper left boolean UR = inside(x, y-1); // upper right boolean LL = inside(x-1, y); // lower left boolean LR = inside(x, y); // lower right //xpoints[0] = x; //ypoints[0] = y; int count = 0; //IJ.write(""); //IJ.write(count + " " + x + " " + y + " " + direction + " " + insideValue); do { index = 0; if (LR) index |= 1; if (LL) index |= 2; if (UR) index |= 4; if (UL) index |= 8; newDirection = table[index]; if (newDirection==UP_OR_DOWN) { if (direction==RIGHT) newDirection = UP; else newDirection = DOWN; } if (newDirection==LEFT_OR_RIGHT) { if (direction==UP) newDirection = LEFT; else newDirection = RIGHT; } if (newDirection!=direction) { xpoints[count] = x; ypoints[count] = y; count++; if (count==xpoints.length) { int[] xtemp = new int[maxPoints*2]; int[] ytemp = new int[maxPoints*2]; System.arraycopy(xpoints, 0, xtemp, 0, maxPoints); System.arraycopy(ypoints, 0, ytemp, 0, maxPoints); xpoints = xtemp; ypoints = ytemp; maxPoints *= 2; } //if (count<10) IJ.write(count + " " + x + " " + y + " " + newDirection + " " + index); } switch (newDirection) { case UP: y = y-1; LL = UL; LR = UR; UL = inside(x-1, y-1); UR = inside(x, y-1); break; case DOWN: y = y + 1; UL = LL; UR = LR; LL = inside(x-1, y); LR = inside(x, y); break; case LEFT: x = x-1; UR = UL; LR = LL; UL = inside(x-1, y-1); LL = inside(x-1, y); break; case RIGHT: x = x + 1; UL = UR; LL = LR; UR = inside(x, y-1); LR = inside(x, y); break; } direction = newDirection; } while ((x!=xstart || y!=ystart || direction!=startingDirection)); npoints = count; } }