I have the following Python program that endeavours to use the normalized iteration count algorithm to colour the Mandelbrot set:
from PIL import Image
import numpy as np
from matplotlib.colors import hsv_to_rgb
steps = 256 # maximum iterations
bailout_radius = 64 # bailout radius
def normalized_iteration(n, abs_z):
return n + 1 - np.log2(np.log(abs_z))/np.log(2)
def make_set(real_start, real_end, imag_start, imag_end, height):
width = \
int(abs(height * (real_end - real_start) / (imag_end - imag_start)))
real_axis = \
np.linspace(real_start, real_end, num = width)
imag_axis = \
np.linspace(imag_start, imag_end, num = height)
complex_plane = \
np.zeros((height, width), dtype = np.complex_)
real, imag = np.meshgrid(real_axis, imag_axis)
complex_plane.real = real
complex_plane.imag = imag
pixels = \
np.zeros((height, width, 3), dtype = np.float_)
new = np.zeros_like(complex_plane)
is_not_done = np.ones((height, width), dtype = bool)
# cosine_interpolation = lambda x: (np.cos(x * np.pi + np.pi) + 1) / 2
for i in range(steps):
new[is_not_done] = \
new[is_not_done] ** 2 + complex_plane[is_not_done]
mask = np.logical_and(np.absolute(new) > bailout_radius, is_not_done)
pixels[mask, :] = (i, 0.6, 1)
is_not_done = np.logical_and(is_not_done, np.logical_not(mask))
new_after_mask = np.zeros_like(complex_plane)
new_after_mask[np.logical_not(is_not_done)] = \
new[np.logical_not(is_not_done)]
new_after_mask[is_not_done] = bailout_radius
pixels[:, :, 0] = \
normalized_iteration(pixels[:, :, 0], np.absolute(new_after_mask)) / steps
image = Image.fromarray((hsv_to_rgb(np.flipud(pixels)) * 255).astype(np.uint8))
image.show()
make_set(-2, 1, -1, 1, 2000)
It produces a fairly nice image. However, when I compare it to other sets employing this algorithm, the colours in my set barely change. If I reduce steps
, I get a more varied gradient, but that reduces the quality of the fractal. The important parts of this code are my normalized_iteration
definition, which varies slightly from this Wikipedia article's version,
def normalized_iteration(n, abs_z):
return n + 1 - np.log2(np.log(abs_z))/np.log(2)
where I use that definition (mapping the function to the array of pixels),
pixels[:, :, 0] = \
normalized_iteration(pixels[:, :, 0], np.absolute(new_after_mask)) / steps
and the final array, where I convert the HSV format to RGB and turn the pixel values on [0, 1) to values on [0, 255)
image = Image.fromarray((hsv_to_rgb(np.flipud(pixels)) * 255).astype(np.uint8))
I have been fighting with this problem for a while now, and I am not sure of what is going wrong. Thanks for helping me determine how to make the gradient more varied in colour and for bearing with my perhaps hard-to-read code. Also, I realize that there is room for optimization in there.