Mandelbrot java source code

Hobby
A zoom sequence illustrating the set of complex numbers termed the Mandelbrot set.
Source image: https://hu.wikipedia.org/wiki/F%C3%A1jl:Mandelbrot_zoom_1.gif

Interesting facts

The basic theory on the iteration of complex functions was developed by Gaston Julia and Pierre Fatou in the 1910s. The extraordinarily intricate form of sets related to these iterations was revealed at the time when computer graphics were sufficiently advanced. The first images of the somewhat crude set by Robert Brooks and Peter Matelski date from 1978.

Benoit Mandelbrot studied the space of quadratic polynomial parameters in an article that appeared in 1980 and sparked global interest in it.

The rigorous mathematical study of this set really began with the work of mathematicians Adrien Douady and John H. Hubbard, who demonstrated many of its fundamental properties and named the set after Mandelbrot. Among other properties, they proved that it is a related set and formulated the MLC conjecture, which formulates the belief that the Mandelbrot set is locally related.

It was not until the first digital computers appeared that this fractal could be visualized in all its complexity.

In the series detailed below we can see how the definition of the fractal is improving, as we increase the number of iterations. The points that converge to a certain value appear pale yellow, and belong to the Mandelbrot’s set. The points that present divergence to infinity have been colored with a chromatic range that goes from gray to black, depending on the number of necessary iterations (algorithm of the escape velocity). The fewer iterations required to diverge to infinity, the darker the color is applied.

About program

The displays of the Mandelbrot set can be calculated in the following way: Take a point (a, b) in the xy plane. Set (x, y) = (a, b) . Do the calculation (x, y) = (a 2 – b 2 + a, 2xy + b) repeatedly until one of two things happens; or (1) the point (x, y) is more than 2 units away from (0.0); or (2) the number of times you have done the calculation is equal to some established upper limit. The number of times you perform the calculation is called the number of iterations , and the upper limit of the number of iterations is called MaxIterations in the program. In case (1), the starting point (a, b) is definitely not in the Mandelbrot Set; in case (2), the starting point (a, b) is possibly in the Mandlebrot set (but increasing the maximum number of iterations could show that (a, b) is not actually in the set). This type of calculation cannot prove that a point is in the Mandelbrot Set, since that would require doing an infinite number of iterations and verifying that (x, y) never moves more than two units away from (0.0).

In the program, each pixel in an image is colored as follows: The image represents a rectangle in the xy plane bounded by xmin on the left, xmax on the right, ymin at the bottom and ymax at the top. Take (a, b) as the point in the center of the pixel and use (a, b)as the starting point in the calculation described in the previous paragraph. If case (1) applies, then the pixel is assigned a color that depends on the number of iterations that were performed. The colors used for such points are called a “palette”; you can modify the palette using the “Show Palette Editor” and “Apply Default Palette” commands on the “Control” menu. If case (2) is applied, the pixel is assigned a color representing a point possibly in the Mandelbrot set; by default, this color is black, but the color can be set with the “Mandelbrot color” submenu on the “Control” menu. Note that increasing the MaxIteration count can convert pixels from case (2) to case (1), i.e.

The “interesting” regions of the xy plane are those along the border of the Mandelbrot complex, between black and colored areas. The goal of the program is to find an interesting region and make its structure visible – and beautiful – with well-chosen colors. You can zoom in on a point along the boundary and you will always find more structure. To zoom in, click and drag the mouse to enclose a rectangular area. When you release the mouse, the inside of the rectangle will expand to fill the entire image. (The mouse can also be used for other functions; see the “Help …” command on the “Tools” menu for more information). As you get closer, you’ll need to zoom in on MaxIterations, and you’ll want to use the Palette Editor to adjust the colors.

After calculating the image as described here, the program will perform a second pass in which it calculates the iteration counts in additional points. The data for these additional points are averaged with the data from the first pass. This can give a smoother and more attractive image. You can disable this behavior, if you wish, using the “Enable Sub-Pixel Sampling” command on the “Control” menu.

If you get too close, the program will switch to “High Precision” calculation. Numbers that are normally used have only about 18 digits of precision. You can easily zoom in to the point where more digits are required. High-precision calculation allows any number of digits, but it takes much longer than normal precision (mainly because normal precision numbers are implemented very efficiently in hardware).

The program has a “File” menu that can be used to save images created by the program. You can also save and load “Mandelbrot Configuration” files, which are small files containing specifications of all the data needed to reconstruct an image.

The program also has the ability to distribute its calculations to a group of networked computers; more information about this can be found below.

Source code

