Barnsley Fern in Python 3

In this article, I will show you how to render the Barnsley Fern in Python 3. The Barnsley Fern is a fractal that can be generated using four simple affine transformations of the form:

where the coefficients of the transform are:

w a b c d e f p 1 0 0 0 0.16 0 0 0.01 2 0.85 0.04 -0.04 0.85 0 1.6 0.85 3 0.2 -0.26 0.23 0.22 0 1.6 0.07 4 -0.15 0.28 0.26 0.24 0 0.44 0.07

In the above table p represents the probability factor for a transform. For example, the second transform will be used 85% of times, third transform 7% and so on.

From a practical point of view, the Barnsley Fern is generated starting with an initial point , , and iteratively calculating the next point using one of the above transforms.

At each step, we generate a random number r from the interval [0, 1) and, interpreting this number as a probability, we pick the corresponding transform. For example, if r is in the interval we pick , if r is in the interval we pick and so on. Note, we define the intervals using the cumulative sum of the probability factors.

This is the result, after 1000000 iterations:

Now, we can implement the Python code that will generate the above fractal. Because we can generate an infinite number of fractals, by varying the coefficients of the affine transforms and their probability factor, I propose to implement a general Barnsley class that will let us use more than a set of coefficients. You can find the entire code on my GitHub https://github.com/sol-prog/Barnsley-Fern.

1 class Barnsley : 2 """Generate Barnsley like fractals using affine transforms""" 3 def __init__ ( self , nr_points , coefficients = "fern" ): 4 self . nr_points = nr_points 5 6 #Initial (starting point) 7 self . x = 0.0 8 self . y = 0.0 9 10 # Store the fractal points 11 self . point_x = [] 12 self . point_y = [] 13 14 # Select the set of coefficients to use 15 if coefficients == "fern" : 16 self . probability_factors = [ 0.01 , 0.85 , 0.07 , 0.07 ] 17 self . a = [ 0 , 0.85 , 0.20 , - 0.15 ] 18 self . b = [ 0 , 0.04 , - 0.26 , 0.28 ] 19 self . c = [ 0 , - 0.04 , 0.23 , 0.26 ] 20 self . d = [ 0.16 , 0.85 , 0.22 , 0.24 ] 21 self . e = [ 0 , 0 , 0 , 0 ] 22 self . f = [ 0 , 1.6 , 1.6 , 0.44 ] 23 24 # other sets of coefficients are present in the code from GitHub: 25 # https://github.com/sol-prog/Barnsley-Fern 26 27 28 self . nr_transforms = len ( self . probability_factors ) 29 30 # Cumulative sum of the probabilty factors, 31 # this defines the intervals corresponding to each transform 32 self . cumulative_probabilities = [ 0 ] * ( self . nr_transforms + 1 ) 33 for i in range ( 1 , len ( self . cumulative_probabilities )): 34 self . cumulative_probabilities [ i ] = self . cumulative_probabilities [ i - 1 ] + \ 35 self . probability_factors [ i - 1 ]

Next, we need a function to select which transform to use:

1 def select_transform ( self ): 2 """Randomly select an affine transform""" 3 rnd = random . random () 4 for i in range ( self . nr_transforms ): 5 if self . cumulative_probabilities [ i ] <= rnd <= self . cumulative_probabilities [ i + 1 ]: 6 self . current_transform = i 7 break

We can calculate and store the next point of the fractal using:

1 def next_point ( self ): 2 """Get the next point of the fractal""" 3 self . select_transform () 4 x_new = self . a [ self . current_transform ] * self . x + self . b [ self . current_transform ] * self . y + self . e [ self . current_transform ] 5 y_new = self . c [ self . current_transform ] * self . x + self . d [ self . current_transform ] * self . y + self . f [ self . current_transform ] 6 self . x = x_new 7 self . y = y_new 8 self . point_x . append ( x_new ) 9 self . point_y . append ( y_new )

Use the above function to iteratively generate the number of points you need for the fractal:

1 def generate_points ( self ): 2 """Generate all the fractal points""" 3 for _ in range ( self . nr_points ): 4 self . next_point () 5 6 # Bounding box for the fractal 7 self . x_min = min ( self . point_x ) 8 self . x_max = max ( self . point_x ) 9 self . y_min = min ( self . point_y ) 10 self . y_max = max ( self . point_y )

Next step is to visualize the generated fractal. A simple solution is to use an image file for this.

In my last article I’ve shown you how to write your own PPM image generator from scratch in Python and you can follow this approach if you want. In the present article I will show you how to use the Pillow image library. On most systems you can install Pillow with:

1 pip install pillow

Next, we initialize the fractal points, transform these points to the image space and save the image:

1 def main (): 2 """Generate and the save as a PNG image a Barnsley Fern""" 3 4 # Initialize the fractal data 5 nr_points = 100000 6 fern = Barnsley ( nr_points ) 7 fern . generate_points () 8 9 # Define the image size and scale factor for the fractal data 10 width , height = 500 , 500 11 scale = min ([ height / ( fern . y_max - fern . y_min ), width / ( fern . x_max - fern . x_min )]) * 0.9 12 13 # Initialize an array that will store the image pixel data 14 image_data = array . array ( 'B' , [ 255 , 255 , 255 ] * width * height ) 15 16 # For every point of the fractal data, transform the point in the image space 17 # and fill the pixel color 18 for i in range ( nr_points ): 19 x = int (( fern . point_x [ i ] - fern . x_min ) * scale ) + int (( width - ( fern . x_max - fern . x_min ) * scale ) / 2 ) 20 y = - int (( fern . point_y [ i ] - fern . y_min ) * scale ) - int (( height - ( fern . y_max - fern . y_min ) * scale ) / 2 ) 21 22 index = 3 * ( y * width + x ) 23 image_data [ index ] = 0 24 image_data [ index + 1 ] = 255 25 image_data [ index + 2 ] = 0 26 27 # Show and save the image 28 img = Image . frombytes ( "RGB" , ( width , height ), image_data . tobytes ()) 29 img . show ( "Barnsley's Fern" ) 30 img . save ( "barnsley_fern.png" )

As previously mentioned, you can generate an infinite number or fractals by altering the coefficients of the transform. Here is what you get with a slightly changed set of coefficients:

w a b c d e f p 1 0 0 0 0.16 0 0 0.04 2 0.7 0.035 -0.04 0.8 0 1.6 0.8 3 0.2 -0.29 0.23 0.22 0 1.6 0.08 4 -0.2 0.28 0.26 0.25 0 0.44 0.08

If you want to read more about the mathematics of fractal generation, I recommend reading Fractals Everywhere by Michael F. Barnsley:

If you want to learn more about Python, I recommend reading Python Crash Course by Eric Matthes: