Skip to content

Instantly share code, notes, and snippets.

@EnzDev
Last active December 6, 2021 07:53
Show Gist options
  • Save EnzDev/8f6dd4524d0fac24351e0ab694d73cd7 to your computer and use it in GitHub Desktop.
Save EnzDev/8f6dd4524d0fac24351e0ab694d73cd7 to your computer and use it in GitHub Desktop.
Mandelbrot in Python
from PIL import Image, ImageDraw
import time
__version__ = "v1.3.4-Optimisations"
__author__ = "Enzo Mallard"
# Dimension of the picture (define the resolution)
WIDTH_PIX = HEIGHT_PIX = 1_000
# Maximum number of iterations
LOOPS = 255
# Zoom boundaries
# X_MIN, X_MAX = -2.05, .55
# Y_MIN, Y_MAX = -1.3, 1.3
v = 0.2
X_MIN, X_MAX = -1+v, -.5-v
Y_MIN, Y_MAX = -0.25+v, 0.25-v
def iter_to_color(iterations):
"""
Transform a number of iteration into a color (#rrggbb format)
The formula is just converting the iteration number into hue where h ∈ [0, 360]
and uses maximum saturation and value.
note: Every 1 are just saturation, value and derived numbers without their variables name
for simplification purposes
:param iterations: The number of iterations that have been required
:return: an [r, g, b] array where r,b and b ∈ [0, 255]
"""
# Stay black when we escape directly
if iterations == 0:
return 0, 0, 0
# Convert the iterations into hue with a cross-multiplication
h = (360 * float(iterations)) / float(LOOPS)
hp = h / 60
x = 1 - abs(hp % 2 - 1)
def rgbp(_hp, _x):
if _hp < 1:
return 1, _x, 0
elif _hp < 2:
return _x, 1, 0
elif _hp < 3:
return 0, 1, _x
elif _hp < 4:
return 0, _x, 1
elif _hp < 5:
return _x, 0, 1
elif _hp < 6:
return 1, 0, _x
return [255 * val for val in rgbp(hp, x)]
# Pre-render colors for each iteration count
colors = {
iteration: "".join(
hex(int(color))[2:].zfill(2)
for color in iter_to_color(iteration)
) for iteration in range(LOOPS)}
def mandel_it(img, width_pix, height_pix, x_min, x_max, y_min, y_max):
"""
Generate the Mandelbrot figure with colored iterations into a PIL Image
:param img: The picture to draw in
:param width_pix: The picture width
:param height_pix: The picture height
:param x_min: The left bound of the fractal
:param x_max: The right bound of the fractal
:param y_min: The bottom bound of the fractal
:param y_max: The top bound of the fractal
"""
# x/y increments for each pixel (resolution)
x_inc = float(abs(x_min - x_max) / float(width_pix))
y_inc = float(abs(y_min - y_max) / float(height_pix))
# Draw cache, prevent calling the point creating each time
to_draw = []
# Frequency to update the progression
percent = int(width_pix / 50)
# Lets render each pixels
for x in range(0, width_pix): # See for loop threading
for y in range(0, height_pix):
# Our complex constant
c = complex(x_min + (x * x_inc), y_max - (y * y_inc))
z = 0
hex_shade = "000000"
for n in range(0, LOOPS): # Perform our loops
z = z * z + c # Square up and add the constant
if abs(z) > 2: # We got too far away, that the end
hex_shade = colors[n]
break
if n == LOOPS - 1: # Still not outside after the iterations
hex_shade = "000000"
to_draw.append((x, y, x, y + 1, "#" + hex_shade))
# Update the picture every 5 lines to prevent overhead
if x % 5 == 0 or x >= width_pix - 2:
for popped in to_draw:
img.point((popped[0], popped[1]), popped[-1])
to_draw = []
# Update every now and then (defined by the percent calculation
if x % percent == 0:
print("\r%1.2f%%%s" % ((100 * x) / width_pix, " " * 40), end="")
image = Image.new("RGB", (HEIGHT_PIX, WIDTH_PIX), (255, 255, 255))
drawing = ImageDraw.Draw(image)
print("%dx - %d Loops - %s" % (WIDTH_PIX, LOOPS, __version__))
print("Start %s" % time.asctime())
start = time.time()
# Let's draw the mandelbrot set into our ImageDraw !
mandel_it(drawing, WIDTH_PIX, HEIGHT_PIX, X_MIN, X_MAX, Y_MIN, Y_MAX)
print("\rStart saving %s (Elapsed: %ss)" % (time.asctime(), int(time.time()-start)))
start_save = time.time()
image.save("./l%s_%dx_%s_(%s,%s)_brot.png" % (
str(LOOPS).zfill(3),
WIDTH_PIX,
__version__,
str(X_MIN)+'_'+str(X_MAX),
str(Y_MIN)+'_'+str(Y_MAX)
))
print("End Saving %s (Elapsed: %ss)" % (time.asctime(), int(time.time()-start_save)))
print("End %s (Elapsed: %ss)" % (time.asctime(), int(time.time()-start)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment