Showing posts with label ImageProcessing. Show all posts
Showing posts with label ImageProcessing. Show all posts

Fractal Advanced : Julia Set Advanced (with Java Code)

Color Mapping Techniques

Now that we have learned how to make Julia set fractals we're all set to discover the color mapping algorithms to create different types of images other then the bright red ones.

Note: In all subsequent examples I'll illustrate the drawing of image in a function called drawFractal() because writing the entire code for each type of manipulation will make the post unnecessarily lengthy.

Hue Shift

Continuing from where we last left, the simplest way to generate images with different colors in different areas will be to shift the Hue by some constant value for each pixel. You can also tweak around with the saturation and brightness values to achieve desired outcome
 
public void drawFractal()throws IOException
{
    double WIDTH = 1600;
    double HEIGHT = 1200;
    float Saturation = 0.6f;
    float HUE_SHIFT = 0f;
    BufferedImage img = new BufferedImage((int)WIDTH, (int)HEIGHT,BufferedImage.TYPE_3BYTE_BGR);
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    
    out.print("Re(c): ");
    double cReal = Double.parseDouble(reader.readLine());
    out.print("Im(c): ");
    double cImag = Double.parseDouble(reader.readLine());
    out.print("Hue Shift (0-1): ");
    HUE_SHIFT = Float.parseFloat(reader.readLine());
    
    ComplexNumber constant = new ComplexNumber(cReal,cImag);
    
    int max_iter = 256;
    for(int X=0; X<WIDTH; X++)
    {
        for(int Y=0; Y<HEIGHT; Y++)
        {
            ComplexNumber oldz = new ComplexNumber();
            ComplexNumber newz = new ComplexNumber(2.0*(X-WIDTH/2)/(WIDTH/2), 1.33*(Y-HEIGHT/2)/(HEIGHT/2) );
            int i;
            for(i=0;i<max_iter; i++)
            {
                oldz = newz;
                newz = newz.square();
                newz.add(constant);
                if(newz.mod() > 2)
                    break;
            }
            float Brightness = i < max_iter ? 0.8f : 0;
            // Mapping the number of iterations to a Hue between 0.0 to 1.0
            float Hue = (i%256)/255.0;
            // If the shifted Hue > 1 then we reduce it by 1 because Hue is circular
            if(Hue + HUE_SHIFT > 1)
                {Hue = Hue - 1;Hue += HUE_SHIFT;}
            else
                Hue += HUE_SHIFT;
            // Creating color using shifted Hue
            Color color = Color.getHSBColor(Hue, Saturation, Brightness);
            img.setRGB(X,Y,color.getRGB());
        }
    }
    ImageIO.write(img,"PNG", new File("hue_shift-julia.png"));
}

The following image was created using the above code with, c = -0.8 + 0.156i and a HUE_SHIFT = 0.15

HUE_SHIFT = 0.15
 Mathematical function to Color

Another way of choosing the color for a pixel is to feel the number of iterations in a mathematical function which has a range of [0,1] and hence use the output value as the Hue for creating an HSB color with constant saturation and brightness. Here I illustrate using a modified sine function to get the result. The algorithm is as follows:

i) Take the modulus of the number of iterations taken for the pixel to escape with 256 to bring it in range 0-255.
ii) Feed the value into the function f(x) = |sin (x)|
ii) Use the output of the function as the Hue.

public void drawFractal()throws IOException
{
    double WIDTH = 800;
    double HEIGHT = 600;
    float Saturation = 0.6f;
    BufferedImage img = new BufferedImage((int)WIDTH, (int)HEIGHT,BufferedImage.TYPE_3BYTE_BGR);
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    out.print("Re(c): ");
    double cReal = Double.parseDouble(reader.readLine());
    out.print("Im(c): ");
    double cImag = Double.parseDouble(reader.readLine());
    ComplexNumber constant = new ComplexNumber(cReal,cImag);
    
    int max_iter = 256;
    for(int X=0; X<WIDTH; X++)
    {
        for(int Y=0; Y<HEIGHT; Y++)
        {
            ComplexNumber oldz = new ComplexNumber();
            ComplexNumber newz = new ComplexNumber(2.0*(X-WIDTH/2)/(WIDTH/2), 1.33*(Y-HEIGHT/2)/(HEIGHT/2) );
            int i;
            for(i=0;i<max_iter; i++)
            {
                oldz = newz;
                newz = newz.square();
                newz.add(constant);
                if(newz.mod() > 2)
                    break;
            }
            float Brightness = i < max_iter ? 0.8f : 0;
            double Hue = (i%256);
            Hue = Math.abs(Math.sin(Hue));
            Color color = Color.getHSBColor((float)Hue, Saturation, Brightness);
            img.setRGB(X,Y,color.getRGB());
        }
    }
    ImageIO.write(img,"PNG", new File("sin-julia.png"));
}


