// Workshop 2 part 2

// This program extends workshop 1 part 3
// Here, we are experimenting with convolution filters.
// The program lets the user open an image,
// displaying the original on the left, and the result on the right.
// The user can specify the filter's kernel, and see the results.

import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.awt.image.*;
import javax.imageio.*;
import javax.swing.table.*;
import java.awt.event.*;
import java.lang.Object;


// This class extends the swing JComponent, and also implements the Scrollable interface
//
// Notes on this 'Scrollable interface':
// We want our ImageViewer to be in a window that has scroll bars at the sides
// so that if our image is larger than our window, we can scroll around to see it.
// To do this, after we have made an instance of our ImageViewer in the main function,
// we will put it into a 'JScrollPane' object.
// However, the JScrollPane needs to know a few things about our class.
// Rather than having to specify these manually when we add the ImageViewer to the JScrollPane,
// we let our ImageViewer implement the 'Scrollable' interface.
// This interface defines a few functions that JScrollPane will call on our class to query its behaviour.
//
// For more information on scroll panes:
//   The Java Tutorial on scroll panes may be found at:
//   http://java.sun.com/docs/books/tutorial/uiswing/components/scrollpane.html#scrollable
//   The Scrollable interface is documented at:
//   http://java.sun.com/javase/6/docs/api/javax/swing/Scrollable.html
class ImageViewer extends JComponent implements Scrollable
{
		// We will be applying filters to our image, but we will actually want to filter the original each time.
		// Therefore we need to keep a copy of our original image.
	protected BufferedImage originalImage, displayedImage;

	public void ImageViewer()
	{
			// Set to null in constructor
		displayedImage = originalImage = null;
	}

		// This function will load our image from a file
	public boolean loadfile(File file)
	{
		try
		{
				// Nice and easy: reads the file
				// and loads it into our image
			BufferedImage temp = ImageIO.read(file);

				// Okay, it's probably best to ignore this...
				// still here? okay, this is a nasty hack to get around a java bug
				// (bug 4957775 for the curious)
				// basically, we cannot reliably perform a convolution operation on a BufferedImage
				// that we have loaded using ImageIO
				// To get around this, we create a new BufferedImage, and copy the loaded data into it!
				// Crazy, I know, but it works..
			originalImage = new BufferedImage(temp.getWidth(),temp.getHeight(),BufferedImage.TYPE_INT_RGB);
			originalImage.setData(temp.getData());
			displayedImage = originalImage;
			return true;
		}
		catch( IOException e )
		{
				// Something went wrong with the image loading
			return false;
		}
	}

		// This function applies a filter operation to our image
	public void applyFilter(BufferedImageOp op)
	{
			// If we have an image, and are given an operation
		if( originalImage != null && op != null )
				// perform the filter operation on the original, saving the output to our displayedImage
			displayedImage = op.filter(originalImage,null);
	}

		// As part of JComponent, this function is called
		// whenever the component needs to be drawn to the screen
	public void paint(Graphics g)
	{
		if( displayedImage != null ) // Draw our displayedImage, if we have one.
			g.drawImage(displayedImage,0,0,null);

		// Note that if we have not yet performed a filter operation, displayedImage == originalImage
		// as specified in the loadfile function.

	}


	// ------------------------------------------------------------------------
	// These functions implement the Scrollable interface
	// For more information, please consult the java documentation

		// Used to query what size we are
	public Dimension getPreferredSize()
	{
			// If we have a fully loaded image, we return its size.
			// Note that we use the displayedImage, since this is the one we are displaying,
			// and it may be a different size to the original (there is a resize filter)
		if( displayedImage != null )
			return new Dimension(displayedImage.getWidth(),displayedImage.getHeight());
		else
			return new Dimension(0,0);
	}

		// Called when the user clicks on the scroll bar's buttons
	public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction)
	{
			// We return how many pixels we want to scroll by
		return 10;
	}

		// Called when the user clicks on the scroll bar's empty slider part
	public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction)
	{
			// Again, we return how many pixels we want to scroll by
		return 20;
	}

		// Again, used to query what size we are
	public Dimension getPreferredScrollableViewportSize()
	{
			// We return the same value as the getPreferredSize function, so we just call this instead:
		return getPreferredSize();
	}

		// Returns if we are forcing ourselves to the width of the scroll pane (i.e. disabling horizontal scrolling)
		// E.g. Useful if we are writing a text application that supports word-wrapping,
		// as we wouldn't want to scroll horizontally.
		// However, we're not writing a text application, so we don't want this ;)
	public boolean getScrollableTracksViewportWidth()
	{
		return false;
	}

		// Returns if we are forcing ourselves to the height of the scroll pane (i.e. disabling vertical scrolling)
		// As above, but vertically
	public boolean getScrollableTracksViewportHeight()
	{
		return false;
	}

	// ------------------------------------------------------------------------
}

