Mandelbrot with Tkinter and ShedSkin

TrustyTony 0 Tallied Votes 2K Views Share

Here is Mandelbrot set viewer in Tkinter and modules for shedskining (included compiled modules for windows in the zip file):

# mandelsh.py

def mandel(real, imag, max_iterations=20):
    '''determines if a point is in the Mandelbrot set based on deciding if,
       after a maximum allowed number of iterations, the absolute value of
       the resulting number is greater or equal to 2.'''
    z_real, z_imag = 0.0, 0.0
    for i in range(0, max_iterations):
        z_real, z_imag = ( z_real*z_real - z_imag*z_imag + real,
                           2*z_real*z_imag + imag )
        if (z_real*z_real + z_imag*z_imag) >= 4:
            return i % max_iterations
    return -1

res = 0
res = mandel(1.0, 1.0, 128)

my_mandel.py

#!/usr/bin/python
from __future__ import print_function
import sys
import time

from py_kohn_bmp import kohn_bmp

from mandelsh import mandel
colors = [[0, 100, 100], [127, 0, 0], [127, 127, 0], [0, 127, 0], [0, 255, 0],
           [0, 255, 0], [0, 255, 127], [0, 127, 127], [255, 0, 0],
           [0, 127, 255], [0, 0, 255], [127, 0, 255],
           [127, 0, 255], [255, 0, 255], [255, 0, 127],
           [127, 127, 0], [0, 0, 0]]

# Changing the values below will change the resulting image
def mandel_file(cx=-0.7, cy=0.0, size=3.2, max_iterations=512, width = 640, height = 480):
    t0 = time.clock()
    try:
        increment = min(size / width, size / height)
        proportion = 1.0 * width / height
        start_real, start_imag = cx - increment * width/2, cy - increment * height/2

        mandel_pos = "%f %fi_%f_%i" % (cx, cy, size, max_iterations)
        fname = "m%s.bmp" % mandel_pos
        my_bmp = kohn_bmp(fname, width, height, 3)

        current_y = start_imag
        for y in range(height):
            if not y % 10:
                sys.stdout.write('\rrow %i / %i'  % (y + 1, height))
            sys.stdout.flush()
            current_x = start_real

            for x in range(width):
                c = mandel(current_x, current_y, max_iterations)
                c %= len(colors) 
                current_x += increment
                my_bmp.write_pixel(colors[c][0], colors[c][1], colors[c][2])
            current_y += increment

        print("\r%.3f s             " % (time.clock() - t0))
        my_bmp.close()
        return fname
    except IOError as e:
         print(e,'x %i, y %i, current_x %f, colors %s' %(x, y, current_x, colors))
        

if __name__ == '__main__':
    mf = mandel_file(max_iterations=256)# -0.743643, 0.131826, 0.00001,
                                #width=600, height = 600,
                                #max_iterations=1024))
    #{ not in shedskin
    import webbrowser
    webbrowser.open(mf)
    #}