Well, this doesn't produce very aesthetic result but the function and color mapping technique can be further tweaked to get one. Here is the output with c = -0.8 + 0.156i:

modified sine function
Histogram Coloring

The earlier methods used the number of iterations taken to escape directly to map the color. In histogram coloring we instead first create a histogram of number of pixels that took a specific number of iterations to escape the radius of 2. Then the colors are mapped in ratio of number of pixels that took the specific iteration number. The algorithm below will clarify the usage:

i) Create an array of size = maximum iterations.
ii) Loop through every pixel of image and for each pixel that took i number of iterations increment the ith element of the array by 1.
iii) This creates a histogram which will now be used to color the image.  
iv) Loop through each pixel once again.
v) For every pixel taking i iterations loop from 0 to i and sum up the fractions of histogram_value of each array element leading to i divided by the total number of pixels.
vi) Use this value as Hue.

public void drawFractal()throws IOException
{
    double WIDTH = 3200;
    double HEIGHT = 2400;
    float Saturation = 0.9f;
    BufferedImage img = new BufferedImage((int)WIDTH, (int)HEIGHT,BufferedImage.TYPE_3BYTE_BGR);
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    out.print("Re(c): ");
    double cReal = Double.parseDouble(reader.readLine());
    out.print("Im(c): ");
    double cImag = Double.parseDouble(reader.readLine());
    
    ComplexNumber constant = new ComplexNumber(cReal,cImag);
    int max_iter = 256;
    // Create an aaray for histogram
    int[] histogram=new int[256];
    for(int X=0; X<WIDTH; X++)
    {
        for(int Y=0; Y<HEIGHT; Y++)
        {
            ComplexNumber oldz = new ComplexNumber();
            ComplexNumber newz = new ComplexNumber(2.0*(X-WIDTH/2)/(WIDTH/2), 1.33*(Y-HEIGHT/2)/(HEIGHT/2) );
            int i;
            for(i=0;i<max_iter; i++)
            {
                oldz = newz;
                newz = newz.square();
                newz.add(constant);
                if(newz.mod() > 2)
                    break;
            }
        if(i == max_iter)
            --i;
        // Increment the ith element by 1 for every pixel that took i number of iterations
        histogram[i]++;
        }
    }
    // Loop through pixels a second time
    for(int X=0; X<WIDTH; X++)
    {
        for(int Y=0; Y<HEIGHT; Y++)
        {
            ComplexNumber oldz = new ComplexNumber();
            ComplexNumber newz = new ComplexNumber(2.0*(X-WIDTH/2)/(WIDTH/2), 1.33*(Y-HEIGHT/2)/(HEIGHT/2) );
            int i;
            for(i=0;i<max_iter; i++)
            {
                oldz = newz;
                newz = newz.square();
                newz.add(constant);
                if(newz.mod() > 2)
                    break;
            }
        if(i == max_iter)
            --i;
        float Brightness = i < max_iter ? 1f : 0;
        double Hue = 0;
        // Loop from 0 to number of iterations this pixel took and sum up all the fractions of histogram_value of each array element leading to i divided by the total number of pixels.
        for (int j = 0; j <= i; j += 1)
            Hue += histogram[j]/(WIDTH*HEIGHT);
        Color color = Color.getHSBColor((float)(Hue), Saturation, Brightness);
        img.setRGB(X,Y,color.getRGB());
        }
    }
    ImageIO.write(img,"PNG", new File("histogram-julia.png"));
}


The following image was created using histogram coloring with c = 0.233 + 0.53780i:

histogram coloring
Gray-scale Color Palette

In this method we first create a 256 gray-scale color palette and then map the number of iterations taken by each pixel to the palette to get the corresponding color. The algorithm of creating the color palette is as follows:

