package ij.process;

import java.util.*;
import java.awt.*;
import java.awt.image.*;
import ij.gui.*;
import ij.ImageStack;

/**
This is an 32-bit RGB image and methods that operate on that image.. Based on the ImageProcessor class from
"KickAss Java Programming" by Tonny Espeset (http://www.sn.no/~espeset).
*/
public class ColorProcessor extends ImageProcessor {

	protected int[] pixels;
	protected int[] snapshotPixels = null;
	private int bgColor = 0xffffffff; //white
	private int min=0, max=255;
	private WritableRaster rgbRaster;
	private SampleModel rgbSampleModel;
	
	// Weighting factors used by getPixelValue(), getHistogram() and convertToByte().
	// Enable "Weighted RGB Conversion" in <i>Edit/Options/Conversions</i>
	// to use 0.299, 0.587 and 0.114.
	private static double rWeight=1d/3d, gWeight=1d/3d,	bWeight=1d/3d; 

	/**Creates a ColorProcessor from an AWT Image. */
	public ColorProcessor(Image img) {
		width = img.getWidth(null);
		height = img.getHeight(null);
		pixels = new int[width * height];
		PixelGrabber pg = new PixelGrabber(img, 0, 0, width, height, pixels, 0, width);
		try {
			pg.grabPixels();
		} catch (InterruptedException e){};
		createColorModel();
		fgColor = 0xff000000; //black
		resetRoi();
	}

	/**Creates a blank ColorProcessor of the specified dimensions. */
	public ColorProcessor(int width, int height) {
		this(width, height, new int[width*height]);
	}
	
	/**Creates a ColorProcessor from a pixel array. */
	public ColorProcessor(int width, int height, int[] pixels) {
		if (pixels!=null && width*height!=pixels.length)
			throw new IllegalArgumentException(WRONG_LENGTH);
		this.width = width;
		this.height = height;
		createColorModel();
		fgColor = 0xff000000; //black
		resetRoi();
		this.pixels = pixels;
	}


	void createColorModel() {
		cm = new DirectColorModel(24, 0xff0000, 0xff00, 0xff);
	}
	
	public Image createImage() {
		if (ij.IJ.isJava16())
			return createBufferedImage();
		if (source==null) {
			source = new MemoryImageSource(width, height, cm, pixels, 0, width);
			source.setAnimated(true);
			source.setFullBufferUpdates(true);
			img = Toolkit.getDefaultToolkit().createImage(source);
		} else if (newPixels) {
			source.newPixels(pixels, cm, 0, width);
			newPixels = false;
		} else
			source.newPixels();
		return img;
	}

	Image createBufferedImage() {
		if (rgbSampleModel==null)
			rgbSampleModel = getRGBSampleModel();
		if (rgbRaster==null) {
			DataBuffer dataBuffer = new DataBufferInt(pixels, width*height, 0);
			rgbRaster = Raster.createWritableRaster(rgbSampleModel, dataBuffer, null);
		}
		if (image==null) {
			image = new BufferedImage(cm, rgbRaster, false, null);
		}
		return image;
	}

	SampleModel getRGBSampleModel() {
		WritableRaster wr = cm.createCompatibleWritableRaster(1, 1);
		SampleModel sampleModel = wr.getSampleModel();
		sampleModel = sampleModel.createCompatibleSampleModel(width, height);
		return sampleModel;
	}

	/** Returns a new, blank ColorProcessor with the specified width and height. */
	public ImageProcessor createProcessor(int width, int height) {
		ImageProcessor ip2 = new ColorProcessor(width, height);
		ip2.setInterpolationMethod(interpolationMethod);
		return ip2;
	}

	public Color getColor(int x, int y) {
		int c = pixels[y*width+x];
		int r = (c&0xff0000)>>16;
		int g = (c&0xff00)>>8;
		int b = c&0xff;
		return new Color(r,g,b);
	}


	/** Sets the foreground color. */
	public void setColor(Color color) {
		fgColor = color.getRGB();
		drawingColor = color;
	}


	/** Sets the fill/draw color, where <code>color</code> is an RGB int. */
	public void setColor(int color) {
		fgColor = color;
	}

	/** Sets the default fill/draw value, where <code>value</code> is interpreted as an RGB int. */
	public void setValue(double value) {
		fgColor = (int)value;
	}

	/** Sets the background fill value, where <code>value</code> is interpreted as an RGB int. */
	public void setBackgroundValue(double value) {
		bgColor = (int)value;
	}

	/** Returns the background fill value. */
	public double getBackgroundValue() {
		return bgColor;
	}

	/** Returns the smallest displayed pixel value. */
	public double getMin() {
		return min;
	}


	/** Returns the largest displayed pixel value. */
	public double getMax() {
		return max;
	}


	/** Uses a table look-up to map the pixels in this image from min-max to 0-255. */
	public void setMinAndMax(double min, double max) {
		setMinAndMax(min, max, 7);
	}

	public void setMinAndMax(double min, double max, int channels) {
		if (max<min)
			return;
		this.min = (int)min;
		this.max = (int)max;
		int v;
		int[] lut = new int[256];
		for (int i=0; i<256; i++) {
			v = i-this.min;
			v = (int)(256.0*v/(max-min));
			if (v < 0)
				v = 0;
			if (v > 255)
				v = 255;
			lut[i] = v;
		}
		reset();
		if (channels==7)
			applyTable(lut);
		else
			applyTable(lut, channels);
	}
	

	public void snapshot() {
		snapshotWidth = width;
		snapshotHeight = height;
		if (snapshotPixels==null || (snapshotPixels!=null && snapshotPixels.length!=pixels.length))
			snapshotPixels = new int[width * height];
		System.arraycopy(pixels, 0, snapshotPixels, 0, width*height);
	}


	public void reset() {
		if (snapshotPixels==null)
			return;
		System.arraycopy(snapshotPixels, 0, pixels, 0, width*height);
	}


