Sunday, 24 June 2012

Make Rye move

The next stage is to draw something on the canvas and make it move. There are various ways to create an image on the canvas. One simple way would be to draw the objects you want to display in a drawing package and store each one as some kind of picture file like a .gif or .png file. There will be lots of images needed eventually, so there would be lots of separate pictures to manage. Another way would be to draw the objects in the program so there are no external pictures. This is the way I chose, partly as an example of how to draw these objects and use them. We need to use Python Imaging Library module to draw the pictures and a module to make these work with the Tkinter module. At the command prompt, with the R-Pi connected to the internet, type

sudo apt-get install python-imaging python-imaging-tk

This will install the two modules in one command.

Lets start with creating a simple object to draw. There will be lots of drawings later on, so lets start a new module of our own to keep this separate from the main code. I have chosen to call it ryepics.py. Create a new file in the rye folder called ryepics.py and open the empty file in Geany. The code is below, and you can download it from here.

#!/usr/bin/env python

from PIL import Image, ImageDraw, ImageTk 1

def createImages(): 2
    #create a dict for the images to return
    imageList = {} 3
   
    #start with rye
    iRye=Image.new('RGBA',(20,20)) 4
    draw = ImageDraw.Draw(iRye) 5
    draw.ellipse((1,1,19,19), fill=(0,255,0), outline=(0,40,0)) 6
    draw.point((7,7),fill=(255,255,200)) 7
    draw.point((7,8),fill=(255,255,200))
    draw.point((8,7),fill=(255,255,200))
    draw.point((8,8),fill=(255,255,200))
   
    drye=ImageTk.PhotoImage(iRye) 8
    imageList['G']=drye 9
   
    return imageList 10


The import line, 1, uses the new modules we have just installed on R-Pi. Modules can use functions, so we define ours on line 2, called createImages(). Next we introduce one of the superb built-in parts of Python, a dictionary 3. More about that below. We now use the Image object from the imported Image module. Line 4 creates an empty image that is 20x20 pixels. It is an RGBA type, that is it has the full range of colours and transparency. Because we didn't define a coloured background it will be transparent, which is really great for our needs. Line 5 creates a drawing object we use to add to the image. Line 6 draws an ellipse in a rectangle. All references to the location on the images and the canvas are based on the normal computer layout that top left is (0,0). The x axis increases to the right and the y axis increases down the screen. The rectangle is defined by the four numbers top left is (1,1) and bottom left is (19,19). This draws a circle, because the rectangle is square. It is filled with green  - the colours are defines as red-green-blue in the range 0-255. The outline is dark green. To make the blob look a bit smarter I added 4 bright pixels from line 7.

The image now needs to be converted to something that a canvas understands which is what line 8 does. At this point we don't want to use the image, we want to store it for later use. We add the image to the dictionary we created earlier in line 9. In later stages we will add other images to the dictionary, but at this stage we just return the imageList dictionary from the function in 10. Functions can always return something, but don't have to.

So what is this dictionary? It is one of the built-in types of collection that Python offers. There are four very commonly used collections: string, tuple, list and dictionary. Programming languages usually have some kind of string to hold pieces of text and they don't usually get thought of as a collection, but in Python it is worth thinking of it in that way, because the same way of breaking up a collection apply to strings too. A tuple is a group of objects and so is a list. Tuples (and strings) are immutable - once created they can't be changed, whereas lists can be changed, sorted appended to etc. Individual elements of lists, tuples and strings can be accessed by their numeric index. Dictionaries are like lists; they are mutable (can be changed) but their index can be any key you like, each key in a dictionary must be unique. There is a lot that you can do with these collections and you might like to read the Python online manual to find out more.

In our imageList dictionary in line 9 we used a key of 'G' to store the image. We can refer to the green blob we have drawn by the key 'G' when we want it later.

Now we have an function that will create an image list that we can use. We need to amend the original start.py program to add some code to use it. I copied the original start.py to moveRye.py which you can download from here

#!/usr/bin/env python

from Tkinter import *

import ryepics 1

def RyeSetup():
    global cnvs 2
    global ryeId 3
    # GUI initialisation
    ryewin.title('Raspberry Rye')
    ryewin.grid()
    # create the canvas for the game
    cnvs = Canvas(ryewin, height=300, width=300)
    cnvs.grid(column=0, row=0, columnspan=4)
   
    # draw Rye at position 2,2 4
    sx=2*20+10
    sy=2*20+10
    ryeId=cnvs.create_image(sx,sy,image=imageList['G']) 5
   
    cnvs.bind("<Key-Down>", ryeDown) 6
    cnvs.bind("<Key-Up>", ryeUp)
    cnvs.bind("<Key-Left>", ryeLeft)
    cnvs.bind("<Key-Right>", ryeRight)
   
    # set the canvas to receive the focus, so keypresses are captured
    cnvs.focus_set() 7

def ryeDown(event): 8
    cnvs.move(ryeId, 0, 20) 9

def ryeUp(event):
    cnvs.move(ryeId, 0, -20)

def ryeLeft(event):
    cnvs.move(ryeId, -20, 0)

def ryeRight(event):
    cnvs.move(ryeId, 20, 0)

if __name__ == "__main__":
    ryewin=Tk()
    imageList=ryepics.createImages() 10
    ryeId=0 11
    cnvs=0 12
    RyeSetup()
    ryewin.mainloop()


We need to import the ryepics module we have just created. Python will find the module if it is in the same folder as the importing program. We import it in line 1. Lines 2 and 3 are to overcome the issue that is we want to share data across functions we need to make the variables global. This is a horrible fudge which we will address later, but for now it keeps things simple.

To display the image on the canvas we created last time we need to decide where we want it. I made the blob an object 20x20 pixels, and everything will be based on this scaling, so to use 'Rye coordinates', based on 20x20 objects I will need to multiply any position by 20. Images are also placed by their centre by default. To define where we want to place the Rye blob 4 we need to multiply the x & y position by 20 and add 10 (half of 20) to get the centre. We then use the canvas create_image method to place the blob in place. We use the imageList with the key 'G' to display the blob 5.  When we create an object on the canvas it returns an id which we can use later to refer to that object. We call it ryeId and because we want to use it elsewhere it is one of the variables we declared as global (that fudge again).

Last time we said that GUI programming is event-driven. So let's use respond to some events. The four lines at 6 declare some events to respond to. We are binding a keyboard press on the canvas to a function. So when the down arrow key is pressed the function called ryeDown will be called and similarly for the other three arrow keys. To make sure that the canvas has the focus to receive any keypresses we force the canvas to have the focus on line 7.

To respond to the events we define four functions; these are sometimes called event handlers. These functions need a parameter (we call it event) which has extra information in it. We ignore it, but we must declare it, as in line 8. Line 9 shows we can move a canvas object when we know its id. We move it down 20 pixels and in the other event handlers up, left or right.

So now we come to how the imageList we created in the separate module is made available here. In line 10 we call the createImage() function in the external module and store the dictionary it returns as imageList.

Lines 11 & 12 are there just to create the fudge of global variables that can be shared by the functions.

If you run the program ./moveRye.py in a terminal, as before, you should see a window with a green blob that moves in response to the arrow keys.

No comments:

Post a Comment

Thank you for taking the time to add a comment.

I have decided, regrettably, to review comments before they are published to ensure that offensive or inappropriate comments do not get shown.

Genuine comments are always welcome and will be published as quickly as possible.