i) Create an array of 256 elements. Loop through each pixel using a loop variable, say n.
ii) The color at each index is given by using the formula: 
    colors[n] = n + (512-512e(-n/50)/3)

public void drawFractal()throws IOException
{
    double WIDTH = 4000;
    double HEIGHT = 3000;
    float Saturation = 1f;
    BufferedImage img = new BufferedImage((int)WIDTH, (int)HEIGHT,BufferedImage.TYPE_3BYTE_BGR);
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    out.print("Re(c): ");
    double cReal = Double.parseDouble(reader.readLine());
    out.print("Im(c): ");
    double cImag = Double.parseDouble(reader.readLine());
    ComplexNumber constant = new ComplexNumber(cReal,cImag);
    int palette[] = new int[256];
    // Making the color palette
    for(int n=0;n<256;n++)
    {
        palette[n]=(int)(n+512-512*Math.exp(-n/50.0)/3.0);
        palette[n]=palette[n]<<16 | palette[n]<<8 | palette[n];
    }
    // The extreme color is black for representation of prisoner set
    palette[255]=0;
    int max_iter = 256;
    for(int X=0; X<WIDTH; X++)
    {
        for(int Y=0; Y<HEIGHT; Y++)
        {
            ComplexNumber oldz = new ComplexNumber();
            ComplexNumber newz = new ComplexNumber(2.0*(X-WIDTH/2)/(WIDTH/2), 1.33*(Y-HEIGHT/2)/(HEIGHT/2) );
            int i;
            for(i=0;i<max_iter; i++)
            {
                oldz = newz;
                newz = newz.square();
                newz.add(constant);
                if(newz.mod() > 2)
                    break;
            }
            float Brightness = i < max_iter ? 1f : 0;
            // Setting the pixel to color from the palette
            img.setRGB(X,Y,palette[i%256]);
        }
    }
    ImageIO.write(img,"PNG", new File("grayscale_palette_julia.png"));
}


The following image was created using the same palette with c = -0.4 + 0.6i:

grayscale palette

Custom Color Mapping

This is one of the most beautiful techniques of mapping a color. In this technique we use two or more colors to create a gradient like color map which is than mapped to the ratio of number of iterations a particular pixel took to the maximum number of iterations.

For generating the color map we are going to use a function makeColorMap(int STEPS, Color ... in_colors) where steps is the number of colors required from the first color to the last color in colors or the size of out_colors array. (Courtesy: Marco13)

/**
* Generates a color map based on the input colors
* @param in_colors the input colors 
* @return an array containing the colors linearly changing from the first input color to the last
*/
public static int[] makeColorMap(int steps, Color ... in_colors)
{
    int colorMap[] = new int[steps];
    if (in_colors.length == 1)
    {
        Arrays.fill(colorMap, in_colors[0].getRGB());
        return colorMap;
    }
    double colorDelta = 1.0 / (in_colors.length - 1);
    for (int i=0; i<steps; i++)
    {
        double globalRel = (double)i / (steps - 1);
        int index0 = (int)(globalRel / colorDelta);
        int index1 = Math.min(in_colors.length-1, index0 + 1);
        double localRel = (globalRel - index0 * colorDelta) / colorDelta;

        Color c0 = in_colors[index0];
        int r0 = c0.getRed();
        int g0 = c0.getGreen();
        int b0 = c0.getBlue();
        int a0 = c0.getAlpha();

        Color c1 = in_colors[index1];
        int r1 = c1.getRed();
        int g1 = c1.getGreen();
        int b1 = c1.getBlue();
        int a1 = c1.getAlpha();

        int dr = r1-r0;
        int dg = g1-g0;
        int db = b1-b0;
        int da = a1-a0;

        int r = (int)(r0 + localRel * dr);
        int g = (int)(g0 + localRel * dg);
        int b = (int)(b0 + localRel * db);
        int a = (int)(a0 + localRel * da);
        int rgb = 
            (a << 24) |
            (r << 16) |
            (g <<  8) |
            (b <<  0);
        colorMap[i] = rgb;
    }
    return colorMap;
}    