/*
Copyright (c) 2011, Tom Van Cutsem, Vrije Universiteit Brussel
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the Vrije Universiteit Brussel nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL Vrije Universiteit Brussel BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/
import java.awt.Canvas;
import java.awt.Frame;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;

/**
 * Demo of using Fork/Join parallelism to speed up the rendering of the
 * Mandelbrot fractal. The fractal is shown centered around the origin
 * of the Complex plane with x and y coordinates in the interval [-2, 2].
 * 
 * @author tvcutsem
 */
public class Mandelbrot extends Canvas {
  
  // size of fractal in pixels (HEIGHT X HEIGHT)
  private static final int HEIGHT = 1024;
  // how long to test for orbit divergence
  private static final int NUM_ITERATIONS = 50;
  // maximum grid size to process sequentially
  private static final int SEQ_CUTOFF = 64;

  private int colorscheme[];
  
  // 2-dimensional array of colors stored in packed ARGB format
  private int[] fractal;
  
  private int height;
  private boolean optDrawGrid;
  private Image img;
  private String msg;
  
  private ForkJoinPool fjPool = new ForkJoinPool();

  /**
   * Construct a new Mandelbrot canvas.
   * The constructor will calculate the fractal (either sequentially
   * or in parallel), then store the result in an {@link java.awt.Image}
   * for faster drawing in its {@link #paint(Graphics)} method.
   * 
   * @param height the size of the fractal (height x height pixels).
   * @param optParallel if true, render in parallel
   * @param optDrawGrid if true, render the grid of leaf task pixel areas
   */
  public Mandelbrot(int height, boolean optParallel, boolean optDrawGrid) {
    this.optDrawGrid = optDrawGrid;
    
    this.colorscheme = new int[NUM_ITERATIONS+1];
    // fill array with color palette going from Red over Green to Blue
    int scale = (255 * 2) / NUM_ITERATIONS;

    // going from Red to Green
    for (int i = 0; i < (NUM_ITERATIONS/2); i++)
      //               Alpha=255  | Red                   | Green       | Blue=0
      colorscheme[i] = 0xFF << 24 | (255 - i*scale) << 16 | i*scale << 8;

    // going from Green to Blue
    for (int i = 0; i < (NUM_ITERATIONS/2); i++)
      //                         Alpha=255 | Red=0 | Green              | Blue
      colorscheme[i+NUM_ITERATIONS/2] = 0xFF000000 | (255-i*scale) << 8 | i*scale;

    // convergence color
    colorscheme[NUM_ITERATIONS] = 0xFF0000FF; // Blue
    
    this.height = height;
    // fractal[x][y] = fractal[x + height*y]
    this.fractal = new int[height*height];

    long start = System.currentTimeMillis();    
    if (optParallel) {
      // parallel calculation through Fork/Join
      fjPool.invoke(new FractalTask(0, 0, height));
    } else {
      // sequential calculation by the main Thread
      calcMandelBrot(0, 0, height, height);
    }
    long end = System.currentTimeMillis();
    msg = (optParallel ? "parallel" : "sequential") +
          " done in " + (end - start) + "ms.";
    
    this.img = getImageFromArray(fractal, height, height);
  }
  
  /**
   * Draws part of the mandelbrot fractal.
   * 
   * This method calculates the colors of pixels in the square:
   * 
   * (srcx, srcy)           (srcx+size, srcy)
   *      +--------------------------+
   *      |                          |
   *      |                          |
   *      |                          |
   *      +--------------------------+
   * (srcx, srcy+size)      (srcx+size, srcy + size)
   */
  private void calcMandelBrot(int srcx, int srcy, int size, int height) {
    double x, y, t, cx, cy;
    int k;
    
    // loop over specified rectangle grid
    for (int px = srcx; px < srcx + size; px++) {
      for (int py = srcy; py < srcy + size; py++) {
        x=0; y=0;
        // convert pixels into complex coordinates between (-2, 2)
        cx = (px * 4.0) / height - 2;
        cy = 2 - (py * 4.0) / height;
        // test for divergence
        for (k = 0; k < NUM_ITERATIONS; k++) {
          t = x*x-y*y+cx;
          y = 2*x*y+cy;
          x = t;
          if (x*x+y*y > 4) break;
        }
        
        fractal[px + height * py] = colorscheme[k];
      }
    }
    
    // paint grid boundaries
    if (optDrawGrid) {
      drawGrid(srcx, srcy, size, Color.BLACK.getRGB());      
    }
  }
  