	public void reset(ImageProcessor mask) {
		if (mask==null || snapshotPixels==null)
			return;	
		if (mask.getWidth()!=roiWidth||mask.getHeight()!=roiHeight)
			throw new IllegalArgumentException(maskSizeError(mask));
		byte[] mpixels = (byte[])mask.getPixels();
		for (int y=roiY, my=0; y<(roiY+roiHeight); y++, my++) {
			int i = y * width + roiX;
			int mi = my * roiWidth;
			for (int x=roiX; x<(roiX+roiWidth); x++) {
				if (mpixels[mi++]==0)
					pixels[i] = snapshotPixels[i];
				i++;
			}
		}
	}

	/** Swaps the pixel and snapshot (undo) arrays. */
	public void swapPixelArrays() {
		if (snapshotPixels==null) return;	
		int pixel;
		for (int i=0; i<pixels.length; i++) {
			pixel = pixels[i];
			pixels[i] = snapshotPixels[i];
			snapshotPixels[i] = pixel;
		}
	}

	public void setSnapshotPixels(Object pixels) {
		snapshotPixels = (int[])pixels;
		snapshotWidth=width;
		snapshotHeight=height;
	}

	/** Returns a reference to the snapshot pixel array. Used by the ContrastAdjuster. */
	public Object getSnapshotPixels() {
		return snapshotPixels;
	}

	/** Fills pixels that are within roi and part of the mask.
		Does nothing if the mask is not the same as the the ROI. */
	public void fill(ImageProcessor mask) {
		if (mask==null)
			{fill(); return;}
		int roiWidth=this.roiWidth, roiHeight=this.roiHeight;
		int roiX=this.roiX, roiY=this.roiY;
		if (mask.getWidth()!=roiWidth||mask.getHeight()!=roiHeight)
			return;
		byte[] mpixels = (byte[])mask.getPixels();
		for (int y=roiY, my=0; y<(roiY+roiHeight); y++, my++) {
			int i = y * width + roiX;
			int mi = my * roiWidth;
			for (int x=roiX; x<(roiX+roiWidth); x++) {
				if (mpixels[mi++]!=0)
					pixels[i] = fgColor;
				i++;
			}
		}
	}

	/** Returns a copy of the pixel data. Or returns a reference to the
		snapshot buffer if it is not null and 'snapshotCopyMode' is true.
		@see ImageProcessor#snapshot
		@see ImageProcessor#setSnapshotCopyMode
	*/
	public Object getPixelsCopy() {
		if (snapshotPixels!=null && snapshotCopyMode) {
			snapshotCopyMode = false;
			return snapshotPixels;
		} else {
			int[] pixels2 = new int[width*height];
        	System.arraycopy(pixels, 0, pixels2, 0, width*height);
			return pixels2;
		}
	}

	public int getPixel(int x, int y) {
		if (x>=0 && x<width && y>=0 && y<height)
			return pixels[y*width+x];
		else
			return 0;
	}

	public final int get(int x, int y) {
		return pixels[y*width+x];
	}

	public final void set(int x, int y, int value) {
		pixels[y*width + x] = value;
	}

	public final int get(int index) {
		return pixels[index];
	}
	public final void set(int index, int value) {
		pixels[index] = value;
	}

	public final float getf(int x, int y) {
		return getf(y*width+x);
	}

	public final void setf(int x, int y, float value) {
		pixels[y*width + x] = (int)value;
	}

	public final float getf(int index) {
		int c = pixels[index];
		int r = (c&0xff0000)>>16;
		int g = (c&0xff00)>>8;
		int b = c&0xff;
		return (float)(r*rWeight + g*gWeight + b*bWeight);
	}

	//public final float getf(int index) {
	//	return pixels[index];
	//}

	public final void setf(int index, float value) {
		pixels[index] = (int)value;
	}

    /** Returns the 3 samples for the pixel at (x,y) in an array of int.
		Returns zeros if the the coordinates are not in bounds. iArray
		is an optional preallocated array. */
	public int[] getPixel(int x, int y, int[] iArray) {
		if (iArray==null) iArray = new int[3];
		int c = getPixel(x, y);
		iArray[0] = (c&0xff0000)>>16;
		iArray[1] = (c&0xff00)>>8;
		iArray[2] = c&0xff;
		return iArray;
	}

	/** Sets a pixel in the image using a 3 element (R, G and B)
		int array of samples. */
	public final void putPixel(int x, int y, int[] iArray) {
		int r=iArray[0], g=iArray[1], b=iArray[2];
		putPixel(x, y, (r<<16)+(g<<8)+b);
	}

 	/** Calls getPixelValue(x,y). */
	public double getInterpolatedPixel(double x, double y) {
		int ix = (int)(x+0.5);
		int iy = (int)(y+0.5);
		if (ix<0) ix = 0;
		if (ix>=width) ix = width-1;
		if (iy<0) iy = 0;
		if (iy>=height) iy = height-1;
		return getPixelValue(ix, iy);
	}

	final public int getPixelInterpolated(double x,double y) {
		if (x<0.0 || y<0.0 || x>=width-1 || y>=height-1)
			return 0;
		else
			return getInterpolatedPixel(x, y, pixels);
	}

	/** Stores the specified value at (x,y). */
	public final void putPixel(int x, int y, int value) {
		if (x>=0 && x<width && y>=0 && y<height)
			pixels[y*width + x] = value;
	}

	/** Stores the specified real grayscale value at (x,y).
		Does nothing if (x,y) is outside the image boundary.
		The value is clamped to be in the range 0-255. */
	public void putPixelValue(int x, int y, double value) {
		if (x>=0 && x<width && y>=0 && y<height) {
			if (value>255.0)
				value = 255;
			else if (value<0.0)
				value = 0.0;
			int gray = (int)(value+0.5);
			pixels[y*width + x] = 0xff000000 + (gray<<16) + (gray<<8) + gray;

		}
	}

	/** Converts the specified pixel to grayscale using the
		formula g=(r+g+b)/3 and returns it as a float. 
		Call setWeightingFactors() to specify different conversion
		factors. */
	public float getPixelValue(int x, int y) {
		if (x>=0 && x<width && y>=0 && y<height) {
			int c = pixels[y*width+x];
			int r = (c&0xff0000)>>16;
			int g = (c&0xff00)>>8;
			int b = c&0xff;
			return (float)(r*rWeight + g*gWeight + b*bWeight);
		}
		else 
			return 0;
	}