public void drawFractal()throws IOException
{
    double WIDTH = 4000;
    double HEIGHT = 3000;
    float Saturation = 1f;
    BufferedImage img = new BufferedImage((int)WIDTH, (int)HEIGHT,BufferedImage.TYPE_3BYTE_BGR);
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    out.print("Re(c): ");
    double cReal = Double.parseDouble(reader.readLine());
    out.print("Im(c): ");
    double cImag = Double.parseDouble(reader.readLine());
    ComplexNumber constant = new ComplexNumber(cReal,cImag);
    // Making a color map based based on the colors #000004, #ffffff, #ff033f and #000000
    int out_colors[] = makeColorMap(1024, new Color(0x000004), Color.WHITE,new Color(0xFF033E),Color.BLACK);
    int max_iter = 256;
    for(int X=0; X<WIDTH; X++)
    {
        for(int Y=0; Y<HEIGHT; Y++)
        {
            ComplexNumber oldz = new ComplexNumber();
            ComplexNumber newz = new ComplexNumber(2.0*(X-WIDTH/2)/(WIDTH/2), 1.33*(Y-HEIGHT/2)/(HEIGHT/2) );
            int i;
            for(i=0;i<max_iter; i++)
            {
                oldz = newz;
                newz = newz.square();
                newz.add(constant);
                if(newz.mod() > 2)
                    break;
            }
            float Brightness = i < max_iter ? 1f : 0;
            // Taking the ratio of number of iterations to max_iter
            double value = (double)i / max_iter;
            double d = Math.max(0.0, Math.min(1.0, value));
            // Multiplying the ratio with the length of the array to get an index
            int index = (int)(d * (out_colors.length - 1));
            // Using the color at the index to write the pixel
            img.setRGB(X,Y,out_colors[index]);
        }
    }
    ImageIO.write(img,"PNG", new File("custom_color_julia.png"));
}


Here is the output from the above snippet:

i) With colors #000004, #ffffff, #ff033f and #000000 and c = -0.8 + 0.156i


ii) With colors #000004, #ffffff,#bfff00, and #000000 and c = -0.4 + 0.6i


Another example,


Cubic and Higher Julia Functions 

Julia set is not just limited to two degree complex equations. Here is an example of a 3 degree complex fractal, f(z) = z3 + C.
 
public void drawFractal()throws IOException
{
    double WIDTH = 2000;
    double HEIGHT = 1500;
    float HUE_SHIFT = 0f;
    float Saturation = 1f;
    BufferedImage img = new BufferedImage((int)WIDTH, (int)HEIGHT,BufferedImage.TYPE_3BYTE_BGR);
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    out.print("Re(c): ");
    double cReal = Double.parseDouble(reader.readLine());
    out.print("Im(c): ");
    double cImag = Double.parseDouble(reader.readLine());
    ComplexNumber constant = new ComplexNumber(cReal,cImag);
    // Making a color map based based on the colors #000060, #FF007F, Yellow and #7B3F00
    int out_colors[] = makeColorMap(1024,new Color(0x000060),new Color(0xFF007F),Color.YELLOW,new Color(0x7B3F00));
    int max_iter = 64;
    for(int X=0; X<WIDTH; X++)
    {
        for(int Y=0; Y<HEIGHT; Y++)
        {
            ComplexNumber oldz = new ComplexNumber();
            ComplexNumber newz = new ComplexNumber(2.0*(X-WIDTH/2)/(WIDTH/2), 1.33*(Y-HEIGHT/2)/(HEIGHT/2) );
            int i;
            for(i=0;i<max_iter; i++)
            {
                oldz = newz;
                // Multiplying the number with its square gives the cube
                newz.multiply(newz.square());
                newz.add(constant);
                if(newz.mod() > 2)
                    break;
            }
            float Brightness = i < max_iter ? 1f : 0;
            double value = (double)i / max_iter;
            double d = Math.max(0.0, Math.min(1.0, value));
            int index = (int)(d * (out_colors.length - 1));
            img.setRGB(X,Y,out_colors[index]);
        }
    }
    ImageIO.write(img,"PNG", new File("julia_cubic.png"));
}


Here are some output images:

i) Z = Z3 + 0.400 + 0i



ii) Z = Z4 + 0.484 + 0i


iii) Z = Z5 + 0.544 + 0i 


Zooming your Fractals

