Speed drawing

In general, creating canvas objects is relatively efficient and rarely causes a performance problem. However, for very complex drawings, you may notice a delay in drawing the canvas. This is particularly noticeable when the display contains a large number of objects or when they contain complex line segments.

One way of improving drawing performance is to draw the canvas as an image. The Python Imaging Library, which was introduced briefly in chapter 5 on page 89, has the facility to draw directly to a GIF file. We will use this facility to draw a quite challenging image. I always found Mandelbrot diagrams, now generally referred to as fractals, fascinating. While I was looking at Douglas A. Young's The X Window System: Programming and Applications with Xt, I noticed the fractal on the cover. Here is an adaptation of the fractal in Python, Tkinter and PIL.

fractal.py from Tkinter import *

import Pmw, AppShell, Image, ImageDraw, os class Palette:

def getpalette(self):

# flatten the palette palette = []

def loadpalette(self, cells): import random for i in range(cells-2):

self.palette.append((

random.choice(range(0, 255)), # red random.choice(range(0, 255)), # green random.choice(range(0, 255)))) # blue class Fractal(AppShell.AppShell): usecommandarea = 1

appname = 'Fractal Demonstration1

frameWidth = 780 frameHeight = 580

def createButtons(self):

self.buttonAdd('Save', helpMessage='Save current image1, statusMessage='Write current image as "out.gif" command=self.save) self.buttonAdd('Close', helpMessage='Close Screen1, statusMessage='Exit', command=self.close)

def createDisplay(self):

self.width = self.root.winfo_width()-10 self.height = self.root.winfo_height()-95 self.form = self.createcomponent('form', (), None, Frame, (self.interior(),), width=self.width, height=self.height) self.form.pack(side=TOP, expand=YES, fill=BOTH) self.im = Image.new("P", (self.width, self.height), 0) self.d = ImageDraw.ImageDraw(self.im) self.d.setfill(0)

self.label = self.createcomponent('label', (), None, Label, (self.form,),)

self.label.pack()

def initData(self): self.depth = 20 self.origin = -1.4+1.0j self.range = 2.0 self.maxDistance = 4.0 self.ncolors = 256 self.rgb = Palette() self.rgb.loadpalette(255) self.save = FALSE

def createlmage(self):

self.updateProgress(0, self.height) for y in range(self.height):

for x in range(self.width): z = 0j k = complex(self.origin.real + \

h float(x)/float(self.width)*self.range, self.origin.imag - \

float(y) / float(self.height)*self.range) # calculate z = (z +k) * (z + k) over and over for iteration in range(self.depth): real_part = z.real + k.real imag_part = z.imag + k.imag del z z = complex(real_part * real_part - imag_part * \

imag_part, 2 * real_part * imag_part) distance = z.real * z.real + z.imag * z.imag if distance >= self.maxDistance:

cidx = int(distance % self.ncolors) self.pixel(x, y, cidx) break self.updateProgress(y) self.updateProgress(self.height, self.height) self.im.putpalette(self.rgb.getpalette()) self.im.save("out.gif") /O

self.img = PhotoImage(file=Mout.gif") self.label['image'] = self.img po def pixel(self, x, y, color): self.d.setink(color) self.d.point((x, y))

def save(self):

self.save = TRUE

self.updateMessageBar('Saved as "out.gif"1)

def close(self):

if not self.save:

os.unlink("out.gif") self.quit()

def createlnterface(self):

AppShell.AppShell.createlnterface(self)

self.createButtons()

self.initData()

self.createDisplay()

fractal = Fractal()

fractal.root.after(10, fractal.createImage()) fractal.run()

Code comments

1 The Palette class is responsible for creating a random palette (loadpalette) and generating an RGB list for inclusion in the GIF image (getpalette).

2 We create a new image, specifying pixel mode (p), and we instantiate the ImageDraw class, which provides basic drawing functions to the image. We fill the image with black, initially with the setfill method.

self.im = Image.new("P", (self.width, self.height), 0) self.d = ImageDraw.ImageDraw(self.im) self.d.setfill(0)

3 At the center of the computational loop, we select a color and set the corresponding pixel to that color.

cidx = int(distance % self.ncolors) self.pixel(x, y, cidx)

4 When complete, we add the palette to the image, save it as a GIF file, and then load the image as a Tkinter Photolmage.

self.im.putpalette(self.rgb.getpalette())

self.im.save("out.gif")

self.img = PhotoImage(file=Mout.gif")

self.label['image'] = self.img

5 The pixel method is very simple. We set the color of the ink and place the pixel at the specified x,y coordinate.

def pixel(self, x, y, color): self.d.setink(color) self.d.point((x, y))

Running fractal.py on a moderately fast workstation will generate an 800 X 600 pixel image in about 2-3 minutes. If you are interested, you will find slowfractal.py online. This version is written using Tkinter canvas methods and it takes considerably longer to complete.

Figure 10.13 Generating fractals

10.8 Summary

This is another important chapter for those readers who want to manipulate objects on a screen. Whether building a drawing program or a drafting system of a UML editor, the principles are similar and you will find many of the techniques are readily transferable.

One thing is very important when designing interfaces such as these: think carefully about the range of pointing devices that may be used with your program. While it is quite easy to drag an object to resize it when you are using a mouse, it may not be as easy if the user has a trackball or is using one of the embedded keyboard mouse buttons.

CHAPTER 11

0 0

Post a comment