	/** Draws a pixel in the current foreground color. */
	public void drawPixel(int x, int y) {
		if (x>=clipXMin && x<=clipXMax && y>=clipYMin && y<=clipYMax)
			pixels[y*width + x] = fgColor;
	}


	/**	Returns a reference to the int array containing
		this image's pixel data. */
	public Object getPixels() {
		return (Object)pixels;
	}


	public void setPixels(Object pixels) {
		this.pixels = (int[])pixels;
		resetPixels(pixels);
		if (pixels==null) snapshotPixels = null;
		rgbRaster = null;
		image = null;
	}


	/** Returns hue, saturation and brightness in 3 byte arrays. */
	public void getHSB(byte[] H, byte[] S, byte[] B) {
		int c, r, g, b;
		float[] hsb = new float[3];
		for (int i=0; i < width*height; i++) {
			c = pixels[i];
			r = (c&0xff0000)>>16;
			g = (c&0xff00)>>8;
			b = c&0xff;
			hsb = Color.RGBtoHSB(r, g, b, hsb);
			H[i] = (byte)((int)(hsb[0]*255.0));
			S[i] = (byte)((int)(hsb[1]*255.0));
			B[i] = (byte)((int)(hsb[2]*255.0));
		}
	}
	
	/** Returns an ImageStack with three 8-bit slices,
	    representing hue, saturation and brightness */
	public ImageStack getHSBStack() {
		int width = getWidth();
		int height = getHeight();
		byte[] H = new byte[width*height];
		byte[] S = new byte[width*height];
		byte[] B = new byte[width*height];
		getHSB(H, S, B);
		ColorModel cm = getDefaultColorModel();
		ImageStack stack = new ImageStack(width, height, cm);
		stack.addSlice("Hue", H);
		stack.addSlice("Saturation", S);
		stack.addSlice("Brightness", B);
		return stack;
	}

	/** Returns brightness as a FloatProcessor. */
	public FloatProcessor getBrightness() {
		int c, r, g, b;
		int size = width*height;
		float[] brightness = new float[size];
		float[] hsb = new float[3];
		for (int i=0; i<size; i++) {
			c = pixels[i];
			r = (c&0xff0000)>>16;
			g = (c&0xff00)>>8;
			b = c&0xff;
			hsb = Color.RGBtoHSB(r, g, b, hsb);
			brightness[i] = hsb[2];
		}
		return new FloatProcessor(width, height, brightness, null);
	}

	/** Returns the red, green and blue planes as 3 byte arrays. */
	public void getRGB(byte[] R, byte[] G, byte[] B) {
		int c, r, g, b;
		for (int i=0; i < width*height; i++) {
			c = pixels[i];
			r = (c&0xff0000)>>16;
			g = (c&0xff00)>>8;
			b = c&0xff;
			R[i] = (byte)r;
			G[i] = (byte)g;
			B[i] = (byte)b;
		}
	}

	/** Returns the specified plane as a byte array. */
	public byte[] getChannel(int channel) {
		int size = width*height;
		byte[] bytes = new byte[size];
		int c, r, g, b;
		switch (channel) {
			case 1:
				for (int i=0; i<size; i++) {
					c = pixels[i];
					r = (c&0xff0000)>>16;
					bytes[i] = (byte)r;
				}
				break;
			case 2:
				for (int i=0; i<size; i++) {
					c = pixels[i];
					g = (c&0xff00)>>8;
					bytes[i] = (byte)g;
				}
				break;
			case 3:
				for (int i=0; i<size; i++) {
					c = pixels[i];
					b = c&0xff;
					bytes[i] = (byte)b;
				}
				break;
		}
		return bytes;
	}

	/** Sets the current pixels from 3 byte arrays (reg, green, blue). */
	public void setRGB(byte[] R, byte[] G, byte[] B) {
		int c, r, g, b;
		for (int i=0; i < width*height; i++)
			pixels[i] = 0xff000000 | ((R[i]&0xff)<<16) | ((G[i]&0xff)<<8) | B[i]&0xff;
	}


	/** Sets the current pixels from 3 byte arrays (hue, saturation and brightness). */
	public void setHSB(byte[] H, byte[] S, byte[] B) {
		float hue, saturation, brightness;
		for (int i=0; i < width*height; i++) {
			hue = (float)((H[i]&0xff)/255.0);
			saturation = (float)((S[i]&0xff)/255.0);
			brightness = (float)((B[i]&0xff)/255.0);
			pixels[i] = Color.HSBtoRGB(hue, saturation, brightness);
		}
	}
	
	/** Updates the brightness using the pixels in the specified FloatProcessor). */
	public void setBrightness(FloatProcessor fp) {
		int c, r, g, b;
		int size = width*height;
		float[] hsb = new float[3];
		float[] brightness = (float[])fp.getPixels();
		if (brightness.length!=size)
			throw new IllegalArgumentException("fp is wrong size");
		for (int i=0; i<size; i++) {
			c = pixels[i];
			r = (c&0xff0000)>>16;
			g = (c&0xff00)>>8;
			b = c&0xff;
			hsb = Color.RGBtoHSB(r, g, b, hsb);
			float bvalue = brightness[i];
			if (bvalue<0f) bvalue = 0f;
			if (bvalue>1.0f) bvalue = 1.0f;
			pixels[i] = Color.HSBtoRGB(hsb[0], hsb[1], bvalue);
		}
	}
	
	/** Copies the image contained in 'ip' to (xloc, yloc) using one of
		the transfer modes defined in the Blitter interface. */
	public void copyBits(ImageProcessor ip, int xloc, int yloc, int mode) {
		ip = ip.convertToRGB();
		new ColorBlitter(this).copyBits(ip, xloc, yloc, mode);
	}

	/* Filters start here */

	public void applyTable(int[] lut) {
		int c, r, g, b;
		for (int y=roiY; y<(roiY+roiHeight); y++) {
			int i = y * width + roiX;
			for (int x=roiX; x<(roiX+roiWidth); x++) {
				c = pixels[i];
				r = lut[(c&0xff0000)>>16];
				g = lut[(c&0xff00)>>8];
				b = lut[c&0xff];
				pixels[i] = 0xff000000 + (r<<16) + (g<<8) + b;
				i++;
			}
		}
		showProgress(1.0);
	}
	