# py_kohn_bmp - Copyright 2007 by Michael Kohn
# http://www.mikekohn.net/
# mike@mikekohn.net
#
# Feel free to use this class in your own programs (commerical or not)
# as long as you don't remove my name, email address, webpage, or copyright.
#
# Example how to use:
#
# my_bmp.kohn_bmp("out.bmp",image_width,image_height,3) <-- depth of 3 is color
# my_bmp.write_pixel(red,green,blue)    <-- do this width*height times
# my_bmp.close()
#
# if depth is set to 1 (black and white image) call write_pixel_bw(y) where
# y is between 0 and 255
from __future__ import print_function
class kohn_bmp:
    def __init__(self, filename, width, height, depth):
        self.width = width
        self.height = height
        self.depth = depth
        self.xpos = 0

        self.width_bytes = width * depth
        if (self.width_bytes % 4) != 0:
            self.width_bytes = self.width_bytes + (4 - (self.width_bytes % 4))

        self.out=open(filename,"wb")
        self.out.write("BM")                    # magic number
        
        self.write_int(self.width_bytes * height + 54 + (1024 if depth==1 else 0))

        self.write_word(0)
        self.write_word(0)
        self.write_int(54 + (1024 if depth==1 else 0))
        self.write_int(40)                               # header_size
        self.write_int(width)                        # width
        self.write_int(height)                       # height
        self.write_word(1)                               # planes
        self.write_word(depth * 8)                   # bits per pixel
        self.write_int(0)                                # compression
        self.write_int(self.width_bytes * height * depth) # image_size
        self.write_int(0)                                # biXPelsperMetre
        self.write_int(0)                                # biYPelsperMetre

        if depth == 1:
            self.write_int(256)                          # colors used
            self.write_int(256)                          # colors important
            self.out.write(''.join(chr(c) * 3 + chr(0) for c in range(256)))
        else:
            self.write_int(0)                            # colors used - 0 since 24 bit
            self.write_int(0)                            # colors important - 0 since 24 bit

    def write_int(self, n):
        self.out.write('%c%c%c%c' % ((n&255),(n>>8)&255,(n>>16)&255,(n>>24)&255))

    def write_word(self, n):
        self.out.write('%c%c' % ((n&255),(n>>8)&255))

    def write_pixel_bw(self, y):
        self.out.write(chr(y))
        self.xpos = self.xpos + 1
        if self.xpos == self.width:
            while self.xpos < self.width_bytes:
                self.out.write(chr(0))
                self.xpos = self.xpos + 1
            self.xpos = 0

    def write_pixel(self, red, green, blue):
        self.out.write(chr((blue&255)))
        self.out.write(chr((green&255)))
        self.out.write(chr((red&255)))
        self.xpos = self.xpos+1
        if self.xpos == self.width:
            self.xpos = self.xpos * 3
            while self.xpos < self.width_bytes:
                self.out.write(chr(0))
                self.xpos = self.xpos + 1
            self.xpos = 0

    def close(self):
        self.out.close()

if __name__ == "__main__":
    k = kohn_bmp('test.bmp', 640, 512, 3)
    k.write_int(1234)
    k.write_word(1234)
    k.write_pixel_bw(123)
    k.write_pixel(123,123,123)
    k.close()
""" class for Python Tkinter for Mandelbrot set
    compile modules with shedskin for best performance
    http://code.google.com/p/shedskin/
    debug prints left, not much commenting"""
try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

import os
from PIL import Image, ImageTk

from my_mandel import mandel_file

class MandelbrotTk(tk.Tk):
    def __init__(self, width=640, height=480, image_file='m-1.000000 0.000000i_3.500000_240.bmp'):
        tk.Tk.__init__(self)
        self.canvas = tk.Canvas(width=width, height=height)
        self.geometry("%sx%s+100+100" % (width,height+40))
        self.canvas.pack()
        self.item = None
        self.label = tk.Label(self.canvas, font=('courier', 16, 'bold'), 
            width=10)
        self.label.pack(side=tk.BOTTOM)
        self.cx = None
        self.image_file = image_file
        self.canvas.image = self.load_image()
        self.x, self.y, self.size = None, None, (0,)
        # Linux
        self.bind("<Button-1>", self.zoom_in)
        self.bind("<Button-3>", self.zoom_out)
        # Windows
        self.bind("<MouseWheel>", self.mouse_wheel)
  
        
    def load_image(self):   
        self.canvas.delete('m')
        photo = Image.open(self.image_file)
        im = ImageTk.PhotoImage(photo)
        self.canvas.create_image(0, 0, image=im, anchor='nw', tag='m')
        self.canvas.pack(fill=tk.BOTH, expand=tk.YES)

        if self.cx is None:
            # extract parameters from name
            ipos = self.image_file.find('i')
            self.cx, self.cy = map(float, self.image_file[1:ipos].split())
            self.fsize, rest = self.image_file[ipos+2:].split('_', 1)
            self.fsize = float(self.fsize)
            self.max_iterations = int(rest.split('.',1)[0])

        self.max_iterations = min(max(self.max_iterations, 256), 10000)
        self.label['text'] = self.max_iterations
        self.step = self.fsize / max(im.width(), im.height())
        print('cx: %f, cy: %f, fsize: %f, max_iterations: %i, step: %f'  %
              (self.cx, self.cy, self.fsize, self.max_iterations, self.step))
        return im

    def zoom_in(self, event):
        print('ZOOM in')
        xshift, yshift = event.x - self.canvas.image.width() // 2, self.canvas.image.height() // 2 - event.y
        print('Click at %i, %i, xshift %i, yshift %i' % (event.x, event.y, xshift, yshift))

        self.fsize /= 4.0
        self.cx, self.cy = xshift * self.step + self.cx, yshift * self.step + self.cy
        self.image_file = mandel_file(self.cx, self.cy, self.fsize, self.max_iterations)
        self.canvas.image = self.load_image()

    def zoom_out(self, event):
        print('zoom out')
        xshift, yshift = event.x - self.canvas.image.width() // 2, self.canvas.image.height() // 2 - event.y
        print('Click at %i, %i, xshift %i, yshift %i' % (event.x, event.y, xshift, yshift))
        self.fsize *= 4.0
        self.cx, self.cy =  xshift * self.step + self.cx, yshift * self.step + self.cy
        self.image_file = mandel_file(self.cx, self.cy, self.fsize, self.max_iterations)
        self.canvas.image = self.load_image()

    def mouse_wheel(self, event):
        """respond to Linux or Windows wheel event"""
        print('Mousewheel event.num %s, ent.delta %s' % (event.num, event.delta))
        if event.num == 5 or event.delta == -120:
            self.max_iterations -= 120
        if event.num == 4 or event.delta == 120:
            self.max_iterations += 120
        self.label['text'] = self.max_iterations

