Mandelbrot Set II

Over a year ago, I published my first Mandelbrot Set viewer, a Python program using pygame. Since then, I have published a rather short program highlighting errors that can occur when calculating the set (Mandelbrot Set Miscalculations).
Since my first viewer was in Python, which is an interpreted programming language, and I wanted to make my viewer faster, I decided to write one in Java. I was hoping for a speed increase since Java is compiled and thus should run at higher speeds. I was not disappointed. The new Java-based viewer runs noticeably faster and additionally I added a lot of new features, all listed below.

Controls

  • Left-clicking and dragging draws a zoom frame, single left-clicking removes the frame
  • Right clicking (and optionally dragging) moves the zoom frame
  • Space zooms into the zoom frame
  • F1 moves one step back the zoom history
  • F2 shows the path a complex number at the cursor’s position follows when the function is iteratively applied
  • F3 shows the \mathbb{R} and \mathbb{R}i axis
  • F4 displays the current cursor’s corresponding complex number
  • F5 toggles between showing and hiding the menu (text in the left upper corner describing the viewer’s functions and current states)
  • F6 increments the exponent (going from f_c(z)=z^2+c to f_c(z)=z^5+c in whole-number steps)
  • F7 toggles between the Mandelbrot set and the filled Julia set
  • F8 toggles between previewing a small filled Julia set at the cursor’s position based upon the cursor’s complex number
  • F9 completely resets the zoom and zoom history
  • F11 (or F) toggles between fullscreen and windowed mode
  • F12 quits the application
  • L increases the color depth (starting at 256 and increasing in steps of 256)
  • Q saves the current image to disk

To use this application, you can either trust me and download the .jar-file or view the source code listed below, verify it and compile the program yourself.
The program will start in fullscreen mode, to change to windowed mode, just press F11 (as listed above).

The standard Mandelbrot setA Filled Julia setA Mandelbrot set using the fourth powerA deeper zoom into a fourth power Mandelbrot setA filled Julia set of the fourth power


// Java Code
// Jonathan Frech 14th of September, 2016
//         edited 15th of September, 2016
//         edited 16th of September, 2016
//         edited 17th of September, 2016
//         edited 18th of September, 2016
//         edited 19th of September, 2016
//         edited 23rd of September, 2016
//         edited 24th of September, 2016
//         edited 26th of September, 2016
//         edited 27th of September, 2016
//         edited 28th of September, 2016
//         edited 29th of September, 2016
//         edited 30th of September, 2016
//         edited  1st of October  , 2016
//         edited  2nd of October  , 2016
//         edited  3rd of October  , 2016
//         edited  4th of October  , 2016
//         edited 21st of November , 2016
//         edited 23rd of November , 2016
//         edited 14th of December , 2016
//         edited 13th of January  , 2017

// import
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.io.File;

// J-Utilities
class jutil {
    // map x from a to b to be y from c to d
    static double map   (double x, double a, double b, double c, double d) { return        c+(d-c)*((x-a)/(b-a)) ; }
    static int    mapint(double x, double a, double b, double c, double d) { return (int) (c+(d-c)*((x-a)/(b-a))); }

    // get a color int from red, green and blue
    static int color(int r, int g, int b) { return ((r << 16) | (g << 8) | b); }

    // get a color object from color int
    static Color color(int c) {
        int r = (c                       ) >> 16;
        int g = (c - (r << 16)           ) >>  8;
        int b = (c - (r << 16) - (g << 8))      ;
        return new Color(r, g, b);
    }

    // display a list of strings as a block
    static void textblock(Graphics2D g, String[] strs) {
        for (int s = 0; s < strs.length; s++) {
            g.drawString(strs[s], 5, 15 + 20 * s);
        }
    }
}

// main
public class Main {
    // version
    private String version = "Mandelbrot Set";

    // dimensions
    private int width;
    private int height;
    private boolean fullscreen = true;

    // JFrame
    private JFrame frame;

    // keys
    int key_quit       = KeyEvent.VK_F12;
    int key_switch     = KeyEvent.VK_F7;
    int key_fullscreen = KeyEvent.VK_F11;
    int key_fullscreen2 = KeyEvent.VK_F;
    int key_save       = KeyEvent.VK_Q;

    // canvas and label
    private ImageIcon canvas;
    private JLabel label;

