//import java.awt.*;
import javax.swing.*;
import java.awt.Image;
import java.awt.Graphics;
import java.awt.image.*;
import java.awt.Dimension;
import java.awt.MediaTracker;
import java.util.*;
import java.io.*;
import java.net.*;
import java.awt.event.*;
import java.awt.BorderLayout;

/**
 * <PRE>
 * Class for manipulating graphics images
 * originally developed in C++
 * revision history for C++ version
 * <P>
 *  Modified: 3/21/94 
 *            11/29/94
 *            4/13/95 
 * <P>
 * Ported: 10/16/1996 to Java (Syam Gadde)
 * re-implemented, ported to 1.1 6/1/97 (Owen Astrachan)
 * <P>
 *
 * this class represents an image that supports
 * manipulation, i.e., reflection, expansion, inversion, etc.
 * It has an analog in C++ for comparison between the two languages
 * although there is more support in Java for images than there is
 * in C++.
 * <P>
 *
 * Creating a pixmap requires a filename that should be a gif or jpg
 * image (or others if getImage() supports them).  Currently the
 * filename represents a local image, but changing the URL to support
 * network retrievable images should be straightforward
 * </PRE>
 *
 * @author Owen Astrachan
 *
 */       

public class Pixmap extends JPanel implements ActionListener
{
    /**
     * create a pixmap (by reading a local file), the pixmap is added
     * to a PixApp, and displayed in a frame
     *
     * @param app the Application to which this pixmap will be added
     * @param filename complete pathname of the local file that can
     *        be read by getImage()
     */
    
    public Pixmap(PixController control, String filename)
    {
	myName = filename;
	myControl = control;
	
	// start loading the image and wait until completed
	
	MediaTracker tracker = new MediaTracker(this);
	try {
	    myImage = 
		this.getToolkit().getImage(
				      new URL("file:" + filename));
	}
	catch (MalformedURLException e) {
	    System.err.println("Bad URL!");
	}
	tracker.addImage(myImage,0);
    
	// wait for image to load
    
	try   { tracker.waitForID(0); }
	catch (InterruptedException e){
	    System.out.println("image loading interrupted");
	    return;
	}  

	// initialize dimensions of pixmap
	
	myIcon = null;
	myRows = myImage.getHeight(this);
	myCols = myImage.getWidth(this);
	myD = new Dimension(myCols,myRows);
	setSize(myCols,myRows);

	// add to application and then display
	
	myControl.addPixmap(this);
	display();
    }

    /**
     * redisplays this pixmap as the active pixmap
     * @param ev the action event causing the redisplay
     */
    
    public void actionPerformed(ActionEvent ev)
    {
	myControl.setActiveMap(this);
	display();
    }

    /**
     * @return the PixIcon associated with this Pixmap
     */
    
    public PixIcon getIcon()
    {
      if (myIcon == null)
      {
	  myIcon = new PixIcon(myImage,myName);
	  myIcon.addActionListener(this);
      }
      return myIcon;
    }

    /**
     * overrides Component.getPreferredSize()
     */
    
    public Dimension getPreferredSize()
    {
	return myD;
    }

    /**
     * overrides JComponent.paintComponent()
     */
    
    public void paintComponent(Graphics g)
    {
	super.paintComponent(g);
	g.drawImage(myImage, 0, 0, this);
    }

    private void grabPixels()
    {
	// create room to store all the pixels,than grab
	// them from the image
	
	myPixels = new int[myRows * myCols];
	PixelGrabber pg = new PixelGrabber(myImage,
					   0, 0, myCols, myRows,
					   myPixels, 0, myCols);
	try {
	    pg.grabPixels();
	}
	catch (InterruptedException e) {
	    System.out.println("pixel grab interrupted!");
	    return;
	}
    
    }

    private int getPixel(int row, int col)
    {
	return myPixels[row*myCols + col];
    }

    private void setPixel(int row, int col, int pixValue)
    {
	myPixels[row*myCols + col] = pixValue;
    }

    private void resetImage()
    {
	myImage = this.createImage(
	    new MemoryImageSource(myCols, myRows, myPixels, 0, myCols));
	repaint();	    
    }


   /**
     * reflect this vertically (which means through a
     * vertical line drawn through the middle of the
     * Pixmap)   ---> | <---
     *                                                
     */

    public void vertReflect()  
    {
	grabPixels();


	// loop over each row, reflecting left half to right
	int k;
	for(k=0; k < myRows; k++)
	{
	    int j;
	    int limit = myCols/2;
	    for(j=0; j < limit; j++)
	    {
		int temp = getPixel(k,j);
		setPixel(k,j,getPixel(k,myCols-j-1));
		setPixel(k,myCols-j-1,temp);
	    }
	}    

	// reset the image tothe pixel array and repaint it	
	resetImage();
    }

    /**
     * reflect this horizontally (which means through a 
     * horizontal line drawn through the middle of the Pixmap
     * <PRE>
     *    |
     *    v
     *  ----
     *    ^
     *    |
     * </PRE>
     *
     */

