/*
 * ImageMaskFilter.java
 * Code: Jan Humble
 *
 */

import java.awt.event.*;
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.util.StringTokenizer;
import java.net.*;

public class ImageMaskFilter extends Applet {
    
    
    int mask[][];         // current mask to apply
    int maskSizeX = 5;    // mask size
    int maskSizeY = 5;    // mask size

    double weight = 1;    // weight for the mask values

    InputPanel inputPanel;  
    TextArea messageArea;
    
    ImageCanvas imageCanvas;
    int imageCanvasdX = 350;
    int imageCanvasdY = 350;
    
    String imageFile = "figures.jpg";  // file name for image file
    Image imgOriginal;                 // Original image
    Image img;                         // Image to paint
    int[] imagePixels;                 // Pixel values for image
    int imagePixelsWidth, imagePixelsHeight;
    
    public void init(){
	
	// set the layout
	
	setLayout(new BorderLayout());
	
	Label title = new Label("Image Mask Filtering", Label.CENTER);
	title.setFont(new Font("Helvetica", Font.BOLD, 14));
	title.setBackground(Color.white);
	

	inputPanel = new InputPanel(this);
	inputPanel.setSize(180, 200);
	
	imageCanvas = new ImageCanvas(this);
	
	add("North", title);
	add("West", imageCanvas);
	add("Center", inputPanel);
	
        

	messageArea = new TextArea("", 2, 1);
	messageArea.setEditable(false);	
	messageArea.setBackground(Color.white);
	add("South", messageArea);
	

	// load default file
	loadImage(imageFile, getCodeBase());

	// create default mask & set values
	createMask();
	inputPanel.maskPanel.setValues(mask);
	
	imageCanvas.repaint();
	
     

    }
    
    
    public void createMask() {
	
	mask = new int[maskSizeY][maskSizeX];
	
	
    }
    
    
    // Separate colors and alpha value from given pixel-int

    public int[] separateColors(int argb) {
	
	int result[] = {	    
	    (argb >> 24) & 0xff,
	    (argb >> 16) & 0xff,
	    (argb >>  8) & 0xff,
	    (argb      ) & 0xff
	};
	
	return result;
    }
    
    
    // Perform convolution between mask and 
    // function of equal size as mask
    
    public int convolute(int[][] pixels, int[][] mask, double weight) {
       
	double result = 0;
	double norm = 1;
	
	for (int j = 0; j < maskSizeY; j++)		    
	    for (int i = 0; i < maskSizeX; i++) {
		result += weight*pixels[j][i]*mask[j][i];
		
	    }
	
	if (result > 255)
	    result = 255;
	else if (result < 0)
	    result = -result;
	
	return (int) result;
	
    }
    
    
    // Apply filter
    
    public void applyFilter() {
	
	messageArea.setText("Calculating ...");
	
	int[] filtImage = 
	    new int[(imagePixelsWidth - 4)*(imagePixelsHeight - 4)];
	
      
	int p = 0;
	for (int y = 2; y < imagePixelsHeight - 2; y++)
	    for (int x = 2; x < imagePixelsWidth -2; x++) {
	      
		
		// get Pixels needed to calculate mask
		int [][] alphaPixels = new int[maskSizeX][maskSizeY];
		int [][] redPixels = new int[maskSizeX][maskSizeY];
		int [][] greenPixels = new int[maskSizeX][maskSizeY];	
		int [][] bluePixels = new int[maskSizeX][maskSizeY];
		
		for (int j = 0; j < maskSizeY; j++)		    
		    for (int i = 0; i < maskSizeX; i++) {
			
			// NOTE: Take into account only 5X5 mask
			int[] argb = 
			    separateColors(imagePixels
					   [(x - 2 + i)+(y - 2 + j)*
					   imagePixelsWidth]);
			
			
			// Works with all colors
			// Calculations can be improved for
			// greyscale images (only 1 calc)
			
			alphaPixels[j][i] = argb[0];
		       	redPixels[j][i] = argb[1];
			greenPixels[j][i] = argb[2];
		       	bluePixels[j][i] = argb[3];
			
		    }
		
		// for each pixel calculate new value
		int newalpha = 255;   // unchanged
		int newred = convolute(redPixels, mask, weight);
		int newgreen = convolute(greenPixels, mask, weight);
		int newblue = convolute(bluePixels, mask, weight);
		
		
		filtImage[p] = 
		    (newalpha << 24) | (newred << 16) 
		    | (newgreen << 8 ) | newblue;
		p++;
	    }
	
	// create new image to display from pixel values
	img = createImage(new MemoryImageSource(imagePixelsWidth  - 4, 
						imagePixelsHeight - 4, 
						filtImage, 0, 
						imagePixelsWidth - 4)); 
	
	// change filtered image to applet image
	imagePixelsWidth  = imagePixelsWidth  - 4; 
	imagePixelsHeight = imagePixelsHeight - 4;
	imagePixels = filtImage;
	
    
	messageArea.setText("Done Calculating Filter!");
	imageCanvas.repaint();
		
    }
    