    // fractals
    private Fractal fractal_mandelbrot;
    private Fractal fractal_julia;
    private Fractal[] fractals;
    private int fractalid;

    // refresh img
    void refresh(BufferedImage img) {
        canvas.setImage(img);
        frame.repaint();
    }

    // save img
    private void save() {
        String path = "./img/";

        // generate 'out' directory
        File dir = new File(path);
        if (!dir.exists()) {
            dir.mkdir();
        }
        try {
            String name = path + "img" + dir.listFiles().length + " [" + fractal().getname() + "]" + ".png";
            File f = new File(name);
            ImageIO.write(fractal().getfimg(), "PNG", f);
        }
        catch(IOException e) {}
    }

    // return current fractal
    private Fractal fractal() {
        return fractals[fractalid];
    }

    // restart the frame
    private void restart() {
        // clear old frame
        frame.dispose();

        // determine size
        if (!fullscreen) {
            width = 1080;
            height = 720;
        } else {
            width  = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();
            height = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();
        }

        // init frame
        frame = new JFrame(version);
        frame.setResizable(false);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());

        if (!fullscreen) {
            frame.setUndecorated(false);
            frame.setLocationRelativeTo(null);
        } else {
            frame.setUndecorated(true);
            frame.setLocation(0, 0);
        }

        // other
        canvas = new ImageIcon();
        label = new JLabel(canvas);
        frame.getContentPane().add(label);
        fractal_mandelbrot = new Fractal("mandelbrot", width, height, this);
        fractal_julia      = new Fractal("julia"     , width, height, this);

        fractals = new Fractal[] {fractal_mandelbrot, fractal_julia};
        fractalid = 0;
        fractal().initzoom();

        // listeners
        initlisteners();

        // final
        frame.setSize(width, height);
        frame.setVisible(true);
        frame.pack();
    }

    // init
    private Main() {
        frame = new JFrame(version);
        restart();
    }

    // add listeners to JFrame
    private void initlisteners() {
        // keys
        frame.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                // quit
                if (e.getKeyCode() == key_quit) {
                    frame.dispose();
                }

                // switch screen
                else if(e.getKeyCode() == key_switch) {
                    // old fractal
                    Fractal f = fractal();

                    // new fractal
                    fractalid = (fractalid+1)%2;
                    fractal().setexp(f.getexp());

                    fractal().setcolordepth(f.getcolordepth());
                    fractal().setcolormode(f.getcolormode());
                    fractal().initcolors();

                    // refresh
                    if (fractal().isfractal("julia")) {
                        fractal().setCD(f.cursorposcomplexC(), f.cursorposcomplexD());
                        fractal().initzoom();
                    } else {
                        fractal().newfractal();
                        fractal().refresh();
                    }


                }

                // change fullscreen
                else if (e.getKeyCode() == key_fullscreen || e.getKeyCode() == key_fullscreen2) {
                    fullscreen = !fullscreen;
                    restart();
                }

                // save
                else if (e.getKeyCode() == key_save) {
                    save();
                }

                // handle key by screen
                else {
                    fractal().keyPressed(e);
                }
            }
        });

        // mouse listeners are added to content pane so that they are not relative to
        // the top left window corner, but the top left content pane corner!

        // mouse dragment / movement
        frame.getContentPane().addMouseMotionListener(new MouseInputAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                fractal().mouseDragged(e);
            }

            @Override
            public void mouseMoved(MouseEvent e) {
                fractal().mouseMoved(e);
            }

        });

        // mouse click
        frame.getContentPane().addMouseListener(new MouseInputAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                fractal().mousePressed(e);
            }

        });
    }

    // main main
    public static void main(String[] args) {
        new Main();
    }
}

// fractal class
class Fractal {
    // parent
    private Main parent;

    // type
    private String type;

    // dimensions
    private int width, height;

    // box, fixed box and cursor position
    private int b1x;
    private int b1y;
    private int b2x;
    private int b2y;
    private double fixed_b1x;
    private double fixed_b1y;
    private double fixed_b2x;
    private double fixed_b2y;
    private int cursorx;
    private int cursory;

    // complex numbers (p for position)
    // define the the upper left and lower right corners
    private double p1y;
    private double p2y;
    private double p1x;
    private double p2x;

    // standard zoom
    private double std_p1y;
    private double std_p2y;
    private double std_p1x;
    private double std_p2x;

    // color
    private int[] colors;
    private int colordepth = 256;
    private int textcolor;
    private String colormode = "b&w";