    public void horizReflect()
    {
	grabPixels();

	// loop over each column, reflecting top half to bottom
	
	int k;
	for(k=0; k < myCols; k++)
	{
	    int j;
	    int limit = myRows/2;          // stop swapping halfway
	    for(j=0; j < limit; j++)
	    {
		int temp = getPixel(j,k);
		setPixel(j,k,getPixel(myRows-j-1,k));
		setPixel(myRows-j-1,k,temp);
	    }
	}    

	// reset the image to the pixel array and repaint it
	
	resetImage();
    }

    public void expand(int rowExpand,int colExpand)
    {
       int newRows = myRows * rowExpand;
       int newCols = myCols * colExpand;

       // create room to store all the pixels,than grab
       // them from the image

	myPixels = new int[newRows * newCols];
	PixelGrabber pg = new PixelGrabber(myImage,
					   0, 0, myCols, myRows,
					   myPixels, 0, myCols);
	try {
	    pg.grabPixels();
	}
	catch (InterruptedException e) {
	    System.out.println("Expand: pixel grab interrupted!");
	    return;
	}
	int k;
	for(k=newRows-1; k >= 0; k--)
	{
	    int j;
	    int oldRow = k/rowExpand; 
	    for(j=newCols-1; j >= 0; j--)
	    {
	        myPixels[k*newCols+j] = myPixels[oldRow*myCols+(j/colExpand)] ;
	    }
	}
	// reset image to pixel array and repaint it
	myImage = this.createImage(
            new MemoryImageSource(newCols,newRows,myPixels,0,newCols));
	myRows = newRows;
	myCols = newCols;
	myD = new Dimension(myCols,myRows);
	setSize(myCols,myRows);
	myIcon.setImage(myImage);
	display();
    }


    /**
     * turn all pixels to there 'opposite color'. Black to white,
     * white to black, and invert RGB pixel by taking negative
     * relative to 255 (max RGB value), i.e., red = 255 - red, and
     * similarly for blue and green pixel values
     *
     */
    
    public void invert()
    {
	// get instance of inverter filter, use it and repaint
	
	ImageFilter inverter = InvertFilter.getInstance();
	myImage = createImage(new
			      FilteredImageSource(myImage.getSource(),
						  inverter));
	repaint();
    }

    /**
     * displays this in its associated frame
     * and updates its application that active pixmap
     * has changed (for use in re-displaying thumbnail and
     * original image)
     */
    
    private void display()
    {
	repaint();
	ourFrame.setTitle("Pixmap: " + myName);
	ourFrame.getContentPane().removeAll();
	ourFrame.getContentPane().add(this,BorderLayout.CENTER);
	((JComponent)ourFrame.getContentPane()).revalidate();
	((JComponent)ourFrame.getContentPane()).repaint();
	ourFrame.pack();
	ourFrame.setVisible(true);
	
    }

    
    private Image myImage = null;   // the actual image for this
    private int myRows = 0;         // # rows in image
    private int myCols = 0;         // # columns in image
    private PixController myControl; // associated controller
    private PixIcon myIcon;         // iconified (thumbnail) image
    private String myName;          // filename source of image
    private Dimension myD;          // dimension for getPreferredSize()
    private int myPixels[];

    // frame in which image displayed, one per app
    
    private static JFrame ourFrame = new JFrame("Pixmap image");   
}


/**
 * package accessible class for filtering images
 * (could be a nested class for Pixmap in jdk1.1.1)
 *
 * this class uses a Singleton pattern (see Design Patterns by
 * Gamma, Helm, Johnson, Vlissides) which ensures that only
 * one instance of the filter is created
 * to use an InvertFilter call the getInstance() method (which
 * will create the filter the first time it's called)
 */

class InvertFilter extends RGBImageFilter
{
    /**
     * return the single instance of the InvertFilter
     * 
     */
    public static InvertFilter getInstance()
    {
	if (ourInstance == null)
	{
	    ourInstance = new InvertFilter();
	}
	return ourInstance;
    }

    /**
     * private constructor enforces Singleton Pattern
     */
    
    private InvertFilter()
    {
	canFilterIndexColorModel = true;
    }

    /**
     * overrides RGBImageFilter.filterRGB
     */
    public int filterRGB(int x, int y, int rgb)
    {
	// get the color model for use with Java and this app
	
	DirectColorModel dm = (DirectColorModel)
	    ColorModel.getRGBdefault();

	// invert all pixels (leave alpha/transparency value alone)
	
	int red   = 255 - dm.getRed(rgb);
	int blue  = 255 - dm.getBlue(rgb);
	int green = 255 - dm.getGreen(rgb);
	int alpha = dm.getAlpha(rgb);

	// construct a pixel/int with rgb in appropriate bytes
	
	return (alpha << 24) | (red << 16) | (green << 8) | blue;
    }

    private static InvertFilter ourInstance = null;
}
