Fractal Coloring

Fractal in 24-bit colors

When I began fractal plotting in the early 1980s using Apple III and its Pascal compiler, there were only two colors, black and white, available and the coloring job was quite simple. Because fractal plotting was such a novel idea back then, I was genuinely excited every time I saw an output fractal finally emerged on the little computer screen after days of computation, even though the picture was usually boring by today's standard.

As I upgraded my desktop computers and compilers a few times since then, the color depth improved from so-called 1-bit colors (with 21 = 2 available colors) to 4-bit colors (24 = 16 colors) and then to 8-bit colors (28 = 256 colors), allowing me to plot more colorful fractals. In the late 1990s when the university I worked at equipped my office with a PC with a 24-bit "true color" capacity that allowed me to use whopping 224 = 16,777,216 colors, coloring was no longer simple and I had to develop a purely mathematical way of coloring fractals to take full advantage of the new hardware.

Written below is the coloring method I have been using since then. Because good human eyes can distinguish "only" up to 10 million colors out of the 16 million+ colors, I expect that the 24-bit colors would stay with us for a long time without a substantial change regardless of the progress in computer technologies.

We begin our discussion with a few preliminary ideas needed for our fractal coloring.

Pre-Colored Fractals.  When we plot a fractal by, say, the
divergence scheme and a dynamical system like the Mandelbrot equation zn+1 = zn2 + p, we first compute an array of nonnegative integers d(i, j) over the canvas in pixel coordinates (i, j) by the formula:

(†0)   d(i, j) = the least iteration index m < M such that the orbit zn of p = (i, j) satisfies |zm| > T,

if such m exists, and d(i, j) = M, otherwise. Here, M is a prescribed maximum number of iterations and T is an appropriate threshold value. Thus, each d(i, j) represents, unless d(i, j) = M, the number of iterations m or "time" it takes for the orbit zn of p = (i, j) to escape from the circle of radius T before taking a long journey toward ∞.

We call an array (or matrix) of nonnegative integers d(i, j) over a canvas (i, j) a pre-colored fractal if it can be converted to colors that show a visual fractal on the canvas. The array d(i, j) given by (†0) is an example of pre-colored fractals.

Example 1.  As explained in the
divergence scheme, we iterate the Mandelbrot equation for each pixel p = (i, j) using M = 500 and T = 2 and compute an array d(i, j) using (†0). For example, if 310 happens to be the least (i.e., the first) iteration index m such that |zm| > 2, set d(i, j) = 310 and stop the iteration for this pixel so as to thwart the computer from wasting its runtime. If we color the pixels (i, j) black or white depending upon the parity (evenness or oddness) of d(i, j), we get a fractal similar to Figure 1 below.

It is often convenient to decompose the canvas into a disjoint union of blocks (subsets) and "flag" every block by another array, say, f(i, j) of smaller nonnegative integers such as bits, 0 and 1, or bytes, 0 - 255.

For example, for each pixel (i, j) in the canvas of Example 1, set f(i, j) = 0 if d(i, j) < M, and f(i, j) = 1, otherwise. Thus, the block flagged 1 is where the Mandelbrot set lies and the block flagged 0 corresponds to the complement of the Mandelbrot set consisting of the parameters whose orbits diverge to ∞. If we color each pixel (i, j) in the block (flagged) 0 red or black depending upon the parity of d(i, j), and color (i, j) in the block 1 white, we get a fractal similar to
Figure 2.1 shown in Stories About Fractal Plotting.

If k is a positive integer and ε is a small real number such as 10-6, then the convergence scheme with period index k also gives rise to an array d(i, j) of nonnegative integers by the formula:

(†k)   d(i, j) = the least iteration index m < M such that the orbit zn of p = (i, j) satisfies |zm+k - zm| < ε,

if such m exists, and d(i, j) = M, otherwise. When we apply a combination of the formulas (†0) and (†k) on computing a pre-colored fractal, the block decomposition becomes especially useful in keeping the matter organized.

Example 2.  Plotting a reasonably detailed "global" multicolor image of the Mandelbrot set requires more than a dozen formulas (†0), (†1), (†2),  · · · as shown in the following steps:

Step 1. Apply the divergence scheme on the entire canvas and extract the block consisting of the pixels whose orbits diverge to ∞. Flag the block 0 by setting f(i, j) = 0 and compute d(i, j) on this block using the formula (†0).

Step 2. Apply the convergence scheme with period index 1 on the remainder of the canvas and extract the block consisting of the pixels whose orbits converge to a 1-cycle (a point). Flag the block 1 and compute d(i, j) on this block using (†1).

