Version 1 of Creating an animated display

Updated 2002-10-10 11:25:05

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:

  • Redrawing the individual pictures is fast enough, so anybody can

run the script directly and watch the movie.

  • Redrawing them takes either too much time or we need a compact form

in which to distribute the result (say as an animated GIF file).

The first part requires knowledge of the after command, such as in Keeping a GUI alive. 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:

  • In a procedure, draw the picture.
  • When it completes, change the "time" parameter, use the after

command to wait a short interval and then restart the procedure.

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.

The second approach just adds two steps:

  • When the picture is finished, save the picture in a bitmap file
  • When the animation is complete and we have all pictures stored on

disk, use some tool to create the animation file.

On UNIX/Linux, the tool to save the canvas in a bitmap file that I use frequently is "xwd":

   exec xwd -id [winfo $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.


 # 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

[ Arts and crafts of Tcl-Tk programming Category Graphics ]