if __name__ == '__main__':
    if not os.path.isfile('m-1.000000 0.000000i_3.500000_240.bmp'):
        mandel_file(-1.000000, 0.000000, 3.500000, 240)
    mand = MandelbrotTk()
    mand.mainloop()
TrustyTony 888 ex-Moderator Team Colleague Featured Poster

Also included in zip are separate modules compiled. I would like to know why my_mandel.pyd runs only from direct execution of click_sel.py, not inside Idle, Python source instead have no problems running with other modules compiled (bmp and mandelbrot main iteration). Kohn code is quite heavily massaged by me. Lines 15 and 16 in mandelsh are hints for Shedskin (functions need to be called for type analysis).

Mouse wheel controls the number of iterations for bailout and left click zooms in right click zooms out (it would make sense to record the file names and not actually recalculate, but now does recalculation).

TrustyTony 888 ex-Moderator Team Colleague Featured Poster

Linux part of mousewheel routine was droped from vegaseat's code for mousewheel, so here the init again, and possibility to recalculate current image with click of mousewheel (button-2)

def __init__(self, width=640, height=480, image_file='m-1.000000 0.000000i_3.500000_240.bmp'):
        tk.Tk.__init__(self)
        self.canvas = tk.Canvas(width=width, height=height)
        self.geometry("%sx%s+100+100" % (width,height+40))
        self.canvas.pack()
        self.item = None
        self.label = tk.Label(self.canvas, font=('courier', 16, 'bold'), 
            width=10)
        self.label.pack(side=tk.BOTTOM)
        self.cx = None
        self.image_file = image_file
        self.canvas.image = self.load_image()
        self.x, self.y, self.size = None, None, (0,)
        self.bind("<Button-1>", self.zoom_in)
        self.bind("<Button-2>", self.recalculate)
        self.bind("<Button-3>", self.zoom_out)
        # mousewheel routine by vegaseat:
        # http://www.daniweb.com/software-development/python/threads/191210/1158775#post1158775
        # Linux
        self.bind("<Button-4>", self.mouse_wheel)
        self.bind("<Button-5>", self.mouse_wheel)
        # Windows
        self.bind("<MouseWheel>", self.mouse_wheel)
  
    def recalculate(self, event):
        print('recalculating')
        self.image_file = mandel_file(self.cx, self.cy, self.fsize, self.max_iterations)
        self.canvas.image = self.load_image()
TrustyTony 888 ex-Moderator Team Colleague Featured Poster