Step 3. Apply the convergence scheme with period index 2 on the remainder of the canvas and extract the block consisting of the pixels whose orbits converge to a 2-cycle. Flag the block 2 and compute d(i, j) on this block using (†2).

Repeat the process up to, say, Step 20, and set d(i, j) = M and f(i, j) = 20 for the remaining pixels (i, j) untouched by Step 1 though Step 20.

Plotting a visual fractal becomes easy once the block decomposition of the canvas is done: If we color the block (flagged) 0 like in
Figure 1, the block (flagged) 1 red, the blocks 2 and 3 orange, the block 4 purple, etc., we get Figure 2 shown above. Later, we'll upgrade it to Figures 7 and 8 using 24-bit colors.

Remark.  The method in Example 2 is a brute force algorithm and requires a long computing time. It can be made more efficient, but we probably don't plot a global image of the Mandelbrot set very often. By comparison, Julia sets are much simpler to deal with.

Example 3.  Consider the dynamical system

(1)   zn+1 = fp(zn) = p(1 - zn2) zn

with the fixed parameter p = (1.18, 0.376). Figure 3 on the left is a Julia fractal and its pre-colored fractal is given by Steps 1 and 2 of the previous example plus the last step of setting d(i, j) = M and f(i, j) = 2 for the remaining pixels (i, j) untouched by Steps 1 and 2.

The block (or the subset of the canvas) flagged 2 happens to depict the Julia set of p separating its exterior (which is the block 0 colored gray and white) from its interior (which is the block 1 colored black and yellow).

Computing a pre-colored fractal is generally a time-consuming process as it often involves millions of iterations of the dynamical system, one iteration process for each pixel on the canvas. So, it is important that we store it in the hard disk.

Fractal coloring, on the other hand, occupies a small part in terms of the computer's runtime, but it is reminiscent of painting a newly built house and can improve or ruin the entire effort. We can also make different images from the same pre-colored fractal by applying different colors. For example, the first and second images, called "Racing Elephants" and "Flock of Owls," are based on the same pre-colored fractal, which we can see through the third image. (They are painted by 24-bit colors by the method we have not yet discussed.)

The Color Cube.  Red, green, and blue are the primary colors of light―they can be combined in different proportions to make all other colors. For example, red light and green light added together are seen as yellow light. This additive color system is used by light sources, such as televisions and computer monitors, to create a wide range of colors. When different proportions of red, green, and blue light enter your eye, your brain is able to interpret the different combinations as different colors. ... From Harvard-Smithsonian Center for Astrophysics.

Mathematically speaking, the 24-bit colors we mentioned earlier are packed in the 3D color cube shown in Figure 5 below. It is given by the RGB coordinate system with R-, G- and B-axes representing the three primary colors of red, green and blue, respectively. The R-axis, for example, consists of 28 = 256 shades of red, namely,

    black = (0, 0, 0),   (1, 0, 0),   (2, 0, 0),   (3, 0, 0),   · · ·,   (255, 0, 0) = red,

and likewise for the G- and B-axes with green = (0, 255, 0) and blue = (0, 0, 255).  (0, 0, 0), which is black, is the origin of the coordinate system and white = (255, 255, 255) is the vertex of the color cube opposite from the origin (black).

Just like in the Cartesian 3D space with x-, y-, z-axes, therefore, each color in the color cube is represented by an ordered triple (R, G, B) of integers between 0 and 255, called bytes; hence the total number of colors in the color cube is 256 3 = 224 = 16,777,216 as we have seen.

The color cube shows, for example,

  yellow = (255, 255, 0) = (255, 0, 0) + (0, 255, 0) = red + green,

  purple = (255, 0, 255) = (255, 0, 0) + (0, 0, 255) = red + blue,

  white = (255, 255, 255) = red + green + blue,

and orange is the midpoint (average) between red and yellow, namely 0.5 x [(255, 0, 0) + (255, 255, 0)] =(255, 128, 0). Because black = (0, 0, 0), a color is generally darker when all of its components are closer to 0. For example, a brownish color is given by darkening the components of orange like 0.7 x (255, 128, 0) = (179, 90, 0).

Thus, we get a color by adding two colors or multiplying a color by a nonnegative real number as long as all of the color components stay as bytes. Here, the non-integer components are always rounded to bytes.

Line Segments in the Color Cube.  Suppose C1 = (R1, G1, B1) and C2 = (R2, G2, B2) are two colors in the color cube. Then by the line segment from C1 to C2, we mean the set of all colors C = (R, G, B) satisfying the parametric equations

