ppm2fli - create a FLI animation from a set of static images


ppm2fli [ options ] [ list-file [ animation-file ] ]


The program ppm2fli generates a FLI/FLC-animation from a series of images. The file list-file contains a list of the names of image files, one per line separated by the new-line character. These images are merged together in the supplied order. The generated animation is written to animation-file.
The file formats which can be read directly are PPM, PGM and PBM (ascii and raw: magic 'P1' to 'P6') as well as FBM (mapped 8bpp and rgb 24bpp). Ppm2fli can invoke external filters which convert other file formats to the PPM or FBM standard. For example such filters are included in the PBMPLUS, NETPBM and FBM packages. Together with these packages a large number of graphic formats can be handled.
The generation of the animation is done in two separate steps:
Computation of a common color table for all images
Quantization of the images and computing the FLI/FLC frames
The first step is done by scanning all images and using a modified version of the Octree algorithm to compute a color table from the shades found in the images. This color table is supposed to be a -- more or less -- best fit to the color shades which were found in the images.
When the input images are already quantized the result depends on the number of colors which they use altogether. If this number is equal or less than the given maximum (default: 256), the scanning step just evaluates these colors and assembles the color table without any modification. When the input images contain more colors, some colors are put together and assigned to an average value, until the total number is sufficiently low.
Alternatively it's possible to generate the color table by scanning only one image, a so called map image. It may be one from the given list or an extra image. This procedure is faster but the resulting color table may be less suitable for the other images.
Third possibility is to do the quantization separately and generate an extra color table for each image. This may result in a change of the color table between the frames of the FLI animation. However, the program tries to keep the color table as static as possible.
The area of the image that the final animation produces when played is denoted as display_area. Default is a display_area of 640x480 pixels. It is allowed that the size of the input images varies. If the width of an input image is greater than the width of the display_area, an equal number of pixels is removed from each side of the input image. If the width of an input image is less than the width of the display_area, this results in the creation of a border around the image (default: center the image in the display_area). The same is done in vertical direction.