I did some search of preparing palette of colors. Had to add prototype callings to colorsys module to get it compile with shedskin, just added these lines to end of it:

hls_to_rgb(1.0, 0.5, 0.7)
rgb_to_hls(1.0, 0.5, 0.7)
yiq_to_rgb(1.0, 0.5, 0.7)
rgb_to_yiq(1.0, 0.5, 0.7)
hsv_to_rgb(1.0, 0.5, 0.7)
rgb_to_hsv(1.0, 0.5, 0.7)

I added make_colors routine to my_mandel in place of old fixed list:

# colors can be writen over to module by user
def make_colors(number_of_colors, saturation=0.8, value=0.9):
    number_of_colors -= 1  # first reserved for black
    tuples = [hsv_to_rgb(x*1.0/number_of_colors, saturation, value) for x in range(number_of_colors)]
    return [(0,0,0)] + [(int(256*r),int(256*g),int(256*b)) for r,g,b in tuples]

colors = make_colors(256)

I also changed color picker modulo line in inner loop in my_mandel to:

c = (c % (len(colors) - 1) + 1) if c != -1 else 0

mandelsh module has unnecessary modulo itself from old experiments with fixed palette, it can be removed as it does nothing.

Then I check for wrong size palette when generating the image by adding the lines:

if len(my_mandel.colors) != self.max_iterations:
             my_mandel.colors = my_mandel.make_colors(self.max_iterations)
TrustyTony 888 ex-Moderator Team Colleague Featured Poster

Added finally stack of parameters and images and zoom before calculate:

""" class for Python Tkinter for Mandelbrot set
    compile modules with shedskin for best performance
    http://code.google.com/p/shedskin/
    debug prints left, not much commenting"""
try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

import os
import colorsys

from PIL import Image, ImageTk


import my_mandel

