import java.awt.*; import java.awt.event.*; public class SimpleMandel extends java.applet.Applet { MandelCanvas myCanvas; Panel myPanel; Label zoom, approx; Choice exponents; Font boldFont = new Font("Helvetica", Font.BOLD, 12); public static final int maxcol = 640; public static final int maxrow = 480; // The program relies on width and height being evenly divisable by 4. public void init () { resize(maxcol, maxrow + 51); // width / height should be 4 / 3, add 20 to height for each row of buttons setLayout(new BorderLayout()); // read applet parameters int maxExponent = 9; try { maxExponent = Integer.parseInt(getParameter("maxExponent")); } catch (Exception ex) { } if (maxExponent < 2) maxExponent = 2; int initialExponent = 2; try { initialExponent = Integer.parseInt(getParameter("initialExponent")); } catch (Exception ex) { } if (initialExponent < 2) initialExponent = 2; if (initialExponent > maxExponent) initialExponent = maxExponent; double bounds = 4.0; try { bounds = Double.valueOf(getParameter("bounds")).doubleValue(); } catch (Exception ex) { } if (bounds <= 0.0) bounds = 4.0; int maxIterLimit = 100000; try { maxIterLimit = Integer.parseInt(getParameter("maxIterations")); } catch (Exception ex) { } if (maxIterLimit < 120) maxIterLimit = 120; boolean approximationFirst = true; try { String tmp = getParameter("approximationFirst"); approximationFirst = (tmp != null) && (tmp.toLowerCase().equals("true")); } catch (Exception ex) { } myPanel = new Panel(); myPanel.setBackground(Color.lightGray); myPanel.setLayout(new GridLayout(2,5,5,5)); // first row Button west = new Button("Left"); west.setFont(boldFont); west.setBackground(Color.white); west.addActionListener(new ButtonAL(1)); myPanel.add(west); Button north = new Button("Up"); north.setFont(boldFont); north.setBackground(Color.white); north.addActionListener(new ButtonAL(2)); myPanel.add(north); Button south = new Button("Down"); south.setFont(boldFont); south.setBackground(Color.white); south.addActionListener(new ButtonAL(3)); myPanel.add(south); Button east = new Button("Right"); east.setFont(boldFont); east.setBackground(Color.white); east.addActionListener(new ButtonAL(4)); myPanel.add(east); Button reset = new Button("Reset"); reset.setFont(boldFont); reset.setBackground(Color.white); reset.addActionListener(new ButtonAL(7)); myPanel.add(reset); // second row Button zoom_in = new Button("Zoom in"); zoom_in.setFont(boldFont); zoom_in.setBackground(Color.white); zoom_in.addActionListener(new ButtonAL(5)); myPanel.add(zoom_in); zoom = new Label(); zoom.setFont(boldFont); zoom.setAlignment(Label.CENTER); myPanel.add(zoom); Button zoom_out = new Button("Zoom out"); zoom_out.setFont(boldFont); zoom_out.setBackground(Color.white); zoom_out.addActionListener(new ButtonAL(6)); myPanel.add(zoom_out); approx = new Label("Approximation: " + (approximationFirst ? "On" : "Off")); approx.setFont(boldFont); approx.setAlignment(Label.CENTER); approx.addMouseListener(new MouseAdapter() { public void mouseClicked (MouseEvent e) { myCanvas.toggleApproximationFirst(approx); } }); myPanel.add(approx); exponents = new Choice(); exponents.addItem("Select Exponent"); for (int i=2; i<=maxExponent; i++) exponents.addItem("" + i); exponents.select(initialExponent-1); exponents.addItemListener(new ChoiceIL(this)); myPanel.add(exponents); add("South", myPanel); myCanvas = new MandelCanvas(this, initialExponent, bounds, maxIterLimit, approximationFirst); myCanvas.init(); add("Center", myCanvas); } public void start () { myCanvas.start(); } public void stop () { myCanvas.stop(); } private class ChoiceIL implements ItemListener { SimpleMandel parent; ChoiceIL (SimpleMandel parent) { this.parent = parent; } public void itemStateChanged (ItemEvent e) { // exponent hasn't changed - no need to reinit try { String text = (String) e.getItem(); if ("select exponent".equals(text.toLowerCase())) { parent.exponents.select(myCanvas.getExponent()-1); return; } int newExponent = Integer.parseInt(text); if (myCanvas.getExponent() == newExponent) return; myCanvas.stop(); myCanvas.setExponent(newExponent); myCanvas.init(); myCanvas.repaint(); } catch (NumberFormatException nfex) { // no sense to create a new Canvas if the exponent somehow isn't a number } } } private class ButtonAL implements ActionListener{ int which; ButtonAL (int _which) { which = _which; } public void actionPerformed (ActionEvent e) { switch (which) { case 1: myCanvas.shift('W'); break; case 2: myCanvas.shift('N'); break; case 3: myCanvas.shift('S'); break; case 4: myCanvas.shift('E'); break; case 5: myCanvas.zoom('I'); break; case 6: myCanvas.zoom('O'); break; case 7: myCanvas.stop(); myCanvas.init(); myCanvas.repaint(); break; default: } } } } // ************************* MandelCanvas class ************************* class MandelCanvas extends Canvas implements Runnable { int maxColors=12, exponent, tos=0; // tos = top of stack Color cmap[]; SimpleMandel parent; double xMax, xMin, yMax, yMin, stepx, stepy, bounds; // maxIter must be a multiple of maxColors, otherwise // points having more than "maxIter" iterations will not be black long zoom, maxIter, maxIterLimit; boolean approximationFirst; int[] myStack; private Thread runner = null; MandelCanvas (SimpleMandel parent, int exponent, double bounds, int maxIterLimit, boolean approximationFirst) { this.parent = parent; this.exponent = exponent; this.bounds = bounds; this.maxIterLimit = maxIterLimit; this.approximationFirst = approximationFirst; myStack = new int[SimpleMandel.maxcol * SimpleMandel.maxrow]; // theoretically the stack needs to be 4*maxcol*maxrow entries big, // but we compute areas smaller than 4 by 4 directly, so this is sufficient cmap = new Color[maxColors]; cmap[0] = Color.black; cmap[1] = Color.red; cmap[2] = Color.green; cmap[3] = Color.blue; cmap[4] = Color.cyan; cmap[5] = Color.magenta; cmap[6] = Color.yellow; cmap[7] = Color.pink; cmap[8] = Color.white; cmap[9] = Color.orange; cmap[10] = Color.lightGray; cmap[11] = Color.gray; } void setExponent (int _exponent) { this.exponent = _exponent; } int getExponent() { return(exponent); } void toggleApproximationFirst (Label approxLabel) { this.approximationFirst = ! this.approximationFirst; approxLabel.setText("Approximation: " + (approximationFirst ? "On" : "Off")); } public void start() { if (runner == null) { runner = new Thread(this); runner.start(); } } public void stop() { if ((runner != null) && runner.isAlive()) runner.stop(); runner = null; tos = 0; } void plot (int x, int y, int color_index, Graphics g) { g.setColor(cmap[color_index % maxColors]); g.drawLine(x,y, x,y); } public void init() { if (exponent != 2) { xMax = 2.0; xMin = -2.0; yMax = 1.5; yMin = -1.5; } else { xMax = 0.75; xMin = -2.25; yMax = 1.125; yMin = -1.125; } zoom = 1; setZoom(zoom); } void push (int x, int y, int w, int h) { myStack[tos++] = x; myStack[tos++] = y; myStack[tos++] = w; myStack[tos++] = h; } public void paint (Graphics g) { if (tos == 0) push(0, 0, this.getSize().width, this.getSize().height); start(); } int iterate (double x, double y, long maxIter) { int i=0, loop; double zre = x, zim = y, tmp; double dre = zre * zre, dim = zim * zim; double zre1 = zre, zim1 = zim; // exponents 2 and 3 are special-cased for slightly better performance if (exponent == 2) { while ((dre + dim <= 4.0) && (i < maxIter)) { // (re + im)^2 = re^2 + 2*re*im + im^2 zim = 2.0*zre*zim + y; zre = dre - dim + x; dre = zre * zre; dim = zim * zim; i++; } } else if (exponent == 3) { while ((dre + dim <= bounds) && (i < maxIter)) { // (re + im)^3 = re^3 + 3*re^2*im + 3*re*im^2 + im^3 zim = 3.0*dre*zim - dim*zim + y; zre = dre*zre - 3.0*zre*dim + x; dre = zre * zre; dim = zim * zim; i++; } } else { while ((dre + dim <= bounds) && (i < maxIter)) { for (loop=1; loop> 3; while (tos != tosFront) { x = myStack[tosFront++]; y = myStack[tosFront++]; w = myStack[tosFront++]; h = myStack[tosFront++]; if (((w > 4) || (h > 4)) && (w*h > 16)) { // the second condition is required so we don't run outside the array bounds re = xMin + (x + w/2) * stepx; im = yMin + (y + h/2) * stepy; color = iterate(re, im, approxMaxIter); g.setColor(cmap[(color+2) % maxColors]); g.fillRect(x, y, w, h); push(x, y, w/2, h/2); push(x, y+h/2, w/2, h-h/2); push(x+w/2, y, w-w/2, h/2); push(x+w/2, y+h/2, w-w/2, h-h/2); } } } tos = 0; push(xMem, yMem, wMem, hMem); while (tos != 0) { h = myStack[--tos]; w = myStack[--tos]; y = myStack[--tos]; x = myStack[--tos]; if ((w < 4) || (h < 4)) { for (i=y; i < y + h; i++) for (j=x; j < x + w; j++) { re = xMin + j * stepx; im = yMin + i * stepy; color = iterate(re, im, maxIter); plot(j, i, color, g); } } else { re = xMin + (x + w/2) * stepx; im = yMin + (y + h/2) * stepy; color = iterate(re, im, maxIter); flag = true; for (i=x; i < x + w; i++) { re = xMin + i * stepx; im = yMin + y * stepy; temp = iterate(re, im, maxIter); plot(i, y, temp, g); flag &= (color == temp); re = xMin + i * stepx; im = yMin + (y + h - 1) * stepy; temp = iterate(re, im, maxIter); plot(i, y + h - 1, temp, g); flag &= (color == temp); } for (i=y; i < y + h; i++) { re = xMin + x * stepx; im = yMin + i * stepy; temp = iterate(re, im, maxIter); plot(x, i, temp, g); flag &= (color == temp); re = xMin + (x + w - 1) * stepx; im = yMin + i * stepy; temp = iterate(re, im, maxIter); plot(x + w - 1, i, temp, g); flag &= (color == temp); } if (flag) { if (color == maxIter) g.setColor(cmap[0]); else g.setColor(cmap[color % maxColors]); g.fillRect(x, y, w, h); } else { dh = 1 - (h - 2 * (h/2)); dw = 1 - (w - 2 * (w/2)); push(x+w/2, y+h/2, w/2-dw, h/2-dh); push(x+w/2, y+1, w/2-dw, h/2-1); push(x+1, y+h/2, w/2-1, h/2-dh); push(x+1, y+1, w/2-1, h/2-1); } } } parent.getToolkit().sync(); runner = null; } void newArea (int x, int y, int w, int h) { // System.out.println("x="+x+" y="+y); // System.out.println("w="+w+" h="+h); stop(); push(x, y, w, h); repaint(x, y, w, h); } void setZoom (long zoom) { maxIter = 120 * zoom; long tmp = zoom>>2; while ((tmp >>= 2) > 0) maxIter >>= 1; while (maxIter > maxIterLimit) maxIter >>= 1; parent.zoom.setText("Zoom : " + zoom); } public void zoom (char where) { // where='I' is zoom in, 'O' is zoom out double xshift = (xMax - xMin) / 4.0; double yshift = (yMax - yMin) / 4.0; where = Character.toUpperCase(where); if (where == 'I') { xMin += xshift; xMax -= xshift; yMin += yshift; yMax -= yshift; zoom <<= 1; } else if ((where == 'O') && (zoom > 1)) { xMin -= 2 * xshift; xMax += 2 * xshift; yMin -= 2 * yshift; yMax += 2 * yshift; zoom >>= 1; } setZoom(zoom); newArea(0, 0, this.getSize().width, this.getSize().height); } public void shift (char direction) { double xshift = (xMax - xMin) / 4.0; double yshift = (yMax - yMin) / 4.0; int w = this.getSize().width; int h = this.getSize().height; switch (direction) { case 'N': case 'n': yMin -= yshift; yMax -= yshift; if ((runner != null) && runner.isAlive()) { newArea(0, 0, w, h); } else { this.getGraphics().copyArea(0, 0, w, 3*(h>>2), 0, h>>2); newArea(0, 0, w, h>>2); } break; case 'S': case 's': yMin += yshift; yMax += yshift; if ((runner != null) && runner.isAlive()) { newArea(0, 0, w, h); } else { this.getGraphics().copyArea(0, h>>2, w, 3*(h>>2), 0, -(h>>2)); newArea(0, 3*(h>>2), w, h>>2); } break; case 'E': case 'e': xMin += xshift; xMax += xshift; if ((runner != null) && runner.isAlive()) { newArea(0, 0, w, h); } else { this.getGraphics().copyArea(w>>2, 0, 3*(w>>2), h, -(w>>2), 0); newArea(3*(w>>2), 0, w>>2, h); } break; case 'W': case 'w': xMin -= xshift; xMax -= xshift; if ((runner != null) && runner.isAlive()) { newArea(0, 0, w, h); } else { this.getGraphics().copyArea(0, 0, 3*(w>>2), h, w>>2, 0); newArea(0, 0, w>>2, h); } break; default: } } }