    // history
    private List<double[]> history;

    // keys
    private int key_zoom        = KeyEvent.VK_SPACE;
    private int key_historyback = KeyEvent.VK_F1;
    private int key_zoomreset   = KeyEvent.VK_F9;
    private int key_show_path   = KeyEvent.VK_F2;
    private int key_show_axes   = KeyEvent.VK_F3;
    private int key_show_number = KeyEvent.VK_F4;
    private int key_show_menu   = KeyEvent.VK_F5;
    private int key_show_julia  = KeyEvent.VK_F8;

    private int key_increment_exp = KeyEvent.VK_F6;
    private int key_increment_colordepth = KeyEvent.VK_L;
    private int key_switch_colormode = KeyEvent.VK_C;

    // f_c(z) == z**exp+c
    private int exp = new int[] {2, 3, 4, 5}[0];
    private boolean show_path = false;
    private boolean show_axes = false;
    private boolean show_number = false;
    private boolean show_menu = true;
    private boolean show_julia = false;

    // julia set
    private double C = 0;
    private double D = 0;

    // main image, fractal image
    private BufferedImage img;
    private BufferedImage fimg;

    // constructor
    Fractal(String type, int width, int height, Main parent) {
        // parent
        this.parent = parent;

        // dimensions
        this.width = width;
        this.height = height;

        // type
        this.type = type;

        img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

        // colors and dimensions
        initcolors();
    }

    // update fimg variables
    void newfractal() {
        fimg = fractalimg(type, width, height, p1x, p1y, p2x, p2y, C, D);
    }

    // get fractal's type
    boolean isfractal(String t) {
        return type.equals(t);
    }

    // set C and D (only relevant for julia)
    void setCD(double c, double d) {
        C = c;
        D = d;
    }

    int getcolordepth() {
        return colordepth;
    }
    String getcolormode() {
        return colormode;
    }
    void setcolordepth(int depth) {
        colordepth = depth;
    }
    void setcolormode(String mode) {
        colormode = mode;
    }

    // get C and D from current mouse position
    double cursorposcomplexC() {
        return jutil.map(cursorx, 0, width, p1x, p2x);
    }
    double cursorposcomplexD() {
        return -jutil.map(cursory, 0, height, p1y, p2y);
    }

    // get / set exponent
    int getexp() {
        return exp;
    }
    void setexp(int e) {
        exp = e;
    }

    // return fractal image
    BufferedImage getfimg() {
        return fimg;
    }

    // return current fractal's name
    String getname() {
        return type + (isfractal("julia") ? ("(c=" + C + (D<0 ? "":"+") + D + "i)"):"") + ",tl=" + p1x + (p1y<0 ? "":"+") + p1y + "i,br=" + p2x + (p2y<0 ? "":"+") + p2y + "i";
    }

    // listeners
    void keyPressed  (KeyEvent   e) {
        // move back in time
        if (e.getKeyCode() == key_historyback) {
            if (history.size() > 0) {
                p1x = history.get(history.size()-1)[0];
                p1y = history.get(history.size()-1)[1];
                p2x = history.get(history.size()-1)[2];
                p2y = history.get(history.size()-1)[3];
                history.remove(history.size()-1);
                newfractal();
            }
        }

        // zoom
        else if (e.getKeyCode() == key_zoomreset) {initzoom();}
        else if (e.getKeyCode() == key_zoom     ) {zoom();}

        // show
        else if (e.getKeyCode() == key_show_path  ) {show_path   = !show_path;  }
        else if (e.getKeyCode() == key_show_axes  ) {show_axes   = !show_axes;  }
        else if (e.getKeyCode() == key_show_number) {show_number = !show_number;}
        else if (e.getKeyCode() == key_show_menu  ) {show_menu   = !show_menu;  }
        else if (e.getKeyCode() == key_show_julia && type.equals("mandelbrot")) {show_julia  = !show_julia; }

        else if (e.getKeyCode() == key_increment_colordepth) {
            colordepth += 256;
            initcolors();
            newfractal();
        }

        else if (e.getKeyCode() == key_switch_colormode) {
            if (colormode.equals("random")) {
                colormode = "b&w";
            }
            else if (colormode.equals("b&w")) {
                colormode = "b|w";
            }

            else {
                colormode = "random";
            }
            initcolors();
            newfractal();
        }

        // increment exponent
        if (e.getKeyCode() == key_increment_exp && type.equals("mandelbrot")) {
            exp++;
            if (exp > 5) {exp = 2;}
            newfractal();
            refresh();
        }

        // refresh
        refresh();
    }