base = 'm-1 0i_3.5_240.bmp'
class MandelbrotTk(tk.Tk):
    def __init__(self, width=640, height=480, image_file=base):
        tk.Tk.__init__(self)
        self.canvas = tk.Canvas(width=width, height=height)
        self.geometry("%sx%s+100+100" % (width,height+40))
        self.canvas.pack()
        self.item = None
        self.label = tk.Label(self.canvas, font=('courier', 16, 'bold'), 
            width=10)
        self.label.pack(side=tk.BOTTOM)
        self.cx = None
        self.image_file = image_file
        self.canvas.image = self.load_image()
        self.history =  []

        self.bind("<Button-1>", self.zoom_in)
        self.bind("<Button-2>", self.recalculate)
        self.bind("<Button-3>", self.zoom_out)
        # mousewheel routine by vegaseat http://www.daniweb.com/software-development/python/threads/191210/1158775#post1158775
        # Linux
        self.bind("<Button-4>", self.mouse_wheel)
        self.bind("<Button-5>", self.mouse_wheel)
        # Windows
        self.bind("<MouseWheel>", self.mouse_wheel)
  
    def recalculate(self, event):
        print('recalculating')
        self.image_file = my_mandel.mandel_file(self.cx, self.cy, self.fsize, self.max_iterations)
        self.canvas.image = self.load_image()
              
    def load_image(self):   
        self.canvas.delete('m')
        self.photo = Image.open(self.image_file)
        self.image = ImageTk.PhotoImage(self.photo)
        self.canvas.create_image(0, 0, image=self.image, anchor='nw', tag='m')
        self.canvas.pack(fill=tk.BOTH, expand=tk.YES)

        if self.cx is None:
            # extract parameters from name
            ipos = self.image_file.find('i')
            self.cx, self.cy = map(float, self.image_file[1:ipos].split())
            self.fsize, rest = self.image_file[ipos+2:].split('_', 1)
            self.fsize = float(self.fsize)
            self.max_iterations = int(rest.split('.',1)[0])

        if len(my_mandel.colors) != self.max_iterations:
             my_mandel.colors = my_mandel.make_colors(self.max_iterations)
        self.max_iterations = min(max(self.max_iterations, 128), 40000)
        self.label['text'] = self.max_iterations
        self.step = self.fsize / max(self.image.width(), self.image.height())
        print('cx: %g, cy: %g, fsize: %g, max_iterations: %i, step: %g'  %
              (self.cx, self.cy, self.fsize, self.max_iterations, self.step))
        return self.image

    def zoom_in(self, event):
        self.history.append((self.cx, self.cy, self.fsize, self.max_iterations, self.canvas.image))
        print('ZOOM in')
        xshift, yshift = event.x - self.canvas.image.width() // 2, self.canvas.image.height() // 2 - event.y
        print('Click at %i, %i, xshift %i, yshift %i' % (event.x, event.y, xshift, yshift))

        self.fsize /= 2.0
        quarter = [self.canvas.image.width()/ 4, self.canvas.image.height()/ 4]
        self.cx, self.cy = xshift * self.step + self.cx, yshift * self.step + self.cy
        if quarter[0] < event.x < 3 * quarter[0]  and quarter[1] < event.y < 3 * quarter[1]:
            self.canvas.delete('m')
            im = self.photo.crop((event.x - quarter[0], event.y-quarter[1], event.x + quarter[0], event.y + quarter[1]))
            self.photo = im.resize(self.photo.size)
            self.image = ImageTk.PhotoImage(image=self.photo)
            self.canvas.create_image(0, 0, image=self.image, anchor='nw', tag='m')
            self.canvas.pack(fill=tk.BOTH, expand=tk.YES)
            self.canvas.update()
           
        self.image_file = my_mandel.mandel_file(self.cx, self.cy, self.fsize, self.max_iterations)
        self.canvas.image = self.load_image()

    def zoom_out(self, event):
        print('zoom out')
        if self.history:
            self.cx, self.cy, self.fsize, self.max_iterations, self.canvas.image =  self.history.pop()
            self.label['text'] = self.max_iterations
            self.canvas.delete('m')
            self.canvas.create_image(0, 0, image=self.canvas.image, anchor='nw', tag='m')
            self.canvas.pack(fill=tk.BOTH, expand=tk.YES)
            self.canvas.update()           

    def mouse_wheel(self, event):
        """respond to Linux or Windows wheel event"""
        print('Mousewheel event.num %s, ent.delta %s' % (event.num, event.delta))
        if event.num == 5 or event.delta == -120:
            self.max_iterations -= 120
        if event.num == 4 or event.delta == 120:
            self.max_iterations += 120
        self.label['text'] = self.max_iterations
        my_mandel.colors =  my_mandel.make_colors(self.max_iterations)



if __name__ == '__main__':
    if not os.path.isfile(base):
        my_mandel.mandel_file(-1.000000, 0.000000, 3.500000, 240)
    mand = MandelbrotTk()
    mand.mainloop()
TrustyTony 888 ex-Moderator Team Colleague Featured Poster

Shedskin author asked to include my code as example code and will include the colorsys module with next shedskin (0.9). That version will also include faster complex number support, so the calculation routine may also later be updated to use straight complex arithmetic.

Mark Dufour took basically the original version of the code and transformed it to use bmp from class and not put the core math calculation in separate module, but inside the my_mandel module renamed mandel2 (the current example includes one more primitive version of Mandelbrot allready).

Here I have back ported to this code of Mark my recalculate on mousewheel click and %g format for parameters in filename and new colorscheme with the colorsys.

mandelbrot2.py

# interactive mandelbrot program
# copyright Tony Veijalainen, tony.veijalainen@gmail.com

from __future__ import print_function
import sys
import time
import colorsys

