如何更快地渲染曼德布罗集?

1 投票
6 回答
7773 浏览
提问于 2025-04-28 08:53

我现在正在用PhotoImage和tkinter逐个像素地绘制曼德尔布罗特集合。我基本上是直接使用算法,没有做任何修改。有没有什么方法可以让计算更快呢?比如快速填充大面积的颜色,或者预先计算一些常量?

代码的一部分:

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 个回答

1

大部分时间都花在了mandel()函数的内部循环里。用z*z代替z**2会有一点点效果。除此之外,我没看到还有什么能加速的地方。虽然我通常喜欢去掉其他循环中的常量,但这样做的效果不大。选择ITERATIONS时,让ITERATIONS//2 % len(colors) == len(colors)-1,比如46 //2 % 4 == 3,可以让代码更简单。利用x轴的对称性可以把时间缩短一半。从0开始imag可以避免从+/- 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()
1

纯Python在处理数字计算时速度并不快。想要加快速度,最简单的方法就是使用PyPy这个工具。如果用PyPy还是不够快,那就可以试着用numpy来优化你的算法,让它们更高效。如果这样做还是不够快,那就可以考虑使用Cython,或者干脆用C语言重写一遍。

2

为了稍微提高一点速度(但这并不足以弥补编译语言和解释语言之间的差距),你可以提前计算一些值。

现在,你在每次内循环中都在计算 DIAMETER / HEIGHT,而在每次外循环中也在计算 CENTER[1] - 0.5 * DIAMETERDIAMETER / 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 的简单情况下不会优化),所以你可以稍微加快这个计算。

3

这是我的代码,它可以在8到9秒内绘制一个640x480的曼德尔布集合。

每个像素最多进行256次计算,使用了一份颜色映射列表,只需要一次性把结果放到PhotoImage中,并且不依赖对称性,所以它可以显示集合的任何缩放区域。

可惜的是,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()

在这里输入图片描述

5

一次只设置一个像素可能是导致程序变慢的主要原因。与其每次为每个像素都调用一次put,不如先计算整行像素,或者整个像素矩阵,然后在循环结束时只调用一次put。

你可以在这里找到一个例子,其他地方也有类似的内容: https://web.archive.org/web/20170512214049/http://tkinter.unpythonic.net:80/wiki/PhotoImage#Fill_Many_Pixels_at_Once

撰写回答