Python Graphics: Animation Principles

Exclusive offer: get 50% off this eBook here
Python 2.6 Graphics Cookbook

Python 2.6 Graphics Cookbook — Save 50%

Over 100 great recipes for creating and animating graphics using Python

$26.99    $13.50
by Mike Ohlson de Fine | December 2010 | Cookbooks Open Source

In this article by Mike Ohlson de Fine, author of Python 2.6 Graphics Cookbook, we will cover:

  • Static shifting of a ball
  • Timed shifting of a ball
  • Animation – timed draw-and-erase cycles
  • Two balls moving unimpeded
  • A ball that bounces
  • Bouncing in a gravitational field

 

Python 2.6 Graphics Cookbook

Python 2.6 Graphics Cookbook

Over 100 great recipes for creating and animating graphics using Python

  • Create captivating graphics with ease and bring them to life using Python
  • Apply effects to your graphics using powerful Python methods
  • Develop vector as well as raster graphics and combine them to create wonders in the animation world
  • Create interactive GUIs to make your creation of graphics simpler
  • Part of Packt's Cookbook series: Each recipe is a carefully organized sequence of instructions to accomplish the task of creation and animation of graphics as efficiently as possible
        Read more about this book      

(For more resources on Python, see here.)

Introduction

Animation is about making graphic objects move smoothly around a screen. The method to create the sensation of smooth dynamic action is simple:

  1. First present a picture to the viewer's eye.
  2. Allow the image to stay in view for about one-twentieth of a second.
  3. With a minimum of delay, present another picture where objects have been shifted by a small amount and repeat the process.

Besides the obvious applications of making animated figures move around on a screen for entertainment, animating the results of computer code gives you powerful insights into how code works at a detailed level. Animation offers an extra dimension to the programmers' debugging arsenal. It provides you with an all encompassing, holistic view of software execution in progress that nothing else can.

Static shifting of a ball

We make an image of a small colored disk and draw it in a sequence of different positions.

How to do it...

Execute the program shown and you will see a neat row of colored disks laid on top of each other going from top left to bottom right. The idea is to demonstrate the method of systematic position shifting.

# moveball_1.py
#>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("shifted sequence")
cw = 250 # canvas width
ch = 130 # canvas height

chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
# The parameters determining the dimensions of the ball and its
# position.
# =====================================
posn_x = 1 # x position of box containing the ball (bottom)
posn_y = 1 # y position of box containing the ball (left edge)
shift_x = 3 # amount of x-movement each cycle of the 'for' loop
shift_y = 2 # amount of y-movement each cycle of the 'for' loop
ball_width = 12 # size of ball - width (x-dimension)
ball_height = 12 # size of ball - height (y-dimension)
color = "violet" # color of the ball

for i in range(1,50): # end the program after 50 position shifts
posn_x += shift_x
posn_y += shift_y
chart_1.create_oval(posn_x, posn_y, posn_x + ball_width,\
posn_y + ball_height,
fill=color)

root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

How it works...

A simple ball is drawn on a canvas in a sequence of steps, one on top of the other. For each step, the position of the ball is shifted by three pixels as specified by the size of shift_x. Similarly, a downward shift of two pixels is applied by an amount to the value of shift_y. shift_x and shift_y only specify the amount of shift, but they do not make it happen. What makes it happen are the two commands posn_x += shift_x and posn_y += shift_y. posn is the abbreviation for position.

posn_x += shift_x means "take the variable posn_x and add to it an amount shift_x." It is the same as posn_x = posn_x + shift_x.

Another minor point to note is the use of the line continuation character, the backslash "\". We use this when we want to continue the same Python command onto a following line to make reading easier. Strictly speaking for text inside brackets "(...)" this is not needed. In this particular case you can just insert a carriage return character. However, the backslash makes it clear to anyone reading your code what your intention is.

There's more...

The series of ball images in this recipe were drawn in a few microseconds. To create decent looking animation, we need to be able to slow the code execution down by just the right amount. We need to draw the equivalent of a movie frame onto the screen and keep it there for a measured time and then move on to the next, slightly shifted, image. This is done in the next recipe.

Time-controlled shifting of a ball

