package ij.process; import java.util.*; import java.awt.*; import java.awt.image.*; import java.awt.FontMetrics; import java.lang.reflect.*; import ij.gui.*; import ij.util.Java2; /** This abstract class is the superclass for classes that process the four data types (byte, short, float and RGB) supported by ImageJ. @see ByteProcessor @see ShortProcessor @see FloatProcessor @see ColorProcessor */ public abstract class ImageProcessor extends Object { /** Value of pixels included in masks. */ public static final int BLACK = 0xFF000000; /** Value returned by getMinThreshold() when thresholding is not enabled. */ public static final double NO_THRESHOLD = -808080.0; /** Left justify text. */ public static final int LEFT_JUSTIFY = 0; /** Center justify text. */ public static final int CENTER_JUSTIFY = 1; /** Right justify text. */ public static final int RIGHT_JUSTIFY = 2; static public final int RED_LUT=0, BLACK_AND_WHITE_LUT=1, NO_LUT_UPDATE=2, OVER_UNDER_LUT=3; static final int INVERT=0, FILL=1, ADD=2, MULT=3, AND=4, OR=5, XOR=6, GAMMA=7, LOG=8, MINIMUM=9, MAXIMUM=10, SQR=11, SQRT=12, EXP=13; static final int BLUR_MORE=0, FIND_EDGES=1, MEDIAN_FILTER=2, MIN=3, MAX=4; static final String WRONG_LENGTH = "width*height!=pixels.length"; int fgColor = 0; protected int lineWidth = 1; protected int cx, cy; //current drawing coordinates protected Font font; protected FontMetrics fontMetrics; protected boolean antialiasedText; protected boolean boldFont; static Frame frame; static Graphics graphics; ProgressBar progressBar; boolean pixelsModified; protected int width, snapshotWidth; protected int height, snapshotHeight; protected int roiX, roiY, roiWidth, roiHeight; protected int xMin, xMax, yMin, yMax; boolean newSnapshot = false; // true if pixels = snapshotPixels ImageProcessor mask = null; protected ColorModel baseCM; // base color model protected ColorModel cm; protected byte[] rLUT1, gLUT1, bLUT1; // base LUT protected byte[] rLUT2, gLUT2, bLUT2; // LUT as modified by setMinAndMax and setThreshold protected boolean interpolate; protected double minThreshold=NO_THRESHOLD, maxThreshold=NO_THRESHOLD; protected int histogramSize = 256; protected double histogramMin, histogramMax; protected float[] cTable; protected boolean lutAnimation; protected MemoryImageSource source; protected Image img; protected boolean newPixels; protected Color drawingColor = Color.black; protected int clipXMin, clipXMax, clipYMin, clipYMax; // clip rect used by drawTo, drawLine, drawDot and drawPixel protected int justification = LEFT_JUSTIFY; protected int lutUpdateMode; protected WritableRaster raster; protected BufferedImage image; //protected ColorModel cm2; //protected static SampleModel sampleModel; protected static IndexColorModel defaultColorModel; protected void showProgress(double percentDone) { if (progressBar!=null) progressBar.show(percentDone); } protected void hideProgress() { showProgress(1.0); newSnapshot = false; } /** Returns the width of this image in pixels. */ public int getWidth() { return width; } /** Returns the height of this image in pixels. */ public int getHeight() { return height; } /** Returns this processor's color model. For non-RGB processors, this is the base lookup table (LUT), not the one that may have been modified by setMinAndMax() or setThreshold(). */ public ColorModel getColorModel() { if (cm==null) makeDefaultColorModel(); if (baseCM!=null) return baseCM; else return cm; } /** Returns the current color model, which may have been modified by setMinAndMax() or setThreshold(). */ public ColorModel getCurrentColorModel() { if (cm==null) makeDefaultColorModel(); return cm; } /** Sets the color model. Must be an IndexColorModel (aka LUT) for all processors except the ColorProcessor. */ public void setColorModel(ColorModel cm) { if (!(this instanceof ColorProcessor) && !(cm instanceof IndexColorModel)) throw new IllegalArgumentException("Must be IndexColorModel"); this.cm = cm; baseCM = null; rLUT1 = rLUT2 = null; newPixels = true; inversionTested = false; minThreshold = NO_THRESHOLD; source = null; } protected void makeDefaultColorModel() { byte[] rLUT = new byte[256]; byte[] gLUT = new byte[256]; byte[] bLUT = new byte[256]; for(int i=0; i<256; i++) { rLUT[i]=(byte)i; gLUT[i]=(byte)i; bLUT[i]=(byte)i; } cm = new IndexColorModel(8, 256, rLUT, gLUT, bLUT); } /** Inverts the values in this image's LUT (indexed color model). Does nothing if this is a ColorProcessor. */ public void invertLut() { if (cm==null) makeDefaultColorModel(); IndexColorModel icm = (IndexColorModel)cm; int mapSize = icm.getMapSize(); byte[] reds = new byte[mapSize]; byte[] greens = new byte[mapSize]; byte[] blues = new byte[mapSize]; byte[] reds2 = new byte[mapSize]; byte[] greens2 = new byte[mapSize]; byte[] blues2 = new byte[mapSize]; icm.getReds(reds); icm.getGreens(greens); icm.getBlues(blues); for (int i=0; i0.0) stdDev = Math.sqrt(stdDev/(767.0)); else stdDev = 0.0; boolean isPseudoColor = stdDev<20.0; if ((int)stdDev==67) isPseudoColor = true; // "3-3-2 RGB" LUT //ij.IJ.log("isPseudoColorLut: "+(isPseudoColor) + " " + stdDev); return isPseudoColor; } /** Sets the default fill/draw value to the pixel value closest to the specified color. */ public abstract void setColor(Color color); /** Obsolete (use setValue) */ public void setColor(int value) { fgColor = value; } /** Sets the default fill/draw value. */ public abstract void setValue(double value); /** Sets the background fill value used by the rotate() and scale() methods. */ public abstract void setBackgroundValue(double value); /** Returns the smallest displayed pixel value. */ public abstract double getMin(); /** Returns the largest displayed pixel value. */ public abstract double getMax(); /** This image will be displayed by mapping pixel values in the range min-max to screen values in the range 0-255. For byte images, this mapping is done by updating the LUT. For short and float images, it's done by generating 8-bit AWT images. For RGB images, it's done by changing the pixel values. */ public abstract void setMinAndMax(double min, double max); /** For short and float images, recalculates the min and max image values needed to correctly display the image. For ByteProcessors, resets the LUT. */ public void resetMinAndMax() {} /** Sets the lower and upper threshold levels. The 'lutUpdate' argument can be RED_LUT, BLACK_AND_WHITE_LUT, OVER_UNDER_LUT or NO_LUT_UPDATE. Thresholding of RGB images is not supported. */ public void setThreshold(double minThreshold, double maxThreshold, int lutUpdate) { //ij.IJ.write("setThreshold: "+" "+minThreshold+" "+maxThreshold+" "+lutUpdate); if (this instanceof ColorProcessor) return; this.minThreshold = minThreshold; this.maxThreshold = maxThreshold; lutUpdateMode = lutUpdate; if (minThreshold==NO_THRESHOLD) { resetThreshold(); return; } if (lutUpdate==NO_LUT_UPDATE) return; if (rLUT1==null) { if (cm==null) makeDefaultColorModel(); baseCM = cm; IndexColorModel m = (IndexColorModel)cm; rLUT1 = new byte[256]; gLUT1 = new byte[256]; bLUT1 = new byte[256]; m.getReds(rLUT1); m.getGreens(gLUT1); m.getBlues(bLUT1); rLUT2 = new byte[256]; gLUT2 = new byte[256]; bLUT2 = new byte[256]; } int t1 = (int)minThreshold; int t2 = (int)maxThreshold; int index; if (lutUpdate==RED_LUT) for (int i=0; i<256; i++) { if (i>=t1 && i<=t2) { rLUT2[i] = (byte)255; gLUT2[i] = (byte)0; bLUT2[i] = (byte)0; } else { rLUT2[i] = rLUT1[i]; gLUT2[i] = gLUT1[i]; bLUT2[i] = bLUT1[i]; } } else if (lutUpdate==BLACK_AND_WHITE_LUT) for (int i=0; i<256; i++) { if (i>=t1 && i<=t2) { rLUT2[i] = (byte)0; gLUT2[i] = (byte)0; bLUT2[i] = (byte)0; } else { rLUT2[i] = (byte)255; gLUT2[i] = (byte)255; bLUT2[i] = (byte)255; } } else for (int i=0; i<256; i++) { if (i>=t1 && i<=t2) { rLUT2[i] = rLUT1[i]; gLUT2[i] = gLUT1[i]; bLUT2[i] = bLUT1[i]; } else if (i>t2) { rLUT2[i] = (byte)0; gLUT2[i] = (byte)255; bLUT2[i] = (byte)0; } else { rLUT2[i] = (byte)0; gLUT2[i] = (byte)0; bLUT2[i] = (byte)255; } } cm = new IndexColorModel(8, 256, rLUT2, gLUT2, bLUT2); newPixels = true; source = null; } /** Disables thresholding. */ public void resetThreshold() { minThreshold = NO_THRESHOLD; if (baseCM!=null) { cm = baseCM; baseCM = null; } rLUT1 = rLUT2 = null; inversionTested = false; newPixels = true; source = null; } /** Returns the lower threshold level. Returns NO_THRESHOLD if thresholding is not enabled. */ public double getMinThreshold() { return minThreshold; } /** Returns the upper threshold level. */ public double getMaxThreshold() { return maxThreshold; } /** Returns the LUT update mode, which can be RED_LUT, BLACK_AND_WHITE_LUT, OVER_UNDER_LUT or NO_LUT_UPDATE. */ public int getLutUpdateMode() { return lutUpdateMode; } /** Defines a rectangular region of interest and sets the mask to null if this ROI is not the same size as the previous one. @see ImageProcessor#resetRoi */ public void setRoi(Rectangle roi) { if (roi==null) resetRoi(); else setRoi(roi.x, roi.y, roi.width, roi.height); } /** Defines a rectangular region of interest and sets the mask to null if this ROI is not the same size as the previous one. @see ImageProcessor#resetRoi */ public void setRoi(int x, int y, int rwidth, int rheight) { if (x<0 || y<0 || x+rwidth>width || y+rheight>height) { //find intersection of roi and this image Rectangle r1 = new Rectangle(x, y, rwidth, rheight); Rectangle r2 = r1.intersection(new Rectangle(0, 0, width, height)); if (r2.width<=0 || r2.height<=0) { roiX=0; roiY=0; roiWidth=0; roiHeight=0; xMin=0; xMax=0; yMin=0; yMax=0; mask=null; return; } if (mask!=null && mask.getWidth()==rwidth && mask.getHeight()==rheight) { Rectangle r3 = new Rectangle(0, 0, r2.width, r2.height); if (x<0) r3.x = -x; if (y<0) r3.y = -y; mask.setRoi(r3); mask = mask.crop(); } roiX=r2.x; roiY=r2.y; roiWidth=r2.width; roiHeight=r2.height; } else { roiX=x; roiY=y; roiWidth=rwidth; roiHeight=rheight; } if (mask!=null && (mask.getWidth()!=roiWidth||mask.getHeight()!=roiHeight)) mask = null; //setup limits for 3x3 filters xMin = Math.max(roiX, 1); xMax = Math.min(roiX + roiWidth - 1, width - 2); yMin = Math.max(roiY, 1); yMax = Math.min(roiY + roiHeight - 1, height - 2); } /** Defines a non-rectangular region of interest that will consist of a rectangular ROI and a mask. After processing, call reset(mask) to restore non-masked pixels. Here is an example:
		ip.setRoi(new OvalRoi(50, 50, 100, 50));
		ip.fill();
		ip.reset(ip.getMask());
		
The example assumes snapshot() has been called, which is the case for code executed in the run() method of plugins that implement the PlugInFilter interface. @see ij.ImagePlus#getRoi */ public void setRoi(ij.gui.Roi roi) { if (roi==null) resetRoi(); else { setMask(roi.getMask()); setRoi(roi.getBounds()); } } /** Defines a polygonal region of interest that will consist of a rectangular ROI and a mask. After processing, call reset(mask) to restore non-masked pixels. Here is an example:
		Polygon p = new Polygon();
		p.addPoint(50, 0); p.addPoint(100, 100); p.addPoint(0, 100);
		ip.setRoi(triangle);
		ip.invert();
		ip.reset(ip.getMask());
		
The example assumes snapshot() has been called, which is the case for code executed in the run() method of plugins that implement the PlugInFilter interface. @see ij.gui.Roi#getPolygon @see ImageProcessor#drawPolygon @see ImageProcessor#fillPolygon */ public void setRoi(Polygon roi) { if (roi==null) {resetRoi(); return;} Rectangle bounds = roi.getBounds(); for (int i=0; ivalue) v = (int)value; else v = i; break; default: v = i; } if (v < 0) v = 0; if (v > 255) v = 255; lut[i] = v; } applyTable(lut); } /** Returns an array containing the pixel values along the line starting at (x1,y1) and ending at (x2,y2). For byte and short images, returns calibrated values if a calibration table has been set using setCalibrationTable(). @see ImageProcessor#setInterpolate */ public double[] getLine(double x1, double y1, double x2, double y2) { double dx = x2-x1; double dy = y2-y1; int n = (int)Math.round(Math.sqrt(dx*dx + dy*dy)); double xinc = dx/n; double yinc = dy/n; n++; double[] data = new double[n]; double rx = x1; double ry = y1; if (interpolate) { for (int i=0; i=0 && x=0 && (y+length)<=height) // ((ShortProcessor)this).putColumn2(x, y, data, length); //else for (int i=0; i=0?dx:-dx; int absdy = dy>=0?dy:-dy; int n = absdy>absdx?absdy:absdx; double xinc = (double)dx/n; double yinc = (double)dy/n; double x = cx<0?cx-0.5:cx+0.5; double y = cy<0?cy-0.5:cy+0.5; n++; cx = x2; cy = y2; if (n>1000000) return; do { if (lineWidth==1) drawPixel((int)x, (int)y); else if (lineWidth==2) drawDot2((int)x, (int)y); else drawDot((int)x, (int)y); x += xinc; y += yinc; } while (--n>0); if (lineWidth>2) resetRoi(); } /** Draws a line from (x1,y1) to (x2,y2). */ public void drawLine(int x1, int y1, int x2, int y2) { moveTo(x1, y1); lineTo(x2, y2); } /** Draws a rectangle. */ public void drawRect(int x, int y, int width, int height) { if (width<1 || height<1) return; if (lineWidth==1) { moveTo(x, y); lineTo(x+width-1, y); lineTo(x+width-1, y+height-1); lineTo(x, y+height-1); lineTo(x, y); } else { moveTo(x, y); lineTo(x+width, y); lineTo(x+width, y+height); lineTo(x, y+height); lineTo(x, y); } } /** Draws an elliptical shape. */ public void drawOval(int x, int y, int width, int height) { OvalRoi oval = new OvalRoi(x, y, width, height); drawPolygon(oval.getPolygon()); } /** Fills an elliptical shape. */ public void fillOval(int x, int y, int width, int height) { OvalRoi oval = new OvalRoi(x, y, width, height); fillPolygon(oval.getPolygon()); } /** Draws a polygon. */ public void drawPolygon(Polygon p) { moveTo(p.xpoints[0], p.ypoints[0]); for (int i=0; i=width || ymax>=height) { // draw edge dot double r2 = r*r; r -= 0.5; double xoffset=xmin+r, yoffset=ymin+r; double xx, yy; for (int y=ymin; y=00 && cy-h>=0) { Java2.setAntialiasedText(g, true); setRoi(cxx, cy-h, w, h); ImageProcessor ip = crop(); resetRoi(); g.drawImage(ip.createImage(), 0, 0, null); g.setColor(drawingColor); g.drawString(s, 0, h-descent); g.dispose(); ip = new ColorProcessor(img); if (this instanceof ByteProcessor) { ip = ip.convertToByte(false); if (isInvertedLut()) ip.invert(); } //new ij.ImagePlus("ip",ip).show(); insert(ip, cxx, cy-h); cy += h; return; } if (ij.IJ.isMacOSX() || (ij.IJ.isLinux()&&ij.IJ.isJava2())) { Java2.setAntialiasedText(g, false); g.setColor(Color.white); g.fillRect(0, 0, w, h); } g.setColor(Color.black); g.drawString(s, 0, h-descent); g.dispose(); ImageProcessor ip = new ColorProcessor(img); ImageProcessor textMask = ip.convertToByte(false); byte[] mpixels = (byte[])textMask.getPixels(); //new ij.ImagePlus("textmask",textMask).show(); textMask.invert(); if (cxxjustification is CENTER_JUSTIFY, RIGHT_JUSTIFY or LEFT_JUSTIFY. The default is LEFT_JUSTIFY. */ public void setJustification(int justification) { this.justification = justification; } /** Sets the font used by drawString(). */ public void setFont(Font font) { this.font = font; fontMetrics = null; boldFont = font.isBold(); } /** Specifies whether or not text is drawn using antialiasing. Antialiased test requires Java 2 and an 8 bit or RGB image. Antialiasing does not work with 8-bit images that are not using 0-255 display range. */ public void setAntialiasedText(boolean antialiasedText) { if (antialiasedText && ij.IJ.isJava2() && (((this instanceof ByteProcessor)&&getMin()==0.0&&getMax()==255.0) || (this instanceof ColorProcessor))) this.antialiasedText = true; else this.antialiasedText = false; } /** Returns the width in pixels of the specified string. */ public int getStringWidth(String s) { setupFrame(); int w; if (antialiasedText) { //Graphics g = frame.getGraphics(); //-BEGIN-CHANGE Graphics g=graphics; //-END-CHANGE if (g==null) { frame = null; setupFrame(); g = frame.getGraphics(); } Java2.setAntialiasedText(g, true); w = Java2.getStringWidth(s, fontMetrics, g); g.dispose(); } else w = fontMetrics.stringWidth(s); return w; } /** Returns the current FontMetrics. */ public FontMetrics getFontMetrics() { setupFrame(); return fontMetrics; } /** Replaces each pixel with the 3x3 neighborhood mean. */ public void smooth() { if (width>1) filter(BLUR_MORE); } /** Sharpens the image or ROI using a 3x3 convolution kernel. */ public void sharpen() { if (width>1) { int[] kernel = {-1, -1, -1, -1, 12, -1, -1, -1, -1}; convolve3x3(kernel); } } /** Finds edges in the image or ROI using a Sobel operator. */ public void findEdges() { if (width>1) filter(FIND_EDGES); } /** Flips the image or ROI vertically. */ public abstract void flipVertical(); /* { int[] row1 = new int[roiWidth]; int[] row2 = new int[roiWidth]; for (int y=0; yhistMin and histMax are zero. */ public void setHistogramRange(double histMin, double histMax) { if (histMin>histMax) { histMin = 0.0; histMax = 0.0; } histogramMin = histMin; histogramMax = histMax; } /** Returns the minimum histogram value used for histograms of float images. */ public double getHistogramMin() { return histogramMin; } /** Returns the maximum histogram value used for histograms of float images. */ public double getHistogramMax() { return histogramMax; } /** Returns a reference to this image's pixel array. The array type (byte[], short[], float[] or int[]) varies depending on the image type. */ public abstract Object getPixels(); /** Returns a reference to this image's snapshot (undo) array. If the snapshot array is null, returns a copy of the pixel data. The array type varies depending on the image type. */ public abstract Object getPixelsCopy(); /** Returns the value of the pixel at (x,y). For RGB images, the argb values are packed in an int. For float images, the the value must be converted using Float.intBitsToFloat(). Returns zero if either the x or y coodinate is out of range. */ public abstract int getPixel(int x, int y); /** This is a faster version of getPixel() that does not do bounds checking. */ public abstract int get(int x, int y); /** This is a faster version of putPixel() that does not clip out of range values and does not do bounds checking. */ public abstract void set(int x, int y, int value); public abstract float getf(int x, int y); public abstract void setf(int x, int y, float value); public int[][] getIntArray() { int[][] a = new int [width][height]; for(int y=0; y0)) max--; if (min>=max) { histogram[0]= count0; histogram[maxValue]=countMax; level = histogram.length/2; return level; } int movingIndex = min; int inc = Math.min(max/40, 1); do { sum1=sum2=sum3=sum4=0.0; for (int i=min; i<=movingIndex; i++) { sum1 += i*histogram[i]; sum2 += histogram[i]; } for (int i=(movingIndex+1); i<=max; i++) { sum3 += i*histogram[i]; sum4 += histogram[i]; } result = (sum1/sum2 + sum3/sum4)/2.0; movingIndex++; if (max>255 && (movingIndex%inc)==0) showProgress((double)(movingIndex)/max); } while ((movingIndex+1)<=result && movingIndex=width) clipXMax = width-1; if (clipYMin<0) clipYMin = 0; if (clipYMax>=height) clipYMax = height-1; } } protected String maskSizeError(ImageProcessor mask) { return "Mask size ("+mask.getWidth()+"x"+mask.getHeight()+") != ROI size ("+ roiWidth+"x"+roiHeight+")"; } /* protected SampleModel getIndexSampleModel() { if (sampleModel==null) { IndexColorModel icm = getDefaultColorModel(); WritableRaster wr = icm.createCompatibleWritableRaster(1, 1); sampleModel = wr.getSampleModel(); sampleModel = sampleModel.createCompatibleSampleModel(width, height); } return sampleModel; } */ /** Returns the default grayscale IndexColorModel. */ public IndexColorModel getDefaultColorModel() { if (defaultColorModel==null) { byte[] r = new byte[256]; byte[] g = new byte[256]; byte[] b = new byte[256]; for(int i=0; i<256; i++) { r[i]=(byte)i; g[i]=(byte)i; b[i]=(byte)i; } defaultColorModel = new IndexColorModel(8, 256, r, g, b); } return defaultColorModel; } }