    // handle mouse dragged
    void mouseDragged(MouseEvent e) {
        // move box point two
        if (SwingUtilities.isLeftMouseButton(e)) {
            b2x = e.getX();
            b2y = e.getY();
            fixbox();
        }

        // center box
        else if (SwingUtilities.isRightMouseButton(e)) {
            centerbox(e.getX(), e.getY());
        }

        // update cursor pos
        cursorx = e.getX();
        cursory = e.getY();

        // refresh
        refresh();
    }

    // handle mouse moved
    void mouseMoved  (MouseEvent e) {
        // update cursor pos
        cursorx = e.getX();
        cursory = e.getY();

        // refresh
        refresh();
    }

    // handle mouse pressed
    void mousePressed(MouseEvent e) {
        // reset box
        if (SwingUtilities.isLeftMouseButton(e)) {
            b1x = e.getX();
            b1y = e.getY();
            b2x = e.getX();
            b2y = e.getY();
        }

        // center box
        else if (SwingUtilities.isRightMouseButton(e)) {
            centerbox(e.getX(), e.getY());
        }

        refresh();
    }

    // initialize colors
    void initcolors() {
        // pseudo-randomly generated colors
        if (colormode.equals("random")) {
            colors = new int[colordepth];

            int r = 255 / 2;

            int g = 255 / 2;
            int b = 255 / 2;
            colors[0] = jutil.color(r, g, b);
            int a;

            for (int i = 1; i < colordepth; i++) {
                a = (int) (Math.random()*4);
                if (a == 0) {
                    r += (((int) (Math.random() * 2)) * 2 - 1) * 10;
                } else if (a == 1) {
                    g += (((int) (Math.random() * 2)) * 2 - 1) * 10;
                } else if (a == 2) {
                    b += (((int) (Math.random() * 2)) * 2 - 1) * 10;
                }

                if (r < 0) r = 0;
                if (r > 255) r = 255;
                if (g < 0) g = 0;
                if (g > 255) g = 255;
                if (b < 0) b = 0;
                if (b > 255) b = 255;

                colors[i] = jutil.color(r, g, b);
            }

            colors[colors.length - 1] = 0;

            textcolor = jutil.color(255, 255, 255);
        }

        else if (colormode.equals("b&w")) {
            colors = new int[colordepth];
            int k = 0;
            for (int i = 0; i < colordepth; i++) {
                colors[i] = jutil.color(k, k, k);
                k++;
                if (k > 255) {
                    k = 0;
                }
            }

            textcolor = jutil.color(255, 0, 0);
        }

        else if (colormode.equals("b|w")) {
            colors = new int[colordepth];
            for (int i = 0; i < colordepth-1; i++) {
                colors[i] = jutil.color(0, 0, 0);
            }
            colors[colordepth-1] = jutil.color(255, 255, 255);

            textcolor = jutil.color(255, 0, 0);
        }
    }

    // set zoom to default
    void initzoom() {
        // reset history
        history = new ArrayList<>();

        // standard zoom
        std_p1y = -2;
        std_p2y =  2;
        std_p1x = -(std_p2y-std_p1y)/2.*width/height;
        std_p2x =  (std_p2y-std_p1y)/2.*width/height;

        // reset complex numbers
        p1x = std_p1x;
        p2x = std_p2x;
        p1y = std_p1y;
        p2y = std_p2y;

        // refresh
        newfractal();
        refresh();
    }

    // center the box on (X, Y)
    private void centerbox(int X, int Y) {
        // make sure box has correct ratio
        fixbox();

        // center
        int x = (int) ((fixed_b2x-fixed_b1x)/2);
        int y = (int) ((fixed_b2y-fixed_b1y)/2);
        fixed_b1x = X-x;
        fixed_b1y = Y-y;
        fixed_b2x = X+x;
        fixed_b2y = Y+y;
    }

