First off, I would suggest that you start downloading the larger image by right-clicking on the picture above, and either opening the link in a new window or tab, or saving the linked file. It is about 11.9 MB, so will likely take a little bit of time to download, but should be well worth it.
Second, if I have done everything correctly, when you hover your mouse cursor on the image above, it should change to show the overview of the Mandelbrot set that I posted last week. This is not a coincidence—last week’s picture and this week’s picture are related.
Generating the Image
Superficially, there are some obvious similarities. The colors are similar, and both images show exactly the same region of the Mandelbrot set. But where last week’s image is sharp and clear, this week’s image is fuzzy, as though seen through a fog. That is because this week’s image is, in fact, a mosaic of 250,000 Julia sets.
Recall that Julia sets are generated in a manner similar to the Mandelbrot set. However, where the constant term in the Mandelbrot function changes depending on our starting point, the constant term of a Julia set is the same for all points on the complex plane. What this means is that we can pick any point on the plane and generate a Julia set with that constant.
The image above shows a square region of the complex plane with opposite corners at \(-2.5-2i\) (lower left) and \(1.5+2i\) (upper right). This region happily contains the entire Mandelbrot set, and is basically compositionally balanced (i.e. the set is basically centered in the region, and it looks nice on the screen).
To generate the above image, I divided the region into 250,000 squares, each measuring 0.008 units across. That means that I divided the region into 500 rows by 500 columns. I then took the center coordinate of each square, and generated a 400×400 pixel image of the Julia set with that center coordinate as its constant. In doing so, I created 250,000 bitmap images, each of which was about 480 KB in size (for a total of over 100 GB of image data).
Then, using a nifty command line tool called ImageMagick, I scaled each image down to 100×100 pixels (33 KB each, about 7.8 GB total). It might seem odd to create a bunch of 400 pixel images, then immediately scale them down, but there is method to the madness: this is a lazy way to anti-alias an image. An image that has been scaled down to a given size looks better and (in some ways) more accurately represents reality than an unscaled image of the same size. I almost always render my images at a resolution at least twice as large as my intended final resolution in order to get nicer looking anti-aliased images in the end.
After the images were scaled, I once again used ImageMagick to tile them. Images that were generated from the coordinates of adjacent grid cells were glued together. The resulting mosaic was a 50,000×50,000 pixel bitmap image which weighed in at nearly 8 GB. In order to get something approaching tractable, I scaled the image down to 12,500×12,500 and converted it to a .png (this is the highest resolution version of the project that I retain).1If you are really interested in obtaining the high-resolution image, shoot me an email. I am not going to push those pixels over the internet, but I would be happy to mail any interested party a CD for the cost of shipping (i.e. send me a SASE and a blank CD, and I am sure that I can make it happen).
The resulting file is 130 MB. This was still far to large to upload, but should look really nice when I get around to printing it and framing it. For this post, I scaled the image down to 5,000×5,000 pixels (one 100th of the original size). At this scale, each of the original grid cells is 10 pixels by 10 pixels in size. This is large enough that you can still make out some of the detail of the associated Julia set if you zoom in enough, but small enough to create an image that is reasonably shared on the internet. Finally, the 600×600 pixel image above was created by scaling the 12,500 pixel version down to 600 pixels. Basically all of the original detail is lost, as each tile of the mosaic is reduced to a little bit more than one pixel in width.
The entire process took about 5 days to complete: the initial rendering of images took a day and a night (give or take); the first round of scaling took about 10 hours; the montaging/tiling took the better part of two days; and the final round of scaling took an additional four or five hours. While I am not entirely satisfied with the result (a greyscale image, or a simple blue to white gradient may have worked better), I am quite proud of this mosaic of Julia sets. I am also forced to stand in awe of what modern personal computers can do—such a mosaic of Julia sets would not have been possible 100 years ago, and probably would have taken months to render on a person computer as little as 10 years ago.
At any scale, the result is impressive. A large number of Julia sets are used to create a mosaic image that looks like the Mandelbrot set. This is an interesting result, but not too surprising when we think about it.
The functions that generate the Mandelbrot set and Julia sets are basically identical. The differences only relate to what we use as a constant term. It turns out that the Mandelbrot set is a kind of map to the Julia sets. If we select a constant that is in the Mandelbrot set, the associated Julia set has certain properties—for instance, it consists of a single connected component.2The exact meaning of a “single connected component” is a bit beyond the scope of this post. However, the basic idea is to think of a connected component as a lake. You could start anywhere in the lake, and swim to any other point in the lake. If a set is disconnected (i.e. it contains more than one connected component), you might have to cross land in order to get from one point to another. If you look at the Julia sets that are within the Mandelbrot set in the large image, you will notice that they look like black blobs.
Julia sets with constants outside of the Mandelbrot, on the other hand, are not necessarily connected, and may, in fact, consists of an infinite number of discrete points. Because of this, the Julia sets that are outside of the Mandelbrot set have very little black.
As the constant gets nearer and nearer to the Mandelbrot set, the associated Julia set becomes more and more complicated, and it can take longer for points to “escape.” Hence more white and purple can be seen in those cells.
The overall effect is that Julia sets associated with points in the Mandelbrot set will look mostly black using the color scheme selected above, and Julia sets far from the Mandelbrot set will look mostly blue. As they get closer to the Mandelbrot set, they will include more white and purple, which means that the average color of the Julia set at a particular should approach the color of the Mandelbrot set at this point. The end result is that a mosaic of Julia sets is a pretty good approximation of the Mandelbrot set.
Since writing most of the above, I have found ways to significantly streamline the process. I can now generate images like the one above in about two hours, and with a maximum storage footprint under 20 GB. In part, this is done by generating the images at a smaller initial resolution (rendering 400×400 pixel files was crazy, as the tiles were eventually scaled down to just 25×25 pixels—an initial render size of 100×100 pixels is more than adequate, and cuts down the amount of storage required to a 16th of the original); and in part it is due to my learning how to better use the ImageMagick tools.
Computers are neat, huh?