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