//
// WaveletGen.java
//
// Simple 2D Wavelet Transform Generator
//
// Copyright (c) 1997, Benjamin Nason Lipchak
//

import java.applet.Applet;
import java.net.*;
import java.awt.*;
import java.awt.image.*;
import benj.awt.image.*;

public class WaveletGen extends Applet implements Runnable {
    TextField imageURLField;
    Label imageURLLabel;
    Label errorMessage;
    ImageCanvas origCanvas;
    Image origImage;
    ImageCanvas waveletCanvas;
    Image waveletImage;
    String inputURL;
    Image animation[];
    AnimationCanvas animCanvas;

    Thread workThread;
    boolean processNewImage = false;
    Object processNewImageSemaphore;

    public void init() {
	processNewImageSemaphore = new Object();

	imageURLField = new TextField("http://");
	imageURLLabel = new Label("image URL: ", Label.RIGHT);
	errorMessage = new Label("", Label.LEFT);

	origCanvas = new ImageCanvas(this, 256, 256);
	waveletCanvas = new ImageCanvas(this, 256, 256);
	animCanvas = new AnimationCanvas(this, 256, 256);

	GridBagLayout myGridBag = new GridBagLayout();
	GridBagConstraints c = new GridBagConstraints();
	setLayout(myGridBag);

	c.fill = GridBagConstraints.HORIZONTAL;
	c.insets = new Insets(10, 10, 0, 0);
	c.gridx = 0;
	c.gridwidth = 1;
	myGridBag.setConstraints(imageURLLabel, c);
	add(imageURLLabel);
	c.insets = new Insets(10, 0, 0, 10);
	c.gridx = 1;
	c.gridwidth = GridBagConstraints.REMAINDER;
	c.weightx = 1.0;
	myGridBag.setConstraints(imageURLField, c);
	add(imageURLField);
	c.insets = new Insets(0, 0, 5, 10);
	myGridBag.setConstraints(errorMessage, c);
	add(errorMessage);

	c.gridwidth = 2;
	c.gridx = 0;
	c.insets = new Insets(5, 10, 5, 5);
	myGridBag.setConstraints(origCanvas, c);
	add(origCanvas);
	c.gridx = 2;
	c.insets = new Insets(5, 5, 5, 10);
	myGridBag.setConstraints(waveletCanvas, c);
	add(waveletCanvas);

	c.gridwidth = GridBagConstraints.REMAINDER;
	c.gridx = 1;
	c.insets = new Insets(5, 50, 10, 50);
	myGridBag.setConstraints(animCanvas, c);
	add(animCanvas);

	validate();
    }

    public void start() {
	if (workThread == null) {
	    workThread = new Thread(this, "Work Thread");
	    workThread.start();
	}
    }

