Julia fractal in generativepy
- Categories:
- generative art
- fractal
The Julia set is a close relative of the Mandelbrot set. There are far more variants of the Julia set, in fact every point in the Mandelbrot set corresponds to a complete Julia set.
Basic equation
The equation for the Julia set is:
znext = z*z + C
Where z
is a complex variable, and C
is a complex number constant. Just like the Mandelbrot case, we can rewrite this equation as a pair of equations in x
and y
:
xnext = x*x - y*y + C1 ynext = 2*x*y + C2
Plotting the fractal
We start by choosing values for C1
and C2
. This value will remain constant for the entire fractal. We can choose any value we want for C1
and C2
, and different pairs will often give a different result (although not every pair will produce an interesting result).
To create the fractal we loop over every possible combination of (x0, y0)
values (each corresponding to a pixel in the final image). For each pair of values:
- We use
(x, y)
as the initial values in the equations above. - We loop over the equations, using the current
x
andy
to create the nextx
andy
. The loop runs until either:- The distance of point
(x, y)
from the origin is greater than 2 or - We have looped more that 256 times.
- The distance of point
In the first case, we say that the initial point diverges, so the point is not part of the Julia set. We record the number of iterations that it takes to diverge (ie how many iteration until the distance is greater than 2) and use that to select a colour for the pixel.
In the second case, we say that the initial point converges, so the point is part of the Julia set. The pixel will be coloured black.
Colorising the values
In a different article we saw how to colour a Tinkerbell fractal. We will use a similar technique here.
In summary:
- We will use
make_nparray_data
to write the count values for each pixel to an integer NumPy array dimensions height x width x 1. - Use a
colorise
function to convert the counts array to a height x width x 3 array (an RGB value for each pixel). - Use
save_nparray_image
to save the RGB data as an image.
Here is the result:
The complete code
Here is the complete code for the Julia set:
from generativepy.bitmap import Scaler from generativepy.nparray import make_nparray_data, save_nparray, load_nparray, make_npcolormap, apply_npcolormap, save_nparray_image from generativepy.color import Color from generativepy.analytics import print_stats, print_histogram import numpy as np MAX_COUNT = 256 C1 = -0.79 C2 = 0.15 def calc(x, y): for i in range(MAX_COUNT): x, y = x*x - y*y + C1, 2*x*y + C2 if x*x + y*y > 4: return i+1 return 0 def paint(image, pixel_width, pixel_height, frame_no, frame_count): scaler = Scaler(pixel_width, pixel_height, width=3.2, startx=-1.6, starty=-1.2) for px in range(pixel_width): for py in range(pixel_height): x, y = scaler.device_to_user(px, py) count = calc(x, y) image[py, px] = count def colorise(counts): counts = np.reshape(counts, (counts.shape[0], counts.shape[1])) colormap = make_npcolormap(MAX_COUNT+1, [Color('black'), Color('darkblue'), Color('green'), Color('cyan'), Color('yellow'), Color('black')], [16, 16, 32, 32, 128]) outarray = np.zeros((counts.shape[0], counts.shape[1], 3), dtype=np.uint8) apply_npcolormap(outarray, counts, colormap) return outarray data = make_nparray_data(paint, 800, 600, channels=1) frame = colorise(data)
This code is available on github in blog/fractals/julia.py.
Here is what the colorise
function does:
- Reshape our counts array from
(height, width, 1)
to(height, width)
. - Create a
colormap
withMAX_COUNT+1
elements. - Create an output array that is height by width by 3, to hold RGB image data. The array is of type uint8, which is an unsigned byte value. We call
apply_npcolormap
to convert the normalised count array into an RGB image array.
The colormap
goes from black to dark blue to green to cyan to white. However, we have also supplied a bands array [16, 16, 32, 32, 128]
. This specifies the relative size of each band. This means that first three bands are quite small, but the cyan-yellow band is bigger, and the yellow-white band is even bigger. This equalises the transitions over the image. so that the less interesting areas well away from the fractal boundary have subtle colour changes, whereas the more interesting part of the image is enhanced by a rapid green-yellow-cyan-white change.
You can easily experiment with other colour schemes.
Comparison with Mandelbrot
If you looked at the Mandelbrot fractal, you will probably have noticed that they basically use the same equation:
xnext = x*x - y*y + C1 ynext = 2*x*y + C2
The difference is:
- For the Mandelbrot set, we try every possible combination of
c1
andc2
. We always start with initial values of zero forx
andy
. The pixel(c1, c2)
is set according to the divergence time. - For the Julia set, we used a fixed value of
C1
andC2
. We we try every possible combination of initial values forx
andy
. The pixel(x, y)
is set according to the divergence time.
Variants
Different combinations of C1
and C2
will create a different fractal. However, many of the fractals will not be interesting, for example they might be completely black.
Generally the most interesting combination occur for values of (C1, C2)
that are just outside the boundary of the Mandelbrot set.