(☆)   R = (1 - t) x R1 + t x R2,   G = (1 - t) x G1 + t x G2,   B = (1 - t) x B1 + t x B2,

where the parameter t satisfies 0 < t < 1.  Note that if  t = 0,  C = C1,  and if  t = 1,  C = C2,  so we imagine that C moves along the line segment by gradually changing its color from C1 to C2  as "time" t progresses from 0 to 1.

Applications.  We have seen that a pre-colored fractal is an array of nonnegative integers d(i, j) over a canvas comprising pixels (i, j) and that the canvas is decomposed into a disjoint union of finitely many blocks. The canvas may have only one block as is the case in
Example 1 or as many as we want to handle in our computer program as shown in Example 2. If the canvas has two or more blocks, we flag each block by a unque number f(i, j) like f(i, j) = 5 for all pixels belonging to the block; see Example 2.

So, given a pre-colored fractal d(i, j) over a canvas, let S be any of the blocks in the canvas and let n1 and n2 be the minimum and maximum of the integers d(i, j) over S, respectively. Suppose we wish to paint all pixels in S using the colors in the line segment between colors C1 and C2. Assume also that, in the following discussion, all pixels (i, j) belong to the block S.

For any integer n = d(i, j), we have n1nn2, so, normalize n to the range between 0 and 1 by setting

(2)   s = (n - n1)/(n2 - n1),

and then let

(3)   t = s

in (☆).  Observe that if n = n1, t = s = 0, so, C = C1,  and if  n = n2, t = s = 1, so, C = C2,  Therefore, as n = d(i, j) increases from n1 to n2, the corresponding color C, which we use to paint the pixels (i, j), changes from C1 to C2. Note that usually numerous pixels (i, j) in S are mapped onto a single number n = d(i, j) and are painted by the color C.

For example, Figure 6 on the left shows how the color of the pixels (i, j) changes with n = d(i, j) from red to black. Because of (2) and (3), t is a linear function of n.

Example 4. (Continuation of
Example 2)  We have seen that the canvas for the Mandelbrot set is decomposed into 21 blocks named (or flagged) block 0 through block 20. Block 0 is the complement of the Mandelbrot set, block 1 the interior of the cardioid, block 2 the interior of the largest circular disk, block 3 the interior of the second largest disks (there are two of them), and so on so forth; see Figure 2.

Painting the Mandelbrot set using 24-bit colors is now easy as the pre-colored fractal d(i, j) has already been computed over the entire canvas. We use the linear coloring of Figure 6 to paint the block 1 (the interior of the cardioid) by the color change from red to black and similarly paint the block 0 by the color change from black to green, the blocks 2 and 3 from orange to black, the block 4 from purple to black, etc. This gives us Figure 7 shown below.

We note in Figure 7 that the interior of the cardioid, for instance, has its vast area painted by brighter red despite the fact that the coloring is linear, and that is because the higher numbers d(i, j) are concentrated near the boundary of the Mandelbrot set. By the way, the block numbers 1, 2, 3, · · ·,  coincide with the numbers in Wikipedia's
Periodicity Diagram and are very useful in plotting various Julia sets.

We also note that if we change the equation (3) by

(4)   t = 1 - s

then we get another linear coloring scheme shown by Figure 9 on the left, where color changes from C2 to C1 with n.

Now, using the linear coloring schemes of Figures 6 and 9, it is fairly easy to program a computer to generate the periodic coloring schemes shown in Figure 10 below. Here, the color changes between yellow and brown instead of red and black, and the coloring scheme on the right is given by squaring the function on the left to change the "V function" to the parabolic "U function." In fact, we can use any power function instead of squaring to change the way color oscillates between yellow and brown.

Example 5. (Continuation of
Example 3)  Figure 3 depicts the Julia set of Example 3 whose exterior (block 0) and interior (block 1) are painted by 2-bit colors. By comparison, Figure 11 given below illustrates various ways of painting the Julia set using 24-bit colors. The interior of the Julia set in the first two images are given by the periodic linear and parabolic coloring schemes (with a different frequency) of Figure 10, respectively.

The interior of the Julia set in the last two images of Figure 11 are painted by the periodic coloring schemes given by the diagrams below. They involve four colors, blue, yellow, orange and red, instead of two colors, yellow and brown.
Finally, Figure 8 shows that we can partition each block into sub-blocks and apply different coloring on each sub-block. With patience, we can develop a wider variety of coloring schemes and certainly paint immensely colorful fractals.