Here we introduce the time control function canvas.after(milliseconds) and the canvas.update() function that refreshes the image on the canvas. These are the cornerstones of animation in Python.

Control of when code gets executed is made possible by the time module that comes with the standard Python library.

How to do it...

Execute the program as previously. What you will see is a diagonal row of disks being laid in a line with a short delay of one fifth of a second (200 milliseconds) between updates. The result is shown in the following screenshot showing the ball shifting in regular intervals.

Python 2.6 Graphics Cookbook

# timed_moveball_1.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Time delayed ball drawing")

cw = 300 # canvas width
ch = 130 # canvas height

chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)

cycle_period = 200 # time between fresh positions of the ball
# (milliseconds).
# The parameters determining the dimensions of the ball and it's
# position.
posn_x = 1 # x position of box containing the ball (bottom).
posn_y = 1 # y position of box containing the ball (left edge).
shift_x = 3 # amount of x-movement each cycle of the 'for' loop.
shift_y = 3 # amount of y-movement each cycle of the 'for' loop.
ball_width = 12 # size of ball - width (x-dimension).
ball_height = 12 # size of ball - height (y-dimension).
color = "purple" # color of the ball

for i in range(1,50): # end the program after 50 position shifts.
posn_x += shift_x
posn_y += shift_y

chart_1.create_oval(posn_x, posn_y, posn_x + ball_width,\
posn_y + ball_height, fill=color)
chart_1.update() # This refreshes the drawing on the canvas.
chart_1.after(cycle_period) # This makes execution pause for 200
# milliseconds.

root.mainloop()

How it works...

This recipe is the same as the previous one except for the canvas.after(...) and the canvas.update() methods. These are two functions that come from the Python library. The first gives you some control over code execution time by allowing you to specify delays in execution. The second forces the canvas to be completely redrawn with all the objects that should be there. There are more complicated ways of refreshing only portions of the screen, but they create difficulties so they will not be dealt with here.

The canvas.after(your-chosen-milliseconds) method simply causes a timed-pause to the execution of the code. In all the preceding code, the pause is executed as fast as the computer can do it, then when the pause, invoked by the canvas.after() method is encountered, execution simply gets suspended for the specified number of milliseconds. At the end of the pause, execution continues as if nothing ever happened.

The canvas.update() method forces everything on the canvas to be redrawn immediately rather than wait for some unspecified event to cause the canvas to be refreshed.

There's more...

The next step in effective animation is to erase the previous image of the object being animated shortly before a fresh, shifted clone is drawn on the canvas. This happens in the next example.

The robustness of Tkinter

It is also worth noting that Tkinter is robust. When you give position coordinates that are off the canvas, Python does not crash or freeze. It simply carries on drawing the object 'off-the-page'. The Tkinter canvas can be seen as just a tiny window into an almost unlimited universe of visual space. We only see objects when they move into the view of the camera which is the Tkinter canvas.

Python 2.6 Graphics Cookbook Over 100 great recipes for creating and animating graphics using Python
Published: November 2010
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on Python, see here.)

Complete animation using draw-move-pause-erase cycles

This recipe gives you the whole animation procedure. All the actions necessary for the human brain to interpret images on the retina as moving objects are present in this example. The whole craft of animation and the million dollar movies based thereon is demonstrated here in its simplest and purest form.

How to do it...

Execute this program as we have done before. Note that this time we have reduced the timed pause to 50 milliseconds which is 20 times per second. This is close to the standard 24 frames per second used in movies. However, without a graphics card this time becomes less accurate as shorter pauses are specified. In addition, the distance moved between position shifts of the ball has been reduced to one pixel.

# move_erase_cycle_1.py
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("move-and-erase")
cw = 230 # canvas width
ch = 130 # canvas height

chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 50 # time between new positions of the ball
# (milliseconds).

# The parameters determining the dimensions of the ball and its
# position.
posn_x = 1 # x position of box containing the ball (bottom).
posn_y = 1 # y position of box containing the ball (left edge).
shift_x = 1 # amount of x-movement each cycle of the 'for' loop.
shift_y = 1 # amount of y-movement each cycle of the 'for' loop.
ball_width = 12 # size of ball - width (x-dimension).
ball_height = 12 # size of ball – height (y-dimension).
color = "hot pink" # color of the ball

