Welcome, guest | Sign In | My Account | Store | Cart

This is a mini-framework for exploring the Mandelbrot Set.

It outputs to a Tkinter window or to a PNG file (Pillow package required). It includes a disk caching mechanism for saving and later retrieving Mandelbrot calculations to avoid recalculating the Set on each run.

See module documentation for further information. Compatible with Python 2 and 3.

Python, 248 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
"""
recipe_mandelbrot.py

A mini-framework for exploring the Mandelbrot set. 

The Mandelbrot Set is the set of points which when iterated over by a
particular math procedure repeatedly, the cumulative value does not go  
to infinity. There are more complete explanations elsewhere.

Suffice it to say that the output of calc_mandelbrot_set() returns
an array of values for how quickly the point escaped to infinity, which 
has been proven equivalent to the absolute value of the cumulative value > 2.

If the point does not escape, its escape value equals the maximum number
of iterations. The higher the number of iterations, the more accurate
the output of calc_mandelbrot_values().

Strictly speaking the Mandelbrot Set, as calculated here, is only the 
points whose escape values equal the maximum number of iterations. 
Rendering those points as black presents the standard bug-shaped image.

The spectacular colored Mandelbrot images are created by mapping all the 
escape values to various colors. There any number of color mapping schemes.
The art of exploring the Mandelbrot Set visually is choosing or creating
a mapping which works with the region of the Set one has calculated.
(The smaller the region, the higher the number of iterations should be
used.)

In this recipe escapevals_to_color() provides one such mapping. Change
that function to change the color mapping.

Since it is time-consuming to calculate the Mandelbrot escape values,
it's convenient to cache the calculations to disk, then use the cached
values as one experiments with color mappings.

This recipe contains a disk caching mechanism which saves the calculations
from one run to a file. The parameters of those calculations are encoded
in the filename, so the next time those particular calculations are
required, the recipe will find those calculations automatically.

The recipe outputs to a PNG file and to a Tkinter window. The PNG file 
output requires installation of the Pillow version of the Python Imaging 
Library. 

Suggestions:

* Run the recipe as is to see the output in Tkinter.
* Experiment by varying the number of iterations (MAX_ITERS).
* Experiment by varying the graph rectangle (X_MIN, X_MAX, Y_MIN, Y_MAX).
* Experiment by changing the color mapping function (escapeval_to_color).
"""
__author__ = "Jack Trainor"
__date__ = "2015-12-28"

import sys
import re
import os.path
import time
import array

########################################################################
""" 
Customize these constants 

IMG_WD/IMG_HT should be set roughly equal to (X_MAX-XMIN)/(Y_MAX-Y_MIN).
"""
MAX_ITERS = 250
IMG_WD = 400
IMG_HT = 400
X_MIN = -2.125
X_MAX = 0.875
Y_MIN = -1.5
Y_MAX = 1.5
OUTPUT_DIR = "C:/"
DISK_CACHING = True

########################################################################
def calc_mandelbrot_vals(maxiters, xmin, xmax, ymin, ymax, imgwd, imght):
    escapevals = []
    xwd = xmax - xmin
    yht = ymax - ymin
    for y in range(imght):
        for x in range(imgwd):
            z  = 0
            r = xmin + xwd * x / imgwd
            i = ymin + yht * y / imght
            c = complex(r, i)  
            for n in range(maxiters + 1):
                z = z*z + c
                if abs(z) > 2.0:  # escape radius
                    break
            escapevals.append(n)
    return escapevals

########################################################################
def escapeval_to_color(n, maxiters):
    """ 
    http://www.fractalforums.com/index.php?topic=643.msg3522#msg3522
    """
    v = float(n) / float(maxiters)
    n = int(v * 4096.0)
    
    r = g = b = 0
    if (n == maxiters):
        pass
    elif (n < 64):
        r = n * 2
    elif (n < 128):
        r = (((n - 64) * 128) / 126) + 128
    elif (n < 256):
        r = (((n - 128) * 62) / 127) + 193
    elif (n < 512):
        r = 255
        g = (((n - 256) * 62) / 255) + 1
    elif (n < 1024):
        r = 255
        g = (((n - 512) * 63) / 511) + 64
    elif (n < 2048):
        r = 255
        g = (((n - 1024) * 63) / 1023) + 128
    elif (n < 4096):
        r = 255
        g = (((n - 2048) * 63) / 2047) + 192
    
    return (int(r), int(g), int(b))

########################################################################
def get_mb_corename(maxiters, xmin, xmax, ymin, ymax, imgwd, imght):
    return "mb_%d_wd_%d_ht_%d_xa_%f_xb_%f_ya_%f_yb_%f_" % (maxiters, imgwd, imght, xmin, xmax, ymin, ymax)

def extract_mb_filename(filename):
    maxiters = xmin = xmax = ymin = ymax = imgwd = imght = ""
    filename_re = re.compile("mb_(.+)_wd_(.+)_ht_(.+)_xa_(.+)_xb_(.+)_ya_(.+)_yb_(.+)_.*")
    match = filename_re.match(filename)
    if match:
        maxiters = int(match.group(1))
        imgwd = int(match.group(2))
        imght = int(match.group(3))
        xmin = float(match.group(4))
        xmax = float(match.group(5))
        ymin = float(match.group(6))
        ymax = float(match.group(7))
    return maxiters, xmin, xmax, ymin, ymax, imgwd, imght
    
