package ij.plugin.filter;
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.io.*;
import ij.plugin.TextReader;
import ij.plugin.frame.Recorder;
import ij.util.Tools;
import java.awt.*;
import java.util.*;
import java.awt.event.*;
import java.io.*;

/** This plugin convolves images using user user defined kernels. */
public class Convolver implements ExtendedPlugInFilter, DialogListener, ActionListener {

	private ImagePlus imp;
	private int kw, kh;
	private boolean canceled;
	private float[] kernel;
	private boolean isLineRoi;
	private Button open, save;
	private GenericDialog gd;
	private boolean normalize = true;
	private int nSlices;
	private int flags = DOES_ALL|CONVERT_TO_FLOAT|SUPPORTS_MASKING|KEEP_PREVIEW|FINAL_PROCESSING|SNAPSHOT;
	private int nPasses = 1;
	private boolean kernelError;
	private PlugInFilterRunner pfr;
	private Thread mainThread;
	private int pass;

	
	static String kernelText = "-1 -1 -1 -1 -1\n-1 -1 -1 -1 -1\n-1 -1 24 -1 -1\n-1 -1 -1 -1 -1\n-1 -1 -1 -1 -1\n";
	static boolean normalizeFlag = true;

	public int setup(String arg, ImagePlus imp) {
 		this.imp = imp;
        mainThread = Thread.currentThread();
		if (imp==null)
			{IJ.noImage(); return DONE;}
		if (arg.equals("final")&&imp.getRoi()==null) {
			imp.getProcessor().resetMinAndMax();
			imp.updateAndDraw();
			return DONE;
		}
		IJ.resetEscape();
		Roi roi = imp.getRoi();
		isLineRoi= roi!=null && roi.isLine();
		nSlices = imp.getStackSize();
		if (imp.getStackSize()==1)
			flags |= PARALLELIZE_IMAGES;
		else
			flags |= PARALLELIZE_STACKS;
		imp.startTiming();
		return flags;
	}

	public void run(ImageProcessor ip) {
		if (canceled) return;
		if (isLineRoi) ip.resetRoi();
		if (!kernelError)
			convolve(ip, kernel, kw, kh);
	}
	