    public void run() {
	while (workThread != null) {
	    synchronized (processNewImageSemaphore) {
		while (processNewImage == false) {
		    try {
			processNewImageSemaphore.wait();
		    } catch (InterruptedException e) {
		    }
		}
	    }

	    origCanvas.setImage(null);
	    waveletCanvas.setImage(null);

	    //Track image loading.  Indicate failure.
	    Image sourceImage;
	    try {
		sourceImage = getImage(new URL(inputURL));
	    } catch (MalformedURLException e) {
		errorMessage.setText("ERROR: Bad URL!");
		synchronized (processNewImageSemaphore) {
		    processNewImage = false;
		    processNewImageSemaphore.notify();
		}
		continue;
	    }
	    MediaTracker tracker = new MediaTracker(this);
	    tracker.addImage(sourceImage, 0);
	    boolean doneWaiting = false;
	    while (!doneWaiting) {
		try {
		    tracker.waitForAll();
		    doneWaiting = true;
		} catch (InterruptedException e) {
		    continue;
		}
	    }
	    if (tracker.isErrorAny()) {
		errorMessage.setText("ERROR: Unable to load image!");
		synchronized (processNewImageSemaphore) {
		    processNewImage = false;
		    processNewImageSemaphore.notify();
		}
		continue;
	    }

	    //Crop if necessary
	    int imageWidth = sourceImage.getWidth(this);
	    int imageHeight = sourceImage.getHeight(this);
	    if (imageWidth <= 0 || imageHeight <= 0) {
		errorMessage.setText("ERROR: Bad image!");
		synchronized (processNewImageSemaphore) {
		    processNewImage = false;
		    processNewImageSemaphore.notify();
		}
		continue;
	    }
	    int newWidth = nextLowerPowerOfTwo(imageWidth);
	    int newHeight = nextLowerPowerOfTwo(imageHeight);
	    if (newWidth > newHeight)
		newWidth = newHeight;
	    else if (newHeight > newWidth)
		newHeight = newWidth;

	    if (imageWidth != newWidth || imageHeight != newHeight) {
		errorMessage.setText("Cropping original image...");

		ImageFilter filter = new CropImageFilter(0, 0, newWidth, newHeight);
		ImageProducer producer = new FilteredImageSource(sourceImage.getSource(), filter);
		origImage = createImage(producer);

		tracker.addImage(origImage, 0);
		doneWaiting = false;
		while (!doneWaiting) {
		    try {
			tracker.waitForAll();
			doneWaiting = true;
		    } catch (InterruptedException e) {
			continue;
		    }
		}
		if (tracker.isErrorAny()) {
		    errorMessage.setText("ERROR: Unable to crop image!");
		    synchronized (processNewImageSemaphore) {
			processNewImage = false;
			processNewImageSemaphore.notify();
		    }
		    continue;
		}
	    } else {
		origImage = sourceImage;
	    }
	    origCanvas.setImage(origImage);

	    errorMessage.setText("Generating wavelet transform...");
	    ImageFilter filter = new WaveletTransformFilter();
	    ImageProducer producer = new FilteredImageSource(origImage.getSource(), filter);
	    waveletImage = createImage(producer);

	    tracker.addImage(waveletImage, 0);
	    doneWaiting = false;
	    while (!doneWaiting) {
		try {
		    tracker.waitForAll();
		    doneWaiting = true;
		} catch (InterruptedException e) {
		    continue;
		}
	    }
	    if (tracker.isErrorAny()) {
		errorMessage.setText("ERROR: Unable to transform image!");
		synchronized (processNewImageSemaphore) {
		    processNewImage = false;
		    processNewImageSemaphore.notify();
		}
		continue;
	    }
	    waveletCanvas.setImage(waveletImage);

	    //Generate frames for animation
	    errorMessage.setText("Generating wavelet subimages...");
	    int n = logBase2(newWidth);
	    animation = new Image[n + 1];
	    for (int j = 0; j <= n; j++) {
		filter = new WaveletTransformFilter(j);
		producer = new FilteredImageSource(origImage.getSource(), filter);
		animation[j] = createImage(producer);
		tracker.addImage(animation[j], 0);
	    }
	    doneWaiting = false;
	    while (!doneWaiting) {
		try {
		    tracker.waitForAll();
		    doneWaiting = true;
		} catch (InterruptedException e) {
		    continue;
		}
	    }
	    if (tracker.isErrorAny()) {
		errorMessage.setText("ERROR: Unable to generate animation!");
		synchronized (processNewImageSemaphore) {
		    processNewImage = false;
		    processNewImageSemaphore.notify();
		}
		continue;
	    }
	    animCanvas.setAnim(animation, n + 1);

	    //Ready for another
	    synchronized (processNewImageSemaphore) {
		processNewImage = false;
		processNewImageSemaphore.notify();
	    }

	    errorMessage.setText("Done.");
	}
    }

    public boolean action(Event event, Object what) {
	if (event.target == imageURLField) {
	    synchronized (processNewImageSemaphore) {
		if (processNewImage)
		    return true;
	    }

	    errorMessage.setText("Loading image...");

	    inputURL = imageURLField.getText();

	    synchronized (processNewImageSemaphore) {
		processNewImage = true;
		processNewImageSemaphore.notify();
	    }

	    return true;
	}

	return false;
    }