Perform an individual quantize of each image independently of all other images. In this case no common table is used and it may occur that new colors are loaded between the FLI/FLC frames. Usually a common color table should be preferred. Only when this results in very poor quantization of the images a varying color table is recommended.
-Qc numcolors
Employ numcolors as the maximum number of colors used in the quantized images (range: 9-256; default: 256). A low value may result in a poor quantization.
-Qd bits
Employ bits as the color depth used in the color map of the quantized images (range: 2-8; default: 8). Lower values reduce the number of colors in the quantized images.
-Qn nodelimit
Set the maximum number of nodes that the Octree is allowed to have in the second-deepest level. A larger value may result in a better quantization but slows down the scanning of the images. Recommended values: between one time and four times the numcolors value (range: 16-2048; default: 512). See section "TECHNICAL NOTES" for more details.
-Qr reducelevels
Set the number of levels which are considered when reducing the Octree (range: 0-8; default: 8). See section "TECHNICAL NOTES" for more details.
-m file
The common color table is generated by scanning only the image in the given file. Usually this is much faster than scanning all images, but the color table represents only colors which occur in the given image. If this image is not representative for the others, their quantization may become very poor.
-w file
The generated color table is written to the given file as a 256x1 PPM ascii image. Then the program terminates without assembling the fli animation. Later this color table file can be used with the `-m' option to generate the fli animation. When the `-w' option is given the parameter animation-file can be omitted. If both `-m' and `-w' are given also the parameter list-file isn't necessary.


Include additionally update information with respect to the frame before the last. Default: generated FLI chunks hold update information only for the immediately preceding frame.
In both cases, the resulting animations work with standard players. The additional update information is useful for players that use the double buffering technique (see TECHNICAL NOTES section below).
Same as `-D' but instead of the frame before the last the update information with respect to the following frame is included. This allows the play in reverse direction with the XAnim player. Note that `-D' and `-N' exclude each other.
Generate output using the older FLI format associated with magic number 0xAF11. Some older players cannot handle newer FLI animations associated with the magic number 0xAF12 (these are sometimes known as FLC files). Use the old LC_CHUNKS (12) instead of DELTA_CHUNKS (7), and COLOR_CHUNKS (11) instead of COLOR_256_CHUNKS (4), and set default resolution to 320x200 instead of the usual 640x480.
-b color
If a particular input image does not cover the entire display_area, employ the specified color for all pixels in the border area. Default: zero (See also `+/-ox'). To evaluate which number a certain color has the `-w' option can be used. By that the color table is written to a file (ascii) and then the desired color can be looked up in the table.
-g widthxheight
This defines the width and height of the display_area (allowed range: from 10x10 to 1280x1024). If an odd value of width is specified, it is automatically incremented by one. A particular FLI player may only support a limited set of resolutions. In principle, VGA resolution (320x200) should always work. For players supporting the newer style of FLIs (FLCs) associated with the magic number 0xAF12, 640x480 should also work.
+/-ox hor_position
Disable the automatic centering of input images in horizontal direction. Place the input images at a fix position in the display_area. When `+ox' is used the left border of the input images is kept fix independently of the width of the images. The value gives the distance between the left border of the display_area and the left border of the input image. When `-ox' is used the right border of the input images is kept fix. The value gives the distance between the right border of the display_area and the right border of the input image. In both cases negative values can be specified. Then the respective border of the input images is outside of the display_area and a part of the images is cut off. The option can be used to place input images with varying width at a certain position in the display_area, or to animate a certain section of larger input images.
+/-oy vert_position
Identical to `+/-ox', but controls the vertical position. When `+oy' is used the number of pixels from the upper border of the display_area to the upper border of the input images is set. Respectively `-oy' controls the position with respect to the lower border.
-s speed
Use the specified speed to be stored in the header of the FLI file. The FLI standard requires a speed value in the file. Some players use this number as default when no other speed is given. The meaning of the speed argument depends on the FLI format in use. Higher values reduce the speed. For old format FLIs, the value specifies the number of video ticks between two frames (default: 5). For new format FLIs, the delay between two frames is specified in 1/1000 seconds rather than video ticks (default: 72/1000 seconds, resulting in approximately 15 frames per second).


Print internal information to stdout. `-vv' causes even more things to be written.
+/-f filter
Use the specified filter when reading the input images. This is necessary when other than the default formats are used or when the input files are compressed (see the examples below). The reading is done using the popen subroutine. If `-f' is used the specified filter is supposed to read from stdin. The command used in the popen statement has the form `filter < image'. In the case of `+f' the image name if passed as argument to the filter program. In both cases the filter program has to write the converted image to stdout. The option overwrites the filter defined by the environment variable PPM2FLIFILTER.
Test the file magic of the input files before using the read filter. Only files which have no PPM, PGM, PBM or FBM format are read through the given filter. By default all files are read through a given filter. When no filter is specified the `-t' option has no effect.


A frequently used read filter can be defined using the environment variable PPM2FLIFILTER. The name of the filter can be preceded by a `+' or `-' sign (see the example below). The `-' sign as first character corresponds to the usage of `-f' in the command line and a `+' works like `+f'. By default `-' is assumed which means that the filter reads from stdin.


Basic Usage
Assume the existence of a series of PPM images which have the names
The goal is to produce a FLI animation from these files in the given order. First the list-file is prepared. We choose the name `pics.list' for this file. The file is generated by:
     % ls -1 image??.ppm > pics.list
Note that this only works if no other files in the directory match the specified pattern and the desired order corresponds to the numbering system in the file names. In the second step a FLI file with the name `anim.fli' is generated using the command:
     % ppm2fli pics.list anim.fli
The generated animation has the resolution 640x480. Assume that the given images are only 320x240. Then they appear in the middle of the 640x480 display area and are surrounded by a border area. To avoid this border we fit the FLI resolution at the pixel size of the input images. This can be done by:
     % ppm2fli -g 320x240 pics.list anim.fli
In the next example we begin with a series of 768x512 images. The goal is to animate these images, cutting off the top 20 lines of each image. Again the name of the list-file is `pics.list' and the name of the generated FLI file is `anim.fli'.
The appropriate command line is:
     % ppm2fli -g 768x492 +oy -20 pics.list anim.fli