    public void restoreImage() {

	img = imgOriginal;
	imageCanvas.repaint();
	messageArea.setText("Image restored to original!");

    }
    
    public boolean loadImage(String file, URL url) {
	
	
	Image grab = getImage(url, file);


	// Will only grab 100x100 pixels from Image
	// Otherwise calculations too slow (?)
	int w = 100;
	int h = 100;
	
	imagePixelsWidth = w;
	imagePixelsHeight = h;
	
	imagePixels = new int[w * h];
    
	PixelGrabber pg = 
	    new PixelGrabber(grab, 0, 0, w-1, h-1, imagePixels, 0, w);
        
	try {
            pg.grabPixels();
        } catch (InterruptedException e) {
            messageArea.setText("Interrupted waiting for pixels!");
            return false;
        }
        if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
            messageArea.setText("Image fetch aborted or errored");
            return false;
        }
	
	imgOriginal = 
	    createImage(new MemoryImageSource(w, h, imagePixels, 0, w)); 

	img =  
	    createImage(new MemoryImageSource(w, h, imagePixels, 0, w));
	
	imageCanvas.repaint();
	messageArea.setText("Image file: " + file + " loaded!");
	
	imageFile = file;
	return true;
    } 
    
    public boolean loadExternalImage(String imageURL) {
	
	URL url;
	String filename;
	String urlString = "";

	StringTokenizer st = new StringTokenizer(imageURL, "/");
	
	int nrOfTokens = st.countTokens();
	for (int i = 1; i < nrOfTokens; i++) {
	    urlString += (st.nextToken() + "/");
	}
	
	urlString.trim();
	
	filename = st.nextToken();
	if (urlString.length() == 0) {
	    url = getCodeBase();
	   
	}
	else {   
	    urlString = "http://" + urlString;
	    messageArea.setText("locating URL: " + urlString + 
			    "  File: " + filename);
	    try {
		url = new URL(urlString);
	    } catch (MalformedURLException mue) {
		messageArea.setText("Failed locating URL: " + urlString + 
				"  File: " + filename);
		return false;
	    }
	}
	return loadImage(filename, url);
	
	
    }
    
}






class ImageCanvas extends Canvas {
    
    ImageMaskFilter applet;
    int dX, dY;
    
    ImageCanvas(ImageMaskFilter applet) {	
	
	this.applet = applet;
	this.setSize(applet.imageCanvasdX, applet.imageCanvasdY);
	
	
	this.dX = getSize().width;
	this.dY = getSize().height;

	
	setBackground(Color.white);
    }
    
    
    public void paint(Graphics g) {
	
	// Clear screen
	g.setColor(Color.white);
	g.fillRect(0, 0, dX, dY);
	
	g.drawImage(applet.img, 0, 0, dX, dY, this);
	
    }
    
}




class InputPanel extends Panel implements ActionListener, ItemListener {
    
    ImageMaskFilter applet;
    MaskPanel maskPanel;
    
    Button applyButton, resetButton, undoButton, loadButton;
    
    TextField weightField, urlField;
    

