Arjen Markus (10 October 2002) It occurred to me that the technique of producing an animated display has not been documented with so many words on the Wiki. Well, here is my view on the subject.
Suppose you have a canvas which needs to redrawn after some time. This will be our animated display, as the redrawn picture will look slightly different.
There are roughly two options:
The first part requires knowledge of the after command, such as in Keep a GUI alive during a long calculation. The second part requires additional tools, such as ImageMagick ([L1 ]) to complete the job.
Both approaches are relatively easy (see the script below), but they are certainly easiest on UNIX/Linux, where most tools are command-line driven.
The first approach is this:
Note: the after command is essential here, it will make sure that the next picture is drawn at the appropriate time. In the meantime the canvas can be drawn, events can be processed etc.
Further Note: there are two subtle problems with the naive use of after in doing animation. One arises when trying to get consistent behaviour across varying machines, the other arises when the time required to draw the scene varies. The former can often be seen when you try running an old game on a new fast computer--the game may zips along way too fast to play. The latter is commonly seen during the play of a game when a new animated object appears on the screen and the animation suddenly slows down. Fortunately, for simple animations both problems have the same easy solution: make the after interval non-constant. Specifically, when designing the animation decide how much time you want between successive scenes (often starting in terms of frames per second). Then, before drawing a scene, note the time; when done drawing, compute how much time is left before the next scene is needed and use that value for the interval for after. [For real high quality animation, that wait interval is not wasted but used to start drawing the next scene into a second buffer, but that's a whole other discussion.] KPV
The second approach just adds two steps:
On UNIX/Linux, the tool to save the canvas in a bitmap file that I use frequently is "xwd":
exec xwd -id [winfo id $canvas] -out [format "anim%04d.xwd" $count]
where "canvas" holds the name of the canvas we draw in and "count" is a counter for the picture. By formatting the names of the bitmap files in four figures, anim0000.xwd, anim0001.xwd, ..., the conversion to an animated GIF file via ImageMagick's convert becomes a single instruction:
In the shell, type:
> convert anim*.xwd my_anim.gif
(convert will convert all files to GIF files - this is based on the extensions - and as there is more than one input file, it is going to be an animated GIF file.)
The script below shows an animation of a bouncing ball. Nothing particularly interesting, but it illustrates the ideas.
package require Tk # drawBall -- # Draw a red circle at a certain height above the green ground # # Arguments: # time Time parameter, used to calculate the actual height # # Result: # None # # Note: # Assume a perfectly elastic collision. The time parameter must # be reduced to the time since the last collision. # proc drawBall {time} { global accel global velo0 global cnv_height global cnv_width set period [expr {2.0*$velo0/$accel}] set time2 [expr {$time - $period * int($time/$period)}] set grass_height 20 set radius 7 set ball_height [expr {$velo0*$time2-0.5*$accel*$time2*$time2}] set pix_height [expr { $cnv_height-$grass_height - $radius - $ball_height}] set xl [expr {0.5*$cnv_width-$radius}] set xr [expr {0.5*$cnv_width+$radius}] set yb [expr {int($pix_height)-$radius}] set yt [expr {int($pix_height)+$radius}] .cnv delete all .cnv create rectangle 0 $cnv_height $cnv_width \ [expr {$cnv_height-$grass_height}] -fill green -outline green .cnv create oval $xl $yb $xr $yt -fill red -outline black } # nextPicture -- # Prepare to call the next picture, stop after some predefined # number of steps. # # Arguments: # step Step number (converted to time) # # Result: # None # proc nextPicture {step} { # Here you can insert the code to save the current picture # # exec xwd -id [winfo id .cnv] -out [format "anim%04d.xwd" $step] # # # Draw the picture # drawBall [expr {0.1*$step}] # # Set up the next picture via the [after] command # if { $step < 1000 } { incr step after 100 [list nextPicture $step] } } # main -- # Set up the canvas, start the loop # global cnv_width global cnv_height global velo0 global accel set cnv_width 400 set cnv_height 300 set velo0 70.0 ;# m/s set accel 10.0 ;# m/s2 ;# pixels become m that way :) canvas .cnv -width $cnv_width -height $cnv_height -background white pack .cnv -fill both drawBall 0 # # Wait until the dust settles - important for saving the frames # on a slow system # tkwait visibility . nextPicture 0
See also: Creating an animated display, part 2