Note 1):
492 = 512 - 20.
Note 2):
If in the given example the `+oy' option was omitted, 10 lines at the top and bottom would be cut off.
Read Filters
We assume that all input images are in the GIF format and that we have the PBMPLUS package with the program giftoppm installed. Like in the example described above we create a list file with the names of the GIF files. Then the FLI is generated by the command
     % ppm2fli -fgiftoppm pics.list anim.fli
In this case all images given in `pics.list' have to be in GIF format.
If we use the FBM package instead of PBMPLUS we have to use another utility. Now the command line looks like
     % ppm2fli -ffbcat pics.list anim.fli
The utility fbcat converts the GIF images to FBM format which is read by ppm2fli. Because fbcat understands also other formats, like SUN raster or FBM, the input files can have different formats.
It is also possible to use shell scripts as filters. For instance we want to animate a ray-tracer scene. The images were generated by POV-Ray but we made a big mess. Some are stored in QRT format, some as TGA files and others are already converted to PPM. To save disk space some files are compressed by gzip. We are in luck because we used always the correct extensions in the file names. We have gunzip installed and the utilities qrttoppm and tgatoppm are available. We edit a shell script with the following content:
     #! /bin/ksh


     function isit
          if test "$name" = "$base.$1"; then cmd=$2; fi;

     isit "gz" "gunzip"
     isit "qrt"     "qrttoppm"
     isit "qrt.gz"  "(gunzip | qrttoppm)"
     isit "tga"     "tgatoppm"
     isit "tga.gz"  "(gunzip | tgatoppm)"

     $cmd < $name
The script is stored as `myfilter' in the current directory (... chmod a+x myfilter). To use this filter we type
     % ppm2fli +fmyfilter pics.list anim.fli
Note that now `+f' is required because our filter doesn't read from stdin. It needs the file name as parameter to choose the corresponding utility. The given script works only for the Bourne/Korn shell.
When the filter requires additional parameters they can be passed in an easy way. For instance works
     % ppm2fli -f "gzip -d" pics.list anim.fli
for a series of "gzipped" input files.
Even more complicated things are possible. For example the ghostscript program can be used as read filter to animate a series of PS images. The necessary option may look like
  -f "gs -g320x480 -q -r36 -sDEVICE=ppm -sOutputFile=- -"
which works for the `tiger.ps' example. Note that to reduce the data flow `ppmraw' should be preferred to the `ppm' device.
When a certain filter is often used, the environment variable PPM2FLIFILTER can be set to define the filter. In a bash environment this may look like
     % export PPM2FLIFILTER=-fbcat
Then always the fbcat utility is used as read filter unless something else is defined in the command line.
Usage of a Map File
Again we assume a series of images, but now the goal is to produce two animations with a different order of the images. For this purpose we edit by hand two list files `order1.lst' and `order2.lst'. To save time and to avoid a repeated scanning of the input images the scanning is done in a separate step. The result of the scanning is the common color table. This table has to be stored in an extra file. We choose the name `ct.ppm' for this file. Then the first step looks like:
     % ppm2fli -w ct.ppm order1.lst
In the next step the first animation is generated:
     % ppm2fli -m ct.ppm order1.lst order1.fli
And finally:
     % ppm2fli -m ct.ppm order2.lst order2.fli
