// Mandelbrot generator // // Author Matthew Caryl // Created 28.3.97 // // Although under copywrite to the author (Matthew Caryl) this code can be copied and modified for non-commercial // purposes as long as any derivatives contain this condition. package Mandelbrot; import java.awt.Color; import java.awt.Graphics; import java.awt.Panel; import java.awt.Image; import java.awt.Event; import java.awt.Font; import java.awt.FontMetrics; import java.awt.TextField; import java.awt.Label; import java.awt.Rectangle; import java.awt.FlowLayout; import java.awt.GridLayout; import java.awt.BorderLayout; import java.awt.image.ColorModel; import java.awt.image.MemoryImageSource; import java.applet.Applet; import java.lang.String; import java.lang.Float; import java.lang.Math; import java.lang.Thread; public final class Generator extends Applet implements Runnable { // // Private interface // private static final Color backgroundColor = Color.white; private static final Color textColor = Color.black; private static final Color crossColor = Color.yellow; private static final String EMPTY = new String(); private static final String WAIT_MANDEL = new String("Wait for Mandelbrot Set"); private static final String COMPLETE = new String("% complete"); private static final String PICK_POINT = new String("Select point on Mandelbrot Set"); private static final String[] LABELS = {"Minimum x", "Minimum y", "Maximum x", "Maximum y"}; private static final int LOWER_X = 0; private static final int LOWER_Y = 1; private static final int UPPER_X = 2; private static final int UPPER_Y = 3; private static final int MESSAGE_DELAY_TIME = 1000; private static final int GENERATE_MANDEL = 0; private static final int GENERATE_JULIA = 1; private static final int NO_WORK = 2; private Thread animation; private Graphics graphics; private Panel controls; private int state; private Font font = new Font("Times", Font.PLAIN, 10); private Rectangle mandelRect; private Image mandelPicture; private String mandelMessage = EMPTY; private TextField[] mandelFields; private float[] mandelLimits = {-0.6f, -1.0f, 1.5f, 1.0f}; private Rectangle juliaRect; private Image juliaPicture; private String juliaMessage = EMPTY; private TextField[] juliaFields; private float[] juliaLimits = {-1.5f, -1.0f, 1.5f, 1.0f}; private boolean juliaSelected; private float juliaX, juliaY; // // Public interface // // ready applet public void init() { setBackground(backgroundColor); setFont(font); // create mandelbrot control panel Panel mandelControls = new Panel(); mandelControls.setLayout(new GridLayout(2, 2, 5, 5)); mandelFields = new TextField[mandelLimits.length]; for (int i = 0; i < mandelLimits.length; i++) { mandelFields[i] = new TextField(Float.toString(mandelLimits[i])); mandelFields[i].setEditable(true); Label l = new Label(LABELS[i]); l.setAlignment(Label.RIGHT); mandelControls.add(l); mandelControls.add(mandelFields[i]); } Panel mandelAlign = new Panel(); mandelAlign.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0)); mandelAlign.add(mandelControls); // create julia control panel Panel juliaControls = new Panel(); juliaControls.setLayout(new GridLayout(2, 2, 5, 5)); juliaFields = new TextField[juliaLimits.length]; for (int i = 0; i < juliaLimits.length; i++) { juliaFields[i] = new TextField(Float.toString(juliaLimits[i])); juliaFields[i].setEditable(true); Label l = new Label(LABELS[i]); l.setAlignment(Label.RIGHT); juliaControls.add(l); juliaControls.add(juliaFields[i]); } Panel juliaAlign = new Panel(); juliaAlign.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0)); juliaAlign.add(juliaControls); // put both panel at bottom of applet controls = new Panel(); controls.setLayout(new GridLayout(1, 2, 1, 1)); controls.add(mandelAlign); controls.add(juliaAlign); this.setLayout(new BorderLayout()); this.add("South", controls); // Save graphics context for later graphics = this.getGraphics(); state = GENERATE_MANDEL; } // stop animation and get rid of graphics public void destroy() { if (animation != null && animation.isAlive()) animation.stop(); if (graphics != null) graphics.dispose(); } // new julia sets or changing limits on either sets public boolean mouseUp(Event e, int x, int y) { if (mandelRect.inside(x, y)) { // new limits for mandelbrot set if (captureLimits(mandelFields, mandelLimits)) { stop(); state = GENERATE_MANDEL; start(); return true; } // new julia set else if (mandelPicture != null) { stop(); juliaSelected = true; float relative_x = (float) (x - mandelRect.x) / (float) mandelRect.width; float relative_y = (float) (y - mandelRect.y) / (float) mandelRect.height; juliaX = mandelLimits[LOWER_X] + (mandelLimits[UPPER_X] - mandelLimits[LOWER_X]) * relative_x; juliaY = mandelLimits[LOWER_Y] + (mandelLimits[UPPER_Y] - mandelLimits[LOWER_Y]) * relative_y; state = GENERATE_JULIA; start(); return true; } else return false; } else if (juliaRect.inside(x, y)) { // new limits for julia set if (mandelPicture != null && juliaSelected && captureLimits(juliaFields, juliaLimits)) { stop(); state = GENERATE_JULIA; start(); return true; } else return false; } else return false; } // calculate image positions and start animation thread public void start() { int width = size().width / 2; int height = size().height - controls.size().height; int size = Math.min(width - 8, height - 8); mandelRect = new Rectangle((width - size) / 2 + 4, (height - size) / 2, size, size); juliaRect = new Rectangle(width + (width - size) / 2 + 4, (height - size) / 2, size, size); if (animation == null) { animation = new Thread(this); animation.start(); } } // stop animation thread public void stop() { if (animation != null && animation.isAlive()) animation.stop(); animation = null; } // either generate mandelbrot or julia set public void run() { switch (state) { case GENERATE_MANDEL: createMandel(); break; case GENERATE_JULIA: createJulia(juliaX, juliaY); break; default: // do nothing break; } } // display images or messages public void paint(Graphics g) { g.clearRect(0, 0, size().width, size().height - controls.size().height); if (mandelPicture != null) { drawPicture(mandelPicture, mandelRect); // work out selected julia set point if (juliaSelected) { float relative_x = (juliaX - mandelLimits[LOWER_X]) / (mandelLimits[UPPER_X] - mandelLimits[LOWER_X]); float relative_y = (juliaY - mandelLimits[LOWER_Y]) / (mandelLimits[UPPER_Y] - mandelLimits[LOWER_Y]); int x = mandelRect.x + (int) (relative_x * mandelRect.width); int y = mandelRect.y + (int) (relative_y * mandelRect.height); if (mandelRect.inside(x, y)) { g.setColor(crossColor); g.drawLine(x, y - 1, x, y + 1); g.drawLine(x - 1, y, x + 1, y); } } } else drawMessage(mandelMessage, mandelRect); if (juliaPicture != null) drawPicture(juliaPicture, juliaRect); else drawMessage(juliaMessage, juliaRect); } // display message centered on area private void drawMessage(String message, Rectangle area) { FontMetrics metrics = graphics.getFontMetrics(graphics.getFont()); int mw = metrics.stringWidth(message); int mh = metrics.getHeight(); int mx = area.x + (area.width - mw) / 2; int my = area.y + (area.height - mh) / 2; graphics.setColor(textColor); graphics.clearRect(area.x, area.y, area.width, area.height); graphics.drawString(message, mx, my); } // display image centered on area private void drawPicture(Image picture, Rectangle area) { int pw = picture.getWidth(this); int ph = picture.getHeight(this); int px = area.x + (area.width - pw) / 2; int py = area.y + (area.height - ph) / 2; graphics.clearRect(area.x, area.y, area.width, area.height); graphics.drawImage(picture, px, py, this); } // capture limiting values private boolean captureLimits(TextField[] fields, float[] limits) { boolean changed = false; for (int i = limits.length - 1; i >= 0; i--) { try { float o = limits[i]; limits[i] = Float.valueOf(fields[i].getText()).floatValue(); changed = changed | (o != limits[i]); } catch (NumberFormatException e) { fields[i].setText(Float.toString(mandelLimits[i])); } } return changed; } // calculate new mandelbrot image private void createMandel() { // prepare display mandelPicture = null; mandelMessage = EMPTY; juliaMessage = WAIT_MANDEL; paint(graphics); // get space for pixel values int w = mandelRect.width; int h = mandelRect.height; int[] pixels = new int[w * h]; int index = w * h - 1; // need for percentage complete messagea long messageTime = System.currentTimeMillis() + MESSAGE_DELAY_TIME; // calculate boundary and step values float low_x = mandelLimits[LOWER_X], high_x = mandelLimits[UPPER_X]; float low_y = mandelLimits[LOWER_Y], high_y = mandelLimits[UPPER_Y]; float dy = (high_y - low_y) / (float) h; float dx = (high_x - low_x) / (float) w; // loop up through y and x values float real_y = high_y; for (int y = h; y > 0; y--, real_y -= dy) { float real_x = high_x; for (int x = w; x > 0; x--, real_x -= dx) { // percentage complete message if (messageTime < System.currentTimeMillis()) { int percentage = (int) (100f - 100f * (float) index / (float) (w * h)); mandelMessage = new String(percentage + COMPLETE); drawMessage(mandelMessage, mandelRect); Thread.yield(); messageTime = System.currentTimeMillis() + MESSAGE_DELAY_TIME; } // start with zn = (0, 0) and no colour set int R = 0, G = 0; float a = 0, b = 0; // check for divergent or simple convergent series for (int s = 0; s < 127; s++) { float oa = a, ob = b; a = oa * oa - ob * ob - real_x; b = 2 * oa * ob - real_y; if (a * a + b * b >= 100) { G = 2 * s; break; } if (a == oa & b == ob) { R = 2 * s; break; } } pixels[index--] = (255 << 24) | (R << 16) | (G << 8) | (0 << 0); } } // make image and redisplay mandelPicture = createImage(new MemoryImageSource(w, h, ColorModel.getRGBdefault(), pixels, 0, w)); juliaMessage = PICK_POINT; state = NO_WORK; paint(graphics); } // calculate new julia set image private void createJulia(float cr, float ci) { // prepare display juliaPicture = null; juliaMessage = EMPTY; paint(graphics); // get space for pixels int w = juliaRect.width; int h = juliaRect.height; int[] pixels = new int[w * h]; int index = w * h - 1; // need for percentage complete message long messageTime = System.currentTimeMillis() + MESSAGE_DELAY_TIME; // calculate boundary and step values float low_x = juliaLimits[LOWER_X], high_x = juliaLimits[UPPER_X]; float low_y = juliaLimits[LOWER_Y], high_y = juliaLimits[UPPER_Y]; float dy = (high_y - low_y) / (float) h; float dx = (high_x - low_x) / (float) w; // loop up through y and x values float real_y = high_y; for (int y = h; y > 0; y--, real_y -= dy) { float real_x = high_x; for (int x = w; x > 0; x--, real_x -= dx) { // percentage complete message if (messageTime < System.currentTimeMillis()) { int percentage = (int) (100f - 100f * (float) index / (float) (w * h)); juliaMessage = new String(percentage + COMPLETE); drawMessage(juliaMessage, juliaRect); Thread.yield(); messageTime = System.currentTimeMillis() + MESSAGE_DELAY_TIME; } // start with zn = (real_x, real_y) and no colour set int R = 0, G = 0; float a = real_x, b = real_y; // check for divergent or simple convergent series for (int s = 0; s < 127; s++) { float oa = a, ob = b; a = oa * oa - ob * ob - cr; b = 2 * oa * ob - ci; if (a * a + b * b >= 100) { G = 2 * s; break; } if (a == oa & b == ob) { R = 2 * s; break; } } pixels[index--] = (255 << 24) | (R << 16) | (G << 8) | (0 << 0); } } // make image and redisplay juliaPicture = createImage(new MemoryImageSource(w, h, ColorModel.getRGBdefault(), pixels, 0, w)); state = NO_WORK; paint(graphics); } }