// This file is the result of an attempt to write the shortest possible readable,
// functional, interactive Mandelbrot Set generator in Java. It should provide
// an easy-to-follow example of the calculations involved, although some of it is
// left a bit tricky in the interest of brevity! 
//
// As a result, it isn't especially object-oriented or reusable in its current form,
// but should be very easy to adapt to any purpose. One of the first things to do is 
// to extend the calculation to more than 255 iterations, so that more detail will 
// be achieved at higher magnifications.
//
// I consider this to be fairly succinct. If you have modifications that make it 
// shorter without unduly sacrificing functionality, readablilty, or aesthetics, 
// I implore you to suggest them to me for inclusion.
//
// 12 March 2002
// moss@theprescotts.com
//
// This is Public Domain software. Use it at your own risk.
//
// Copyright 2002 Moss Prescott 

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.MemoryImageSource;

public class Minibrot {
	static final int width = 512;
	static final int height = 512;

	static double acenter =   -0.5;
	static double bcenter =    0.0;
	static double half =       1.2;

	public static void 
	main(String[] args) {
		final int[] pixels = new int[width*height];
		final int[] colors = colorTable();
		
		calculate(pixels, colors);
		
		final MemoryImageSource source = new MemoryImageSource(width, height, pixels, 0, width);
		source.setAnimated(true);
		Image img = Toolkit.getDefaultToolkit().createImage(source);
		JLabel label = new JLabel(new ImageIcon(img));
		
		JFrame f = new JFrame("Minibrot");
		f.getContentPane().add(label);
		f.pack();
		f.show();

		label.addMouseListener(new MouseAdapter() {
			public void mouseReleased(MouseEvent evt) {
				double aclicked = acenter - half + (2.0*half*evt.getX())/width;
				double bclicked = bcenter - half + (2.0*half*(height - evt.getY()))/height;
			
				acenter = aclicked;
				bcenter = bclicked;
				half *= evt.isAltDown() ? 2.0 : 0.5;
				
				calculate(pixels, colors); // re-generate the pixels
				source.newPixels(0, 0, width, height); // re-render the image
			}
		});
	}
	
	private static void
	calculate(int[] pixels, int[] colors) {
		byte[] counts = new byte[width*height];

		for (int h = 0; h < height; h++) {
			for (int w = 0; w < width; w++) {
				double ca = acenter - half + (2.0*half*w)/width;
				double cb = bcenter - half + (2.0*half*(height - h))/height;

				double za = 0;
				double zb = 0;
				
				int i;
				for (i = 0; i < 255; i++) {
					// z-squared:
					double qa = za*za - zb*zb;	// (a^2 + (bi)^2)
					double qb = 2.0*za*zb;		// 2abi
					
					// z':
					za = qa + ca;
					zb = qb + cb;
					
					if (za*za + zb*zb > 4.0)
						break;
				}
				
				counts[h*width + w] = (byte) i; // save 8 bits without sign
			}
		}
		
		for (int i = 0; i < pixels.length; i++) {
			pixels[i] = colors[counts[i] & 0xFF]; // strip sign extension
		}
	}

	// Note: this is actually generating a static table, so it could be 
	// hard-coded, but what fun is that?
	private static int[]
	colorTable() {
		int[] redCodes = { 0, 1, 2, 2, 3, 0 };
		int[] greenCodes = { 2, 2, 3, 0, 0, 1 };
		int[] blueCodes = { 3, 0, 0, 1, 2, 2 };
		int[] colors = new int[256];

		for (int i=0; i < 255; i++) {
			int hue = (i/42) % 6;
			int local = i%42;
			colors[i] = (0xFF << 24) |
						(color(redCodes[hue], local) << 16) | 
						(color(greenCodes[hue], local) << 8) | 
						color(blueCodes[hue], local);
		}
		colors[255] = 0xFF000000;
		
		return colors;
	}
	
	private static int 
	color(int code, int local) {
		switch (code) {
			default:
				return 255;
			case 1:
				return 255 - local*6;
			case 2:
				return 0;
			case 3:
				return local*6;
		}
	}
}