Usually, if no individual quantization is used, all images are read twice. Once for generating the color table and the second time when the quantize & assembling is done. Thus, the options `-m' and `-w' can help to save a lot of time, especially when the input files are compressed or have another file format and each reading through a filter requires more time.
Note that the map file is not read through a given filter. Thus, this file always has to be in one of the formats which can be read directly.
Quantization using an external utility
A modified version of the Octree algorithm is implemented in ppm2fli. By default this algorithm is used when the input files contain more than 256 colors. This maximum of colors can be changed to lower values using the option `-Qc'. Sometimes it may be desirable to use a different quantization algorithm. In the following example we want to animate a ray-tracer scene generated by the POV-Ray program. The image files are in gzipped QRT format and we have the PBM package with the utility qrttoppm installed. Again the list-file with the names of the QRT images has the name `pics.list'. The quantization shall be done with the ppmquant program of the PBM package, because it offers the possibility of Floyd-Steinberg error diffusion. Nevertheless we would like to get a FLI animation where the color table is static. Then we do in the first step
     % ppm2fli pics.list -w ct.ppm -f"(gunzip | qrttoppm)"
By that the color table is evaluated by the Octree algorithm and stored in the file `ct.ppm'. To avoid a exceeding command line we edit a shell script with the content
     #! /bin/csh
     gunzip | qrttoppm | ppmquant -fs -map ct.ppm
and store this file as `myquant' in the current directory (... chmod a+x myquant). When this is done we generate the FLI animation `anim.fli' by the command
     % ppm2fli pics.list anim.fli -m ct.ppm -fmyquant
Note that usually the Floyd-Steinberg error diffusion results in a large number of isolated pixels in the quantized image. This is used to avoid the typical color steps which occur in regions with smooth transitions between different colors. But the compression method used in the FLI format is inefficient when too many isolated pixels occur within an image. Thus, the better image quality is payed by a much larger size of the FLI file.