	public void applyTable(int[] lut, int channels) {
		int c, r=0, g=0, b=0;
		for (int y=roiY; y<(roiY+roiHeight); y++) {
			int i = y * width + roiX;
			for (int x=roiX; x<(roiX+roiWidth); x++) {
				c = pixels[i];
				if (channels==4) {
					r = lut[(c&0xff0000)>>16];
					g = (c&0xff00)>>8;
					b = c&0xff;
				} else if (channels==2) {
					r = (c&0xff0000)>>16;
					g = lut[(c&0xff00)>>8];
					b = c&0xff;
				} else if (channels==1) {
					r = (c&0xff0000)>>16;
					g = (c&0xff00)>>8;
					b = lut[c&0xff];
				} else if ((channels&6)==6) {
					r = lut[(c&0xff0000)>>16];
					g = lut[(c&0xff00)>>8];
					b = c&0xff;
				} else if ((channels&5)==5) {
					r = lut[(c&0xff0000)>>16];
					g = (c&0xff00)>>8;
					b = lut[c&0xff];
				} else if ((channels&3)==3) {
					r = (c&0xff0000)>>16;
					g = lut[(c&0xff00)>>8];
					b = lut[c&0xff];
				}
				pixels[i] = 0xff000000 + (r<<16) + (g<<8) + b;
				i++;
			}
		}
		showProgress(1.0);
	}

	/** Fills the current rectangular ROI. */
	public void fill() {
		for (int y=roiY; y<(roiY+roiHeight); y++) {
			int i = y * width + roiX;
			for (int x=roiX; x<(roiX+roiWidth); x++)
				pixels[i++] = fgColor;
			if (y%20==0)
				showProgress((double)(y-roiY)/roiHeight);
		}
		showProgress(1.0);
	}
	
	public static final int RGB_NOISE=0, RGB_MEDIAN=1, RGB_FIND_EDGES=2,
		RGB_ERODE=3, RGB_DILATE=4, RGB_THRESHOLD=5, RGB_ROTATE=6,
		RGB_SCALE=7, RGB_RESIZE=8, RGB_TRANSLATE=9;

 	/** Performs the specified filter on the red, green and blue planes of this image. */
 	public void filterRGB(int type, double arg) {
 		filterRGB(type, arg, 0.0);
 	}

 	final ImageProcessor filterRGB(int type, double arg, double arg2) {
		showProgress(0.01);
		byte[] R = new byte[width*height];
		byte[] G = new byte[width*height];
		byte[] B = new byte[width*height];
		getRGB(R, G, B);
		Rectangle roi = new Rectangle(roiX, roiY, roiWidth, roiHeight);
		
		ByteProcessor r = new ByteProcessor(width, height, R, null);
		r.setRoi(roi);
		ByteProcessor g = new ByteProcessor(width, height, G, null);
		g.setRoi(roi);
		ByteProcessor b = new ByteProcessor(width, height, B, null);
		b.setRoi(roi);
		r.setBackgroundValue((bgColor&0xff0000)>>16);
		g.setBackgroundValue((bgColor&0xff00)>>8);
		b.setBackgroundValue(bgColor&0xff);
		r.setInterpolationMethod(interpolationMethod);
		g.setInterpolationMethod(interpolationMethod);
		b.setInterpolationMethod(interpolationMethod);
		
		showProgress(0.15);
		switch (type) {
			case RGB_NOISE:
				r.noise(arg); showProgress(0.40);
				g.noise(arg); showProgress(0.65);
				b.noise(arg); showProgress(0.90);
				break;
			case RGB_MEDIAN:
				r.medianFilter(); showProgress(0.40);
				g.medianFilter(); showProgress(0.65);
				b.medianFilter(); showProgress(0.90);
				break;
			case RGB_FIND_EDGES:
				r.findEdges(); showProgress(0.40);
				g.findEdges(); showProgress(0.65);
				b.findEdges(); showProgress(0.90);
				break;
			case RGB_ERODE:
				r.erode(); showProgress(0.40);
				g.erode(); showProgress(0.65);
				b.erode(); showProgress(0.90);
				break;
			case RGB_DILATE:
				r.dilate(); showProgress(0.40);
				g.dilate(); showProgress(0.65);
				b.dilate(); showProgress(0.90);
				break;
			case RGB_THRESHOLD:
				r.autoThreshold(); showProgress(0.40);
				g.autoThreshold(); showProgress(0.65);
				b.autoThreshold(); showProgress(0.90);
				break;
			case RGB_ROTATE:
				ij.IJ.showStatus("Rotating red");
				r.rotate(arg); showProgress(0.40);
				ij.IJ.showStatus("Rotating green");
				g.rotate(arg); showProgress(0.65);
				ij.IJ.showStatus("Rotating blue");
				b.rotate(arg); showProgress(0.90);
				break;
			case RGB_SCALE:
				ij.IJ.showStatus("Scaling red");
				r.scale(arg, arg2); showProgress(0.40);
				ij.IJ.showStatus("Scaling green");
				g.scale(arg, arg2); showProgress(0.65);
				ij.IJ.showStatus("Scaling blue");
				b.scale(arg, arg2); showProgress(0.90);
				break;
			case RGB_RESIZE:
				int w=(int)arg, h=(int)arg2;
				ij.IJ.showStatus("Resizing red");
				ImageProcessor r2 = r.resize(w, h); showProgress(0.40);
				ij.IJ.showStatus("Resizing green");
				ImageProcessor g2 = g.resize(w, h); showProgress(0.65);
				ij.IJ.showStatus("Resizing blue");
				ImageProcessor b2 = b.resize(w, h); showProgress(0.90);
				R = (byte[])r2.getPixels();
				G = (byte[])g2.getPixels();
				B = (byte[])b2.getPixels();
				ColorProcessor ip2 = new ColorProcessor(w, h);
				ip2.setRGB(R, G, B);
				showProgress(1.0);
				return ip2;
			case RGB_TRANSLATE:
				ij.IJ.showStatus("Translating red");
				r.translate(arg, arg2); showProgress(0.40);
				ij.IJ.showStatus("Translating green");
				g.translate(arg, arg2); showProgress(0.65);
				ij.IJ.showStatus("Translating blue");
				b.translate(arg, arg2); showProgress(0.90);
				break;
		}
		
		R = (byte[])r.getPixels();
		G = (byte[])g.getPixels();
		B = (byte[])b.getPixels();
		
		setRGB(R, G, B);
		showProgress(1.0);
		return null;
	}

