ANI formal definition
This information is copied from the Descent Developer's Network Ani specs page
Introduction
The ANI file format is similar to the 256-color-PCX compression. It was however improved to provide transparent pixels as well as of course multiple images that make together the ANImation. All original ANI files are in FREESPACE.VP in Conflict, Descent: FreeSpace. In FreeSpace 2 they have been divided over several VP files.
This document was written by Heiko Herrmann with help by John Slagel of Volition Inc. (header information) and Garry Knudson (image data).
File format
The file begins with a fixed-size header and a variable number of keyframe definitions:
//Definitions typedef struct ani_header { short should_be_zero; // always 0 short version; // >= 2 short fps; // played with <fps> frames per second pal rgb; // ? short w,h; // width, height short nframes; // number of frames char packer_code; // code used for compressed (repeated) bytes pal palette[256]; // rgb-palette short num_keys; // ? } ani_header; typedef struct ani_key { short twobyte; short startcount; } ani_key; //File format ani_header anifile.header; for(i=0; i<anifile.header.num_keys; i++) ani_key anifile.key[i]; int anifile.endcount;
A keyframe is a frame that is provided *as is*, that means with no transparent pixels. So drawing that frame is not dependant of having the data from the previous frame (which is needed for transparent pixels). Usually ANImations have only one keyframe (the initial image = frame no.1). Some have more This is used for the following purposes:
The main hall ANImations have only key frames so that they can be played backwards (when moving the mouse away from a room in the main hall). The weapon/ship ANImations have 2 (one additional) key frames: the second is the so-called "loop point": everything before is the animation how the weapon/ship appears in the briefings screen, everything after is the part that loops endless - the rotation. The header is followed by the actual image data, as discussed below.
Image data
To understand the image data format, I chose to provide some code here:
// ANI reader example code // Taken from Descent Manager ANIVIEW32 - an adaption of a code part of FSView // Written by Garry Knudson and Heiko Herrmann // Copyright 1998-2000 by Descent Network Team // // Note: the header must be loaded into the structure anifile.header. // The image data must then be loaded into memory and available at pointer char *cf. // The bitmap data is then loaded into anifile.frames[i].buffer byte runvalue,runcount; int cur,max; byte *p,*p2; byte flagbyte; max=anifile.header.w; if(anifile.header.w % 4!=0) { //Garry Knudson 9-23-99 max += (4-(anifile.header.w%4)); } max=max * anifile.header.h; byte *cur_frame=new byte[max]; byte *last_frame=new byte[max]; memset(last_frame,anifile.header.rgb.r+256*anifile.header.rgb.g+65536*anifile.header.rgb.b,max); //Load frames for (i=0;i<anifile.header.nframes;i++) { cf=readfrombuffer(cf,&flagbyte,1); cur=0; /*char x[256]; sprintf(x,"Frame %i: %i\n",i,anif->GetPosition()); OutputDebugString(x);*/ p=cur_frame; if(i>0) p2=last_frame; //for transparent pixels else p2=cur_frame; int x0,y0; runcount=0; cur=0; for(y0=0;y0<anifile.header.h;y0++) { for(x0=0;x0<anifile.header.w;x0++) { if (runcount>0) { runcount--; } else { cf=readfrombuffer(cf,&runvalue,1); if(runvalue==anifile.header.packer_code) { cf=readfrombuffer(cf,&runcount,1); if(runcount<2) runvalue=anifile.header.packer_code; else cf=readfrombuffer(cf,&runvalue,1); } } *p=runvalue; if(runvalue==254) //if transparent pixel, then use the pixel from the last frame *p=*p2; p++; p2++; cur++; } if(anifile.header.w % 4!=0) { //Garry Knudson 9-23-99 p +=(4-(anifile.header.w%4)); p2 +=(4-(anifile.header.w%4)); cur +=(4-(anifile.header.w%4)); } } //Process frame anifile.frames[i].buffer=new byte[max]; memcpy(last_frame,cur_frame,max); memcpy(anifile.frames[i].buffer,cur_frame,max); } --------------- char *readfrombuffer(char *org,void *dest,int num) { memcpy(dest,org,num); org+=num; return org; }
Note to this source: the header must be loaded into the structure anifile.header. The image data must then be loaded into memory and available at pointer char *cf. The bitmap data is then loaded into anifile.frames[i].buffer.