How To Render Mandelbrot Set Faster?
Solution 1:
Setting one pixel at a time is likely the main source of the slowdown. Instead of calling put for each pixel, computer a whole row of pixels, or an entire matrix of pixels, and then call put one time at the end of the loop.
You can find an example here, among other places: https://web.archive.org/web/20170512214049/http://tkinter.unpythonic.net:80/wiki/PhotoImage#Fill_Many_Pixels_at_Once
Solution 2:
Here is my code, it draws a 640x480 Mandelbrot in 8-9 seconds.
It does up to 256 iterations per pixel, uses a color map list, 'puts' only once to PhotoImage
and doesn't rely on symetry, so it could show any zoomed area of the set.
It's a pity that Tkinter doesn't allow access to the raster information of PhotoImage
as a buffer and that the clumsy string is required.
from tkinter import Tk, Canvas, PhotoImage,NW,mainloop
from time import clock
defmandel(kx,ky):
""" calculates the pixel color of the point of mandelbrot plane
passed in the arguments """global clr
maxIt = 256
c = complex(kx, ky)
z = complex(0.0, 0.0)
for i inrange(maxIt):
z = z * z + c
ifabs(z) >= 2.0:
return (255-clr[i],0,0)
return(0,0,0)
defprepare_mdb(xa,xb,ya,yb):
""" pre-calculates coordinates of the mandelbrot plane required for each
pixel in the screen"""global x,y,xm,ym
xm.clear
ym.clear
xm=[xa + (xb - xa) * kx /x for kx inrange(x)]
ym=[ya + (yb - ya) * ky /y for ky inrange(y)]
x=640
y=480#corners of the mandelbrot plan to display
xa = -2.0; xb = 1.0
ya = -1.5; yb = 1.5#precalculated color table
clr=[ int(255*((i/255)**12)) for i inrange(255,-1,-1)]
xm=[]
ym=[]
prepare_mdb(xa,xb,ya,yb)
#Tk
window = Tk()
canvas = Canvas(window, width = x, height = y, bg = "#000000")
t1=clock()
img = PhotoImage(width = x, height = y)
canvas.create_image((0, 0), image = img, state = "normal", anchor = NW)
pixels=" ".join(("{"+" ".join(('#%02x%02x%02x' % mandel(i,j) for i in xm))+"}"for j in ym))
img.put(pixels)
canvas.pack()
print(clock()-t1)
mainloop()
Solution 3:
Pure python is not that fast for numeric code. The easiest way to speed things up would be to use PyPy. If that is not fast enough, vectorize your algorithms using numpy. If that is still not fast enough, use Cython, or consider rewriting it in C.
Solution 4:
For a modest increase in speed (but not enough to offset the difference between a compiled language and an interpreted one), you can precalculate some of the values.
Right now, you're calculating DIAMETER / HEIGHT
once per inner loop, and CENTER[1] - 0.5 * DIAMETER
as well as DIAMETER / WIDTH
once per outer loop. Do this beforehand.
len(colors)
also won't change and can be replaced by a constant. In fact, I'd probably write that function as
def color(i):
if i == ITERATIONS:
return"#000000"else:
return ("#0000AA", "#88DDFF", "#FF8800", "#000000")[(i//2) % 4]# are you sure you don't want ("#0000AA", "#88DDFF", "#FF8800")[(i//2) % 3] ?
Also, x**2
is slower than x*x
(because the x**y
operator doesn't shortcut for the trivial case of y==2
), so you can speed that calculation up a bit.
Solution 5:
Most time is spent in the inner loop in mandel(). z*z
instead of z**2
had a slight effect. There is not much else to speed up there that I can see. Removing constants from other loops had little effect, though I tend to prefer doing so. Choosing ITERATIONS so that ITERATIONS//2 % len(colors) == len(colors)-1
, as in 46 //2 % 4 == 3
, allows simplification of the code. Exploiting symmetry around the x-axis cuts time in half. Starting imag at 0 avoids the roundoff error of 300 subtractions from +/- DIAMETER / 2 and results in a clean center line in the image.
from tkinter import *
ITERATIONS = 46
WIDTH, HEIGHT = 601, 601 # odd for centering and exploiting symmetry
DIAMETER = 2.5
start = (-.5 - DIAMETER / 2, 0) # Start y on centerline
d_over_h = DIAMETER / HEIGHT
d_over_w = DIAMETER / WIDTH
def mandel(c):
z = 0for i in range(ITERATIONS):
z = z*z + c
if abs(z) > 2:
return i
return ITERATIONS
root = Tk()
canvas = Canvas(root, width=WIDTH,height=HEIGHT)
canvas.pack()
img = PhotoImage(width=WIDTH, height=HEIGHT)
canvas.create_image(((WIDTH+1)//2, (HEIGHT+1)//2), image=img, state="normal")real, imag = start
colors = ("#0000AA", "#88DDFF", "#FF8800", "#000000")
ncolors = len(colors)
yrange = range(HEIGHT//2, -1, -1) # up from centerline
ymax = HEIGHT - 1for x in range(WIDTH):
for y in yrange:
i = mandel(complex(real, imag))
color = colors[i//2 % ncolors]
img.put(color, (x, y))
img.put(color, (x, ymax - y))
imag += d_over_h
imag = start[1]
real += d_over_w
mainloop()
Post a Comment for "How To Render Mandelbrot Set Faster?"