   public void noise(double range) {
    	filterRGB(RGB_NOISE, range);
    }

	public void medianFilter() {
    	filterRGB(RGB_MEDIAN, 0.0);
	}
	
	public void findEdges() {
    	filterRGB(RGB_FIND_EDGES, 0.0);
	}		
		
	public void erode() {
    	filterRGB(RGB_ERODE, 0.0);
	}
			
	public void dilate() {
    	filterRGB(RGB_DILATE, 0.0);

	}
			
	public void autoThreshold() {
   		filterRGB(RGB_THRESHOLD, 0.0);
	}
	
	/** Scales the image or selection using the specified scale factors.
		@see ImageProcessor#setInterpolate
	*/
	public void scale(double xScale, double yScale) {
        if (interpolationMethod==BICUBIC) {
        	filterRGB(RGB_SCALE, xScale, yScale);
        	return;
        }
		double xCenter = roiX + roiWidth/2.0;
		double yCenter = roiY + roiHeight/2.0;
		int xmin, xmax, ymin, ymax;
		
		if ((xScale>1.0) && (yScale>1.0)) {
			//expand roi
			xmin = (int)(xCenter-(xCenter-roiX)*xScale);
			if (xmin<0) xmin = 0;
			xmax = xmin + (int)(roiWidth*xScale) - 1;
			if (xmax>=width) xmax = width - 1;
			ymin = (int)(yCenter-(yCenter-roiY)*yScale);
			if (ymin<0) ymin = 0;
			ymax = ymin + (int)(roiHeight*yScale) - 1;
			if (ymax>=height) ymax = height - 1;
		} else {
			xmin = roiX;
			xmax = roiX + roiWidth - 1;
			ymin = roiY;
			ymax = roiY + roiHeight - 1;
		}
		int[] pixels2 = (int[])getPixelsCopy();
		boolean checkCoordinates = (xScale < 1.0) || (yScale < 1.0);
		int index1, index2, xsi, ysi;
		double ys, xs;
		double xlimit = width-1.0, xlimit2 = width-1.001;
		double ylimit = height-1.0, ylimit2 = height-1.001;
		for (int y=ymin; y<=ymax; y++) {
			ys = (y-yCenter)/yScale + yCenter;
			ysi = (int)ys;
			if (ys<0.0) ys = 0.0;			
			if (ys>=ylimit) ys = ylimit2;
			index1 = y*width + xmin;
			index2 = width*(int)ys;
			for (int x=xmin; x<=xmax; x++) {
				xs = (x-xCenter)/xScale + xCenter;
				xsi = (int)xs;
				if (checkCoordinates && ((xsi<xmin) || (xsi>xmax) || (ysi<ymin) || (ysi>ymax)))
					pixels[index1++] = bgColor;
				else {
					if (interpolationMethod==BILINEAR) {
						if (xs<0.0) xs = 0.0;
						if (xs>=xlimit) xs = xlimit2;
						pixels[index1++] = getInterpolatedPixel(xs, ys, pixels2);
					} else
						pixels[index1++] = pixels2[index2+xsi];
				}
			}
			if (y%20==0)
			showProgress((double)(y-ymin)/height);
		}
		showProgress(1.0);
	}

	public ImageProcessor crop() {
		int[] pixels2 = new int[roiWidth*roiHeight];
		for (int ys=roiY; ys<roiY+roiHeight; ys++) {
			int offset1 = (ys-roiY)*roiWidth;
			int offset2 = ys*width+roiX;
			for (int xs=0; xs<roiWidth; xs++)
				pixels2[offset1++] = pixels[offset2++];
		}
		return new ColorProcessor(roiWidth, roiHeight, pixels2);
	}
	
	/** Returns a duplicate of this image. */ 
	public synchronized ImageProcessor duplicate() { 
		int[] pixels2 = new int[width*height]; 
		System.arraycopy(pixels, 0, pixels2, 0, width*height); 
		return new ColorProcessor(width, height, pixels2); 
	} 

	/** Uses bilinear interpolation to find the pixel value at real coordinates (x,y). */
	public int getInterpolatedRGBPixel(double x, double y) {
		if (width==1||height==1)
			return getPixel((int)x, (int)y);
		if (x<0.0) x = 0.0;
		if (x>=width-1.0)
			x = width-1.001;
		if (y<0.0) y = 0.0;
		if (y>=height-1.0) y = height-1.001;
		return getInterpolatedPixel(x, y, pixels);
	}

	/** Uses bilinear interpolation to find the pixel value at real coordinates (x,y). */
	private final int getInterpolatedPixel(double x, double y, int[] pixels) {
		int xbase = (int)x;
		int ybase = (int)y;
		double xFraction = x - xbase;
		double yFraction = y - ybase;
		int offset = ybase * width + xbase;
		
		int lowerLeft = pixels[offset];
		int rll = (lowerLeft&0xff0000)>>16;
		int gll = (lowerLeft&0xff00)>>8;
		int bll = lowerLeft&0xff;
		
		int lowerRight = pixels[offset + 1];
		int rlr = (lowerRight&0xff0000)>>16;
		int glr = (lowerRight&0xff00)>>8;
		int blr = lowerRight&0xff;

		int upperRight = pixels[offset + width + 1];
		int rur = (upperRight&0xff0000)>>16;
		int gur = (upperRight&0xff00)>>8;
		int bur = upperRight&0xff;

		int upperLeft = pixels[offset + width];
		int rul = (upperLeft&0xff0000)>>16;
		int gul = (upperLeft&0xff00)>>8;
		int bul = upperLeft&0xff;
		
		int r, g, b;
		double upperAverage, lowerAverage;
		upperAverage = rul + xFraction * (rur - rul);
		lowerAverage = rll + xFraction * (rlr - rll);
		r = (int)(lowerAverage + yFraction * (upperAverage - lowerAverage)+0.5);
		upperAverage = gul + xFraction * (gur - gul);
		lowerAverage = gll + xFraction * (glr - gll);
		g = (int)(lowerAverage + yFraction * (upperAverage - lowerAverage)+0.5);
		upperAverage = bul + xFraction * (bur - bul);
		lowerAverage = bll + xFraction * (blr - bll);
		b = (int)(lowerAverage + yFraction * (upperAverage - lowerAverage)+0.5);

		return 0xff000000 | ((r&0xff)<<16) | ((g&0xff)<<8) | b&0xff;
	}