  /**
   * Draw the rectangular outline of the grid to show the
   * subdivision of the canvas into grids processed in parallel.
   */
  private void drawGrid(int x, int y, int size, int color) {
    for (int i = 0; i < size; i++) {
      fractal[x+i + height * (y       )] = color;
      fractal[x+i + height * (y+size-1)] = color;
    }
    for (int j = 0; j < size; j++) {
      fractal[x          + height * (y+j)] = color;
      fractal[x + size-1 + height * (y+j)] = color;
    }
  }
  
  /**
   * Divide the grid into four equally-sized subgrids until they
   * are small enough to be drawn sequentially.
   */
  private class FractalTask extends RecursiveAction {
    final int srcx;
    final int srcy;
    final int size;
    public FractalTask(int sx, int sy, int siz) {
      srcx = sx; srcy = sy; size = siz;
    }
    @Override
    protected void compute() {
      if (size < SEQ_CUTOFF) {
        calcMandelBrot(srcx, srcy, size, height);
      } else {
        FractalTask ul = new FractalTask(srcx,        srcy,        size/2);
        FractalTask ur = new FractalTask(srcx+size/2, srcy,        size/2);
        FractalTask ll = new FractalTask(srcx,        srcy+size/2, size/2);
        FractalTask lr = new FractalTask(srcx+size/2, srcy+size/2, size/2);
        // forks and immediately joins the four subtasks
        invokeAll(ul, ur, ll, lr);
      }
    }
  }
  
  @Override
  public void paint(Graphics g) {
    // draw the fractal from the stored image
    g.drawImage(this.img, 0, 0, null);
    // draw the message text in the lower-right-hand corner
    byte[] data = this.msg.getBytes();
    g.drawBytes(
      data,
      0,
      data.length,
      getWidth() - (data.length)*8,
      getHeight() - 20);
  }
  
  /**
   * Auxiliary function that converts an array of pixels into a BufferedImage.
   * This is used to be able to quickly draw the fractal onto the canvas using
   * native code, instead of us having to manually plot each pixel to the canvas.
   */
  private static Image getImageFromArray(int[] pixels, int width, int height) {
    // RGBdefault expects 0x__RRGGBB packed pixels
    ColorModel cm = DirectColorModel.getRGBdefault();
    SampleModel sampleModel = cm.createCompatibleSampleModel(width, height);
    DataBuffer db = new DataBufferInt(pixels, height, 0);
    WritableRaster raster = Raster.createWritableRaster(sampleModel, db, null);
    BufferedImage image = new BufferedImage(cm, raster, false, null);
    return image;
  }
  
  /**
   * Supported command-line options:
   *  -p : render in parallel (default: sequential)
   *  -g : draw grid of pixels drawn by leaf tasks (default: off)
   */
  public static void main(String args[]) {
    boolean optParallel = false;
    boolean optDrawGrid = false;
    for (int i = 0; i < args.length; i++) {
      if (args[i].equals("-p")) {
        optParallel = true;
      } else if (args[i].equals("-g")) {
        optDrawGrid = true;
      } else {
        System.err.println("unknown option: " + args[i]);
      }
    }
    
    Frame f = new Frame();
    Mandelbrot canvas = new Mandelbrot(HEIGHT, optParallel, optDrawGrid);
    f.setSize(HEIGHT, HEIGHT);
    f.add(canvas);
    f.addWindowListener(new WindowAdapter() {
       public void windowClosing(WindowEvent e) {
          System.exit(0);
       }
    });
    f.setVisible(true);
  }
}

The Applet Source Code

import java.applet.Applet;
import java.awt.*;

public final class Mandel extends Applet {
  private static final int MAX = 32;

  public void paint(Graphics g) {
    Dimension size = getSize();
    for (int y = 0; y < size.height; y++) {
      for (int x = 0; x < size.width; x++) {
        // squeeze (x, y) to the proper interval
        double dx = 2.5 * x / size.width - 2.0;
        double dy = 1.25 - 2.5 * y / size.height;
        int value = mandel(dx, dy); // compute value
        value = value * 255 / MAX; // stretch to 0-255
        g.setColor(new Color(value, value, value));
        g.drawLine(x, y, x, y); // draw point
      }
    }
  }

  private int mandel(double px, double py) {
    int value = 0;
    double zx = 0.0, zy = 0.0, zx2 = 0.0, zy2 = 0.0;
    while (value < MAX && zx2 + zy2 < 4.0) {
      zy = 2.0 * zx * zy + py;
      zx = zx2 - zy2 + px;
      zx2 = zx * zx;
      zy2 = zy * zy;
      value++;
    }
    return MAX - value;
  }
}