########################################################################
def write_mb(maxiters, xmin, xmax, ymin, ymax, imgwd, imght):
    escapevals = calc_mandelbrot_vals(maxiters, xmin, xmax, ymin, ymax, imgwd, imght)
    path = get_mb_path(maxiters, xmin, xmax, ymin, ymax, imgwd, imght)
    write_array_file(path, escapevals, 'i')
    
def read_mb(maxiters, xmin, xmax, ymin, ymax, imgwd, imght):
    path = get_mb_path(maxiters, xmin, xmax, ymin, ymax, imgwd, imght)
    array_ = read_array_file(path, 'i', imght*imgwd)
    return array_

def get_mb_path(maxiters, xmin, xmax, ymin, ymax, imgwd, imght):
    corename = get_mb_corename(maxiters, xmin, xmax, ymin, ymax, imgwd, imght)
    path = os.path.join(OUTPUT_DIR, corename+".data")
    return path
    
########################################################################  
def write_array_file(path, list_, typecode):
    array_ = array.array(typecode, list_)
    f = open(path, "wb")
    array_.tofile(f)
    f.close()    
    
def read_array_file(path, typecode, count):
    f = open(path, 'rb')
    array_ = array.array(typecode)
    array_.fromfile(f, count)
    return array_

########################################################################  
def get_mandelbrot(maxiters, xmin, xmax, ymin, ymax, imgwd, imght):
    if DISK_CACHING:
        path = get_mb_path(maxiters, xmin, xmax, ymin, ymax, imgwd, imght)
        if not os.path.exists(path):
            write_mb(maxiters, xmin, xmax, ymin, ymax, imgwd, imght)
        return read_mb(maxiters, xmin, xmax, ymin, ymax, imgwd, imght)
    else:
        return calc_mandelbrot_vals(maxiters, xmin, xmax, ymin, ymax, imgwd, imght)

########################################################################
def mb_to_png(maxiters, xmin, xmax, ymin, ymax, imgwd, imght):
    try:
        from PIL import Image
        from PIL import ImageDraw
        array_ = get_mandelbrot(maxiters, xmin, xmax, ymin, ymax, imgwd, imght)
        img = Image.new("RGB", (imgwd, imght))
        d = ImageDraw.Draw(img)
     
        i = 0
        for y in range(imght):
            for x in range(imgwd):
                n = array_[i]
                color = escapeval_to_color(n, maxiters)
                d.point((x, y), fill=color)
                i += 1
          
        del d
        corename = get_mb_corename(maxiters, xmin, xmax, ymin, ymax, imgwd, imght)
        path = os.path.join(OUTPUT_DIR, corename+".png")
        img.save(path)
    except ImportError:
        println("mb_to_png failed: Pillow not installed.")
  
########################################################################
def mb_to_tkinter(maxiters, xmin, xmax, ymin, ymax, imgwd, imght):
    try:
        import tkinter as tk
    except ImportError:
        import Tkinter as tk
    
    array_ = get_mandelbrot(maxiters, xmin, xmax, ymin, ymax, imgwd, imght)
    window = tk.Tk()
    canvas = tk.Canvas(window, width=imgwd, height=imght, bg="#000000")
    img = tk.PhotoImage(width=imgwd, height=imght)
    canvas.create_image((0, 0), image=img, state="normal", anchor=tk.NW)

    i = 0
    for y in range(imght):
        for x in range(imgwd):
            n = array_[i]
            color = escapeval_to_color(n, maxiters)
            r = hex(color[0])[2:].zfill(2)
            g = hex(color[1])[2:].zfill(2)
            b = hex(color[2])[2:].zfill(2)
            img.put("#" + r + g + b, (x, y))     
            i += 1
            
    println("mb_to_tkinter %s" % time.asctime())
    canvas.pack()
    tk.mainloop()
  
########################################################################
def main():
    println("Start         %s" % time.asctime())
    mb_to_png(MAX_ITERS, X_MIN, X_MAX, Y_MIN, Y_MAX, IMG_WD, IMG_HT)
    println("mb_to_png     %s" % time.asctime())
    mb_to_tkinter(MAX_ITERS, X_MIN, X_MAX, Y_MIN, Y_MAX, IMG_WD, IMG_HT)
    
########################################################################
def println(text):
    sys.stdout.write(text + "\n")

if __name__ == "__main__":
    main()

There are many programming recipes available online for the Mandelbrot Set. My intention here is to provide a simple framework for understanding and exploring the Mandelbrot Set in a single Python module.

Most sample Mandelbrot code is a big loop through (x,y) pairs to calculate the Mandelbrot escape value, then map that value to a color, then output that color an image on the screen or to a file.

I've decoupled the Mandelbrot calculation from the coloring and output to avoid the same, usually lengthy, recalculations. I've added a disk caching mechanism so Mandelbrot values previously calculated can be easily reused.

This frees the programmer to focus on the main issues when producing a Mandelbrot image -- choosing a section of the Set to calculate, to how many iterations, and the color mapping (see escapeval_to_color function) for viewing the resulting image.