	/** Creates a new ColorProcessor containing a scaled copy of this image or selection.
		@see ImageProcessor#setInterpolate
	*/
	public ImageProcessor resize(int dstWidth, int dstHeight) {
        if (interpolationMethod==BICUBIC)
        	return filterRGB(RGB_RESIZE, dstWidth, dstHeight);
		double srcCenterX = roiX + roiWidth/2.0;
		double srcCenterY = roiY + roiHeight/2.0;
		double dstCenterX = dstWidth/2.0;
		double dstCenterY = dstHeight/2.0;
		double xScale = (double)dstWidth/roiWidth;
		double yScale = (double)dstHeight/roiHeight;
		double xlimit = width-1.0, xlimit2 = width-1.001;
		double ylimit = height-1.0, ylimit2 = height-1.001;
		if (interpolationMethod==BILINEAR) {
			//if (xScale<=0.25 && yScale<=0.25)
			//	return makeThumbnail(dstWidth, dstHeight, 0.6);
			dstCenterX += xScale/2.0;
			dstCenterY += yScale/2.0;
		}
		ImageProcessor ip2 = createProcessor(dstWidth, dstHeight);
		int[] pixels2 = (int[])ip2.getPixels();
		double xs, ys;
		int index1, index2;
		for (int y=0; y<=dstHeight-1; y++) {
			ys = (y-dstCenterY)/yScale + srcCenterY;
			if (interpolationMethod==BILINEAR) {
				if (ys<0.0) ys = 0.0;
				if (ys>=ylimit) ys = ylimit2;
			}
			index1 = width*(int)ys;
			index2 = y*dstWidth;
			for (int x=0; x<=dstWidth-1; x++) {
				xs = (x-dstCenterX)/xScale + srcCenterX;
				if (interpolationMethod==BILINEAR) {
					if (xs<0.0) xs = 0.0;
					if (xs>=xlimit) xs = xlimit2;
					pixels2[index2++] = getInterpolatedPixel(xs, ys, pixels);
				} else
		  			pixels2[index2++] = pixels[index1+(int)xs];
			}
			if (y%20==0)
			showProgress((double)y/dstHeight);
		}
		showProgress(1.0);
		return ip2;
	}
	
	/** Uses averaging to creates a new ColorProcessor containing 
		a downsized copy  of this image or selection. */
	public ImageProcessor makeThumbnail(int width2, int height2, double smoothFactor) {
		return resize(width2, height2, true);
	}

	/** Rotates the image or ROI 'angle' degrees clockwise.
		@see ImageProcessor#setInterpolationMethod
	*/
	public void rotate(double angle) {
        if (angle%360==0)
        	return;
        if (interpolationMethod==BICUBIC) {
        	filterRGB(RGB_ROTATE, angle);
        	return;
        }
		int[] pixels2 = (int[])getPixelsCopy();
		double centerX = roiX + (roiWidth-1)/2.0;
		double centerY = roiY + (roiHeight-1)/2.0;
		int xMax = roiX + this.roiWidth - 1;
		
		double angleRadians = -angle/(180.0/Math.PI);
		double ca = Math.cos(angleRadians);
		double sa = Math.sin(angleRadians);
		double tmp1 = centerY*sa-centerX*ca;
		double tmp2 = -centerX*sa-centerY*ca;
		double tmp3, tmp4, xs, ys;
		int index, ixs, iys;
		double dwidth = width, dheight=height;
		double xlimit = width-1.0, xlimit2 = width-1.001;
		double ylimit = height-1.0, ylimit2 = height-1.001;
		
		for (int y=roiY; y<(roiY + roiHeight); y++) {
			index = y*width + roiX;
			tmp3 = tmp1 - y*sa + centerX;
			tmp4 = tmp2 + y*ca + centerY;
			for (int x=roiX; x<=xMax; x++) {
				xs = x*ca + tmp3;
				ys = x*sa + tmp4;
				if ((xs>=-0.01) && (xs<dwidth) && (ys>=-0.01) && (ys<dheight)) {
					if (interpolationMethod==BILINEAR) {
						if (xs<0.0) xs = 0.0;
						if (xs>=xlimit) xs = xlimit2;
						if (ys<0.0) ys = 0.0;			
						if (ys>=ylimit) ys = ylimit2;
				  		pixels[index++] = getInterpolatedPixel(xs, ys, pixels2);
				  	} else {
				  		ixs = (int)(xs+0.5);
				  		iys = (int)(ys+0.5);
				  		if (ixs>=width) ixs = width - 1;
				  		if (iys>=height) iys = height -1;
						pixels[index++] = pixels2[width*iys+ixs];
					}
				} else
					pixels[index++] = bgColor;
			}
			if (y%30==0)
			showProgress((double)(y-roiY)/roiHeight);
		}
		showProgress(1.0);
	}
	
	public void flipVertical() {
		int index1,index2;
		int tmp;
		for (int y=0; y<roiHeight/2; y++) {
			index1 = (roiY+y)*width+roiX;
			index2 = (roiY+roiHeight-1-y)*width+roiX;
			for (int i=0; i<roiWidth; i++) {
				tmp = pixels[index1];
				pixels[index1++] = pixels[index2];
				pixels[index2++] = tmp;
			}
		}
	}
	