    // fix box's ratio
    private void fixbox() {
        // correct ratio
        fixed_b1x = b1x;
        fixed_b1y = b1y;
        fixed_b2x = b2x;
        fixed_b2y = b2y;

        // make sure 1x/1y are top left and 2x/2y are bottom right
        double t;
        if (fixed_b1x > fixed_b2x) {t = fixed_b1x;fixed_b1x = fixed_b2x;fixed_b2x = t;}
        if (fixed_b1y > fixed_b2y) {t = fixed_b1y;fixed_b1y = fixed_b2y;fixed_b2y = t;}

        // determine height based on current ratio, current width and ideal ratio
        fixed_b2y = fixed_b1y+(int) (1.*(fixed_b2x-fixed_b1x)*height/width);
    }

    // check if box's size is 0 in any dimension
    private boolean validbox() { return (b1x != b2x && b1y != b2y); }

    // convert cursor positions into complex numbers
    private void zoom() {
        // you can only zoom with a valid box
        if (validbox()) {
            // put old zoom in history
            double[] h = {p1x, p1y, p2x, p2y};
            history.add(h);

            // turn cursor pos into complex numbers
            double x1 = fixed_b1x / width;
            double y1 = fixed_b1y / height;
            double x2 = fixed_b2x / width;
            double y2 = fixed_b2y / height;
            double x = p2x - p1x;
            double y = p2y - p1y;
            double _1x = x1 * x + p1x;
            double _1y = y1 * y + p1y;
            double _2x = x2 * x + p1x;
            double _2y = y2 * y + p1y;

            // update
            p1x = _1x;
            p1y = _1y;
            p2x = _2x;
            p2y = _2y;

            // update fractal image
            newfractal();
        }
    }

