// Filter application
// (c) Julian J. Bunn, 1998,1999

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.image.*;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D.Double;
import java.awt.geom.Ellipse2D.*;
import java.awt.font.TextLayout;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.event.WindowAdapter;


public class FilterTest extends JFrame {

	private Container frameContainer;
    private BufferedImage bi,bimg1,bimg2,bHough;
	private BufferedImageOp biop,houghOp;
    private Image img;
	private String sourceImage;

	private int dWidth=600,dHeight=600; // Display dimensions
	private int iWidth,iHeight;         // Source image dimensions

	private double [] c = new double[0]; // Line intercepts
	private double [] m = new double[0]; // Line gradients

	private double scalex,scaley;


    public static void main(String argv[]) {
        FilterTest app = new FilterTest(argv[0]);
    }

	public FilterTest(String image) {

		super(image);

		sourceImage = image;

		addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {System.exit(0);}
        });

		start();

		process();

		repaint();

	}


    public void start() {

        img = getToolkit().getImage(sourceImage);
		if(img == null) {
			System.out.println("Error opening image \""+sourceImage+"\"");
			System.exit(0);
		}
		System.out.println("Source Image \""+sourceImage+"\"");
        try {
             MediaTracker tracker = new MediaTracker(this);
             tracker.addImage(img, 0);
             tracker.waitForID(0);
        } catch ( Exception e ) {
			System.out.println("MediaTracker error:"+e);
		}

        int iw = img.getWidth(this);
        int ih = img.getHeight(this);
		if(iw <= 0 || ih <= 0) {
			System.out.println("Source image is invalid size");
			System.exit(0);
		} else {
			System.out.println("Size "+iw+"x"+ih+" pixels");
		}

		setSize(dWidth,dHeight);

        bi = new BufferedImage(iw, ih, BufferedImage.TYPE_INT_RGB);
        Graphics2D big = bi.createGraphics();
        big.drawImage(img,0,0,this);

		setupSizes();

    }

	public void setupSizes() {

        dWidth = getSize().width;
        dHeight = getSize().height;
        
		iWidth = bi.getWidth(this);
        iHeight = bi.getHeight(this);

        bimg1 = new BufferedImage(iWidth,iHeight,BufferedImage.TYPE_INT_RGB);
        bimg2 = new BufferedImage(iWidth,iHeight,BufferedImage.TYPE_INT_RGB);

        AffineTransform at = new AffineTransform();
        scalex = (double) (dWidth-14) / (double) iWidth / 2.0;
        scaley = (double) (dHeight-14) / (double) iHeight / 2.0;
        at.scale(scalex, scaley);
        biop = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);

        //AffineTransform atHough = new AffineTransform();
        //double scalehx = (double) (dWidth-14) / (double) lendim / 2.0;
        //double scalehy = (double) (dHeight-14) / (double) lendim / 2.0;
        //atHough.scale(scalehx, scalehy);
        //houghOp = new AffineTransformOp(atHough, AffineTransformOp.TYPE_BILINEAR);

		setVisible(true);

	}

	public void process() {

		System.out.println("Processing image ...");

		System.out.println("Convert to greyscale");
		GrayScale(bi,bimg1);

		System.out.println("Apply Mexican Hat convolution");
		int hatSize=11;
		MexicanHat(hatSize,2.f,2.f,bimg1,bimg2);

		//CrudeEdge(bimg2,bimg3);
		//Threshold(127,bimg3,bimg3);

		System.out.println("Find lines using Hough Transform");
		bHough = null;
        int linesRequired = 15;
		// c,m are parameters of the straight lines to be returned
		c = new double[linesRequired];
		m = new double[linesRequired];
        int lendim = 150; // Linear size of Hough space
		LineHough(lendim,hatSize,c,m,bimg2,bHough);
		for(int i=0;i<c.length;i++) {
			System.out.println("Line "+(i+1)+" Gradient="+m[i]+" Intercept="+c[i]);
		}

		System.out.println("Finished");

	}

    public void paint(Graphics g) {

		int x,y;

        Graphics2D g2 = (Graphics2D) g;

        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setRenderingHint(RenderingHints.KEY_RENDERING,
                            RenderingHints.VALUE_RENDER_QUALITY);
        int w = getSize().width;
        int h = getSize().height;

		if(w != dWidth || h != dHeight) {
			setupSizes();
			process();
		}

   		x = 5; y = 5; // draw the original image
        g2.drawImage(bi,biop,x,y);

   		x = w/2+3; y = 5; // draw the greyscale image
        g2.drawImage(bimg1,biop,x,y);

		x = 5; y = h/2+3; // draw the edge detected image
        g2.drawImage(bimg2,biop,x,y);

        g2.setPaint(Color.yellow);
		x = w/2+3; y = h/2+3;
        g2.drawImage(bi,biop,x,y); // draw the original image and add lines
        double ymax = (double) (iHeight-1);
        double xmax = (double) (iWidth-1);
        double y1,x1,y2,x2;
		for(int i=0;i<c.length;i++) {
            // Find start and end points
            x1 = 0.;
            y1 = c[i];
            if(y1 < 0.) {
                x1 = -c[i]/m[i];
                y1 = 0.;
            } else if (y1 > ymax) {
                x1 = (ymax-c[i])/m[i];
                y1 = ymax;
            }
            x2 = xmax;
            y2 = m[i]*xmax + c[i];
            if(y2 < 0.) {
                y2 = 0.;
                x2 = -c[i]/m[i];
            } else if (y2 > ymax) {
                y2 = ymax;
                x2 = (ymax-c[i])/m[i];
            }
            x1 *= scalex;
            x2 *= scalex;
            y1 *= scaley;
            y2 *= scaley;
            g2.draw(new java.awt.geom.Line2D.Double(x+x1, y+y1, x+x2, y+y2));
        }
     }


	void CrudeEdge(BufferedImage imageIn, BufferedImage imageOut) {
		// Applies a crude 3x3 edge detection operator to imageIn
		// and places the result in imageOut
		float[] EDGE3x3_3 = {
			0.f, -1.f, 0.f,
			-1.f, 4.f, -1.f,
			0.f, -1.f, 0.f};
        Kernel kernel = new Kernel(3,3,EDGE3x3_3);
        ConvolveOp cop = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
        cop.filter(imageIn,imageOut);
	}

	void MexicanHat(int hatSize, float sigmaSquared, float aPrime,
		BufferedImage imageIn, BufferedImage imageOut) {

		// Takes an input image, imageIn, and applies a Mexican Hat
		// convolution to it, to produce an output image imageOut.
		// The hatSize is the size of the operator, the sigmaSquared
		// is the width of the "hat", squared, aPrime is the
		// normalisation.
		//
		// NB sigmaSquared=2 (good for SAR) = 4 (good for faces!)
		//    aPrime=2       (good for SAR) = 4 (good for faces!)

		int hatWidth = (hatSize-1)/2;
		float mexicanHat [] = new float [hatSize*hatSize];
		for (int i=0;i<hatSize*hatSize;i++) {
			int ix = i%hatSize;
			int iy = i/hatSize;
			float dx = (float) (ix-hatWidth);
			float dy = (float) (iy-hatWidth);
			float x2py2 = dx*dx + dy*dy;
			mexicanHat[i] = aPrime*(2.f - x2py2/sigmaSquared)*(float)Math.exp(-0.5*x2py2/sigmaSquared);
		}

		Kernel kernel = new Kernel(hatSize,hatSize,mexicanHat);
        ConvolveOp cop = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
        cop.filter(imageIn,imageOut);
	}

	void GrayScale(BufferedImage imageIn, BufferedImage imageOut) {
		// Converts the input image to grey scale in the output image
	    ColorSpace csp = ColorSpace.getInstance(ColorSpace.CS_GRAY);
	    ColorConvertOp cspop = new ColorConvertOp(csp,null);
	    cspop.filter(imageIn,imageOut);
	}

	void Threshold(int cutOff, BufferedImage imageIn, BufferedImage imageOut) {
		// Assumes a grey-scale image in imageIn
		// Applies a simple threshold to imageIn: pixels with a value > cutOff
		// are set to max., those below set to zero, and the resulting
		// image put in imageOut
        byte chlut[] = new byte[256];
        for ( int j=0;j<256 ;j++ ) {
 			  chlut[j] = (byte) 0;
			  if(j > cutOff) chlut[j] = (byte) 255;
		}

        ByteLookupTable blut=new ByteLookupTable(0,chlut);
        LookupOp lop = new LookupOp(blut, null);
        lop.filter(imageIn,imageOut);
	}

	void LineHough(int size, int border, double [] c, double [] m,
					BufferedImage imageIn, BufferedImage imageHough) {

		int threshold=127;
		int numLines = c.length;
		int width = imageIn.getWidth(this);
		int height = imageIn.getHeight(this);
		int npixels = width*height;
		int pixel [] = new int[npixels];
		imageIn.getRGB(0,0,width,height,pixel,0,width);

		double sinTheta [] = new double[size];
		double cosTheta [] = new double[size];
		double r2index = 0.5 * (double) size / (double) width;
		int hough [][] = new int[size][size];

		imageHough = new BufferedImage(size,size,BufferedImage.TYPE_INT_RGB);
        for (int i=0;i<size;i++) {
            double dTheta = (double) i * 2.0 * Math.PI / (double) size;
            sinTheta[i] = Math.sin(dTheta);
            cosTheta[i] = Math.cos(dTheta);
            for (int j=0;j<size;j++) {
                hough[i][j] = 0;
				imageHough.setRGB(i,j,0x00000000);
            }
        }
        int lmax = 0;
        int kmax = 0;
        int hmax = 1;
		// Accumulate the Hough array
        for (int i=border;i<height-border;i++) {
            for (int j=border;j<width-border;j++) {
                int ipix = i*width + j;
                int b = pixel[ipix] & 0x000000FF;
                if(b < threshold) continue;
                double xt = (double) j;
                double yt = (double) i;
                for (int k=0;k<size;k++) {
                    double r = xt*cosTheta[k] + yt*sinTheta[k];
                    if (r < 0.) continue;
                    int l = (int) (r*r2index);
                    if(l<0) l = 0;
                    if(l>=size) l = size-1;
                    hough[k][l]++;
                    if(hough[k][l] > hmax) { lmax = l; kmax = k; hmax = hough[k][l]; }
                }
            }
        }
        // Create the image of the Hough array
        int lend2 = size*size;
        int value [] = new int[lend2];
        int num [] = new int[lend2];
        for (int k=0;k<size;k++) {
            for (int l=0;l<size;l++) {
                int i = k*size + l;
                num[i] = i;
                value[i] = hough[k][l];
                int b = (int) ( 255.f * (float) value[i] / (float) hmax );
                b |= b<<8;
                b |= b<<16;
                imageHough.setRGB(k,l,b);
            }
        }
		// Do a simple (time-consuming) shuffle sort on the array
		// This could be speeded up using a more efficient sort!
        int nshuffle;
        do {
            nshuffle = 0;
            for (int i=1;i<lend2;i++) {
                if(value[i] > value[i-1]) {
                    int iv = value[i-1];
                    int in = num[i-1];
                    value[i-1] = value[i];
                    num[i-1] = num[i];
                    value[i] = iv;
                    num[i] = in;
                    nshuffle++;
                }
            }
        } while (nshuffle > 0);

		// Now we have the Hough array sorted in order of decreasing
		// value

        int linek [] = new int[numLines];
        int linel [] = new int[numLines];
        int nlines = 0;
        double ymax = (double) (height-1);
        double xmax = (double) (width-1);
        double y1,x1,y2,x2;
line_loop:
        for (int i=0;i<lend2;i++) {
            if(nlines >= numLines) break;
            int kh = num[i]/size;
            int lh = num[i]%size;
            if(Math.abs(sinTheta[kh]) < 0.000001) continue;
            // Find the candidate line parameters (m,c) in y = mx + c
            double r = (double) lh / r2index;
            double mm = -cosTheta[kh]/sinTheta[kh];
            double cc = r/sinTheta[kh];
			// Reject lines that are artifacts due to the edges of the image itself
			if(Math.abs(cc) < 0.1 && Math.abs(mm) < 0.1) continue line_loop;
			if(Math.abs(cc) > 0.99*ymax && Math.abs(mm) < 0.1) continue line_loop;
			// Reject lines that are very close to lines already in the list
            boolean tooclose = false;
            for (int iline=0;iline<nlines;iline++) {
				int kdiff = kh - linek[iline];
				kdiff *= kdiff;
				int ldiff = lh - linel[iline];
				ldiff *= ldiff;
                double diff = Math.sqrt( (double) (kdiff+ldiff) );
                if (diff < 4.) { // A very arbitrary cut ...
                   tooclose = true;
                   continue line_loop;
                }
            }
            linek[nlines] = kh;
            linel[nlines] = lh;
			c[nlines] = cc;
			m[nlines] = mm;
            nlines++;
        }
	}




}
