import java.lang.*; import java.awt.*; import java.awt.event.*; import java.applet.*; import Bitmap; final class MandelbrotCanvas extends Canvas implements KeyListener, MouseListener, MouseMotionListener { private Mandelbrot _mandelbrot; private boolean _fRubberBand; private int _xRubberStart, _yRubberStart, _xRubberEnd, _yRubberEnd; private int _iAscent, _iDescent, _iStringWidth, _xBase, _yBase; MandelbrotCanvas(Mandelbrot mandelbrot) { _mandelbrot = mandelbrot; Font f = new Font("Monospaced", Font.BOLD, 30); setFont(f); FontMetrics fm = getFontMetrics(f); _iAscent = fm.getMaxAscent(); _iDescent = fm.getMaxDescent(); _iStringWidth = fm.stringWidth("100%"); addKeyListener(this); addMouseListener(this); addMouseMotionListener(this); } final public void update(Graphics g) { paint(g); } final public void paint(Graphics g) { Image image = _mandelbrot.getImage(); if (image != null) { g.drawImage(image, 0, 0, this); if (_fRubberBand) { g.setXORMode(Color.white); int x, cx, y, cy; g.drawRect(x = Math.min(_xRubberStart, _xRubberEnd), y = Math.min(_yRubberStart, _yRubberEnd), cx = Math.max(_xRubberStart, _xRubberEnd) - x, cy = Math.max(_yRubberStart, _yRubberEnd) - y); if (cx >= 2 && cy >= 2) { g.drawRect(x + 1, y + 1, cx - 2, cy - 2); } } } else { int iPercentage = _mandelbrot.getPercentage(); Dimension dimension = getSize(); g.clearRect(0, 0, dimension.width, dimension.height); StringBuffer sb = new StringBuffer(); if (iPercentage < 100) sb.append(' '); if (iPercentage < 10) sb.append(' '); sb.append(String.valueOf(iPercentage)).append('%'); g.drawString(sb.toString(), (dimension.width - _iStringWidth) / 2, (dimension.height + _iAscent - _iDescent) / 2); } } final public void repaintPercent() { Dimension dimension = getSize(); int iAscent = _iAscent; repaint((dimension.width - _iStringWidth) / 2, (dimension.height + iAscent - _iDescent) / 2 - iAscent, _iStringWidth, iAscent + _iDescent); } final public void keyPressed(KeyEvent ke) { int iKey; char cChar; if ((iKey = ke.getKeyCode()) == KeyEvent.VK_ESCAPE) { if (_fRubberBand) { _fRubberBand = false; repaint(); } else { _mandelbrot.abort(); } } else if (iKey == KeyEvent.VK_UP) { _mandelbrot.moveWindow(0, 1); } else if (iKey == KeyEvent.VK_DOWN) { _mandelbrot.moveWindow(0, -1); } else if (iKey == KeyEvent.VK_LEFT) { _mandelbrot.moveWindow(-1, 0); } else if (iKey == KeyEvent.VK_RIGHT) { _mandelbrot.moveWindow(1, 0); } else if ((cChar = ke.getKeyChar()) == 'z' || cChar == 'Z') { _mandelbrot.scale(0.5); } else if (cChar == 'p' || cChar == 'P') { _mandelbrot.scale(2); } } final public void keyReleased(KeyEvent ke) { } final public void keyTyped(KeyEvent ke) { } final public void mousePressed(MouseEvent me) { if (_mandelbrot.getImage() != null) { _xRubberStart = _xRubberEnd = me.getX(); _yRubberStart = _yRubberEnd = me.getY(); _fRubberBand = true; } } final public void mouseClicked(MouseEvent me) { } final public void mouseEntered(MouseEvent me) { } final public void mouseExited(MouseEvent me) { } final public void mouseMoved(MouseEvent me) { } final public void mouseDragged(MouseEvent me) { if (_fRubberBand) { int xRubberEnd = _xRubberEnd, yRubberEnd = _yRubberEnd; int x = me.getX(), y = me.getY(); if (x != xRubberEnd || y != yRubberEnd) { _xRubberEnd = x; _yRubberEnd = y; int xMin = Math.min(Math.min(x, xRubberEnd), _xRubberStart), yMin = Math.min(Math.min(y, yRubberEnd), _yRubberStart); repaint(xMin, yMin, Math.max(Math.max(x, xRubberEnd), _xRubberStart) - xMin + 1, Math.max(Math.max(y, yRubberEnd), _yRubberStart) - yMin + 1); } } } final public void mouseReleased(MouseEvent me) { if (_fRubberBand) { _fRubberBand = false; _mandelbrot.clip(_xRubberStart, _yRubberStart, me.getX(), me.getY()); } } final public Dimension getPreferredSize() { return new Dimension(400, 400); } } final public class Mandelbrot extends Applet implements Runnable, ActionListener { private double _adValues[] = { -0.6, 0, 2, 100, 0, 0 }; private TextField _atf[] = new TextField[6]; // center x, center y, radius, max iteration, julia x, julia y; private double _dFactor; private Thread _thread = null; private Dimension _dimension = null; private int _iPercentage = 0; private Image _image = null; private boolean _fJulia = false; private final static double LOG2 = Math.log(2); private final static double _dLimit2 = 1e4 * 1e4, _d2LogLimit = Math.log(1e4) * 2; private String _strGo, _strStop, _strLoad, _strMandelbrot, _strJulia, _strRadius, _strIteration, _strCenter; private MandelbrotCanvas _mandelbrotCanvas; private Button _buttonGoStop, _buttonLoad; private Choice _choiceType; final private TextField createLabelField(Panel panel, String str) { panel.add(new Label(str, Label.RIGHT)); TextField tf = new TextField(); panel.add(tf); return tf; } final private Button createButton(Panel panel, String str) { Button button = new Button(str); panel.add(button); button.addActionListener(this); return button; } final private String getString(String strName) { String strValue = getParameter(strName); return strValue != null ? strValue : strName; } final public void init() { // initialize components; _strGo = getString("Go"); _strStop = getString("Stop"); _strLoad = getString("Load"); _strMandelbrot = getString("Mandelbrot"); _strJulia = getString("Julia"); _strRadius = getString("Radius"); _strIteration = getString("Iteration"); _strCenter = getString("Center"); Color color = new Color(0xd0, 0xd0, 0xd0); setBackground(color); Panel panel = new Panel(); panel.setLayout(new GridLayout(3, 5)); panel.setBackground(color); // first row; _choiceType = new Choice(); _choiceType.addItem(_strMandelbrot); _choiceType.addItem(_strJulia); panel.add(_choiceType); _atf[0] = createLabelField(panel, _strCenter + " x: "); _atf[1] = createLabelField(panel, _strCenter + " y: "); // second row; _buttonGoStop = createButton(panel, _strStop); _atf[2] = createLabelField(panel, _strRadius + ": "); _atf[3] = createLabelField(panel, _strIteration + ": "); // third row; _buttonLoad = createButton(panel, _strLoad); _atf[4] = createLabelField(panel, _strJulia + " x: "); _atf[5] = createLabelField(panel, _strJulia + " y: "); setLayout(new BorderLayout()); add(panel, "North"); add(_mandelbrotCanvas = new MandelbrotCanvas(this), "Center"); } final public void start() { _dimension = _mandelbrotCanvas.getSize(); for (int iIndex = 0; iIndex < 6; iIndex++) { double dValue = _adValues[iIndex]; _atf[iIndex].setText(iIndex == 3 ? String.valueOf((int)dValue) : String.valueOf(dValue)); } calculate(); } final private void calculate() { if (_thread == null) { try { _adValues[3] = Integer.parseInt(_atf[3].getText()); } catch (Exception e) { _atf[3].setText(String.valueOf((int)_adValues[3])); } _fJulia = (_choiceType.getSelectedIndex() != 0); (_thread = new Thread(this)).start(); _buttonGoStop.setLabel(_strStop); _mandelbrotCanvas.requestFocus(); } } final public void stop() { abort(); } final public void run() { int iWidth = _dimension.width, iHeight = _dimension.height; double dFactor; _dFactor = dFactor = _adValues[2] * 2 / Math.max(iWidth, iHeight); _iPercentage = 0; _image = null; _mandelbrotCanvas.repaint(); int iPrevTime = (int)System.currentTimeMillis(); double dXCenter = _adValues[0], dYCenter = _adValues[1], dJuliaX = _adValues[4], dJuliaY = _adValues[5]; int cMaxIteration = (int)_adValues[3]; boolean fJulia = _fJulia; Bitmap bitmap = new Bitmap(iWidth, iHeight); double dYOffset = iHeight * dFactor * -0.5, dXOffsetStart = iWidth * dFactor * -0.5; for (int y = 0; y < iHeight; y++) { double dYCalc = dYCenter + dYOffset; dYOffset += dFactor; double dXOffset = dXOffsetStart; for (int x = 0; x < iWidth; x++) { double dXCalc = dXCenter + dXOffset; dXOffset += dFactor; double dPotential = (fJulia ? getPotential(dXCalc, dYCalc, cMaxIteration, dJuliaX, dJuliaY) : getPotential(dXCalc, dYCalc, cMaxIteration, dXCalc, dYCalc)); if (dPotential < 0) { dPotential = Math.log(-dPotential); bitmap.put(x, y, Color.HSBtoRGB((float)(dPotential - Math.floor(dPotential)), 1, 1)); } } int iNewPercentage = (y + 1) * 100 / iHeight; if (iNewPercentage > _iPercentage) { _iPercentage = iNewPercentage; // The update frequency is 200 milliseconds; int iNewTime = (int)System.currentTimeMillis(); if (iNewTime - iPrevTime > 200) { iPrevTime = iNewTime; _mandelbrotCanvas.repaintPercent(); } } } _image = createImage(bitmap); _mandelbrotCanvas.repaint(); _buttonGoStop.setLabel(_strGo); _thread = null; } final static double getPotential(double dXn, double dYn, int cMaxIteration, double dX0, double dY0) { double dXn2, dYn2; for (int iIteration = 0; iIteration < cMaxIteration; iIteration++) { if ((dXn2 = dXn * dXn) + (dYn2 = dYn * dYn) >= _dLimit2) { // h = 2^-n * log(|Zn|); // k = log(-h / log(limit)); return Math.log(Math.log(dXn2 + dYn2) / _d2LogLimit) - iIteration * LOG2; } dYn = (dXn * dYn) * 2 + dY0; dXn = dXn2 - dYn2 + dX0; } if ((dXn2 = dXn * dXn) + (dYn2 = dYn * dYn) > 4) { return Math.log(Math.log(dXn2 + dYn2) / _d2LogLimit) - cMaxIteration * LOG2; } return 0; } final public void actionPerformed(ActionEvent ae) { String strCommand = ae.getActionCommand(); if (strCommand.equals(_strGo)) { for (int iIndex = 0; iIndex < 6; iIndex++) { if (iIndex != 3) { TextField tf = _atf[iIndex]; String strText = tf.getText(), strValue = String.valueOf(_adValues[iIndex]); if (!strText.equals(strValue)) { try { _adValues[iIndex] = Double.valueOf(strText).doubleValue(); } catch (Exception e) { tf.setText(strValue); } } } } calculate(); } else if (strCommand.equals(_strStop)) { abort(); _mandelbrotCanvas.repaint(); } else if (strCommand.equals(_strLoad)) { for (int iIndex = 4; iIndex <= 5; iIndex++) { _adValues[iIndex] = _adValues[iIndex - 4]; _atf[iIndex].setText(String.valueOf(_adValues[iIndex])); } } } final public void clip(int x0, int y0, int x1, int y1) { int xMin = Math.min(x0, x1), cx = x0 + x1 - xMin - xMin, yMin = Math.min(y0, y1), cy = y0 + y1 - yMin - yMin; if (cx > 0 || cy > 0) { // The bitmap format is bottom-top; int iWidth = _dimension.width, iHeight = _dimension.height; yMin = (iHeight - 1 - (yMin + cy)); _adValues[0] += (xMin + (cx - iWidth) / 2.0) * _dFactor; _adValues[1] += (yMin + (cy - iHeight) / 2.0) * _dFactor; _adValues[2] *= Math.max((double)cx / iWidth, (double)cy / iHeight); for (int iIndex = 0; iIndex <= 2; iIndex++) { _atf[iIndex].setText(String.valueOf(_adValues[iIndex])); } calculate(); } } final public void scale(double dScale) { if (_image != null) { _adValues[2] *= dScale; _atf[2].setText(String.valueOf(_adValues[2])); calculate(); } } final public void moveWindow(double dXFactor, double dYFactor) { if (_image != null) { _adValues[0] += _adValues[2] * dXFactor; _adValues[1] += _adValues[2] * dYFactor; _atf[0].setText(String.valueOf(_adValues[0])); _atf[1].setText(String.valueOf(_adValues[1])); calculate(); } } final public void abort() { if (_thread != null && _thread.isAlive()) { _thread.stop(); } _thread = null; _iPercentage = 0; _buttonGoStop.setLabel(_strGo); _image = null; } final public int getPercentage() { return _iPercentage; } final public Image getImage() { return _image; } }