Fractals are repetitive structures so if we zoom out fractals we see that they repeat themselves on very small scales too. On zooming the details are more prominent and beautiful. Zooming the fractal is pretty each. Just define a ZOOM factor and while setting the z0 for each pixel change the formula as follows:

Re(Z0) = 2 * (X-WIDTH/2)/(WIDTH*ZOOM/2)
Im(Z0) = 1.33 * (Y-HEIGHT/2)/(HEIGHT*ZOOM/2), where (x,y) is the co-ordinate of the pixel.

Besides zooming the fractal we can also shift each pixel by a factol (shiftX, shiftY). Here is how:

Re(Z0) = 2 * (X-WIDTH/2)/(WIDTH*ZOOM/2) + shiftX
Im(Z0) = 1.33 * (Y-HEIGHT/2)/(HEIGHT*ZOOM/2) + shiftY, where (x,y) is the co-ordinate of the pixel.
public void drawFractal()throws IOException
{
    double WIDTH = 800;
    double HEIGHT = 600;
    float Saturation = 1f;
    BufferedImage img = new BufferedImage((int)WIDTH, (int)HEIGHT,BufferedImage.TYPE_3BYTE_BGR);
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    out.print("Re(c): ");
    double cReal = Double.parseDouble(reader.readLine());
    out.print("Im(c): ");
    double cImag = Double.parseDouble(reader.readLine());
    out.print("Zoom: ");
    double ZOOM = Double.parseDouble(reader.readLine());
    out.print("X-Shift: ");
    double shiftX = Double.parseDouble(reader.readLine());
    out.print("Y-Shift: ");
    double shiftY = Double.parseDouble(reader.readLine());
    ComplexNumber constant = new ComplexNumber(cReal,cImag);
    int out_colors[] = makeColorMap(1024, new Color(0x000004), Color.WHITE,new Color(0xBFFF00),Color.BLACK);
    int max_iter = 256;
    for(int X=0; X<WIDTH; X++)
    {
        for(int Y=0; Y<HEIGHT; Y++)
        {
            ComplexNumber oldz = new ComplexNumber();
            ComplexNumber newz = new ComplexNumber(2.0*(X-WIDTH/2)/(WIDTH*ZOOM/2) + shiftX, 1.33*(Y-HEIGHT/2)/(HEIGHT*ZOOM/2) + shiftY );
            int i;
            for(i=0;i<max_iter; i++)
            {
                oldz = newz;
                newz = newz.square();
                newz.add(constant);
                if(newz.mod() > 2)
                    break;
            }
            float Brightness = i < max_iter ? 1f : 0;
            double value = (double)i / max_iter;
            double d = Math.max(0.0, Math.min(1.0, value));
            int index = (int)(d * (out_colors.length - 1));
            img.setRGB(X,Y,out_colors[index]);
        }
    }
    ImageIO.write(img,"PNG", new File("julia_zoomed.png"));
}

Here are few outputs:

i) C = -0.8 + 0.156i, ZOOM = 100, X-Shift = 0, Y-Shift = 0


ii) C = -0.65488 - 0.447706i, ZOOM = 1000, X-Shift = 0, Y-Shift = 0

ii) C = 0.233 + 0.5378i, ZOOM = 50, X-Shift = 0.3, Y-Shift = 0.4



References and Further readings:

[1] http://en.wikipedia.org/wiki/Julia_set
[2] http://en.wikipedia.org/wiki/Mandelbrot_set#Computer_drawings
[3] http://spanishplus.tripod.com/maths/FractalBurningShip.htm
[4] http://lodev.org/cgtutor/juliamandelbrot.html
[5] stackoverflow

Fractal Advanced : Julia Set Basics (with Java Code)

Complex Numbers

Complex numbers are one of the most fascinating topics of mathematics. It'll be correct to say that this is a work of pure imagination because the basis of complex numbers are imaginary numbers. Any number (positive or negative) squared is always a positive entity so we cannot take the square root of a negative quantity? Well, apparently you can. Welcome to the world of complex numbers.

A complex number is reported in the form a + ib where i2=-1 hence i (read: iota) is the square root of -1 which isn't actually possible hence the coefficient multiplied with i i.e. b is called the imaginary part of complex number. Since an imaginary number cannot be represented on a real number line we cannot compare them i.e. we cannot say if sqrt(-6) is greater or sqrt(-2). The other coefficient a is called the real part of a complex number and can be represented on a real number line.