The Modified Octree Algorithm
The Octree algorithm does the quantization in three phases:
Scanning of the image
Reduction of the Octree
First the images are scanned to evaluate what colors are present. For this purpose internally a list of colors is created. To speed up the sorting a tree structure -- the so called Octree -- is used rather than a linear list. This tree structure has a main node which can have up to 8 sub-nodes. Each sub-note itself can have again 8 sub-sub-nodes and so on. So the sub-nodes can be regarded as sub-trees.
A geometric interpretation exists for the sorting of RGB colors by the Octree: A RGB color can be regarded as a point with coordinates (red,green,blue) in a 3-dimensional space. The orthogonal coordinate system of this space has axis in the directions red, green and blue. Total black is defined as the origin of this space (0,0,0). Because of the discrete 8bit representation of each RGB component the point (255,255,255) corresponds to maximum bright white. All possible
256*256*256 > 16 Million
RGB combinations form a regular three-dimensional grid in this space. The grid can be covered by a cube with side length 255. This cube corresponds to the main node of the Octree. The sorting is done by subdividing the cube symmetrically in 8 sub-cubes of equal size. The sub-cubes contain the points
1. (r,g,b) = (  0 - 127,   0 - 127,   0 - 127)
2. (r,g,b) = (  0 - 127,   0 - 127, 128 - 255)
3. (r,g,b) = (  0 - 127, 128 - 255,   0 - 127)
4. (r,g,b) = (  0 - 127, 128 - 255, 128 - 255)
5. (r,g,b) = (128 - 255,   0 - 127,   0 - 127)
6. (r,g,b) = (128 - 255,   0 - 127, 128 - 255)
7. (r,g,b) = (128 - 255, 128 - 255,   0 - 127)
8. (r,g,b) = (128 - 255, 128 - 255, 128 - 255)
These sub-cubes correspond to sub-nodes of the main node in the Octree.
At the beginning all 8 bits of the RGB values are taken into account when sorting the colors. It is checked for a pixel in which sub-cube its color belongs. Then it is checked if a corresponding sub-node for this sub-cube already exists. If not a new node in the Octree is generated which represents this sub-cube. Then the sub-cube itself is divided in eight sub-sub-cubes and so on. The subdivision is done until the color is located in the finest cube which contains only one of the possible discrete RGB points. This results in 8 levels of sub-cubes, which all represent a node in different levels of the Octree. The nodes in the finest level (also called deepest level), which have no sub-nodes, are called leaves.
The sorting procedure is done for all pixels in the input images. Additionally the modified algorithm does for each sub-cube a count of the pixels which where located in this cube. Furthermore a sum of the RGB values is computed for all pixels in each sub cube.
In principle all pixels in a true color picture can have different shades. Thus, the Octree might grow and grow, and it may become impossible to store the information for all nodes in the main memory. Of course this depends on the content of the input images. To avoid a very large Octree a reduction is done, when the number of nodes that the Octree has in the second-deepest level exceeds a certain threshold. The reduction is done by throwing away all leaves in the deepest level. The sorting is stopped in the second-deepest level where the nodes of the Octree are now leaves. But, theoretically 7 levels still allow
128*128*128 > 2 Million
different RGB combinations or possible finest sub-cubes. So it can be necessary to reduce the level of sort accuracy further until the Octree remains small enough. But this may cause later a poor result of the quantization, because all pixels which colors share a common leaf of the Octree will have the same color in the resulting quantized image. Thus, it is desirable to keep the accuracy level a high as possible and a compromise between computational effort and quantize quality has to be obtained.
In the second phase of the quantization the color table is computed. For this purpose the Octree which was generated during the scanning process has to be reduced further until the number of leaves is lower (or equal) than the specified maximum of colors. Compared to the reduction during the scanning process, which is done only "level-wise", the final reduction is performed in a more complicated way. It is taken into account how many pixels belong to each node in the Octree. The node with the minimum pixel count is searched. It's sub-trees are discarded and it becomes a leaf. This is repeated until the number of leaves is low enough. Then for each leaf the average color values are computed: The sums of the red, green and blue values are divided by the number of pixels. These averaged RGB values are replaced by the nearest integers and the resulting numbers are taken as entry in the color table.
After the color table is generated the final stage is done. All pixels in the images are sorted again using the -- now reduced -- Octree until a leaf is reached. Then the pixel is mapped to the respective entry in the color table. For all pixels which were used during the scanning process a leaf in the reduced Octree exists. This leaf represents a cube in the color space that contains the color of the pixel (beside other colors depending on the level of the leaf).
In some circumstances it is useful to be able to map also images which were not scanned previously. If such an "unknown" image is mapped pixels may occur which have colors that are not represented by any leaf in the Octree. These "unknown" colors are located in cubes which were not considered previously. The sorting process is extended to handle also such situations. In all cases the sorting starts regularly and it is evaluated in which sub-cube the color of a pixel belongs. If a node for this sub-cube exists in the Octree the search is continued regularly. If no node exists -- this means no scanned pixel had a color which belonged to the same sub-cube -- an extra procedure is started. For all sub-nodes that exist the distance in the 3-dimensional color space between the color of the pixel and the average color of this sub-node is computed (NOTE: In ppm2fli the maximum norm is used as the distance, not the geometric distance). Then the search is continued with the sub-node that represents the lowest distance. The procedure is repeated until a leaf is reached. Like in the regular case the pixel is mapped to the color of this leaf. If this extra search procedure is necessary for some pixels, ppm2fli counts them and writes in a message how many "non-fitting" pixels were found.
Controlling the quantization
In the following we assume that the maximum number of colors was determined previously and the numcolors parameter is kept fix. Usually this value is 256 anyway. Then the program has two parameters by which the selection of the output color table can be controlled. These are the nodelimit and the reducelevels parameters. The first one sets the maximum number of nodes in the second-finest level. Like described above the level of the Octree is reduced when during the scanning of the input images this value is exceeded. With a deeper level in the Octree, finer nuances between output colors are possible. This is useful when smooth transitions are present in the input images. Typically, steps occur in such transitions due to the quantization. These steps may be reduced when a larger nodelimit is used and the final Octree is one level deeper. The depth of the Octree can be watched using the `-vv' option. After the scanning of each file a lines occurs which look like
Octree - node count (5): 1 8 23 66 228 999 0 0 0
The number in the brackets indicates the deepest level. Then follow the number of nodes in each level from 0 to 8. The zero level contains only the main node of the Octree and the first number is always one. Recommended values of nodelimit are between one time and four times the numcolors value. Higher values increase the computational effort and slow down the scanning process.
The reducelevels parameter controls the final reduction of the Octree. It determines the number of levels in which the search for the node with the minimum pixel count is done. When reducelevels is zero, the reduction is done only in the finest level. Therefore, the resulting Octree contains leaves only in two different levels: the deepest and the second-deepest.
The influence of this parameter on the color table becomes clear in the following example: Assume a 640x480 image which shows a small, bright-red object in front of a green dominated background. The small object consists of only 1000 pixels. The green and blue components of the colors in this object are zero. The red values rage from 150 to 250. In the green background the red and blue values are zero. The green values rage from 1 to 255. For each of this green values much more than 1000 pixels can be found in the image (which has more than 300,000 pixels). Thus, we have a total of
different colors in the image. We assume that the nodelimit parameter is larger than 356. Then the Octree has a leaf for each color after the scanning. Until now no reduction was necessary. But now 100 leaves have to be removed from the Octree. By default, the nodes in all levels are searched for the node with minimum pixel count. This will be always a node which represents a red color, because the 1000 red pixels can't stand the statistical superiority of the green background. What happens is that all red pixels are going to receive the same (averaged) red by the quantization and the green nuances in the background are resolved in an optimal way.
When the reduction is limited to the finest level, the number of leaves that represent red colors can only be reduced by a factor of 8. From 101 leaves 13 will remain and the same number of entries in the color table is occupied by red shades. Thus, the red object doesn't look totally poor like in the first case. Now the green background has to be represented by 12 entries less. The preference of shades with higher pixel count is limited with a zero reducelevels value. By default, the nodes in all levels may be turned to leaves, which results in the strongest preference of dominant shades in the color table. Also the statistic of the reduced Octree can be watched using the `-vv' option. When the scanning is finished a lines occurs which look like
Octree - leaf count (4): 0 4 14 53 184 0 0 0 0
The number in the brackets indicates the deepest leaf level. This is followed by the number of leaves in each level from 0 to 8.
In general, the effect of changes of the above mentioned parameters depends strongly on the input data. To get optimal results, some experiments with different values should be done.
FLI/FLC optimization for special players
One special feature of ppm2fli is activated by the `-D' option. By that the program generates optimized animations for players which use double buffering. A double-buffer player uses two buffers for the display. When playing an animation both buffers have to be updated separately. For this a the player needs the update information with respect to the frame which was located previously in the same buffer. This frame is not the last one, which was processed in exactly the other buffer, but the frame before the last. Ordinary FLI/FLC animations contain in each frame the update information with respect to the previous frame. Thus a double buffer player has to combine somehow the updates of two FLI frames to make the changes in one of the buffers. This becomes much easier when the FLI already contains this information in each frame. Still the animation can be played with a regular player. In this case only some unnecessary updates are done which may slow down the animation.
A second special feature is activated by the `-N' option. Then the frames of the generated FLI animation contain the update information with respect to the last and the following frame. This allows the animation to be played in reverse direction. For instance with the XAnim player the play direction can be altered interactively. This may help when visualizing and analyzing some unsteady phenomena or just can be used as a fancy effect. Together with the utility unflick an existing FLI animation can easily be converted to be suitable for reverse play.


fbm(1), unflick(1), and ppm(5)


The quantization is based on the Octree algorithm introduced by Michael Gervautz and Werner Purgathofer (Technical University Vienna, Austria). It is described in the article `A Simple Method for Color Quantization: Octree Quantization' published in Graphic Gems edited by Andrew Glassner pp. 287 ff.


The special feature, to generated FLI animations which can be played forward and backward, is based on contributions and ideas of Marc Podlipec (podlipec@wellfleet.com). This manual page includes parts form that one of fbm2fli which was assembled by R. P. C. Rodgers (rodgers@nlm.nih.gov).


Klaus Ehrenfried (klaus.ehrenfried@tu-berlin.de). Release of January 2002.