for i in range(1,500): # end the program after 500 position shifts.
posn_x += shift_x
posn_y += shift_y
chart_1.create_oval(posn_x, posn_y, posn_x + ball_width,\
posn_y + ball_height, fill=color)
chart_1.update() # This refreshes the drawing on the canvas.
chart_1.after(cycle_period) # This makes execution pause for 200
# milliseconds.
chart_1.delete(ALL) # This erases everything on the canvas.

root.mainloop()
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

How it works...

The new element in this self-contained animation is the canvas.delete(ALL) method that clears the entire canvas of everything that was drawn on it. It is possible to erase only specific objects on the screen through the use of identification tags. This is not needed now.

There's more...

How accurate is the timing of the pause() method?.

With modern computers, pauses of five milliseconds are realistic but the animation becomes jerky as the pause times get shorter.

More than one moving object

We want to be able to develop programs where more than one independent graphic object co-exists and interacts according to some rules. This is how most computer games work. Pilot training simulators and serious engineering design models are designed on the same principles. We start this process simply by working up to an application that ends up with two balls bouncing off the walls and each other under the influence of gravity and energy loss.

How to do it...

The following code is very similar to that in the previous recipe, except that two similar objects are created. They are independent of each other and do not interact in any way.

# two_balls_moving_1.py
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Two balls")
cw = 200 # canvas width
ch = 130 # canvas height

chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 100 # time between new positions of the ball
# (milliseconds).

# The parameters defining ball no 1.
posn_x_1 = 1 # x position of box containing the ball (bottom).
posn_y_1 = 1 # y position of box containing the ball (left edge).
shift_x_1 = 1 # amount of x-movement each cycle of the 'for' loop.
shift_y_1 = 1 # amount of y-movement each cycle of the 'for' loop.
ball_width_1 = 12 # size of ball - width (x-dimension).
ball_height_1 = 12 # size of ball - height (y-dimension).
color_1 = "blue" # color of ball #1

# The parameters defining ball no 2.
posn_x_2 = 180 # x position of box containing the ball (bottom).
posn_y_2 = 180 # x position of box containing the ball (left
# edge).
shift_x_2 = -2 # amount of x-movement each cycle of the 'for'
# loop.
shift_y_2 = -2 # amount of y-movement each cycle of the 'for'
# loop.
ball_width_2 = 8 # size of ball - width (x-dimension).
ball_height_2 = 8 # size of ball - height (y-dimension).
color_2 = "green" # color of ball #2.

for i in range(1,100): # end the program after 50 position shifts.
posn_x_1 += shift_x_1
posn_y_1 += shift_y_1
posn_x_2 += shift_x_2
posn_y_2 += shift_y_2

chart_1.create_oval(posn_x_1, posn_y_1, posn_x_1 + ball_width_1,\
posn_y_1 + ball_height_1, fill=color_1)
chart_1.create_oval(posn_x_2, posn_y_2, posn_x_2 + ball_width_2,\
posn_y_2 + ball_height_2, fill=color_2)
chart_1.update() # This refreshes the drawing on the canvas.
chart_1.after(cycle_period) # This makes execution pause for 100
# milliseconds.
chart_1.delete(ALL) # This erases everything on the canvas
root.mainloop()
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

How it works...

The main point to note is that these programs are divided into five parts:

  1. Creating the environment where objects will exist.
  2. Defining the individual objects and their attributes.
  3. Defining the rules of engagement between objects.
  4. Creating the objects.
  5. Using a loop to simulate the march of time by changing properties such as position at rates that mimic real-time motion.
  6. Controlling the environment inside which the objects exist.

The environment in most of our examples is the Tkinter canvas. The objects that are going to exist inside the canvas environment in this example are two colored balls. The rules of engagement are that they will not have any effect on each other at all and they will not be affected by the edges of the canvas. Another rule of engagement is how their positions will shift each time the for loop is executed.

Finally the environment is controlled by the time regulated canvas.update() and canvas.delete(ALL) methods.

There's more...

The principle idea demonstrated in this recipe is that we can create more than one similar, but different objects exist and react independently. This gives rise to the idea of object-oriented programming.