    // refresh the JLabel's image
    void refresh() {
        Graphics2D g = img.createGraphics();

        //g.setFont(new Font("Courier", Font.PLAIN, 20));
        g.setColor(jutil.color(textcolor));

        // draw fractal image
        g.drawImage(fimg, null, 0, 0);

        // draw julia set
        if (show_julia && type.equals("mandelbrot")) {
            g.drawImage(juliaimg(jutil.map(cursorx, 0, width, p1x, p2x), -jutil.map(cursory, 0, height, p1y, p2y), (int) (width * .1), (int) (height * .1), std_p1x, std_p1y, std_p2x, std_p2y), null, cursorx, cursory);
        }

        // draw box
        if (validbox()) {
            /*g.setStroke(new BasicStroke(3, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{9}, 0));
            g.drawLine(b1x, b1y, b2x, b1y);
            g.drawLine(b1x, b1y, b1x, b2y);
            g.drawLine(b2x, b2y, b2x, b1y);
            g.drawLine(b2x, b2y, b1x, b2y);*/

            g.setStroke(new BasicStroke());
            g.drawLine((int) fixed_b1x, (int) fixed_b1y, (int) fixed_b2x, (int) fixed_b1y);
            g.drawLine((int) fixed_b1x, (int) fixed_b1y, (int) fixed_b1x, (int) fixed_b2y);
            g.drawLine((int) fixed_b2x, (int) fixed_b2y, (int) fixed_b2x, (int) fixed_b1y);
            g.drawLine((int) fixed_b2x, (int) fixed_b2y, (int) fixed_b1x, (int) fixed_b2y);

        }

        // MANDELBROT / JULIA SET CALCULATIONS
        // on the JFrame y gets higher going down, on the complex plane it is the opposite
        double a =   jutil.map(cursorx, 0, width , p1x, p2x);
        double b =  -jutil.map(cursory, 0, height, p1y, p2y);
        double c =   jutil.map(cursorx, 0, width , p1x, p2x);
        double d =  -jutil.map(cursory, 0, height, p1y, p2y);

        // draw the number's path
        // last position
        int lx = cursorx;
        int ly = cursory;
        int px;
        int py;

        int i = 0;

        // Z and C
        if (type.equals("mandelbrot")) {
            a = 0;
            b = 0;
        } else {
            c = C;
            d = D;
        }
        double asqr = a*a;
        double bsqr = b*b;

        // test for abs(Z) <= 2
        while (asqr+bsqr <= 4.0) {
            // Z -> Z**exp + C
            if (exp == 2) {
                b = 2*a*b+d;
                a = asqr-bsqr+c;
            }
            else if (exp == 3) {
                b = 3*asqr*b-bsqr*b+d;
                a = asqr*a-3*a*bsqr+c;
            }
            else if (exp == 4) {
                b = 4*asqr*a*b-4*a*bsqr*b+d;
                a = asqr*asqr-6*asqr*bsqr+bsqr*bsqr+c;
            }
            else if (exp == 5) {
                b = 5*asqr*asqr*b-10*asqr*bsqr*b+bsqr*bsqr*b+d;
                a = asqr*asqr*a-10*asqr*a*bsqr+5*a*bsqr*bsqr+c;
            }

            // do not do squaring calculations too often, use variables instead
            asqr = a*a;
            bsqr = b*b;

            // draw line
            if (show_path) {
                px = (int) jutil.map(a, p1x, p2x, 0, width);

                py = (int) jutil.map(-b, p1y, p2y, 0, height);
                g.drawLine(lx, ly, px, py);
                lx = px;
                ly = py;
            }

            // one more iteration needed
            i++;

            // too many iterations needed, close enough to being inside the set
            if (i > 254) {
                break;
            }
        }

        // axes
        if (show_axes) {
            g.drawLine(jutil.mapint( 0, p1x, p2x, 0, width), jutil.mapint( 10, p1y, p2y, 0, height), jutil.mapint(  0, p1x, p2x, 0, width), (int) jutil.map( -10, p1y, p2y, 0, height));
            g.drawLine(jutil.mapint(10, p1x, p2x, 0, width), jutil.mapint(  0, p1y, p2y, 0, height), jutil.mapint(-10, p1x, p2x, 0, width), (int) jutil.map(   0, p1y, p2y, 0, height));
            g.drawLine(jutil.mapint(-2, p1x, p2x, 0, width), jutil.mapint(.01, p1y, p2y, 0, height), jutil.mapint( -2, p1x, p2x, 0, width), (int) jutil.map(-.01, p1y, p2y, 0, height));
            g.drawLine(jutil.mapint( 2, p1x, p2x, 0, width), jutil.mapint(.01, p1y, p2y, 0, height), jutil.mapint(  2, p1x, p2x, 0, width), (int) jutil.map(-.01, p1y, p2y, 0, height));
        }

        // draw current complex number
        if (show_number) {
            g.drawString(c + (d<0 ? " - ":" + ") + (d<0 ? -d:d) + "i" + (i<255 ? " (outside)":" (inside)"), cursorx + 10, cursory - 5);
        }

        // draw menu
        if (show_menu) {
            String[] strs = {};
            // mandelbrot set
            if (isfractal("mandelbrot")) {
                strs = new String[] {
                        "f_c(z) = z^" + exp + " + c; " + type,
                        "tl = " + p1x + (p1y<0 ? "":"+") + p1y + "i",
                        "br = " + p2x + (p2y<0 ? "":"+") + p2y + "i",
                        "",
                        "decrement history length [" + KeyEvent.getKeyText(key_historyback) + " back]: " + history.size(),
                        "show path [" + KeyEvent.getKeyText(key_show_path) + "]: " + show_path,
                        "show axes [" + KeyEvent.getKeyText(key_show_axes) + "]: " + show_axes,
                        "show number [" + KeyEvent.getKeyText(key_show_number) + "]: " + show_number,
                        "show menu [" + KeyEvent.getKeyText(key_show_menu) + "]",
                        "",
                        "increment exponent [" + KeyEvent.getKeyText(key_increment_exp) + "]: " + exp,
                        "switch fractal [" + KeyEvent.getKeyText(parent.key_switch) + "]: " + type,
                        "show julia [" + KeyEvent.getKeyText(key_show_julia) + "]: " + show_julia,
                        "increment color depth [" + KeyEvent.getKeyText(key_increment_colordepth) + "]: " + colordepth,
                        "switch color mode [" + KeyEvent.getKeyText(key_switch_colormode) + "]: " + colormode,
                        "",
                        "save image [" + KeyEvent.getKeyText(parent.key_save) + "]",
                        "zoom reset [" + KeyEvent.getKeyText(key_zoomreset) + "]",
                        "swap fullscreen [" + KeyEvent.getKeyText(parent.key_fullscreen) + "/" +  KeyEvent.getKeyText(parent.key_fullscreen2) + "], resolution (" + width + "x" + height + ")",
                        "",
                        "quit [" + KeyEvent.getKeyText(parent.key_quit) + "]"
                };
            }

            // filled julia set
            else {
                strs = new String[] {
                        "f_c(z) = z^" + exp + " + c; " + type + "; c = " + C + (D<0 ? "":"+") + D + "i",
                        "tl = " + p1x + (p1y<0 ? "":"+") + p1y + "i",
                        "br = " + p2x + (p2y<0 ? "":"+") + p2y + "i",
                        "",
                        "decrement history length [" + KeyEvent.getKeyText(key_historyback) + " back]: " + history.size(),
                        "show path [" + KeyEvent.getKeyText(key_show_path) + "]: " + show_path,
                        "show axes [" + KeyEvent.getKeyText(key_show_axes) + "]: " + show_axes,
                        "show number [" + KeyEvent.getKeyText(key_show_number) + "]: " + show_number,
                        "show menu [" + KeyEvent.getKeyText(key_show_menu) + "]",
                        "",
                        "//",
                        "switch fractal [" + KeyEvent.getKeyText(parent.key_switch) + "]: " + type,
                        "//",
                        "increment color depth [" + KeyEvent.getKeyText(key_increment_colordepth) + "]: " + colordepth,
                        "switch color mode [" + KeyEvent.getKeyText(key_switch_colormode) + "]: " + colormode,
                        "",
                        "save image [" + KeyEvent.getKeyText(parent.key_save) + "]",
                        "zoom reset [" + KeyEvent.getKeyText(key_zoomreset) + "]",
                        "swap fullscreen [" + KeyEvent.getKeyText(parent.key_fullscreen) + "/" +  KeyEvent.getKeyText(parent.key_fullscreen2) + "], resolution (" + width + "x"+ height + ")",
                        "",
                        "quit [" + KeyEvent.getKeyText(parent.key_quit) + "]"
                };
            }

            jutil.textblock(g, strs);
        }

        // destroy graphics object
        g.dispose();

        // refresh jframe
        parent.refresh(img);
    }