class part2
{
	public static void main(String[] args)
	{

			// This will hold the file that we will be opening
		File file;

			// This is a very useful Java 2D class:
			// We create an instance of the file chooser...
		JFileChooser chooser = new JFileChooser();
			// ...and then tell it to appear to our user.
			// It will present the user with an "open dialog"
			// file browser / chooser window.
			// The user selects a file, and then the function returns.
		int chooser_result = chooser.showOpenDialog(null);

			// What we do next depends on what the user clicked
		switch(chooser_result)
		{
				// If the user clicked on the "Open" button:
			case JFileChooser.APPROVE_OPTION:
					// Retrieve the selected file
				file = chooser.getSelectedFile();
					// We can print something out to help us debug if need be
				System.out.println("Using file: " + file.getPath());
				break;
			default:
					// Something went wrong - perhaps the user clicked "Cancel"
				System.out.println("Open dialog did not return a file");
					// Returning from main will exit
				return;
		}


		// If program control reaches this point here, we have asked the user
		// to find a file for us to open, and we have retrieved that file.


			// We are going to create two image viewers
			// One will show the original version, the other will show the filtered version
			// (Later on an inner class needs to access the filtered viewer, so we declare it as final)
		ImageViewer originalViewer = new ImageViewer();
		final ImageViewer filteredViewer = new ImageViewer();

			// Send the file to both our image viewers
			// Our load function returns true if the file loaded okay...
		if( originalViewer.loadfile(file) && filteredViewer.loadfile(file) )
			System.out.println("File loaded");
		else
		{
				// ... and false if it failed
			System.out.println("File failed to load");
			return;
		}

		

			// A JFrame is a framed window for our desktop.
			// We shall create one of these so that we can display our image.
			// The string we give it will appear as the window title
		JFrame frame = new JFrame("My Image Viewer");


			// We are putting our image viewers into JScrollPanes.
			// These basically provide scroll bars, so we can scroll around our
			// image viewers if the image is too large for the window.
			// For more information, see the excellent description on: 
			// http://java.sun.com/docs/books/tutorial/uiswing/components/scrollpane.html

			// We create the scroll panes, giving them the objects that we want to put inside.
			// We call them left and right because we want to put them next to each other...
		JScrollPane leftScroller = new JScrollPane( originalViewer );
		JScrollPane rightScroller = new JScrollPane( filteredViewer );

			// To put our image viewers next to each other, we will use something called a JSplitPane.
			// This literally splits our window in two, and allows us to place components either side.
			// Again, for more information, please see:
			// http://java.sun.com/docs/books/tutorial/uiswing/components/splitpane.html
			// We have to specify whether we want the split horizontal or vertical,
			// and we give our scrolling image viewers as arguements
		JSplitPane imageSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftScroller, rightScroller);
			// We can manually place the split divider.
			// We want our window to be 640 by 480, so let's tell it to split directly in half:
		imageSplit.setDividerLocation(320);



			// We want to provide some sort of data input method so that
			// the user can specify the kernel of the convolution filter.
			// To do this, we will use a table.

			// We want to provide the table with default values
		Float[][] data = new Float[3][3];
			// The headers will not be used
		String[] headers = new String[] {"a","b","c"};

			// Create our table
			// (Again, notice that this is also final)
		final JTable table = new JTable(data,headers)
		{
				// Our kernel must take a float array as its parameter,
				// so we will force our table's values to stay as floats.
				// We do this by overriding one of its functions:
				// this function returns the class type of a column,
				// so we force it to always return a Float type.
			public Class getColumnClass(int column) { return Float.class; }
		};

			// We will also provide a button for the user to press
			// once they have finished entering their kernel data.
		JButton goButton = new JButton("Go!");

			// We need to specify what happens when the user clicks on our button.
			// The abstract inner class instance of ActionListener listens for events from our button
		goButton.addActionListener( new ActionListener() {

					// This function is called whenever our button is pressed
				public void actionPerformed(ActionEvent event)
				{
						// We first need to retrieve the data from our table:
					float[] currentData = new float[9];
					for(int i = 0; i < 3; i++ )
						for( int j = 0; j < 3; j++ )
							currentData[i*3+j] = ((Float)table.getValueAt(i,j)).floatValue();

						// Now, we can create the convolution filter.
							// This takes a Kernel instance as a parameter.
								// We give our table data to the kernel, specifying its size
					ConvolveOp convolutionFilter = new ConvolveOp( new Kernel(3,3,currentData));

						// We now have a convolution filter, and can apply it to our image viewer:
					filteredViewer.applyFilter(convolutionFilter);

						// It is inefficient to be constantly re-drawing our window,
						// so the underlying system only re-draws it when it really needs to.
						// (for example, if we scroll around our image etc.)
						// Therefore, we need to specify that our filtered image viewer needs to be redrawn.
						// To do this, we call its repaint function, (defined as part of the JComponent class).
						// For efficiency reasons, the function allows us to specify what parts need updating.
						// Since our filter works on the whole image, we tell it to redraw the whole thing:
					filteredViewer.repaint(0,filteredViewer.getX(),filteredViewer.getY(),filteredViewer.getWidth(),filteredViewer.getHeight());
				}
			} );


			// A JPanel is a general purpose container... 
		JPanel panel = new JPanel();
			// ...which we shall use to hold our table and button:
		panel.add(table);
		panel.add(goButton);
			// For more information on panels, see:
			// http://java.sun.com/docs/books/tutorial/uiswing/components/panel.html


			// We are going to create another split pane, this time vertical though.
			// It will hold our first split pane (which contains our image viewers), and our panel (which contains our table and button)
		JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT, imageSplit,panel);
			// Again, set the divider:
		split.setDividerLocation(240);



		// the rest is the same as workshop 1 part 3, except that we add our 'split' to the frame's content pane

			// In Java 2D, windows have multiple 'panes'.
			// We need to put our viewer on one of these to allow it to be displayed.
			// There are a variety of different panes available,
			// but we usually want to use the "content pane" as it's for general purpose use.
		frame.getContentPane().add(split);

			// Setting window size
		frame.setSize(640,480);

			// We need to specify what to do when the user clicks on the close widget.
			// We shall exit.
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

			// Finally, make our window visible.
		frame.setVisible(true);
	}
}