// Use the MandelbrotSet class. import java.awt.*; import java.util.*; import java.awt.image.*; import java.applet.*; import java.net.*; import java.io.*; public class Fracster extends java.applet.Applet implements Runnable, MandelbrotUser { boolean pause = false; boolean showMarks = true; double xsMarks[]; double ysMarks[]; double dMarks[]; String nMarks[]; String tMarks[]; double factor = 1.5; Thread mainThread; MandelbrotSet mset; String getBase() { String base = getDocumentBase().toString(); if (!base.endsWith("/")) { base += "/"; } return base; } double stringDouble(String p) { return Double.valueOf(p).doubleValue(); } double getDouble(String p, double def) { if (getParameter(p) != null) { return Double.valueOf(getParameter(p)).doubleValue(); } else { return def; } } int getInteger(String p) { if (getParameter(p) != null) { return Integer.valueOf(getParameter(p)).intValue(); } else { return 0; } } String getString(String p) { if (getParameter(p) != null) { return getParameter(p); } else { return ""; } } public void init() { int i; double xs, ys, d; int stepsMax; xs = getDouble("x", -2.0); ys = getDouble("y", -2.0); d = getDouble("d", 4.0); stepsMax = getInteger("i"); if (stepsMax == 0) { stepsMax = 200; } mset = new MandelbrotSet(this, 512, 512, xs, ys, d, stepsMax); source = new MemoryImageSource( mset.w, mset.h, mset.pix, 0, mset.w); int total = 0; Vector data = new Vector(); try { URL url = new URL("http://www.boutell.com/fracster/shared.data"); URLConnection urlc = url.openConnection(); urlc.connect(); DataInputStream in = new DataInputStream( urlc.getInputStream()); try { while (true) { String s = in.readLine(); if (s == null) { break; } data.addElement(s); } } catch (java.io.EOFException e) { } } catch (Exception e) { } total = data.size(); xsMarks = new double[total]; ysMarks = new double[total]; dMarks = new double[total]; nMarks = new String[total]; tMarks = new String[total]; for (i = 0; (i < total); i++) { // x y d name label iterations filename String[] fields = split((String) data.elementAt(i)); xsMarks[i] = stringDouble(fields[0]); ysMarks[i] = stringDouble(fields[1]); dMarks[i] = stringDouble(fields[2]); nMarks[i] = fields[3]; tMarks[i] = fields[4]; } source.setAnimated(true); source.setFullBufferUpdates(true); setImage = createImage(source); // Kick off the main update thread mainThread = new Thread(this); mainThread.start(); } String[] split(String s) { // This is pretty brittle and unforgiving, doesn't // like blank lines or know about comments or // clean up other kinds of whitespace other than tabs int count = 0; int i = 0; while (true) { int p = s.indexOf("\t", i); if (p == -1) { break; } count++; i = p + 1; } count++; String[] fields = new String[count]; i = 0; count = 0; while (true) { int p = s.indexOf("\t", i); if (p == -1) { fields[count] = s.substring(i); break; } fields[count] = s.substring(i, p); count++; i = p + 1; } return fields; } MemoryImageSource source; Image setImage = null; public void MandelbrotUpdated() { source.newPixels(); Graphics g = getGraphics(); g.drawImage(setImage, 0, 0, null); marks(g); labels(g); g.dispose(); } public void MandelbrotFinished() { } public void update(Graphics g) { paint(g); } Font font = new Font("Serif", Font.PLAIN, 12); // Font bfont = new Font("Serif", Font.PLAIN, 18); // Font tfont = new Font("Serif", Font.PLAIN, 8); synchronized public void paint(Graphics g) { // Just copy the offscreen image to the screen. if (setImage != null) { g.drawImage(setImage, 0, 0, null); } marks(g); labels(g); } void marks(Graphics g) { if (!showMarks) { return; } int i; for (i = 0; (i < xsMarks.length); i++) { double mx, my, mw; double cx, cy; mx = (int) ((xsMarks[i] - mset.xs) * mset.w / mset.d); my = (int) ((ysMarks[i] - mset.ys) * mset.w / mset.d); mw = (int) (dMarks[i] / mset.d * mset.w); cx = mx + mw / 2; cy = my + mw / 2; if (mx >= mset.w) { continue; } if (my >= mset.w) { continue; } if (mx + mw < 0) { continue; } if (my + mw < 0) { continue; } g.setColor(Color.white); drawLine(g, cx - 4, cy - 4, cx + 4, cy + 4); drawLine(g, cx + 4, cy - 4, cx - 4, cy + 4); if (mw > 8) { drawRect(g, mx, my, mw, mw); } if (mw > 100) { g.setFont(font); FontMetrics fm = g.getFontMetrics(); g.drawString(nMarks[i], (int) (mx + 4), (int) (my + fm.getAscent())); if (mw > 200) { g.drawString(tMarks[i], (int) (mx + 4), (int) (my + fm.getAscent() + fm.getHeight())); } } } } void drawLine(Graphics g, double x1, double y1, double x2, double y2) { g.drawLine((int) x1, (int) y1, (int) x2, (int) y2); } void drawRect(Graphics g, double x1, double y1, double w, double h) { g.drawRect((int) x1, (int) y1, (int) w, (int) h); } int triangleX[] = new int[3]; int triangleY[] = new int[3]; void labels(Graphics g) { g.setFont(font); FontMetrics fm = g.getFontMetrics(); g.setColor(Color.white); g.fillRect(0, mset.h, mset.w, 32); g.setColor(Color.black); g.drawString("X, Y: " + mset.xs + "," + mset.ys, 0, mset.h + fm.getAscent()); g.drawString("D: " + mset.d, 0, mset.h + fm.getHeight() + fm.getAscent()); g.setColor(Color.blue); g.fillRect(mset.w * 4 / 6, mset.h + 2, mset.w / 7, 28); g.setColor(Color.white); g.setFont(font); if (showMarks) { g.drawString("Hide Labels", mset.w * 2 / 3 + 4, mset.h + 16 + 8); } else { g.drawString("Show Labels", mset.w * 2 / 3 + 4, mset.h + 16 + 8); } g.setColor(Color.blue); g.fillRect(mset.w * 5 / 6, mset.h + 2, mset.w / 7, 28); g.setColor(Color.white); g.setFont(font); g.drawString("Click to Share!", mset.w * 5 / 6 + 2, mset.h + 16 + 8); g.setColor(Color.black); g.setFont(font); g.drawString(String.valueOf(mset.stepsMax), mset.w / 2 + 16, mset.h + 16 + 8); g.setColor(Color.blue); triangleX[0] = mset.w / 2 + 12; triangleY[0] = mset.h + 8; triangleX[1] = mset.w / 2 + 4; triangleY[1] = mset.h + 16; triangleX[2] = mset.w / 2 + 12; triangleY[2] = mset.h + 24; g.fillPolygon(triangleX, triangleY, 3); triangleX[0] = mset.w / 2 + 40 + 4; triangleY[0] = mset.h + 8; triangleX[1] = mset.w / 2 + 40 + 12; triangleY[1] = mset.h + 16; triangleX[2] = mset.w / 2 + 40 + 4; triangleY[2] = mset.h + 24; g.fillPolygon(triangleX, triangleY, 3); } void stopCalculation() { mset.stopRequest = true; while (mset.computing) { try { Thread.currentThread().sleep(20); } catch (Exception e) { } } mset.stopRequest = false; } int mx; int my; boolean down = false; public boolean mouseDown(Event evt, int x, int y) { mx = x; my = y; down = true; updateBounds(x, y); return true; } static final int dragThreshold = 4; int bw; int bh; boolean dragging = false; void updateBounds(int x, int y) { int xd = x - mx; int yd = y - my; int val; if (Math.abs(xd) > Math.abs(yd)) { val = Math.abs(xd); } else { val = Math.abs(yd); } if (xd > 0) { bw = val; } else { bw = -val; } if (yd > 0) { bh = val; } else { bh = -val; } if (Math.abs(bw) < dragThreshold) { dragging = false; } else { dragging = true; } } public boolean mouseDrag(Event evt, int x, int y) { if (!down) { return false; } if (((evt.modifiers & Event.META_MASK) != 0) || ((evt.modifiers & Event.SHIFT_MASK) != 0)) { return false; } updateBounds(x, y); Graphics g = getGraphics(); if (setImage != null) { g.drawImage(setImage, 0, 0, null); } g.setColor(Color.white); int x1, y1, w1, h1; x1 = mx; w1 = bw; if (w1 < 0) { x1 = mx + w1; w1 = -w1; } y1 = my; h1 = bh; if (h1 < 0) { y1 = my + h1; h1 = -h1; } g.drawRect(x1, y1, w1, h1); g.dispose(); return true; } public boolean mouseUp(Event evt, int x, int y) { double newd; boolean changeD = true; down = false; if ((x >= (mset.w * 5 / 6)) && (y >= mset.h)) { try { getAppletContext().showDocument(new java.net.URL("http://www.boutell.com/fracster/share.cgi?share=1&x=" + mset.xs + "&y=" + mset.ys + "&d=" + mset.d + "&i=" + mset.stepsMax)); } catch (Exception e) { } return true; } else if ((x >= (mset.w * 4 / 6)) && (y >= mset.h)) { showMarks = !showMarks; repaint(); return true; } else if ((x >= (mset.w / 2)) && (x < (mset.w / 2) + 12) && (y >= mset.h)) { if (mset.stepsMax > 0) { stopCalculation(); mset.stepsMax -= 100; Graphics g = getGraphics(); labels(g); g.dispose(); mset.newSteps(); changeD = false; } } else if ((x >= (mset.w / 2) + 40) && (x < (mset.w / 2) + 40 + 12) && (y >= mset.h)) { if (mset.stepsMax < 9900) { stopCalculation(); mset.stepsMax += 100; Graphics g = getGraphics(); labels(g); g.dispose(); mset.newSteps(); changeD = false; } } else if (y >= mset.h) { return true; } stopCalculation(); int ndp = Math.abs(bw); if (changeD) { int cx = x, cy = y; if (((evt.modifiers & Event.META_MASK) != 0) || ((evt.modifiers & Event.SHIFT_MASK) != 0)) { newd = mset.d * factor; } else { if (dragging) { newd = mset.d * ndp / mset.w; cx = mx + bw / 2; cy = my + bh / 2; } else { newd = mset.d / factor; } } mset.xs = mset.xs + ((double) cx * mset.d / mset.w) - newd / 2.0; mset.ys = mset.ys + ((double) cy * mset.d / mset.w) - newd / 2.0; mset.d = newd; } mainThread.resume(); return true; } public boolean keyDown(Event evt, int key) { return true; } public boolean keyUp(Event evt, int key) { return true; } public void run() { // One thread from now on. We suspend between updates. int i; mainThread.setPriority(Thread.MIN_PRIORITY); while (true) { mset.renderSet(); mainThread.suspend(); } } public void start() { // This is called by the browser // when the user returns to this // web page, and also once right // after init(). Clear the // pause flag. pause = false; } public void stop() { // This is called by the browser // when the user leaves this // web page. Set the pause flag // to avoid wasting CPU. pause = true; } public void destroy() { // This is called by the browser // when the web page is being // thrown out of cache and the // applet is discarded. It is our // task to stop our thread. mainThread.stop(); } };