class kohn_bmp:
    '''py_kohn_bmp - Copyright 2007 by Michael Kohn
       http://www.mikekohn.net/
       mike@mikekohn.net'''

    def __init__(self, filename, width, height, depth):
        self.width = width
        self.height = height
        self.depth = depth
        self.xpos = 0

        self.width_bytes = width * depth
        if (self.width_bytes % 4) != 0:
            self.width_bytes = self.width_bytes + (4 - (self.width_bytes % 4))

        self.out=open(filename,"wb")
        self.out.write("BM")                    # magic number
        
        self.write_int(self.width_bytes * height + 54 + (1024 if depth==1 else 0))

        self.write_word(0)
        self.write_word(0)
        self.write_int(54 + (1024 if depth==1 else 0))
        self.write_int(40)                               # header_size
        self.write_int(width)                        # width
        self.write_int(height)                       # height
        self.write_word(1)                               # planes
        self.write_word(depth * 8)                   # bits per pixel
        self.write_int(0)                                # compression
        self.write_int(self.width_bytes * height * depth) # image_size
        self.write_int(0)                                # biXPelsperMetre
        self.write_int(0)                                # biYPelsperMetre

        if depth == 1:
            self.write_int(256)                          # colors used
            self.write_int(256)                          # colors important
            self.out.write(''.join(chr(c) * 3 + chr(0) for c in range(256)))
        else:
            self.write_int(0)                            # colors used - 0 since 24 bit
            self.write_int(0)                            # colors important - 0 since 24 bit

    def write_int(self, n):
        self.out.write('%c%c%c%c' % ((n&255),(n>>8)&255,(n>>16)&255,(n>>24)&255))

    def write_word(self, n):
        self.out.write('%c%c' % ((n&255),(n>>8)&255))

    def write_pixel_bw(self, y):
        self.out.write(chr(y))
        self.xpos = self.xpos + 1
        if self.xpos == self.width:
            while self.xpos < self.width_bytes:
                self.out.write(chr(0))
                self.xpos = self.xpos + 1
            self.xpos = 0

    def write_pixel(self, red, green, blue):
        self.out.write(chr((blue&255)))
        self.out.write(chr((green&255)))
        self.out.write(chr((red&255)))
        self.xpos = self.xpos+1
        if self.xpos == self.width:
            self.xpos = self.xpos * 3
            while self.xpos < self.width_bytes:
                self.out.write(chr(0))
                self.xpos = self.xpos + 1
            self.xpos = 0

    def close(self):
        self.out.close()

def mandel(real, imag, max_iterations=20):
    '''determines if a point is in the Mandelbrot set based on deciding if,
       after a maximum allowed number of iterations, the absolute value of
       the resulting number is greater or equal to 2.'''
    z_real, z_imag = 0.0, 0.0
    for i in range(0, max_iterations):
        z_real, z_imag = ( z_real*z_real - z_imag*z_imag + real,
                           2*z_real*z_imag + imag )
        if (z_real*z_real + z_imag*z_imag) >= 4:
            return i % max_iterations
    return -1


def make_colors(number_of_colors, saturation=0.8, value=0.9):
    number_of_colors -= 1  # first reserved for black
    tuples = [colorsys.hsv_to_rgb(x*1.0/number_of_colors, saturation, value) for x in range(number_of_colors)]
    return [(0,0,0)] + [(int(256*r),int(256*g),int(256*b)) for r,g,b in tuples]

# colors can be writen over to module by user
colors = make_colors(1024)

# Changing the values below will change the resulting image
def mandel_file(cx=-0.7, cy=0.0, size=3.2, max_iterations=512, width = 640, height = 480):
    t0 = time.clock()
    increment = min(size / width, size / height)
    proportion = 1.0 * width / height
    start_real, start_imag = cx - increment * width/2, cy - increment * height/2

    mandel_pos = "%g %gi_%g_%i" % (cx, cy, size, max_iterations)
    fname = "m%s.bmp" % mandel_pos
    my_bmp = kohn_bmp(fname, width, height, 3)

    current_y = start_imag
    for y in range(height):
        if not y % 10:
            sys.stdout.write('\rrow %i / %i'  % (y + 1, height))
        sys.stdout.flush()
        current_x = start_real

        for x in range(width):
            c = mandel(current_x, current_y, max_iterations)
            c = (c % (len(colors) - 1) + 1) if c != -1 else 0 
            current_x += increment
            my_bmp.write_pixel(colors[c][0], colors[c][1], colors[c][2])
        current_y += increment

    print("\r%.3f s             " % (time.clock() - t0))
    my_bmp.close()
    return fname
        

if __name__ == '__main__':
    res = 0
    res = mandel(1.0, 1.0, 128)
    mandel_file(max_iterations=256)