    // generate filled julia set image (used for preview)
    private BufferedImage juliaimg(double C, double D, int width, int height, double p1x, double p1y, double p2x, double p2y) {
        return fractalimg("julia", width, height, p1x, p1y, p2x, p2y, C, D);
    }

    // generate the full-sized fractal image
    private BufferedImage fractalimg(String type, int width, int height, double p1x, double p1y, double p2x, double p2y, double C, double D) {
        // init image
        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

        // map pixel coordinate to complex number
        double facx = (p2x-p1x)/ width;
        double facy = (p2y-p1y)/height;
        double addx = p1x;
        double addy = p1y;

        // Z, a**2, b**2, C, iterations needed
        double a, b, asqr, bsqr, c, d;
        int i;

        // go through the whole pixel space
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                // iterations
                i = 0;
                if (type.equals("mandelbrot")) {
                    i--;
                }
                a = 0;
                b = 0;
                c = C;
                d = D;

                // Z, a**2 and b**2
                if (type.equals("mandelbrot")) {
                    c =   x*facx+addx;
                    d = -(y*facy+addy);
                }
                else if (type.equals("julia")) {
                    a =   x*facx+addx;
                    b = -(y*facy+addy);
                }
                asqr = a*a;
                bsqr = b*b;

                // test for abs(Z) <= 2
                while (asqr+bsqr <= 4.0) {
                    // Z -> Z**exp + C
                    if (exp == 2) {
                        b = 2*a*b+d;
                        a = asqr-bsqr+c;
                    }
                    else if (exp == 3) {
                        b = 3*asqr*b-bsqr*b+d;
                        a = asqr*a-3*a*bsqr+c;
                    }
                    else if (exp == 4) {
                        b = 4*asqr*a*b-4*a*bsqr*b+d;
                        a = asqr*asqr-6*asqr*bsqr+bsqr*bsqr+c;
                    }
                    else if (exp == 5) {
                        b = 5*asqr*asqr*b-10*asqr*bsqr*b+bsqr*bsqr*b+d;
                        a = asqr*asqr*a-10*asqr*a*bsqr+5*a*bsqr*bsqr+c;
                    }

                    // do not do squaring calculations too often, use variables instead
                    asqr = a*a;
                    bsqr = b*b;

                    // one more iteration needed
                    i++;

                    // too many iterations needed, close enough to being inside the set
                    if (i >= colordepth-1) {
                        break;
                    }
                }
                // set the pixel
                img.setRGB(x, y, colors[i]);
            }
        }

        // return the image
        return img;
    }
}
Advertisements

One thought on “Mandelbrot Set II

  1. Pingback: Multibrot Set – J-Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s