    int pow2(int degree) {
	if (degree < 0)
	    return -1;

	int result = 1;
	while (degree > 0) {
	    result *= 2;
	    degree--;
	}

	return result;
    }

    int logBase2(int number) {
	int result = 0;
	while (pow2(result) != number) {
	    result++;
	    if (result >= number)
		return -1;
	}
	return result;
    }

    int nextLowerPowerOfTwo(int i) {
	int a = 1, b = 2;

	if (i < 1)
	    return 0;

	while (i > b) {
	    a += a;
	    b += b;
	}

	return a;
    }
}

class ImageCanvas extends Canvas {
    Container pappy;
    Image image = null;
    Dimension minSize;
    int w, h;

    public ImageCanvas(Container parent,
	int initialWidth, int initialHeight)
    {
	pappy = parent;

	w = initialWidth;
	h = initialHeight;

	minSize = new Dimension(w, h);
    }

    public synchronized void setImage(Image image) {
	this.image = image;
	repaint();
    }

    public Dimension preferredSize() {
	return minimumSize();
    }

    public Dimension minimumSize() {
	return minSize;
    }

    public void paint(Graphics g) {
	update(g);
    }

    public void update(Graphics g) {
	if (image == null) {
	    g.setColor(Color.black);
	    g.drawRect(0, 0, w - 1, h - 1);
	} else {
	    g.drawImage(image, 0, 0, w, h, this);
	}
    }
}

class AnimationCanvas extends Canvas implements Runnable {
    Container pappy;
    Image[] animation = null;
    int numImages;
    Dimension minSize;
    int w, h;

    int frameNumber = 0;
    int delay = 200;
    Thread animatorThread;

    Dimension offDimension;
    Image offImage;
    Graphics offGraphics;

    public AnimationCanvas(Container parent,
	int initialWidth, int initialHeight)
    {
	pappy = parent;

	w = initialWidth;
	h = initialHeight;

	minSize = new Dimension(w, h);

	animatorThread = new Thread(this);
	animatorThread.start();
    }

    public synchronized void setAnim(Image[] animation, int n) {
	this.animation = animation;
	numImages = n;
    }

    public void run() {
	Thread.currentThread().setPriority(Thread.MIN_PRIORITY);

	long startTime = System.currentTimeMillis();

	boolean goingUp = true;
	while (Thread.currentThread() == animatorThread) {
	    if (animation != null) {
		if (goingUp) {
		    frameNumber++;
		    if (frameNumber >= (numImages - 1)) {
			goingUp = false;
			frameNumber = numImages - 1;
		    }
		} else {
		    frameNumber--;
		    if (frameNumber <= 0) {
			goingUp = true;
			frameNumber = 0;
		    }
		}
		repaint();
	    }

	    try {
		startTime += delay;
		Thread.sleep(Math.max(0, startTime - System.currentTimeMillis()));
	    } catch (InterruptedException e) {
		break;
	    }
	}
    }

    public Dimension preferredSize() {
	return minimumSize();
    }

    public Dimension minimumSize() {
	return minSize;
    }

    public void paint(Graphics g) {
	update(g);
    }

    public void update(Graphics g) {
	if (animation == null) {
	    g.setColor(Color.black);
	    g.drawRect(0, 0, w - 1, h - 1);
	} else {
	    Dimension d = size();

	    if ((offGraphics == null) ||
		(d.width != offDimension.width) ||
		(d.height != offDimension.height))
	    {
		offDimension = d;
		offImage = createImage(d.width, d.height);
		offGraphics = offImage.getGraphics();
	    }

	    offGraphics.setColor(getBackground());
	    offGraphics.fillRect(0, 0, d.width, d.height);
	    offGraphics.setColor(Color.black);

	    offGraphics.drawImage(animation[frameNumber], 0, 0, w, h, this);
	    g.drawImage(offImage, 0, 0, d.width, d.height, this);
	}
    }
}