Pythonでtkinterを使ってマンデルブロ集合を描く(ちょっと遅いけど)

しつこくマンデルブロ集合ネタで、pythonで描いてみたときの記録を残しておこう。tkinterについてはpygletをいじっていたときにサンプルプログラムを入手していたりしたが、どこから手に入れたかは忘れてしまった。当然、描画のロジックはC#と基本的には同じであるが、C#ではforループでbreakしたのかどうかをカウンター変数の値で判断していたが、pythonではループの最後でbreakした場合とbreakしなかった場合とはカウンター変数では判断できないので、わざわざフラグを用意して判断していた。しかしその後、else節を使うとbreakが発生したのかどうかを識別できることを知ったので、フラグは使わないように修正した。

f:id:willwealth:20201108134156j:plain
tkinterで描いたマンデルブロ集合

C#のときと変わっているのは色の数と配色だけのはず。処理の仕方がまずいのか分からないが計算処理が終わってから表示されるまでに20秒くらい待たされる。「スクリプトなので、まあ、そんなものなのだろう」と、あまり疑問は抱かないことにしておいた。

import os
import sys
import tkinter as tk
import tkinter.ttk as ttk

class Window(ttk.Frame):

    def __init__(self, master, cvs):
        super().__init__(master, padding="0.75m")
        self.create_ui()
        self.canvas = cvs

    def create_ui(self):
        self.create_menubar()

    def create_menubar(self):
        self.menubar = tk.Menu(self.master)
        self.master.config(menu=self.menubar)
        self.create_file_menu()

    def create_file_menu(self):
        fileMenu = tk.Menu(self.menubar, name="apple")
        fileMenu.add_command(label="Draw", underline=0,
                command=self.draw, compound=tk.LEFT,
                accelerator="")
        fileMenu.add_separator()
        fileMenu.add_command(label="Quit", underline=0,
                    command=self.close, compound=tk.LEFT,
                    accelerator="")
        self.menubar.add_cascade(label="File", underline=0,
                menu=fileMenu)

    def draw(self):
        width = 800
        height = 500
        pixelunit = 0.005
        iteration = 200
        position_x = -2.35
        position_y = 1.25
        palette = [ "black", "deep sky blue", "yellow", "hot pink", "red",
                "orange", "cyan", "dark green", "sea green", "spring green",
                "gold", "white"]
        maxcycle = len(palette) - 1
        print("maxcycle = ",maxcycle)
        
        for i in range(width):
            for j in range(height):
                zlist = []
                z0 = complex( position_x + pixelunit*i, position_y - pixelunit*j)
                zlist.append(z0)
                for k in range(iteration):
                    z = zlist[k]*zlist[k] + z0
                    zlist.append(z)
                    abs = z.real**2 + z.imag**2
                    if abs > 4:
                        self.canvas.create_line(i, j, i + 1, j,fill = palette[0])
                        break
                else:
                    for n in range(1,maxcycle):
                        dz = zlist[-1]-zlist[-1-n]
                        s = dz.real**2 + dz.imag**2
                        if s < pixelunit :
                            self.canvas.create_line(i, j, i + 1, j,fill = palette[n])
                            break
                    else:
                        self.canvas.create_line(i, j, i + 1, j,fill = palette[maxcycle])
        print("calculation finished.")

    def close(self, event=None):
        self.quit()

def main():
    application = tk.Tk()
    application.withdraw()
    application.title("tkinter canvas sample")
    application.option_add("*tearOff", False)
    canvas = tk.Canvas(application, width = 800, height = 500, bg="black")
    window = Window(application,canvas)
    application.protocol("WM_DELETE_WINDOW", window.close)
    application.deiconify()
    application.geometry("803x503")
    canvas.place(x=0, y=0)
    application.mainloop()

if __name__ == "__main__":
    main()