mandelbrot2_main.py (crashes from WindowsXP IDLE run with F5, for me at least. Run from command line or double-click)

# interactive mandelbrot program
# copyright Tony Veijalainen, tony.veijalainen A T gmail.com

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

import os
from PIL import Image, ImageTk

from mandelbrot2 import mandel_file

main_file = 'm-1 0i_3.5_240.bmp'
class MandelbrotTk(tk.Tk):
    def __init__(self, width=640, height=480, image_file=main_file):
        tk.Tk.__init__(self)
        self.canvas = tk.Canvas(width=width, height=height)
        self.geometry("%sx%s+100+100" % (width,height+40))
        self.canvas.pack()
        self.item = None
        self.label = tk.Label(self.canvas, font=('courier', 16, 'bold'), 
            width=10)
        self.label.pack(side=tk.BOTTOM)
        
        if not os.path.isfile(image_file):
            self.parameters_from_fn(image_file)
            self.image_file = mandel_file(self.cx, self.cy, self.fsize, self.max_iterations)
            print('Mainfile %s generated' % self.image_file)
        else:
            self.image_file = image_file
            
        self.canvas.image = self.load_image()
        self.x, self.y, self.size = None, None, (0,)
        self.bind("<Button-2>", self.recalculate)
        # Linux
        self.bind("<Button-1>", self.zoom_in)
        self.bind("<Button-3>", self.zoom_out)
        # Windows
        self.bind("<MouseWheel>", self.mouse_wheel)

    def recalculate(self, event):
        print('recalculating')
        self.image_file = mandel_file(self.cx, self.cy, self.fsize, self.max_iterations)
        self.canvas.image = self.load_image()
        
    def parameters_from_fn(self, fn):
        # extract parameters from name
        ipos = fn.find('i')
        self.cx, self.cy = map(float, fn[1:ipos].split())
        self.fsize, rest = fn[ipos+2:].split('_', 1)
        self.fsize = float(self.fsize)
        self.max_iterations = int(rest.split('.',1)[0])

    def load_image(self):   
        self.canvas.delete('m')
        photo = Image.open(self.image_file)
        im = ImageTk.PhotoImage(photo)
        self.canvas.create_image(0, 0, image=im, anchor='nw', tag='m')
        self.canvas.pack(fill=tk.BOTH, expand=tk.YES)
        self.max_iterations = min(max(self.max_iterations, 256), 10000)
        self.label['text'] = self.max_iterations
        self.step = self.fsize / max(im.width(), im.height())
        print('cx: %f, cy: %f, fsize: %f, max_iterations: %i, step: %f'  %
              (self.cx, self.cy, self.fsize, self.max_iterations, self.step))
        return im

    def zoom_in(self, event):
        print('ZOOM in')
        xshift, yshift = event.x - self.canvas.image.width() // 2, self.canvas.image.height() // 2 - event.y
        self.fsize /= 4.0
        self.cx, self.cy = xshift * self.step + self.cx, yshift * self.step + self.cy
        self.image_file = mandel_file(self.cx, self.cy, self.fsize, self.max_iterations)
        self.canvas.image = self.load_image()

    def zoom_out(self, event):
        print('zoom out')
        xshift, yshift = event.x - self.canvas.image.width() // 2, self.canvas.image.height() // 2 - event.y
        self.fsize *= 4.0
        self.cx, self.cy =  xshift * self.step + self.cx, yshift * self.step + self.cy
        self.image_file = mandel_file(self.cx, self.cy, self.fsize, self.max_iterations)
        self.canvas.image = self.load_image()

    def mouse_wheel(self, event):
        """respond to Linux or Windows wheel event"""
        if event.num == 5 or event.delta == -120:
            self.max_iterations -= 120
        if event.num == 4 or event.delta == 120:
            self.max_iterations += 120
        self.label['text'] = self.max_iterations

if __name__ == '__main__':
    mand = MandelbrotTk()
    mand.mainloop()
TrustyTony 888 ex-Moderator Team Colleague Featured Poster

