import java.awt.*;
import java.awt.image.*;
import java.applet.*;
import java.util.*;

public class ImageFader extends Applet implements Runnable {
	Image targetImage;
	Vector images;
	int[] targetPix;
	Dimension targetSize;
	Thread thread;
	String message;
	int bgColor = 0xff000000;
	boolean zoomIn = false;
	int speed = 1;
	String imageDir = ".";
	
	public void init() {
		images = new Vector();
		String s = getParameter("imageDir");
		if (s != null) imageDir = s;
		s = getParameter("images");
		StringTokenizer st = new StringTokenizer(s, "|");
		for (int i = 0; st.hasMoreTokens(); i++) {
			Image image = getImage(getDocumentBase(), imageDir + "/" + st.nextToken().trim());
			ImageLoader loader = new ImageLoader(image, new MediaTracker(this));
			images.addElement(loader);
		}
		s = getParameter("bgColor");
		if (s != null) bgColor = Integer.parseInt(s, 16);
		s = getParameter("zoom");
		if (s != null && s.trim().equals("in")) zoomIn = true;
		s = getParameter("speed");
		if (s != null) {
			speed = Integer.parseInt(s);
			if (speed <= 1) speed = 1;
			else if (speed > 10) speed = 10;
		}
		targetSize = size();
		((ImageLoader)images.elementAt(0)).start();
		message = "Loading image...";
	}
	
	public void start() {
		if (thread == null) {
			thread = new Thread(this);
			thread.start();
		}
	}
	
	public void stop() {
		if (thread != null) {
			thread.stop();
			thread = null;
		}
	}
	
	synchronized void draw(ImageLoader loader, int viewSize, ImageLoader next, int nextSize, double rate) {
		int[] lpix = loader.getPixels();
		int lw = loader.getWidth();
		int lh = loader.getHeight();

		int[] npix = next.getPixels();
		int nw = next.getWidth();
		int nh = next.getHeight();

		int lp, np;
		for (int y = 0; y < targetSize.height; y++) {
			for (int x = 0; x < targetSize.width; x++) {
				int xx = lw / 2 + (x - targetSize.width / 2) * lw * viewSize / 200 / targetSize.width;
				int yy = lh / 2 + (y - targetSize.height / 2) * lh * viewSize / 200 / targetSize.height;
				if (xx < 0 || xx >= lw || yy < 0 || yy >= lh) {
					lp = bgColor;
				} else {
					lp = lpix[xx + yy * lw];
				}

				xx = nw / 2 + (x - targetSize.width / 2) * nw * nextSize / 200 / targetSize.width;
				yy = nh / 2 + (y - targetSize.height / 2) * nh * nextSize / 200 / targetSize.height;
				if (xx < 0 || xx >= nw || yy < 0 || yy >= nh) {
					np = bgColor;
				} else {
					np = npix[xx + yy * nw];
				}

				int lr = (lp & 0x00ff0000) >> 16;
				int lg = (lp & 0x0000ff00) >> 8;
				int lb = (lp & 0x000000ff) ;

				int nr = (np & 0x00ff0000) >> 16;
				int ng = (np & 0x0000ff00) >> 8;
				int nb = (np & 0x000000ff) ;

				int r = ((int)(lr * (1.0 - rate) + nr * rate)) << 16;
				int g = ((int)(lg * (1.0 - rate) + ng * rate)) << 8;
				int b = ((int)(lb * (1.0 - rate) + nb * rate));

				targetPix[x + y * targetSize.width] = 0xff000000 + r + g + b;
			}
		}
		targetImage.flush();
		repaint();
	}

	void draw(ImageLoader loader, int viewSize) {
		draw(loader, viewSize, loader, viewSize, 1.0);
	}
	
	public synchronized void paint(Graphics g) {
		if (message != null) {
			g.setColor(Color.black);
			g.fillRect(0, 0, size().width, size().height);
			g.setColor(Color.cyan);
			g.drawString(message, 10, 20);
		} else {
			g.drawImage(targetImage, 0, 0, null);
		}
	}
	
	public void update(Graphics g) {
		paint(g);
	}
	
	public void run() {
		int current = 0;
		int viewSize = 100;		// screen size = 200

		targetPix = new int[targetSize.width * targetSize.height];
		targetImage = createImage(
			new MemoryImageSource(targetSize.width, targetSize.height, targetPix, 0, targetSize.width));
		ImageLoader loader = (ImageLoader)images.elementAt(0);
		for (;;) {
			if (loader.isLoaded()) {
				message = null;
				break;
			} else if (loader.isError()) {
				message = "Error: image loading...";
				repaint();
				return;
			}
			try {
				thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		while (true) {
			if (viewSize >= 300) {
				viewSize -= 200;
				current = (current + 1) % images.size();
			}
			loader = (ImageLoader)images.elementAt(current);
			ImageLoader next = (ImageLoader)images.elementAt((current + 1) % images.size());
			if (next.isLoaded()) {
				if (viewSize >= 200) {
					draw(loader, zoomIn?(300 - viewSize):viewSize,
						next, zoomIn?(500 - viewSize):(viewSize - 200),
						(double)(viewSize - 200)/ 100.0);
				} else {
					draw(loader, zoomIn?(300 - viewSize):viewSize);
				}
			} else {
				if (!next.isAlive()) {
					next.start();
				}
				draw(loader, zoomIn?(300 - viewSize):viewSize);
			}
			try {
				thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			viewSize += speed;
		}
	}
}

class ImageLoader extends Thread {
	protected Image image;
	protected boolean loaded;
	protected boolean error;
	protected int[] pixels;
	protected MediaTracker mt;
	protected int myId;

	protected static int id = 0;

	public ImageLoader(Image image, MediaTracker mt) {
		this.mt = mt;
		this.image = image;
		myId = id;
		mt.addImage(image, id++);
	}

	public boolean isLoaded() {
		return loaded;
	}

	public boolean isError() {
		return error;
	}

	public Image getImage() {
		return image;
	}

	public int[] getPixels() {
		return pixels;
	}

	public int getWidth() {
		return image.getWidth(null);
	}

	public int getHeight() {
		return image.getHeight(null);
	}

	public void run() {
		setPriority(Thread.NORM_PRIORITY - 2);
		try {
			mt.waitForID(myId);
		} catch (InterruptedException e) {
			e.printStackTrace();
			return;
		}
		if (mt.isErrorID(myId)) {
			error = true;
			return;
		}
		int w = image.getWidth(null);
		int h = image.getHeight(null);
		pixels = new int[w * h];
		PixelGrabber pg = new PixelGrabber(image, 0, 0, w, h, pixels, 0, w);
		try {
			pg.grabPixels();
		} catch (InterruptedException e) {
			e.printStackTrace();
			return;
		}
		loaded = true;
	}
}

