I’ve written the major part of this Buddhabrot rendering program in last August, and after a six months break I carried on with it again this weekend to close the project finally. But before going into details, let`s see what it does:
Click on the image for full size: 3201×3206. This image is the result of a half hour render, and then it got post-processed using Photoshop.
The primary goal of the project was to create beautiful Buddhabrot images. This is why the histogram equalization is done in 64 bit floating point precision, and why the resulting raw image is in 16 bit/channel format.
Its main features are:
- Multi-threaded, it can make use of all cores in the machine.
- Reads the input parameters from an XML file.
- The rendering process can be stopped and restarted at any point.
- It does the rendering in two steps, first it creates an exclusion map, which contains the areas that can be skipped.
- Saves all intermediate files on the disk, so those can also be processed with other software.
- Uses cubic Hermite splines for histogram equalization. (built with Fritsch-Carlson method)
- Shows detailed error messages.
- Maintains a log file per project, so the total rendering time can be tracked easily.
External libraries used:
- Executable with a sample config file: buddhabrot.zip (x64, Windows)
- Source code: buddhabrot_src.zip (C++, under GNU GPL)
- The copies of the external libs, which I linked with the project:
The program can be operated via command line. First create a configuration file. The name of the config file must start with the project name and end in the .xml extension. All files related to the same project will start with the project name, but end in different extensions.
A typical config file:
<settings> <threadnumber>8</threadnumber> <size>8000</size> <maxiteration>4000</maxiteration> <exclusionmap> <tasknumber>1000</tasknumber> <oversampling>144</oversampling> <trimming>2</trimming> </exclusionmap> <buddhabrot> <tasknumber>1000</tasknumber> <oversampling>256</oversampling> </buddhabrot> </settings>
Where the fields are:
- threadnumber: The max number of threads, which can run at the same time. Set it to the number of logical cores in your machine. So if your machine has 6 cores, and it is hyper-threaded, then set it to 6*2 = 12.
- size: The width and the height of the resulting image. Must be divisible by 4.
- maxiteration: The maximum iteration count for the Mandelbrot formula. Setting this value is an art, and needs practice. Its ideal value is linear with size*oversampling. See.
- tasknumber: The whole rendering task gets divided into this number of smaller tasks. If it is too small then you might have to wait a lot when you stop the process. If it is too high then the tasks become to short compared to the time required for switching between tasks, which results in degraded performance. Set it so the average run-time for a task is around 2-5 seconds.
- oversampling: The number of complex numbers “inside” a pixel of the image, used as a starting point for trajectories. Higher value makes the image smoother, but slows the process down radically. This value can be lower for the exclusion map (+trimming).
- trimming: The number of pixel layers which gets removed from the surface of the white areas in the exclusion map, making the size of these areas smaller. This makes the usage of the exclusion map safer, by reducing the likelihood that it excludes useful areas.
If the settings are correct, the CPU utilization should be around 100%:
After the rendering has finished, you can generate a 16 bit raw file with the “-genraw” command. You can open that file in Photoshop, using the following settings:
I use these mostly for post-processing: Set it to RGB, Curves (also per channel), Shadows and highlights, Saturation, Selective color, Smart sharpen. (If you want to save it as jpeg, don’t forget to set it to 8 bit/channel first.)
Files per project
As mentioned before, all files which belong to a project, start with the project name, but has different extension. So for example, if the project name were “bb8000″, then we would have the following files:
- bb8000.log: The log file. It is a text file. New entries get appended to it. It never gets truncated.
- bb8000.map: The exclusion map. (1 pixel = 1 byte)
- bb8000.mem: The “counters” for each pixel. This is the original Buddhabrot data, and the largest file. It has a 4 byte integer for each pixel in the Buddhabrot. The raw file gets generated from this.
- bb8000.task: When the process is stopped, the current progress gets saved into this file.
These files are generated by the application. You don’t have to create them.
This idea generalizes some previous methods, which aimed to exclude certain areas of the complex plane to achieve lower run-times. In this example, the white areas are excluded:
The exclusion map works on pixel level. If the oversampling (see next paragraph) is 144, then it divides each pixel into a 12 x 12 grid, and checks each point. If it can find at least one point which has a trajectory that leaves the Mandelbrot set, then the pixel is marked useful (black). Otherwise it is marked to be excluded (white).
It has multiple advantages:
- The same exclusion map can be used for different renders. Just put a finished exclusion map into the project folder with the proper size, and name: <project_name>.map
and the program will detect it, and use it automatically.
- The calculation takes advantage of the symmetry over the real axis, and calculates only the lower half of the image, which gets mirrored to the other side. Sparing the 50% of the time.
- It can has a lower oversampling value than the Buddhabrot render, if trimming is used. (explained in a previous paragraph)
- You can use the generated map for your own programs. The mapping equations are saved in the log file.
At the end of the Buddhabrot rendering process we get a counter value for each pixel. If we interpret these counter values directly as an intensity value, then we get a very bad histogram. All the “nice” parts of the Buddhabrot are in an around 10-15% area of the full dynamic range.
Even if we export a 16 bit image data, the useful part of the dynamic range is so small, that we can loose a lot of details.
This is why histogram equalization is required, which stretches this “useful” area to make up a larger 80-90% part of the whole dynamic range.
For this I’ve implemented a class, which works the same way as the “Curves” tool of Photoshop:
It does an intensity transformation based on curves like the one above. I used cubic Hermite splines as basic building blocks.
The curve is always specific to the current Buddhabrot: the software samples certain areas of the image to evaluate the average level there. Like the “belly” area, or the “third eye” area, etc. It constructs the curve based on these level values, using the Fritsch-Carlson method.