	/** 3x3 convolution contributed by Glynne Casteel. */
	public void convolve3x3(int[] kernel) {
		int p1, p2, p3, p4, p5, p6, p7, p8, p9;
		int k1=kernel[0], k2=kernel[1], k3=kernel[2],
		    k4=kernel[3], k5=kernel[4], k6=kernel[5],
		    k7=kernel[6], k8=kernel[7], k9=kernel[8];

		int scale = 0;
		for (int i=0; i<kernel.length; i++)
			scale += kernel[i];
		if (scale==0) scale = 1;
		int inc = roiHeight/25;
		if (inc<1) inc = 1;
		
		int[] pixels2 = (int[])getPixelsCopy();
		int offset;
		int rsum = 0, gsum = 0, bsum = 0;
        int rowOffset = width;
		for (int y=yMin; y<=yMax; y++) {
			offset = xMin + y * width;
			p1 = 0;
			p2 = pixels2[offset-rowOffset-1];
			p3 = pixels2[offset-rowOffset];
			p4 = 0;
			p5 = pixels2[offset-1];
			p6 = pixels2[offset];
			p7 = 0;
			p8 = pixels2[offset+rowOffset-1];
			p9 = pixels2[offset+rowOffset];

			for (int x=xMin; x<=xMax; x++) {
				p1 = p2; p2 = p3;
				p3 = pixels2[offset-rowOffset+1];
				p4 = p5; p5 = p6;
				p6 = pixels2[offset+1];
				p7 = p8; p8 = p9;
				p9 = pixels2[offset+rowOffset+1];

				rsum = k1*((p1 & 0xff0000) >> 16)
				     + k2*((p2 & 0xff0000) >> 16)
				     + k3*((p3 & 0xff0000) >> 16)
				     + k4*((p4 & 0xff0000) >> 16)
				     + k5*((p5 & 0xff0000) >> 16)
				     + k6*((p6 & 0xff0000) >> 16)
				     + k7*((p7 & 0xff0000) >> 16)
				     + k8*((p8 & 0xff0000) >> 16)
				     + k9*((p9 & 0xff0000) >> 16);
				rsum /= scale;
				if(rsum>255) rsum = 255;
				if(rsum<0) rsum = 0;

				gsum = k1*((p1 & 0xff00) >> 8)
				     + k2*((p2 & 0xff00) >> 8)
				     + k3*((p3 & 0xff00) >> 8)
				     + k4*((p4 & 0xff00) >> 8)
				     + k5*((p5 & 0xff00) >> 8)
				     + k6*((p6 & 0xff00) >> 8)
				     + k7*((p7 & 0xff00) >> 8)
				     + k8*((p8 & 0xff00) >> 8)
				     + k9*((p9 & 0xff00) >> 8);
				gsum /= scale;
				if(gsum>255) gsum = 255;
				else if(gsum<0) gsum = 0;

				bsum = k1*(p1 & 0xff)
				     + k2*(p2 & 0xff)
				     + k3*(p3 & 0xff)
				     + k4*(p4 & 0xff)
				     + k5*(p5 & 0xff)
				     + k6*(p6 & 0xff)
				     + k7*(p7 & 0xff)
				     + k8*(p8 & 0xff)
				     + k9*(p9 & 0xff);
				bsum /= scale;
				if (bsum>255) bsum = 255;
				if (bsum<0) bsum = 0; 

				pixels[offset++] = 0xff000000
				                 | ((rsum << 16) & 0xff0000)
				                 | ((gsum << 8 ) & 0xff00)
				                 |  (bsum        & 0xff);
			}
			if (y%inc==0)
				showProgress((double)(y-roiY)/roiHeight);
		}
		showProgress(1.0);
	}

	/** 3x3 unweighted smoothing. */
	public void filter(int type) {
		int p1, p2, p3, p4, p5, p6, p7, p8, p9;
		int inc = roiHeight/25;
		if (inc<1) inc = 1;
		
		int[] pixels2 = (int[])getPixelsCopy();
		int offset, rsum=0, gsum=0, bsum=0;
        int rowOffset = width;
		for (int y=yMin; y<=yMax; y++) {
			offset = xMin + y * width;
			p1 = 0;
			p2 = pixels2[offset-rowOffset-1];
			p3 = pixels2[offset-rowOffset];
			p4 = 0;
			p5 = pixels2[offset-1];
			p6 = pixels2[offset];
			p7 = 0;
			p8 = pixels2[offset+rowOffset-1];
			p9 = pixels2[offset+rowOffset];

			for (int x=xMin; x<=xMax; x++) {
				p1 = p2; p2 = p3;
				p3 = pixels2[offset-rowOffset+1];
				p4 = p5; p5 = p6;
				p6 = pixels2[offset+1];
				p7 = p8; p8 = p9;
				p9 = pixels2[offset+rowOffset+1];
				rsum = (p1 & 0xff0000) + (p2 & 0xff0000) + (p3 & 0xff0000) + (p4 & 0xff0000) + (p5 & 0xff0000)
					+ (p6 & 0xff0000) + (p7 & 0xff0000) + (p8 & 0xff0000) + (p9 & 0xff0000);
				gsum = (p1 & 0xff00) + (p2 & 0xff00) + (p3 & 0xff00) + (p4 & 0xff00) + (p5 & 0xff00)
					+ (p6 & 0xff00) + (p7 & 0xff00) + (p8 & 0xff00) + (p9 & 0xff00);
				bsum = (p1 & 0xff) + (p2 & 0xff) + (p3 & 0xff) + (p4 & 0xff) + (p5 & 0xff)
					+ (p6 & 0xff) + (p7 & 0xff) + (p8 & 0xff) + (p9 & 0xff);
				pixels[offset++] = 0xff000000 | ((rsum/9) & 0xff0000) | ((gsum/9) & 0xff00) | (bsum/9);
			}
			if (y%inc==0)
				showProgress((double)(y-roiY)/roiHeight);
		}
		showProgress(1.0);
	}

