我正在使用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()
答案 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()
答案 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**2
比x*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)