如何更快地渲染Mandelbrot Set?

时间:2014-10-19 06:22:30

标签: python tkinter mandelbrot

我正在使用PhotoImage和tkinter逐像素地绘制Mandelbrot。我基本上使用的算法没有任何修改。有没有办法让计算更快?也许快速填充大面积的颜色,或预先计算常数?

部分代码:

ITERATIONS = 50
WIDTH, HEIGHT = 600, 600
CENTER = (-.5, 0)
DIAMETER = 2.5

def mandel(c):
    z = 0
    for i in range(ITERATIONS):
        z = z**2 + 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/2, HEIGHT/2), image=img, state="normal")


real = CENTER[0] - 0.5 * DIAMETER
imag = CENTER[1] - 0.5 * DIAMETER

def color(i):
    colors = ("#0000AA", "#88DDFF", "#FF8800", "#000000")
    if i == ITERATIONS:
        return colors[-1]
    else:
        choice = (i//2) % len(colors)
    return colors[choice]

for x in range(WIDTH):
    for y in range(HEIGHT):
        i = mandel(complex(real, imag))

        img.put(color(i), (x, HEIGHT-y))

        imag += DIAMETER / HEIGHT
    imag = CENTER[1] - 0.5 * DIAMETER
    real += DIAMETER / WIDTH

mainloop()

6 个答案:

答案 0 :(得分:5)

一次设置一个像素可能是减速的主要来源。不是为每个像素调用put,而是计算整行像素或整个像素矩阵,然后在循环结束时调用一次。

您可以在此处找到示例,其中包括https://web.archive.org/web/20170512214049/http://tkinter.unpythonic.net:80/wiki/PhotoImage#Fill_Many_Pixels_at_Once

答案 1 :(得分:2)

这是我的代码,它在8-9秒内绘制640x480 Mandelbrot。

每个像素最多256次迭代,使用颜色映射列表,“put”仅一次到PhotoImage并且不依赖于symetry,因此它可以显示集合的任何缩放区域。

令人遗憾的是,Tkinter不允许访问PhotoImage的栅格信息作为缓冲区,并且需要笨拙的字符串。

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

答案 2 :(得分:1)

对于数字代码,纯python并不那么快。加快速度的最简单方法是使用PyPy。如果这还不够快,请使用numpy对您的算法进行矢量化。如果仍然不够快,请使用Cython,或考虑用C语言重写它。

答案 3 :(得分:1)

为了适度提高速度(但不足以抵消编译语言和解释语言之间的差异),您可以预先计算一些值。

现在,您每个内循环计算DIAMETER / HEIGHT一次,每个外循环计算CENTER[1] - 0.5 * DIAMETER以及DIAMETER / WIDTH一次。事先这样做。

len(colors)也不会改变,可以用常量替换。事实上,我可能将该功能写为

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] ?

另外,x**2x*x慢(因为x**y运算符对于y==2的简单情况并不快捷,所以你可以加快速度计算了一下。

答案 4 :(得分:1)

大多数时间花在mandel()的内部循环中。 z*z代替z**2产生了轻微的影响。我能看到的那里没有太多可以加速的东西。从其他循环中删除常量几乎没有影响,但我倾向于这样做。选择ITERATIONS,以便ITERATIONS//2 % len(colors) == len(colors)-1中的46 //2 % 4 == 3允许简化代码。利用x轴周围的对称性将时间减少一半。从0开始成像避免了+/- DIAMETER / 2的300次减法的舍入误差,并在图像中产生干净的中心线。

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()

答案 5 :(得分:0)

python中的复数可能很慢,尤其是如果您每次迭代都调用abs(x)时。对于实部和虚部,用c_r和c_i表示复数可以减少每次迭代的计算量。

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

z = 0代替z_r,z_i=0,0 我们还必须更改参数c。 现在我们有:

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

我们现在可以使用abs(z) > 2而不是使用z_r * z_r + z_i + z_i > 4 此外,我们使用新变量将z**2 + c替换为新版本 (知道(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

最后,您必须更改调用mandelbrot函数的位置,所以

i = mandel(complex(real, imag))

成为

i = mandel(real, imag)