Computation of a value for each point P (complex number) (good values for P are in ([-2, 0.5], [-1.25, 1.25]), MAX is a number of allowed iterations):

 Z[0] := 0
  value := 0
  WHILE |Z[value]| < 2 and value < MAX DO
    Z[value + 1] := Z[value] ^ 2 + P
    value := value + 1;
  END

Rewritten into an optimized java routine the code looks this way:

int mandel(double px, double py) {
    double zx = 0.0, zy = 0.0;
    double zx2 = 0.0, zy2 = 0.0;
    int value = 0;
    while (value < MAX && zx2 + zy2 < 4.0) {
      zy = 2.0 * zx * zy + py;
      zx = zx2 - zy2 + px;
      zx2 = zx * zx;
      zy2 = zy * zy;
      value++;
    }
    return value;
  }

Notes:
Because there is no interaction (mouse, keyboard, …), the color palette is spread evenly to all values (0 to MAX).

Drawbacks of the implementation:
The drawn image is not chached and is recomputed each time the paint event occurs.

Zoomable Interactive Applet

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;

public final class Mandel2 extends Applet implements MouseListener {
  private int max = 64;
  private Color[] colors = new Color[48];
  private double viewX = 0.0;
  private double viewY = 0.0;
  private double zoom = 1.0;
  private int mouseX;
  private int mouseY;

  public void init() {
    addMouseListener(this);
    for (int i = 0; i < colors.length; i++) {
      int c = 2 * i * 256 / colors.length;
      if (c > 255)
        c = 511 - c;
      colors[i] = new Color(c, c, c);
    }
  }

  public void paint(Graphics g) {
    Dimension size = getSize();
    for (int y = 0; y < size.height; y++) {
      for (int x = 0; x < size.width; x++) {
        double r = zoom / Math.min(size.width, size.height);
        double dx = 2.5 * (x * r + viewX) - 2.0;
        double dy = 1.25 - 2.5 * (y * r + viewY);
        int value = mandel(dx, dy);
        g.setColor(colors[value % colors.length]);
        g.drawLine(x, y, x, y);
      }
    }
  }

  public void update(Graphics g) {
    paint(g);
  }

  private int mandel(double px, double py) {
    double zx = 0.0, zy = 0.0;
    double zx2 = 0.0, zy2 = 0.0;
    int value = 0;
    while (value < max && zx2 + zy2 < 4.0) {
      zy = 2.0 * zx * zy + py;
      zx = zx2 - zy2 + px;
      zx2 = zx * zx;
      zy2 = zy * zy;
      value++;
    }
    return value == max ? 0 : value;
  }

  public void mousePressed(MouseEvent e) {
    mouseX = e.getX();
    mouseY = e.getY();
  }

  public void mouseReleased(MouseEvent e) {
    int x = e.getX();
    int y = e.getY();
    if ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0) {
      if (x != mouseX && y != mouseY) {
        int w = getSize().width;
        int h = getSize().height;
        viewX += zoom * Math.min(x, mouseX) / Math.min(w, h);
        viewY += zoom * Math.min(y, mouseY) / Math.min(w, h);
        zoom *= Math.max((double)Math.abs(x - mouseX) / w, (double)Math.abs(y - mouseY) / h);
      }
    }
    else if ((e.getModifiers() & InputEvent.BUTTON3_MASK) != 0) {
      max += max / 4;
    }
    else {
      max = 64;
      viewX = viewY = 0.0;
      zoom = 1.0;
    }
    repaint();
  }

  public void mouseClicked(MouseEvent e) {}
  public void mouseEntered(MouseEvent e) {}
  public void mouseExited(MouseEvent e) {}
}

Controls:

  • Dragging while pressing left mouse button – zooming into the area
  • Right mouse button – increasing the number of iterations (preciseness)
  • Middle mouse button – resetting to the initial state

Notes:
The color distribution is changed and each number of iterations has its own color from the palette (the coloring is independent of zooming and the maximum number of iterations). The selected area is not shown graphically, because repainting an interactive rectangle without recomputing the image is complicated.

Drawbacks of the implementation:
The drawn image is not cached and is recomputed each time the paint event occurs.
The drawing is performed the in paint() method and blocks the AWT thread untill the image is completed. This complicates interaction that is also controlled by the AWT thread.
Running an applet as a Java application
To be able to run any applet independently of a web browser, just add the following main() method into the class source:

public static void main(String[] args) {
    Frame frame = new Frame("Fractal Viewer");
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
    Applet applet = new Mandel6();
    frame.add(applet, BorderLayout.CENTER);
    frame.setSize(301, 301);
    frame.show();
    applet.init();
    applet.start();
  }

The application can be now run both as an applet using web browser and as an application using: “java ClassName“.

admin