A ball that bounces

Now we add rules of engagement that are increasingly complex. The overall objective is to introduce behaviors and interactions into our artificial world to make it behave more like the real world. We use numbers, calculations, and graphical drawings to represent aspects of the real world as we know it.

The first new behavior is that our colored disks will bounce elastically off the walls of the container that is the Tkinter canvas.

How to do it...

The code has purposely been kept as similar as possible to the previous four examples so that we feel we are still in familiar territory as the world we create gets increasingly more complicated. If we did not do this, we would get lost and bewildered. The whole secret in successfully constructing complex computer programs is to build it up gradually and systematically piece-by-piece. It is not a planned journey along a well-mapped road but rather a strenuous exploration through uncharted jungle.

# bounce_ball.py
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
import time
root = Tk()
root.title("The bouncer")
cw = 200 # canvas width
ch = 120 # canvas height

chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 50 # time between new positions of the ball
# (milliseconds).
# The parameters determining the dimensions of the ball and its position.
posn_x = 1 # x position of box containing the ball (bottom).
posn_y = 1 # x position of box containing the ball (left edge).
shift_x = 1 # amount of x-movement each cycle of the 'for' loop.
shift_y = 1 # amount of y-movement each cycle of the 'for' loop.
ball_width = 12 # size of ball - width (x-dimension).
ball_height = 12 # size of ball - height (y-dimension).
color = "firebrick" # color of the ball

# Here is a function that detects collisions with the walls of the
# container
# and then reverses the direction of movement if a collision is
# detected.
def detect_wall_collision():
global posn_x, posn_y, shift_x, shift_y, cw, cy
if posn_x > cw :
# Collision with right-hand container wall.
shift_x = -shift_x # reverse direction.
if posn_x < 0 : # Collision with left-hand wall.
shift_x = -shift_x
if posn_y > ch : # Collision with floor.
shift_y = -shift_y
if posn_y < 0 : # Collision with ceiling.
shift_y = -shift_y

for i in range(1,1000): # end the program after1000 position shifts.
posn_x += shift_x
posn_y += shift_y
chart_1.create_oval(posn_x, posn_y, posn_x + ball_width,\
posn_y + ball_height, fill=color)
detect_wall_collision() # Has the ball collided with
# any container wall?
chart_1.update() # This refreshes the drawing on the canvas.
chart_1.after(cycle_period) # This makes execution pause
# for 200 milliseconds.
chart_1.delete(ALL) # This erases everything on the canvas.

root.mainloop()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

How it works...

The new feature here is the function detect_Wall_Collision(). Whenever it is called, it checks whether the position of the ball has moved outside the boundary of the canvas. If it has, the direction of the ball is reversed. This method is crude because it does not compensate for the size of the ball. Consequently the ball pops out of existence.

Bouncing in a gravity field

In this recipe, the influence of a gravitational field is added to the previous rule of bouncing off the canvas wall.

How to do it...

What makes this recipe different to all the previous ones is a new attribute of the ball named velocity_y. With every cycle of the for i in range(0,300) loop the velocity is modified just as it would be in the gravitational field of our real world.

# gravityball.py
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>
from Tkinter import *
root = Tk()
root.title("Gravity bounce")
cw = 220 # canvas width
ch = 200 # canvas height
GRAVITY = 4
chart_1 = Canvas(root, width=cw, height=ch, background="white")
chart_1.grid(row=0, column=0)
cycle_period = 30

# The parameters determining the dimensions of the ball and its
# position.
posn_x = 15
posn_y = 180
shift_x = 1
velocity_y = 50
ball_width = 12
ball_height = 12
color = "blue"

# The function that detects collisions with the walls and reverses
# direction
def detect_wall_collision():
global posn_x, posn_y, shift_x, velocity_y, cw, cy
if posn_x > cw -ball_width: # Collision with right-hand
# container wall.
shift_x = -shift_x # reverse direction.
if posn_x < ball_width: # Collision with left-hand wall.
shift_x = -shift_x
if posn_y < ball_height : # Collision with ceiling.
velocity_y = -velocity_y
if posn_y > ch - ball_height : # Floor collision.
velocity_y = -velocity_y