A complex number is generally represented by z where z = a + ib. The real part of the complex number is represented as Re(z) and the imaginary part as Im(z) where Re(z) = a and Im(z) = b.

Complex Arithmetic

i) (a + ib) + (c + id) = (a+c) + (b+d)i
ii) (a + ib)(c + id) = (ac-bd) + (ad+bc)i (since i2bd = -bd)

Modulus of a Complex Number

The modulus, magnitude or absolute value of a complex number is the square root of the sum of squares of its real (a) and imaginary (b) parts i.e. |z| = sqrt(a2 + b2).

Conjugate of a Complex Number

The conjugate of a complex number(z) is another complex number (z) with negated imaginary part of z i.e. z = a - ib.
The Complex Conjugate (Source: Wikipedia)

The Complex Plane

The complex plane is a coordinate system where we can represent complex numbers. The real part is represented on the x-axis and the complex part on the y-axis.
z = x + iy
The Complex Plane (Source: Wikipedia)

A Circle in Complex Plane

An equation involving complex numbers is called a complex equation and can represent various geometrical shapes on the complex plane. Any equation of form |z| = c represents a circle with radius equal to c units. Let us analyze the equation by converting it into 2D real plane.

|z| = c in complex plane is equivalent to sqrt(x2 + y2) = c in the real plane. Squaring both sides of the equation we get x2 + y2 = c2 which is indeed the equation of a circle in 2D plane with radius c.

The Prisoner and Escapee Sets

Let there be a complex function f(z) and we feed a complex number z0 into it to get a resultant complex number z1 which is fed again into f to get a z2 and so on. We get a series of complex numbers {z0, z1, z2, z3 ...} which is called the orbit of z0 under f

For an example, let there be a complex function: f(z) = z2 - 0.5, taking z0 as 0 + 0i we get:

z1 = f(0) = -0.5
z2 = f(-0.5) = -0.25
z3 = f(-0.25) = -0.4375
z4 = f(-0.4375) = -0.30859375
z5 = f(-0.30859375) = -0.4047698974609375 and so on.

We see that f(z) never goes very far away from 0 + 0i hence we say that the orbit of z0 = (0+0i) is bounded and hence z0 is a prisoner

In the same function taking z0 as 4 + 0i we get:

z1 = f(4) = 15.5
z2 = f(15.5) = 239.75
z3 = f(239.75) = 57479.5625
z4 = f(57479.5625) = 3303900104.69140625 and so on.

We see that f(z) goes very far away from 4 + 0i on each iteration and hence we say that the orbit of z0 = (4+0i) is unbounded and hence z0 is an escapee

The Julia Set

The Julia set is the boundary between the prisoner set and the escape set. Let us for example take the simplest form of Julia set function f(z) = z2 + c where c is a constant complex number.If the orbit of any complex z0 ever leaves the circle of radius 2 (i.e. exceeds the value 2) then it goes far beyond 2 on further calculations and is kept in the escapee set. If however after a fixed number of calculations (let 256) the orbit does not go beyond 2 we can safely assume that z0 is a prisoner and is kept in prisoner set. Using a computer we can visualize the prisoner and escapee sets with different color tones and we get strikingly marvellous results which are fractals i.e. they repeat themselves even on the smallest scales.

Visualizing the Julia Set (The Algorithm)

We first assume an arbitrary complex number which will be the constant complex number c in the function zn+1 = zn + c. Then for every pixel (x,y) in the image we take a z0 which depends on x and y co-ordinates of the pixel in some way i.e. z0 = f(x,y) the simplest being z0 = x/ImageWidth + (y/ImageHeight)i where (x,y) is the location of the pixel on the image. We define a maximum number of iterations after which we can safely assume that the pixel belongs to prisoner set. Then we feed the z0 of each pixel into the function zn+1 = zn + c till the orbit exceeds the radius of 2 or the number of iterations exceed the maximum iterations. If the iterations stop due to orbit exceeding 2 the pixel is an escapee while if maximum iterations are reached the pixel is a prisoner. The pixel is then mapped to a color palette to generate the image, the simplest mapping being the following:

Hue = f(number of iterations taken to escape the orbit of 2)
Saturation = Constant
Brightness = 0 or 1 (depending on prisoner or escapee respectively)