    InputPanel(ImageMaskFilter applet) {
	
	this.applet = applet;
   	
	this.setBackground(Color.white);
	
	maskPanel = new MaskPanel(5, 5);
	
	

	applyButton = new Button("Apply");	
	resetButton = new Button("Reset");		
	loadButton  = new Button("Load Image in Location:");
	
	weightField = new TextField("1", 4);
	urlField = new TextField(applet.imageFile, 15);

	applyButton.addActionListener(this);	
	resetButton.addActionListener(this);
	loadButton.addActionListener(this);
	
	add(applyButton);
	add(resetButton);
       
	add(new Label("Weight:"));
	add(weightField);	
	add(new Label("Mask (5 X 5):"));
	add(maskPanel);
	

	Choice c = new Choice ();	
	c.addItem("Identity");
	c.addItem("1st Der");
	c.addItem("1st Der  0 deg");
	c.addItem("1st Der 90 deg");
	c.addItem("2nd Der  0 deg");
	c.addItem("2nd Der 90 deg");
	c.addItem("3rd Der  0 deg");
	c.addItem("3rd Der 90 deg");
	c.addItem("Smoothing");
	
	c.addItemListener(this);
	add( c );
	
	
	add(new Label("Image:"));
	Choice ic = new Choice ();	
	ic.addItem("figures.jpg");
	ic.addItem("clarkSmall.jpg");
	ic.addItemListener(this);
	add( ic );
	

	add(loadButton);
	add(urlField);

	update();
	
    } 
    
    
    public void readValues() {
	
	applet.weight = 
	    Double.valueOf(weightField.getText()).doubleValue();
	maskPanel.readValues(applet.mask);
	
    }
    
    
    // Get the choice item events here
    public void itemStateChanged(ItemEvent e) {
	
	String command = e.getItem().toString();
	
	if (command.equals("Identity") ) {
	    maskPanel.setValues(MaskLib.identityMask);
	    weightField.setText(Double.toString(MaskLib.identityWeight));
	}
	
	else if (command.equals("1st Der") ) {
	    maskPanel.setValues(MaskLib.firstDerMask);
	    weightField.setText(Double.toString(MaskLib.firstDerWeight));
	}

	
	else if (command.equals("1st Der  0 deg")) {
	    maskPanel.setValues(MaskLib.firstDer0Mask);
	    weightField.setText(Double.toString(MaskLib.firstDer0Weight));
	    }
	
	else if (command.equals("1st Der 90 deg")) {
	    maskPanel.setValues(MaskLib.firstDer90Mask);
	    weightField.setText(Double.toString(MaskLib.firstDer90Weight));
	}
	
	else if (command.equals("2nd Der  0 deg")) {
	    maskPanel.setValues(MaskLib.secondDer0Mask);
	    weightField.setText(Double.toString(MaskLib.secondDer0Weight));
	    }
	
	else if (command.equals("2nd Der 90 deg")) {
	    maskPanel.setValues(MaskLib.secondDer90Mask);
	    weightField.setText(Double.toString(MaskLib.secondDer90Weight));
	}
	
	
	else if (command.equals("3rd Der  0 deg")) {
	    maskPanel.setValues(MaskLib.thirdDer0Mask);
	    weightField.setText(Double.toString(MaskLib.thirdDer0Weight));
	}
	
	else if (command.equals("3rd Der 90 deg")) {
	    maskPanel.setValues(MaskLib.thirdDer90Mask);
	    weightField.setText(Double.toString(MaskLib.thirdDer90Weight));
	}
	
	else if (command.equals("Smoothing")) {
	    maskPanel.setValues(MaskLib.smoothMask);
	    weightField.setText(Double.toString(MaskLib.smoothWeight));
	}
	
	// Local images to load
	
	else if (command.equals("figures.jpg") ||
		 command.equals("clarkSmall.jpg")) {
	    
	    applet.loadImage(command, applet.getCodeBase());   
					     
	}
	

	
    }
    

    


    public void actionPerformed(ActionEvent ev) {
	
	if (ev.getActionCommand().equals("Apply")) {
	    
	    readValues();
	    applyButton.setEnabled(false);
	    applet.applyFilter();
	    restore();
	    
	}
	
	
	else if (ev.getActionCommand().equals("Reset")) {

	    applet.restoreImage();
	    
	    
	}
	
	else if (ev.getActionCommand().equals("Load Image in Location:")) { 
	    applet.loadExternalImage(urlField.getText());
	    
	    
	}
	
    }
    
    public void update() {
	
	
	
    }
    
    public void restore() {
	applyButton.setEnabled(true);
	
	
    }
    
}




class MaskPanel extends Panel {

    
    int sizeX, sizeY;
    TextField fieldArray[][];
    
    MaskPanel(int sizeX, int sizeY) {
	
	this.sizeX = sizeX;
	this.sizeY = sizeY;
	this.setBackground(Color.white);
	
	setLayout(new GridLayout(sizeX, sizeY));
	
	fieldArray = new TextField[sizeY][sizeX];
	
	for (int y = 0; y < sizeY; y++) 
	    for (int x = 0; x < sizeX; x++) {
		fieldArray[y][x] = new TextField("0");
		add(fieldArray[y][x]);
	    }
	
    }
    
    
    void setValues(int intArray[][]) {
	
	for (int y = 0; y < sizeY; y++)
	    for (int x = 0; x < sizeX; x++)
		
		fieldArray[y][x].setText(Integer.toString(intArray[y][x]));
	
	
     }
    
    
    void readValues(double doubleArray[][]) {
	
	for (int y = 0; y < sizeY; y++)
	    for (int x = 0; x < sizeX; x++)
		
		doubleArray[y][x] = 
		    Double.valueOf(fieldArray[y][x].getText()).doubleValue();
	
	
    }
    
    void readValues(int intArray[][]) {
	
	for (int y = 0; y < sizeY; y++)
	    for (int x = 0; x < sizeX; x++)
		intArray[y][x] = 
		    Integer.valueOf(fieldArray[y][x].getText()).intValue();
	
    }
    
    
} 
    
    