	public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) {
		gd = new GenericDialog("Convolver...");
		gd.addTextAreas(kernelText, null, 10, 30);
		gd.addPanel(makeButtonPanel(gd));
		gd.addCheckbox("Normalize Kernel", normalizeFlag);
		gd.addPreviewCheckbox(pfr);
		gd.addDialogListener(this);
		gd.showDialog();
		if (gd.wasCanceled()) return DONE;
		this.pfr = pfr;
		return IJ.setupDialog(imp, flags);
	}

    public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
		kernelText = gd.getNextText();
		normalizeFlag = gd.getNextBoolean();
		normalize = normalizeFlag;
		kernelError = !decodeKernel(kernelText);
		if (!kernelError) {
			IJ.showStatus("Convolve: "+kw+"x"+kh+" kernel");
			return true;
		} else
			return !gd.isPreviewActive();
    }
    
    boolean decodeKernel(String text) {
    	if (Macro.getOptions()!=null && !hasNewLine(text))
    		return decodeSquareKernel(text);
		String[] rows = Tools.split(text, "\n");
		kh = rows.length;
		if (kh==0) return false;
		String[] values = Tools.split(rows[0]);
		kw = values.length;
		kernel = new float[kw*kh];
		boolean done = gd.wasOKed();
		int i = 0;
		for (int y=0; y=3 && (kw&1)==1) {
			StringBuffer sb = new StringBuffer();
			int i = 0;
			for (int y=0; yip with a kernel of width kw and
		height kh. Returns false if the user cancels the operation. */
	public boolean convolve(ImageProcessor ip, float[] kernel, int kw, int kh) {
		if (canceled || kw*kh!=kernel.length) return false;
		if ((kw&1)!=1 || (kh&1)!=1)
			throw new IllegalArgumentException("Kernel width or height not odd ("+kw+"x"+kh+")");
		boolean notFloat = !(ip instanceof FloatProcessor);
		ImageProcessor ip2 = ip;
		if (notFloat) {
			if (ip2 instanceof ColorProcessor) 
				throw new IllegalArgumentException("RGB images not supported");
			ip2 = ip2.convertToFloat();
		}
		if (kw==1 || kh==1)
			convolveFloat1D((FloatProcessor)ip2, kernel, kw, kh, normalize?getScale(kernel):1.0);
		else
			convolveFloat(ip2, kernel, kw, kh);
		if (notFloat) {
			if (ip instanceof ByteProcessor)
				ip2 = ip2.convertToByte(false);
			else
				ip2 = ip2.convertToShort(false);
			ip.setPixels(ip2.getPixels());
		}
		return !canceled;
	}
	
	/** If 'normalize' is true (the default), the convolve(), convolveFloat() and
		convolveFloat1D() (4 argument version) methods divide each kernel
		coefficient by the sum of the coefficients, preserving image brightness. */
	public void setNormalize(boolean normalizeKernel) {
		normalize = normalizeKernel;
	}
	
	/** Convolves the float image ip with a kernel of width 
		kw and height kh. Returns false if 
		the user cancels the operation by pressing 'Esc'. */
	public boolean convolveFloat(ImageProcessor ip, float[] kernel, int kw, int kh) {
		if (!(ip instanceof FloatProcessor))
			throw new IllegalArgumentException("FloatProcessor required");
		if (canceled) return false;
		int width = ip.getWidth();
		int height = ip.getHeight();
		Rectangle r = ip.getRoi();
		int x1 = r.x;
		int y1 = r.y;
		int x2 = x1 + r.width;
		int y2 = y1 + r.height;
		int uc = kw/2;    
		int vc = kh/2;
		float[] pixels = (float[])ip.getPixels();
		float[] pixels2 = (float[])ip.getSnapshotPixels();
		if (pixels2==null)
			pixels2 = (float[])ip.getPixelsCopy();
		double scale = normalize?getScale(kernel):1.0;
        Thread thread = Thread.currentThread();
        boolean isMainThread = thread==mainThread || thread.getName().indexOf("Preview")!=-1;
        if (isMainThread) pass++;
		double sum;
		int offset, i;
		boolean edgePixel;
		int xedge = width-uc;
		int yedge = height-vc;
		long lastTime = System.currentTimeMillis();
		for (int y=y1; y100) {
				lastTime = time;
				if (thread.isInterrupted()) return false;
				if (isMainThread) {
					if (IJ.escapePressed()) {
						canceled = true;
						ip.reset();
						ImageProcessor originalIp = imp.getProcessor();
						if (originalIp.getNChannels() > 1)
							originalIp.reset();
						return false;
					}
					showProgress((y-y1)/(double)(y2-y1));
				}
			}
			for (int x=x1; x=yedge || x=xedge;
				for (int v=-vc; v <= vc; v++) {
					offset = x+(y+v)*width;
					for(int u = -uc; u <= uc; u++) {
						if (edgePixel) {
 							if (i>=kernel.length) // work around for JIT compiler bug on Linux
 								IJ.log("kernel index error: "+i);
							sum += getPixel(x+u, y+v, pixels2, width, height)*kernel[i++];
						} else
							sum += pixels2[offset+u]*kernel[i++];
					}
		    	}
				pixels[x+y*width] = (float)(sum*scale);
			}
    	}
   		return true;
   	 }

	/** Convolves the image ip with a kernel of width
		kw and height kh. */
	public void convolveFloat1D(FloatProcessor ip, float[] kernel, int kw, int kh) {
		convolveFloat1D(ip, kernel, kw, kh, normalize?getScale(kernel):1.0);
	}

	/** Convolves the image ip with a kernel of width 
		kw and height kh. */
	public void convolveFloat1D(FloatProcessor ip, float[] kernel, int kw, int kh, double scale) {
		int width = ip.getWidth();
		int height = ip.getHeight();
		Rectangle r = ip.getRoi();
		int x1 = r.x;
		int y1 = r.y;
		int x2 = x1 + r.width;
		int y2 = y1 + r.height;
		int uc = kw/2;    
		int vc = kh/2;
		float[] pixels = (float[])ip.getPixels();
		float[] pixels2 = (float[])ip.getSnapshotPixels();
		if (pixels2==null)
			pixels2 = (float[])ip.getPixelsCopy();
		boolean vertical = kw==1;

		double sum;
		int offset, i;
		boolean edgePixel;
		int xedge = width-uc;
		int yedge = height-vc;
		for(int y=y1; y=yedge;
					offset = x+(y-vc)*width;
					for(int v=-vc; v<=vc; v++) {
						if (edgePixel)
							sum += getPixel(x+uc, y+v, pixels2, width, height)*kernel[i++];
						else
							sum += pixels2[offset+uc]*kernel[i++];
						offset += width;
					}
				} else {
					edgePixel = x=xedge;
					offset = x+(y-vc)*width;
					for(int u = -uc; u<=uc; u++) {
						if (edgePixel)
							sum += getPixel(x+u, y+vc, pixels2, width, height)*kernel[i++];
						else
							sum += pixels2[offset+u]*kernel[i++];
					}
				}
				pixels[x+y*width] = (float)(sum*scale);
			}
    	}
    }
   	 
	public static double getScale(float[] kernel) {
		double scale = 1.0;
		double sum = 0.0;
		for (int i=0; i=width) x = width-1;
		if (y<=0) y = 0;
		if (y>=height) y = height-1;
		return pixels[x+y*width];
	}
	
	void save() {
		TextArea ta1 = gd.getTextArea1();
		ta1.selectAll();
		String text = ta1.getText();
		ta1.select(0, 0);
		if (text==null || text.length()==0)
			return;
		text += "\n";
		SaveDialog sd = new SaveDialog("Save as Text...", "kernel", ".txt");
		String name = sd.getFileName();
		if (name == null)
			return;
		String directory = sd.getDirectory();
		PrintWriter pw = null;
		try {
			FileOutputStream fos = new FileOutputStream(directory+name);
			BufferedOutputStream bos = new BufferedOutputStream(fos);
			pw = new PrintWriter(bos);
		}
		catch (IOException e) {
			IJ.error("" + e);
			return;
		}
		IJ.wait(250);  // give system time to redraw ImageJ window
		pw.print(text);
		pw.close();
	}
	
	void open() {
		OpenDialog od = new OpenDialog("Open Kernel...", "");
		String directory = od.getDirectory();
		String name = od.getFileName();
		if (name==null)
			return;
		String path = directory + name;
		TextReader tr = new TextReader();
		ImageProcessor ip = tr.open(path);
		if (ip==null)
			return;
		int width = ip.getWidth();
		int height = ip.getHeight();
		if ((width&1)!=1 || (height&1)!=1) {
			IJ.error("Convolver", "Kernel must be have odd width and height");
			return;
		}
		StringBuffer sb = new StringBuffer();
		boolean integers = true;
		for (int y=0; y 

Ads help maintain this website.