So, a prisoner is definitely painted black because the Brightness is set to 0 for prisoners irrespective of the Hue.The escapees however have maximum Brightness (1) and Hue being a function of number of iterations taken to exceed 2 they are painted in different hues depending on the function. This gives rise to beautiful fractal designs.

The Java Implementation

First we need an implementation of complex numbers in Java so that we can make our task a bit easier. Here is a very simple implementation of Complex Numbers in Java:

package plane.complex;

/**
 * <code>ComplexNumber</code> is a class which implements complex numbers in Java. 
 * It includes basic operations that can be performed on complex numbers such as,
 * addition, subtraction, multiplication, conjugate, modulus and squaring. 
 *
 * @author      Abdul Fatir
 * @version		1.0
 * 
 */
public class ComplexNumber
{
	/**
	* The real, Re(z), part of the <code>ComplexNumber</code>.
	*/
	private double real;
	/**
	* The imaginary, Im(z), part of the <code>ComplexNumber</code>.
	*/
	private double imaginary;
	/**
	* Constructs a new <code>ComplexNumber</code> object with both real and imaginary parts 0 (z = 0 + 0i).
	*/
	
	public ComplexNumber()
	{
		real = 0.0;
		imaginary = 0.0;
	}
	
	/**
	* Constructs a new <code>ComplexNumber</code> object.
	* @param real the real part, Re(z), of the complex number
	* @param imaginary the imaginary part, Im(z), of the complex number
	*/
	
	public ComplexNumber(double real, double imaginary)
	{
		this.real = real;
		this.imaginary = imaginary;
	}
	
	/**
	* Adds another <code>ComplexNumber</code> to the current complex number.
	* @param complex_number the complex number to be added to the current complex number
	*/
	
	public void add(ComplexNumber complex_number)
	{
		this.real = this.real + complex_number.real;
		this.imaginary = this.imaginary + complex_number.imaginary;
	}
	
	/**
	* The complex conjugate of the current complex number.
	* @return a <code>ComplexNumber</code> object which is the conjugate of the current complex number
	*/
	
	public ComplexNumber conjugate()
	{
		return new ComplexNumber(this.real,-this.imaginary);
	}
	
	/**
	* The modulus, magnitude or the absolute value of current complex number.
	* @return the magnitude or modulus of current complex number
	*/
	
	public double mod()
	{
		return Math.sqrt(Math.pow(this.real,2) + Math.pow(this.imaginary,2));
	}
	
	/**
	* The square of the current complex number.
	* @return a <code>ComplexNumber</code> object which is the square of the current complex number
	*/
	
	public ComplexNumber square()
	{
		double _real = this.real*this.real - this.imaginary*this.imaginary;
		double _imaginary = 2*this.real*this.imaginary;
		return new ComplexNumber(_real,_imaginary);
	}
	
	/**
	* Multiplies another <code>ComplexNumber</code> to the current complex number.
	* @param complex_number the complex number to be multiplied to the current complex number
	*/
	
	public void multiply(ComplexNumber complex_number)
	{
		double _real = this.real*complex_number.real - this.imaginary*complex_number.imaginary;
		double _imaginary = this.real*complex_number.imaginary + this.imaginary*complex_number.real;
		
		this.real = _real;
		this.imaginary = _imaginary;
	}
	
	/**
	* Prints the complex number in x + yi format
	*/
	
	public void print()
	{
		System.out.println(this.real+" + "+this.imaginary+"i");
	}
}

Download from pastebin.

Now that we have complex numbers in Java, we are ready to implement the Julia set in Java. Here's the JuliaFractal.java which is capable of creating Julia set images. The code is explained in comments:

import plane.complex.ComplexNumber;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.image.BufferedImage;
import static java.lang.System.out;

/**
 * <code>JuliaFractal</code> is an executable class which can create Julia set fractals for input constant ComplexNumber.
 * @author      Abdul Fatir
 * @version		1.0
 * 
 */

