Buddhabrot Buddhabrot
Play with vars

What are the important variables? Obviously, Z and C. Then, what would happen if we changed the number of Zs calculated for a C? And what about increasing the number of Cs to check before painting the image?

it's important to realize that what we're trying here is to create images of great aesthetic beauty. We've seen this "mathematica object" cannot be faithfully represented; what we want is a series of spectacular pictures, leaving its exactitude to the artist's taste:

Modifying Cs
numCs animation

The number of Cs checked could be associated with the image resolution. Inspecting few Cs would result in a picture representing few points outside M.

As far as I know, checking 100 Cs per pixel produces quite a smooth finish. In the example code, 10000 Cs are inspected per axis, since it's a 1000x1000 image; hence, we're looking at 108 Cs on the complex plane.

To illustrate what happens as we check more Cs, I've prepared this animation (left). We start checking 1 C per pixel, and finish with 10000. By the way, maxN is set to 1000.

Modifying Zs
maxN animation

Whereas increasing the number of Cs produces pictures essentially very similar, changing the number of Zs has a significant impact on the image we'll be looking at. Just take a look at this animation, where maxN goes from 1 to 10000 (where we checked 100 Cs per pixel).

Impressive, right? Is that really the same "object"? The explanation is this: if maxN is very small, many Cs ouside M get confused with part of the set, and their trajectories never get painted. For example, if we paint a Buddhabrot with maxN = 1, we'll get a black circle -since Z1 equals C, and C is always inside the examined complex plane, all Cs within radius 2 from the origin of coordinates are taken as part of M, and nothing gets painted.

On the other extreme, when maxN is huge, certaing initially blurry shapes -the buddha's 'shoulders'- or plain invisible -the flying 'mini-Buddhabrots'- become apparent.

Fact: the closer a C outside M is to the border, the larger maxN will have to be not to confuse it with part of the set. Consequently, it would be a good idea to increase maxN as we examine more and more Cs on the plane.

Coloring the Buddhabrot

This process is analogous to coloring telescopic images. For these, light is not registeres with regular cameras, instead, emissions from different kinds of atoms are captured with electronic detectors, thus producing gray-scale images. Then they assign a different color channel to different takes, merge them on the same picture, et voilà, they've got color.

As Green suggests, we can take advantage of the fact that different maxN give place to different "versions" of the same objectto color the Buddhabrot. We have to create 3 images -one for each color channel- with different maxN, and merge them.

The code should look like this:

import java.awt.image.*;

public static BufferedImage generaBuddhaBrot() {
    int i, j, rojo, verde, azul, maxRojo = 50, maxVerde = 500, maxAzul = 5000; 
    int lado = 1000; 
    float increm = 4 / 10000f;

    int[] pixels = new int[lado * lado];
    BufferedImage imagen;

    // We generate an image for each color channel using different 'maxN'.
    float[][] pixelsRojos  = getPixels(maxRojo, increm, lado);
    float[][] pixelsVerdes = getPixels(maxVerde, increm, lado);
    float[][] pixelsAzules = getPixels(maxAzul, increm, lado);

    // We merge the three color channels on one image.

    float factorRojo  = 255f / encuentraMax(pixelsRojos);
    float factorVerde = 255f / encuentraMax(pixelsVerdes);
    float factorAzul  = 255f / encuentraMax(pixelsAzules);
    for (i = 0; i < ancho; i++) for (j = 0; j < alto; j++) {
        rojo  = (int)(pixelsRojos[i][j] * factorRojo);
        verde = (int)(pixelsVerdes[i][j] * factorVerde);
        azul  = (int)(pixelsAzules[i][j] * factorAzul);
        pixels[i * ancho + j] = rojo << 16 | verde << 8 | azul;
    }
    imagen = new BufferedImage(lado, lado, BufferedImage.TYPE_INT_RGB);
    imagen.setRGB(0, 0, lado, lado, pixels, 0, lado);
    return imagen; 
}


private static float[][] getPixels(int maxN, float increm, int lado) {
    int i, n, coordX, coordY;
    float Cr, Ci, Zr, Zi, ZrAux, ZiAux;
    float[][] trayectoria = new float[maxN][2];
    float[][] pixels = new float[lado][lado];

    for (Cr = -2; Cr < 2; Cr += increm) for (Ci = -2; Ci < 2; Ci += increm) {
        ZrAux = ZiAux = 0;
        for (n = 0; n < maxN; n++) {
            Zr = calculaReal(ZrAux, ZiAux, Cr);
            Zi = calculaImaginaria(ZrAux, ZiAux, Ci);
            trayectoria[n][0] = ZrAux = Zr;
            trayectoria[n][1] = ZiAux = Zi;
            if (Zr * Zr + Zi * Zi > 4) break;
        }
        if (n < maxN) 

            for (i = 0; i < n; i++) {
                coordX = (int)(((trayectoria[i][0] + 2) / 4) * ancho);
                coordY = (int)(((trayectoria[i][1] + 2) / 4) * alto);
                if (coordX < 0 || coordX >= ancho || coordY < 0 || coordY >= alto)
                    continue; 
                pixels[coordX][coordY]++;
            }
    }
    return pixels; 
}

The pixels array now gets filled in a conveniency method, getPixels(int maxN, float increm, int lado), that takes arguments to modify the number of Zs and Cs checked, and the dimensions of the image to generate.

 
Copyright © Albert Lobo Cusidó 2006-2014