Being Animated


CAPTION: ‘AN APOLOGY’
Voice Over     The BBC would like to apologize for the constant repetition in this show.
    DIFFERENT CAPTION READING: ‘AN APOLOGY’
Voice Over     The BBC would like to apologize for the constant repetition in this show.
    ANIMATION: the ‘five frog curse’.
Cut to the five Gumbys standing in a tight group.

In the last tutorial we looked at the Canvas widget from Tkinter and we also had a look at the concept of cartesian coordinate systems. In our example we could identify specific pixels by counting across and down from the top left of the canvas (ordinarily cartesian systems count left and up).  We also bound the motion event to the canvas and used it to display the current coordinates of the mouse in the canvas.  The reason we did this is because to draw anything on the canvas you need to know the coordinates of where it is to be drawn.

Part of the homework was to identify what it was about the readout which didn’t make sense.   I didn’t see any comments on the tutorial, so I assume you’re baffled by what didn’t make sense.

Answer: if you move the mouse around enough, you can get pixel positions of (0,0) and (101,101).  This means that there are 102 pixels (from 1 to 100 is 100 plus one for each of 0 and 101 gives 102).  This is odd because when we set up the program we specifically said we wanted both the height and width to be 100 pixels.   Where are these two extra pixels in each direction coming from?  If you have a screen magnifier you can see it yourself, but you can also see it on the enlarged picture from the previous tutorial.  There is a white border around the blue canvas area:

It is this border where the extra pixels come from.

The canvas widget has a variety of methods for drawing stuff on the canvas area.  To use these methods you need to provide start and end coordinates.  Here is a yellow line drawn in the centre of the canvas from top to bottom:

To create this I added:

    start = (50,0)
    end = (50,100)
    self.canvas.create_line(start, end, fill = "yellow")

to the earlier code after the self.canvas.bind line.  The thing to note here is the use of the coordinate system we discussed.

Exercise 1: swap the values of start and end.  What difference does this make?  Keep end static and try some different combinations for start.  Try to predict what it will look like before you run the program.

Exercise 2: change the code so that the line runs left to right through the middle rather than up and down.

Our next task is to try to animate this line.  We already have code to track mouse movements over the canvas, so let’s repurpose it to draw a line where the mouse pointer is (removing the code for the line in the middle):

# -*- coding: utf-8 -*-
# canvasLine2.py
from Tkinter import *

formatString = "x: %03d, y: %03d"

class Canvassing():
  def __init__(self, parent = None):
    self.canvas = Canvas(width=100,height=100, bg = "blue")
    self.canvas.pack()
    self.readout = Label(text="This is a label")
    self.readout.pack()
    self.canvas.bind("<Motion>",self.updateCoordinates)

  def updateCoordinates(self,event):
    self.readout.config(text = formatString%(event.x, event.y))
    start = (event.x, 0)
    end = (event.x, 100)
    self.canvas.create_line(start, end, fill = "yellow")

Canvassing()
mainloop()

If you run this and move the mouse around you will see something like this:

What’s going on here is that we are drawing new lines, but we aren’t erasing the old ones.  The computer, being “incredibly fast, accurate and stupid” doesn’t realise that we’re only interested in having one of the lines we’ve drawn present at any one time. We could recycle a single line moving it around, but that would require me to explain the concept of deltas and offsets, which you’ll need to look up for yourself.  Instead, we’re going to delete each line and draw a new one each time the mouse is moved.

# -*- coding: utf-8 -*-
#canvasLine2B.py
from Tkinter import *

formatString = "x: %03d, y: %03d"

class Canvassing():
  def __init__(self, parent = None):
    self.canvas = Canvas(width=100,height=100, bg = "blue")
    self.canvas.pack()
    self.readout = Label(text="This is a label")
    self.readout.pack()
    self.canvas.bind("<Motion>",self.updateCoordinates)
    self.line = None

  def updateCoordinates(self,event):  # really should rename this as we're doing something different now
    self.readout.config(text = formatString%(event.x, event.y))
    start = (event.x, 0)
    end = (event.x, 100)
    self.canvas.delete(self.line) # first time through this will be None
    # but Tkinter is ok with that
    self.line = self.canvas.create_line(start, end, fill = "yellow")

Canvassing()
mainloop()

Now when you run the code you should get a single vertical line which follows your mouse pointer as it moves over the canvas.  Hey presto! You’ve just done your first animation.  Animation on computers involves deleting stuff one the screen which is out of date (or “painting” over it) and then replacing it with new stuff.  The value of self.line here is just an integer.  This is a reference that Tkinter uses to identify the objects it has drawn on the screen.

Exercise: add a print statement to print out the value of self.line for each call to updateCoordinates()

Exercise: change the code so that instead of a vertical line there is a cross hair (with a horizontal line running across the canvas) following the mouse pointer.  Hint: you need to draw two lines

Extra points: make the cross hair only 21 pixels wide centred on the mouse pointer.

Extra extra points: why 21 pixels and not 20?

9 Responses to Being Animated

  1. Pingback: Links 28/6/2012: Over a Million Android Activations Per Day, KDE 4.9 RC1 Released | Techrights

  2. Christoph says:

    The code does not run:
    This line
    self.canvas.bind(“”,self.updateCoordinates)
    should be
    self.canvas.bind(“”,self.updateCoordinates)

    Else there is an error: _tkinter.TclError: no events specified in binding

    It was a test, wasn’t it? 😉

  3. Christoph says:

    Eh, should be:
    self.canvas.bind(“”,self.updateCoordinates)
    Of course.

  4. Christoph says:

    Hint to the crosshair exercise 😉
    self.canvas.configure(cursor=”crosshair”)

  5. Pingback: Quadratic Bezier Curves « Python Tutorials for Kids 8+

  6. Pingback: Modelling Chess Positions | Python Tutorials for Kids 8+

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.