public class JuliaFractal
{
	public static void main(String args[])throws IOException
	{
		// Taking the Image WIDTH and HEIGHT variables. Increasing or decreasing the value will affect computation time.
		double WIDTH = 1600;
		double HEIGHT = 1200;
		
		// Setting the Saturation of every pixel to maximum
		// This can be played with to get different results
		float Saturation = 1f;
		
		// Creating a new blank RGB Image to draw the fractal on
		BufferedImage img = new BufferedImage((int)WIDTH, (int)HEIGHT,BufferedImage.TYPE_3BYTE_BGR);
		BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
		
		// Getting the constant ComplexNumber as input from the user for use in the function f(z) = z + c
		out.print("Re(c): ");
		double cReal = Double.parseDouble(reader.readLine());
		out.print("Im(c): ");
		double cImag = Double.parseDouble(reader.readLine());
		
		// Creating the constant complex number from input real and imaginary values
		ComplexNumber constant = new ComplexNumber(cReal,cImag);
		
		// Setting the maximum iterations to 256. This can be increased if you suspect an escapee set may be found beyond this value.
		// Increasing or decreasing the value will affect computation time.
		
		int max_iter = 256;
		
		// Looping through every pixel of image
		for(int X=0; X<WIDTH; X++)
		{
			for(int Y=0; Y<HEIGHT; Y++)
			{
				// Creating an empty complex number to hold the value of last z
				ComplexNumber oldz = new ComplexNumber();
				
				// Setting the value of z0 for every pixel
				// z0 is a function of (x,y) i.e. the pixel co-ordinates.
				// The function can be anything, but should depend on (x,y) in some way and should lie between [-1,1]
				// I use the function, 
				// Re(z) = 2*(X-WIDTH/2)/(WIDTH/2) 
				// Im(z) = 1.33*(Y-HEIGHT/2)/(HEIGHT/2)
				// This gives a good centered fractal.You can play around with the function to get better results.
				
				ComplexNumber newz = new ComplexNumber(2.0*(X-WIDTH/2)/(WIDTH/2), 1.33*(Y-HEIGHT/2)/(HEIGHT/2) );
				
				// Iterating till the orbit of z0 escapes the radius 2 or till maximum iterations are completed
				int i;
				for(i=0;i<max_iter; i++)
				{
					// Saving the current z in oldz
					oldz = newz;
					
					// Applying the function newz = newz^2 + c, where c is the constant ComplexNumber user input
					newz = newz.square();
					newz.add(constant);
					
					// Checking if the modulus/magnitude of complex number has exceeded the radius of 2
					// If yes, the pixel is an escapee and we break the loop
					if(newz.mod() > 2)
						break;
				}
				
				// Checking if the pixel is an escapee
				// If yes, setting the brightness to the maximum
				// If no, setting the brightness to zero since the pixel is a prisoner
				float Brightness = i < max_iter ? 1f : 0;
				
				// Setting Hue to a function of number of iterations (i) taken to escape the radius 2
				// Hue = (i%256)/255.0f;
				// i%256 to bring i in range [0,255]
				// Then dividing by 255.0f to bring it in range [0,1] so that we can pass it to Color.getHSBColor(H,S,B) function
				float Hue = (i%256)/255.0f;
				
				// Creating the color from HSB values and setting the pixel to the computed color
				Color color = Color.getHSBColor(Hue, Saturation, Brightness);
				img.setRGB(X,Y,color.getRGB());
			}
		}
		// Saving the image
		ImageIO.write(img,"PNG", new File("julia.png"));
	}
}

Download the code from pastebin.

Example Outputs


c = -0.4 + 0.6i
c = -0.673 + 0.0123i
c = 0.285 + 0i
c = 0.285 + 0.01i
c = -0.70176 - 0.3842i
c = -0.835 - 0.232i
c = -0.8 + 0.156i
c = -0.62772 + 0.42193i
c = 0.233 + 0.53780i
c = -0.7709787 - 0.08545i



We can definitely add better colors using different color mapping techniques and color smoothing algorithms to get a perfect gradient. This will be covered in an advanced article on Julia set. These are just the results of a two-degree complex function, Julia set however is not restricted to two-degree equations. There are cubic, bi-quadratic, exponential and loads of other complex functions which can power the Julia set. These will also be covered in the next article.


References and Further Readings:
[1] http://en.wikipedia.org/wiki/Julia_set
[2] http://www.jcu.edu/math/vignettes/Julia.htm 
[3] http://en.wikipedia.org/wiki/Complex_number
[4] http://lodev.org/cgtutor/juliamandelbrot.html