The Buddhabrot is closely related to the Mandelbrot set. For both, you would calculate the orbit of z under the dynamic for various values of the constant c (where z and c are numbers on the complex plain).
When rendering the Mandelbrot set we are chiefly concerned with values of c corresponding to pixels in our final image. We check which values of c cause the path of z to enter an orbit around the origin, and which values of c cause z to fly off to infinity (and how quickly). Pixels are then coloured accordingly.
For a Buddhabrot, the value of c is less important. Instead, the frequency with which the orbiting point z visits various pixels is recorded — lighter pixels have received a higher frequency of visits from z. While values of c are chosen at random, the classic Buddhabrot uses only those values of c which will cause z to escape to infinity (non Mandelbrot-set points). I think of this process as being a cross between a Mandelbrot set and a strange attractor.
Here is my first attempt at rendering a Buddhabrot using a program I wrote in Python:
This is a 100 iteration image This means that, when checking to see if an orbiting z point is escaping to infinity, we bail-out after 100 iterations and assume all remaining points are within the Mandelbrot set (their orbits are not used in the Buddhabrot). More iterations means more of the kind of orbit that take longer to escape.
It turns out that adjusting this ‘bail-out’ level can have a dramatic impact on the resulting image. For example, here is a 1000 iteration image:
We can see that higher bail-out values result in finer details.
Finally, a 5000 iteration image (click for super-high-res):
Here is the Python code in full, with gratuitous comments and print statements included — remember that you will need PyPng and Numpy to run this code.
#! /usr/bin/python import png import numpy as np from random import random def c_set(num_samples, iterations): # return a sampling of complex points outside of the mset # Allocate an array to store our non-mset points as we find them. non_msets = np.zeros(num_samples, dtype=np.complex128) non_msets_found = 0 # create an array of random complex numbers (our 'c' points) c = (np.random.random(num_samples)*4-2 + \ (np.random.random(num_samples)*4-2)*1j) # Optimizations: most of the mset points lie within the # within the cardioid or in the period-2 bulb. (The two most # prominant shapes in the mandelbrot set. We can eliminate these # from our search straight away and save alot of time. # see: http://en.wikipedia.org/wiki/Mandelbrot_set#Optimizations print "%d random c points chosen" % len(c) # First elimnate points within the cardioid p = (((c.real-0.25)**2) + (c.imag**2))**.5 c = c[c.real > p- (2*p**2) + 0.25] print "%d left after filtering the cardioid" % len(c) # Next eliminate points within the period-2 bulb c = c[((c.real+1)**2) + (c.imag**2) > 0.0625] print "%d left after filtering the period-2 bulb" % len(c) # optimizations done.. time to do the escape time algorithm. # Use these c-points as the initial 'z' points. # (saves one iteration over starting from origin) z = np.copy(c) for i in range(iterations): # apply mandelbrot dynamic z = z ** 2 + c # collect the c points that have escaped mask = abs(z) < 2 new_non_msets = c[mask == False] non_msets[non_msets_found:non_msets_found+len(new_non_msets)]\ = new_non_msets non_msets_found += len(new_non_msets) # then shed those points from our test set before continuing. c = c[mask] z = z[mask] print "iteration %d: %d points have escaped!"\ % (i + 1, len(new_non_msets)) # return only the points that are not in the mset return non_msets[:non_msets_found] def buddhabrot(c, size): # initialise an empty array to store the results img_array = np.zeros([size, size], int) # use these c-points as the initial 'z' points. z = np.copy(c) while(len(z)): print "%d orbits in play" % len(z) # translate z points into image coordinates x = np.array((z.real + 2.) / 4 * size, int) y = np.array((z.imag + 2.) / 4 * size, int) # add value to all occupied pixels img_array[x, y] += 1 # apply mandelbrot dynamic z = z ** 2 + c # shed the points that have escaped mask = abs(z) < 2 c = c[mask] z = z[mask] return img_array if __name__ == "__main__": size = 400 # size of final image iterations = 200 # bailout value -- higher means more details samples = 10000000 # number of random c points chosen img_array = np.zeros([size, size], int) i = 0 while True: print "get c set..." c = c_set(samples, iterations) print "%d non-mset c points found." % len(c) print "render buddha..." img_array += buddhabrot(c, size) print "adjust levels..." e_img_array = np.array(img_array/float(img_array.max())*((2**16)-1), int) print "saving buddhabrot_n_%di_%03d.png" % (iterations,i) # save to final render to png file imgWriter = png.Writer(size, size, greyscale=True, alpha=False, bitdepth=16) f = open("buddhabrot_n_%di_%03d.png" % (iterations,i), "wb") imgWriter.write(f, e_img_array) f.close() print "Done." i += 1