The code had bug, when base image was ready previously, here the fixed version (after taking out the IOError exception from mandelbrot2, this code does not crash IDLE, but only causes IOError from IDLE):

# interactive mandelbrot program
# copyright Tony Veijalainen, tony.veijalainen@gmail.com

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

import os
from PIL import Image, ImageTk

from mandelbrot2 import mandel_file

main_file = 'm-1 0i_3.5_240.bmp'
class MandelbrotTk(tk.Tk):
    def __init__(self, width=640, height=480, image_file=main_file):
        tk.Tk.__init__(self)
        self.canvas = tk.Canvas(width=width, height=height)
        self.geometry("%sx%s+100+100" % (width,height+40))
        self.canvas.pack()
        self.item = None
        
        self.label = tk.Label(self.canvas, font=('courier', 10))
        self.parameters_from_fn(image_file)
        if not os.path.isfile(image_file):
            self.image_file = mandel_file(self.cx, self.cy, self.fsize, self.max_iterations)
            print('Mainfile %s generated' % self.image_file)
        else:
            self.image_file = image_file           
        self.label.pack(side=tk.BOTTOM)

        self.canvas.image = self.load_image()
        self.x, self.y, self.size = None, None, (0,)
        self.bind("<Button-2>", self.recalculate)
        # Linux
        self.bind("<Button-1>", self.zoom_in)
        self.bind("<Button-3>", self.zoom_out)
        # Windows
        self.bind("<MouseWheel>", self.mouse_wheel)

    def recalculate(self, event):
        print('recalculating')
        self.image_file = mandel_file(self.cx, self.cy, self.fsize, self.max_iterations)
        self.canvas.image = self.load_image()
        self.update_label()
        
    def parameters_from_fn(self, fn):
        # extract parameters from name
        ipos = fn.find('i')
        self.cx, self.cy = map(float, fn[1:ipos].split())
        self.fsize, rest = fn[ipos+2:].split('_', 1)
        self.fsize = float(self.fsize)
        self.max_iterations = int(rest.split('.',1)[0])
        self.update_label()

    def load_image(self):   
        self.canvas.delete('m')
        photo = Image.open(self.image_file)
        im = ImageTk.PhotoImage(photo)
        self.canvas.create_image(0, 0, image=im, anchor='nw', tag='m')
        self.canvas.pack(fill=tk.BOTH, expand=tk.YES)
        self.max_iterations = min(max(self.max_iterations, 256), 10000)
        self.update_label()
        self.step = self.fsize / max(im.width(), im.height())
        return im

    def update_label(self):
        self.label['text'] = ('cx: %f, cy: %f, fsize: %f, max_iterations: %i'  %
              (self.cx, self.cy, self.fsize, self.max_iterations))
        
    def zoom_in(self, event):
        print('ZOOM in')
        xshift, yshift = event.x - self.canvas.image.width() // 2, self.canvas.image.height() // 2 - event.y
        self.fsize /= 4.0
        self.cx, self.cy = xshift * self.step + self.cx, yshift * self.step + self.cy
        self.image_file = mandel_file(self.cx, self.cy, self.fsize, self.max_iterations)
        self.canvas.image = self.load_image()

    def zoom_out(self, event):
        print('zoom out')
        xshift, yshift = event.x - self.canvas.image.width() // 2, self.canvas.image.height() // 2 - event.y
        self.fsize *= 4.0
        self.cx, self.cy =  xshift * self.step + self.cx, yshift * self.step + self.cy
        self.image_file = mandel_file(self.cx, self.cy, self.fsize, self.max_iterations)
        self.canvas.image = self.load_image()

    def mouse_wheel(self, event):
        """respond to Linux or Windows wheel event"""
        if event.num == 5 or event.delta == -120:
            self.max_iterations -= 120
        if event.num == 4 or event.delta == 120:
            self.max_iterations += 120
        self.update_label()

if __name__ == '__main__':
    mand = MandelbrotTk()
    mand.mainloop()
TrustyTony 888 ex-Moderator Team Colleague Featured Poster

Sorry about bumping, but finally from last posts' code line 68, %f's should be replaced with %g format (otherwice smaller size the size shown become zero).

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.