	public int[] getHistogram() {
		if (mask!=null)
			return getHistogram(mask);
		int c, r, g, b, v;
		int[] histogram = new int[256];
		for (int y=roiY; y<(roiY+roiHeight); y++) {
			int i = y * width + roiX;
			for (int x=roiX; x<(roiX+roiWidth); x++) {
				c = pixels[i++];
				r = (c&0xff0000)>>16;
				g = (c&0xff00)>>8;
				b = c&0xff;
				v = (int)(r*rWeight + g*gWeight + b*bWeight + 0.5);
				histogram[v]++;
			}
			if (y%20==0)
				showProgress((double)(y-roiY)/roiHeight);
		}
		showProgress(1.0);
		return histogram;
	}


	public int[] getHistogram(ImageProcessor mask) {
		if (mask.getWidth()!=roiWidth||mask.getHeight()!=roiHeight)
			throw new IllegalArgumentException(maskSizeError(mask));
		byte[] mpixels = (byte[])mask.getPixels();
		int c, r, g, b, v;
		int[] histogram = new int[256];
		for (int y=roiY, my=0; y<(roiY+roiHeight); y++, my++) {
			int i = y * width + roiX;
			int mi = my * roiWidth;
			for (int x=roiX; x<(roiX+roiWidth); x++) {
				if (mpixels[mi++]!=0) {
					c = pixels[i];
					r = (c&0xff0000)>>16;
					g = (c&0xff00)>>8;
					b = c&0xff;
					v = (int)(r*rWeight + g*gWeight + b*bWeight + 0.5);
					histogram[v]++;
				}
				i++;
			}
			if (y%20==0)
				showProgress((double)(y-roiY)/roiHeight);
		}
		showProgress(1.0);
		return histogram;
	}

	/** Performs a convolution operation using the specified kernel. */
	public void convolve(float[] kernel, int kernelWidth, int kernelHeight) {
		int size = width*height;
		byte[] r = new byte[size];
		byte[] g = new byte[size];
		byte[] b = new byte[size];
		getRGB(r,g,b);
		ImageProcessor rip = new ByteProcessor(width, height, r, null);
		ImageProcessor gip = new ByteProcessor(width, height, g, null);
		ImageProcessor bip = new ByteProcessor(width, height, b, null);
		ImageProcessor ip2 = rip.convertToFloat();
		Rectangle roi = getRoi();
		ip2.setRoi(roi);
		ip2.convolve(kernel, kernelWidth, kernelHeight);
		ImageProcessor r2 = ip2.convertToByte(false);
		ip2 = gip.convertToFloat();
		ip2.setRoi(roi);
		ip2.convolve(kernel, kernelWidth, kernelHeight);
		ImageProcessor g2 = ip2.convertToByte(false);
		ip2 = bip.convertToFloat();
		ip2.setRoi(roi);
		ip2.convolve(kernel, kernelWidth, kernelHeight);
		ImageProcessor b2 = ip2.convertToByte(false);
		setRGB((byte[])r2.getPixels(), (byte[])g2.getPixels(), (byte[])b2.getPixels());
   	}

	/** Sets the weighting factors used by getPixelValue(), getHistogram()
		and convertToByte() to do color conversions. The default values are
		1/3, 1/3 and 1/3. Check "Weighted RGB Conversions" in
		<i>Edit/Options/Conversions</i> to use 0.299, 0.587 and 0.114. */
	public static void setWeightingFactors(double rFactor, double gFactor, double bFactor) {
		rWeight = rFactor;
		gWeight = gFactor;
		bWeight = bFactor;
	}

	/** Returns the three weighting factors used by getPixelValue(), 
		getHistogram() and convertToByte() to do color conversions. */
	public static double[] getWeightingFactors() {
		double[] weights = new double[3];
		weights[0] = rWeight;
		weights[1] = gWeight;
		weights[2] = bWeight;
		return weights;
	}

	/** Always returns false since RGB images do not use LUTs. */
	public boolean isInvertedLut() {
		return false;
	}
	
	/** Always returns 0 since RGB images do not use LUTs. */
	public int getBestIndex(Color c) {
		return 0;
	}
	
	/** Does nothing since RGB images do not use LUTs. */
	public void invertLut() {
	}
	
	public void updateComposite(int[] rgbPixels, int channel) {
	}

	/** Not implemented. */
	public void threshold(int level) {}
	
	/** Returns the number of color channels of the image, i.e., 3. */
	public int getNChannels() {
		return 3;
	}
	
	/** Returns a FloatProcessor with one color channel of the image.
	*  The roi and mask are also set for the FloatProcessor.
	*  @param channelNumber   Determines the color channel, 0=red, 1=green, 2=blue
	*  @param fp              Here a FloatProcessor can be supplied, or null. The FloatProcessor
	*                         is overwritten by this method (re-using its pixels array 
	*                         improves performance).
	*  @return A FloatProcessor with the converted image data of the color channel selected
	*/
	public FloatProcessor toFloat(int channelNumber, FloatProcessor fp) {
		int size = width*height;
		if (fp == null || fp.getWidth()!=width || fp.getHeight()!=height)
			fp = new FloatProcessor(width, height, new float[size], null);
		float[] fPixels = (float[])fp.getPixels();
		int shift = 16 - 8*channelNumber;
		int byteMask = 255<<shift;
		for (int i=0; i<size; i++)
			fPixels[i] = (pixels[i]&byteMask)>>shift;
		fp.setRoi(getRoi());
		fp.setMask(mask);
		fp.setMinAndMax(0, 255);
		return fp;
	}
	
	/** Sets the pixels of one color channel from a FloatProcessor.
	*  @param channelNumber   Determines the color channel, 0=red, 1=green, 2=blue
	*  @param fp              The FloatProcessor where the image data are read from.
	*/
	public void setPixels(int channelNumber, FloatProcessor fp) {
		float[] fPixels = (float[])fp.getPixels();
		float value;
		int size = width*height;
		int shift = 16 - 8*channelNumber;
		int resetMask = 0xffffffff^(255<<shift);
		for (int i=0; i<size; i++) {
			value = fPixels[i] + 0.5f;
			if (value<0f) value = 0f;
			if (value>255f) value = 255f;
			pixels[i] = (pixels[i]&resetMask) | ((int)value<<shift);
		}
	}

}

