package com.jcomeau; import java.applet.Applet; import java.awt.*; import java.awt.event.*; import java.util.Vector; import java.math.*; public class Mandelbrot extends Applet implements Runnable, MouseListener, WindowListener, KeyListener { public static final long serialVersionUID = 0; public static double zoom = 2.0; public static Thread painter = null; public static Thread manager = null; public static Point mouseDown = null; public static Vector colors = null; public static Point mouseUp = null; public static Frame frame = null; public static boolean stopRequested = false; public static boolean initialized = false; public static final BigDecimal zero = new BigDecimal(0); public static BigDecimal two = new BigDecimal(2); public static BigDecimal four = new BigDecimal(4); public static BigDecimal[] center = {new BigDecimal(-0.5), zero}; public static final double maxdouble = 7E13; // 2E12 or higher public static final int scale = 30; // larger is slower, more accurate public static final int mode = BigDecimal.ROUND_HALF_EVEN; // most accurate public static int depth = 1; // how many Ktimes to iterate public void init() { Common.debug("initializing"); // some things needed for restart of applet stopRequested = false; initColors(); // frame will be non-null only when run as application if (frame != null) frame.addWindowListener(this); // let painter thread know it's OK to start requestFocus(); initialized = true; } public void initColors() { Common.debug("depth is now " + depth); colors = new Vector (); switch(depth) { /* there should be some way to store methods in an array but if so * i can't for the life of me figure out how... */ case 1: fnbrot(); break; case 2: fnbrot2(); break; case 4: fnbrot4(); break; case 8: fnbrot8(); break; } } public void fnbrot() { // a coloring scheme suggested by my neighbor for (int step = 252; step >= 0; step -= 8) { for (int color = step; color >= 0; color -= 4) { colors.addElement(new Color(step, step - color, color)); } } } public void fnbrot2() { // a coloring scheme suggested by my neighbor for (int step = 252; step >= 0; step -= 4) { for (int color = step; color >= 0; color -= 4) { colors.addElement(new Color(step, step - color, color)); } } } public void fnbrot4() { // a coloring scheme suggested by my neighbor for (int step = 252; step >= 0; step -= 4) { for (int color = step; color >= 0; color -= 2) { colors.addElement(new Color(step, step - color, color)); } } } public void fnbrot8() { // a coloring scheme suggested by my neighbor for (int step = 252; step >= 0; step -= 2) { for (int color = step; color >= 0; color -= 2) { colors.addElement(new Color(step, step - color, color)); } } } public void maxelbrot() { /* switch between different color patterns from white to black * it looks Peter Max-ish, hence the name */ for (int color = 255; color >= 0; color -= 2) { colors.addElement(new Color(color, color, color)); colors.addElement(new Color(color, color, 0)); colors.addElement(new Color(color, 0, color)); colors.addElement(new Color(0, color, color)); colors.addElement(new Color(0, 0, color)); colors.addElement(new Color(color, 0, 0)); colors.addElement(new Color(0, color, 0)); } colors.addElement(new Color(0, 0, 0)); } public void colorForth() { int color = Common.binary("11111011110"); // cyan, like my colorForth program int divisor = Common.binary("100000"); while (color >= 0) { colors.addElement(new Color(0, (color / divisor) << 2, (color % divisor) << 3)); color -= 2; } } public void start() { // this is the Applet start(); a thread "start()" ignores this, just run()s Common.debug("starting"); (manager = new Thread(this)).start(); } public void stop() { Common.debug("stopping"); stopRequested = true; } public void destroy() { colors = null; // enable GC of this RAM } public static void sleep(int milliseconds) { try { Thread.sleep(milliseconds); } catch (InterruptedException exception) { Common.debug("interrupted: " + exception); } } public void paint(Graphics graphics) { (painter = new Thread(this)).start(); // use new thread to paint the Set } public int diameter(Dimension size) { if (size == null) size = getSize(); return Math.min(size.height, size.width); } public Complex constant(Dimension size, double increment, int x, int y) { if (size == null) { size = getSize(); int diameter = diameter(size); increment = (4.0 / zoom) / diameter; } Complex c = new Complex(-((increment * size.width) / 2) + center[0].doubleValue() + (increment * x), ((increment * size.height) / 2) + center[1].doubleValue() - (increment * y)); return c; } public BigDecimal bigDecimal(double number) { return bigDecimal(new BigDecimal(number)); } public BigDecimal bigDecimal(BigDecimal number) { return number.setScale(scale, mode); } public Complex constant(Dimension size, BigDecimal increment, int x, int y) { if (size == null) { size = getSize(); int diameter = diameter(size); increment = bigDecimal(four.divide(bigDecimal(zoom), scale, mode).divide( bigDecimal(diameter), scale, mode)); } Complex c = new Complex(bigDecimal(increment.multiply( bigDecimal(size.width)).divide(two).negate().add(center[0]).add( increment.multiply(bigDecimal(x)))), bigDecimal(increment.multiply( bigDecimal(size.height)).divide(two).add(center[1]).subtract( increment.multiply(bigDecimal(y))))); return c; } public void mandelbrot() { Graphics graphics = getGraphics(); Dimension size = getSize(); Complex z, c; double increment = 0.0; BigDecimal fineIncrement = null; int iteration; int diameter = diameter(size); increment = (4.0 / zoom) / diameter; if (zoom >= maxdouble) fineIncrement = bigDecimal(four.divide( bigDecimal(zoom), scale, mode).divide( bigDecimal(diameter), scale, mode)); for (int y = 0; y < size.height; y++) { if (stopRequested) break; for (int x = 0; x < size.width; x++) { if (stopRequested) break; if (fineIncrement == null) { c = constant(size, increment, x, y); z = new Complex(0, 0); } else { c = constant(size, fineIncrement, x, y); z = new Complex(zero, zero); //Common.debug("c=" + c + ", z=" + z); sleep(100); } for (iteration = 0; iteration < colors.size(); iteration++) { if (z.absSquared() >= 4.0) break; if (z.absSquared(four) >= 4.0) break; z = z.squared().add(c); } synchronized(this) { if (Thread.currentThread() == painter) { graphics.setColor(colors.elementAt(iteration - 1)); graphics.fillRect(x, y, 1, 1); } else { return; } } } } } public void run() { if (Thread.currentThread() == manager) { Common.debug("running"); this.addMouseListener(this); this.addKeyListener(this); Common.debug("listeners deployed"); status(); while (!stopRequested) { sleep(50); } } else { while (!initialized) sleep(25); mandelbrot(); } } public void mousePressed(MouseEvent event) { //Common.debug("button=" + event.getButton() + // " point=" + event.getPoint()); if (event.getButton() == 1) { mouseDown = event.getPoint(); } } public void mouseReleased(MouseEvent event) { double newZoom; Complex start, end; //Common.debug("button=" + event.getButton() + // " point=" + event.getPoint()); if (event.getButton() == 1) { mouseUp = event.getPoint(); if (mouseDown != null) { Common.debug("zoom in"); if (zoom >= maxdouble) { Common.debug("using BigNumber increments"); start = constant(null, zero, mouseDown.x, mouseDown.y); end = constant(null, zero, mouseUp.x, mouseUp.y); center[0] = start.xBig.add(end.xBig).divide(two); center[1] = start.yBig.add(end.yBig).divide(two); } else { start = constant(null, 0.0, mouseDown.x, mouseDown.y); end = constant(null, 0.0, mouseUp.x, mouseUp.y); center[0] = bigDecimal((start.x + end.x) / 2); center[1] = bigDecimal((start.y + end.y) / 2); } newZoom = (double)diameter(null) / diameter(new Dimension( Math.abs(mouseUp.x - mouseDown.x), Math.abs(mouseUp.y - mouseDown.y))); if (newZoom <= 1000) zoom *= newZoom; else zoom *= 10; status(); repaint(); } } else { // either center or right button, zoom out zoom /= 10; status(); repaint(); } } public void message(String text) { try { showStatus(text); } catch (Exception ignored) { System.err.println(text); } } public void status() { message("x " + center[0].doubleValue() + " y " + center[1].doubleValue() + " zoom " + zoom); } public void mouseClicked(MouseEvent event) {} public void mouseEntered(MouseEvent event) {} public void mouseExited(MouseEvent event) {} public void windowClosing(WindowEvent e) { Common.debug("window closed"); stopRequested = true; frame.dispose(); } public void windowOpened(WindowEvent e) {} public void windowClosed(WindowEvent e) {} public void windowIconified(WindowEvent e) {} public void windowDeiconified(WindowEvent e) {} public void windowActivated(WindowEvent e) {} public void windowDeactivated(WindowEvent e) {} public void keyPressed(KeyEvent e) { //Common.debug("key pressed: " + e); if (e.getKeyChar() == 'd') { // decrease depth if (depth > 1) { synchronized(this) {painter = null;} depth /= 2; initColors(); repaint(); } } else if (e.getKeyChar() == 'D') { // increase depth if (depth < 8) { synchronized(this) {painter = null;} depth *= 2; initColors(); repaint(); } } } public void keyReleased(KeyEvent e) {} public void keyTyped(KeyEvent e) {} public static void main(String[] args) { int width = 640, height = 480; if (args.length >= 1 && args[0].length() > 0) width = Integer.parseInt(args[0]); if (args.length >= 2) height = Integer.parseInt(args[1]); if (args.length >= 3) center[0] = new BigDecimal(args[2]); if (args.length >= 4) center[1] = new BigDecimal(args[3]); if (args.length >= 5) zoom = Double.parseDouble(args[4]); frame = new Frame("Mandelbrot"); Mandelbrot app = new Mandelbrot(); frame.add(app); frame.pack(); frame.setLocation(100, 100); frame.setSize(width, height); frame.setVisible(true); app.init(); app.start(); } }