How to render Mandelbrot Set faster?

前端 未结 6 1490
臣服心动
臣服心动 2020-12-20 07:34

I\'m currently drawing the Mandelbrot set pixel by pixel with PhotoImage and tkinter. I\'m using basically the algorithm directly with no modifications. Are there methods to

相关标签:
6条回答
  • 2020-12-20 07:37

    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
    
    def mandel(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 in range(maxIt):
          z = z * z + c
          if abs(z) >= 2.0:
             return (255-clr[i],0,0)
      return(0,0,0)
    
    def prepare_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 in range(x)]
        ym=[ya + (yb - ya) * ky /y  for ky in range(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 in range(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()
    

    enter image description here

    0 讨论(0)
  • 2020-12-20 07:38

    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.

    0 讨论(0)
  • 2020-12-20 07:55

    Complex numbers in python can be slow, especially if you call abs(x) every iteration. Representing the complex number with c_r and c_i, for real and imaginary parts, reduces the number of calculations you do every iteration.

    def mandel(c):
        z = 0
        for i in range(ITERATIONS):
            z = z**2 + c
            if abs(z) > 2:
                return i     
        return ITERATIONS
    

    instead of z = 0, replace it with z_r,z_i=0,0 we also have to change c in the parameters. Now we have:

    def mandel(c_r,c_i):
        z_r = 0
        z_i = 0
        for i in range(ITERATIONS):
            z = z**2 + c
            if abs(z) > 2:
                return i     
        return ITERATIONS
    

    Instead of using abs(z) > 2, we can now use z_r * z_r + z_i + z_i > 4 Also, we replace z**2 + c with a new version using our new variables (Know that (a+bi)^2 = a^2 - b^2 + 2abi

    def mandel(c_r,c_i):
        z_r = 0
        z_i = 0
        z_r_squared = 0
        z_i_squared = 0
        for i in range(ITERATIONS):
            z_r_squared = z_r * z_r
            z_i_squared = z_i * z_i
            z_r = z_r_squared - z_i_squared + c_r
            z_i = 2 * z_r * z_i + c_i
    
            if z_r_squared + z_r_squared > 4:
                return i     
        return ITERATIONS
    

    Finally, you have to change where the mandelbrot function is called, so

    i = mandel(complex(real, imag))
    

    becomes

    i = mandel(real, imag)
    
    0 讨论(0)
  • 2020-12-20 07:57

    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

    0 讨论(0)
  • 2020-12-20 08:01

    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.

    0 讨论(0)
  • 2020-12-20 08:02

    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 = 0
        for 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 - 1
    
    for 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()
    
    0 讨论(0)
提交回复
热议问题