for i in range(1,300):
posn_x += shift_x
velocity_y = velocity_y + GRAVITY # a crude equation
# incorporating gravity.
posn_y += velocity_y
chart_1.create_oval(posn_x, posn_y, posn_x + ball_width, \
posn_y + ball_height, \ fill=color)
detect_wall_collision() # Has the ball collided with any
# container wall?
chart_1.update() # This refreshes the drawing on the canvas.
chart_1.after(cycle_period) # This makes execution pause for 200
# milliseconds.
chart_1.delete(ALL) # This erases everything on the canvas.

root.mainloop()

How it works...

The vertical velocity_y property of our ball is increased by a constant quantity GRAVITY every time a new position is calculated. The net result is that the speed gets faster when the ball is falling downward and slower when it moves upward. Because the y-direction of a Tkinter canvas is positively increasing downward (contrary to our real world) this has the effect of slowing down the ball when moving upward and speeding it up when moving downward.

There's more...

There is a flaw with this simulation of a bouncing ball. The ball disappears off the canvas after about three bounces because the integer arithmetic used in calculating each new position of the ball and the criteria used to detect collisions with the wall are much too coarse. The result of this is that the ball finds itself outside of the conditions we have set up to reverse its direction when it hits the floor. The GRAVITY added to its velocity kick it beyond the interval if posn_y > ch – ball_height, and the ball never gets placed back inside the canvas.

Positions on the canvas are defined as integers only but we need to deal with much greater precision than that when calculating the position of our ball. It turns out there is no problem here. In their wisdom the Python designers have allowed us to work with all our variables as floating point numbers that are very precise and still pass them to the canvas.create_oval(...) method which draws the ball on the canvas. For the final drawing they obviously get converted into integers. Thank you wise Python guys.

Summary

In this artcile we covered the following recipes:

  • Static shifting of a ball
  • Timed shifting of a ball
  • Animation – timed draw-and-erase cycles
  • Two balls moving unimpeded
  • A ball that bounces
  • Bouncing in a gravitational field

In the next article, Animating Graphic Objects using Python, we will cover recipes such as colliding balls with tracer trails, Elastic ball against ball collisions, Dynamic debugging, and Trajectory tracing.


Further resources on this subject:


Python 2.6 Graphics Cookbook Over 100 great recipes for creating and animating graphics using Python
Published: November 2010
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

About the Author :


Mike Ohlson de Fine

Mike is a graduate Electrical Engineer specializing in industrial process measurement and control. He has a Diploma in Electronics and Instrumentation from Technikon Witwatersrand, an Electrical Engineering degree from the University of Cape Town, and a Masters in Automatic Control from Rand Afrikaans University. He has worked for mining and mineral extraction companies for the last 30 years. His first encounter with computers was learning Fortran 4 using punched cards on an IBM 360 as an undergraduate. Since then he has experimented with Pascal, Forth, Intel 8080 Assembler, MS Basic, C, and C++ but was never satisfied with any of these. Always restricted by corporate control of computing activities he encountered Linux in 2006 and Python in 2007 and became free at last.

As a working engineer he needs tools that facilitate the understanding and solution of industrial process control problems using simulations and computer models of real processes. Linux and Python proved to be excellent tools for these challenges. When he retires he would like to be part of setting up a Free and Open Source engineering virtual workshop for his countrymen and people in other poor countries to enable the bright youngsters of these countries to be intellectually free at last.

His hobbies are writing computer simulations, paddling kayaks in wild water, and surf skiing in the sea.

Books From Packt


Python Multimedia
Python Multimedia

Spring Python 1.1
Spring Python 1.1

Python Testing: Beginner's Guide
Python Testing: Beginner's Guide

Inkscape 0.48 Essentials for Web Designers
Inkscape 0.48 Essentials for Web Designers

PostgreSQL 9.0 High Performance
PostgreSQL 9.0 High Performance

Plone 3.3 Site Administration
Plone 3.3 Site Administration

Blender 2.5 Lighting and Rendering
Blender 2.5 Lighting and Rendering

PrestaShop 1.3 Beginner's Guide
PrestaShop 1.3 Beginner's Guide


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software