ocaml/otherlibs/labltk/tkanim/tkAnimGIF.c

909 lines
28 KiB
C

/***********************************************************************/
/* */
/* MLTk, Tcl/Tk interface of Objective Caml */
/* */
/* Francois Rouaix, Francois Pessaux, Jun Furuse and Pierre Weis */
/* projet Cristal, INRIA Rocquencourt */
/* Jacques Garrigue, Kyoto University RIMS */
/* */
/* Copyright 2002 Institut National de Recherche en Informatique et */
/* en Automatique and Kyoto University. All rights reserved. */
/* This file is distributed under the terms of the GNU Library */
/* General Public License, with the special exception on linking */
/* described in file LICENSE found in the Objective Caml source tree. */
/* */
/***********************************************************************/
#define TKANIM_VERSION "1.0"
/* #define TKANIM_DEBUG */
#include <tk.h>
#include <string.h>
/*
* The format record for the Animated GIF file format:
*/
static int FileMatchGIF _ANSI_ARGS_((FILE *f, char *fileName,
char *formatString, int *widthPtr, int *heightPtr));
static int FileReadGIF _ANSI_ARGS_((Tcl_Interp *interp,
FILE *f, char *fileName, char *formatString));
#define INTERLACE 0x40
#define LOCALCOLORMAP 0x80
#define BitSet(byte, bit) (((byte) & (bit)) == (bit))
#define MAXCOLORMAPSIZE 256
#define CM_RED 0
#define CM_GREEN 1
#define CM_BLUE 2
#define MAX_LWZ_BITS 12
#define LM_to_uint(a,b) (((b)<<8)|(a))
#define ReadOK(file,buffer,len) (fread(buffer, len, 1, file) != 0)
/*
* Prototypes for local procedures defined in this file:
*/
static int DoExtension _ANSI_ARGS_((FILE *fd, int label,
int *transparent, int *delay, int *loop));
static int GetCode _ANSI_ARGS_((FILE *fd, int code_size,
int flag));
static int GetDataBlock _ANSI_ARGS_((FILE *fd,
unsigned char *buf));
static int LWZReadByte _ANSI_ARGS_((FILE *fd, int flag,
int input_code_size));
static int ReadColorMap _ANSI_ARGS_((FILE *fd, int number,
unsigned char buffer[3][MAXCOLORMAPSIZE]));
static int ReadGIFHeader _ANSI_ARGS_((FILE *f, int *widthPtr,
int *heightPtr));
static int ReadImage _ANSI_ARGS_((Tcl_Interp *interp,
char *imagePtr, FILE *fd, int len, int height,
unsigned char cmap[3][MAXCOLORMAPSIZE],
int interlace, int transparent));
static int
FileMatchGIF(f, fileName, formatString, widthPtr, heightPtr)
FILE *f; /* The image file, open for reading. */
char *fileName; /* The name of the image file. */
char *formatString; /* User-specified format string, or NULL. */
int *widthPtr, *heightPtr; /* The dimensions of the image are
* returned here if the file is a valid
* raw GIF file. */
{
return ReadGIFHeader(f, widthPtr, heightPtr);
}
static int
FileReadGIF(interp, f, fileName, formatString)
Tcl_Interp *interp; /* Interpreter to use for reporting errors. */
FILE *f; /* The image file, open for reading. */
char *fileName; /* The name of the image file. */
char *formatString; /* User-specified format string, or NULL. */
{
int logicalWidth, logicalHeight;
int nBytes;
Tk_PhotoImageBlock block;
unsigned char buf[100];
int bitPixel;
unsigned int colorResolution;
unsigned int background;
unsigned int aspectRatio;
unsigned char localColorMap[3][MAXCOLORMAPSIZE];
unsigned char colorMap[3][MAXCOLORMAPSIZE];
int useGlobalColormap;
int transparent = -1;
int delay = 0;
Tk_Window winPtr;
int imageLeftPos, imageTopPos, imageWidth, imageHeight;
Tk_PhotoHandle photoHandle;
char widthbuf[32], heightbuf[32];
Tcl_DString resultbuf;
char newresbuf[640];
char *imageName;
char *resultptr;
int prevpos;
int loop = -1;
if((winPtr = Tk_MainWindow(interp)) == NULL){
return TCL_ERROR;
}
#ifdef TKANIM_DEBUG
fprintf(stderr, "\n\t\tHeader check...");
#endif
if (!ReadGIFHeader(f, &logicalWidth, &logicalHeight)) {
Tcl_AppendResult(interp, "couldn't read GIF header from file \"",
fileName, "\"", NULL);
return TCL_ERROR;
}
#ifdef TKANIM_DEBUG
fprintf(stderr, "done ");
#endif
if ((logicalWidth <= 0) || (logicalHeight <= 0)) {
Tcl_AppendResult(interp, "GIF image file \"", fileName,
"\" has dimension(s) <= 0", (char *) NULL);
return TCL_ERROR;
}
if (fread(buf, 1, 3, f) != 3) {
return TCL_OK;
}
bitPixel = 2<<(buf[0]&0x07);
colorResolution = (((buf[0]&0x70)>>3)+1);
background = buf[1];
aspectRatio = buf[2];
if (BitSet(buf[0], LOCALCOLORMAP)) { /* Global Colormap */
if (!ReadColorMap(f, bitPixel, colorMap)) {
Tcl_AppendResult(interp, "error reading color map",
(char *) NULL);
return TCL_ERROR;
}
}
#ifdef TKANIM_DEBUG
fprintf(stderr, "\n\t\tReading frames ");
prevpos = ftell(f);
#endif
sprintf( widthbuf, "%d ", logicalWidth);
sprintf( heightbuf, "%d ", logicalHeight);
Tcl_DStringInit(&resultbuf);
Tcl_DStringAppend(&resultbuf, widthbuf, -1);
Tcl_DStringAppend(&resultbuf, " ", -1);
Tcl_DStringAppend(&resultbuf, heightbuf, -1);
Tcl_DStringAppend(&resultbuf, " ", -1);
Tcl_DStringAppend(&resultbuf, "{", -1);
while (1) {
if (fread(buf, 1, 1, f) != 1) {
/*
* Premature end of image. We should really notify
* the user, but for now just show garbage.
*/
#ifdef TKANIM_DEBUG
fprintf(stderr, "Premature end of image");
#endif
break;
}
if (buf[0] == ';') {
/*
* GIF terminator.
*/
#ifdef TKANIM_DEBUG
fprintf(stderr, ";");
prevpos = ftell(f);
#endif
break;
}
if (buf[0] == '!') {
/*
* This is a GIF extension.
*/
#ifdef TKANIM_DEBUG
fprintf(stderr, "!");
prevpos = ftell(f);
#endif
if (fread(buf, 1, 1, f) != 1) {
Tcl_AppendResult( interp,
"error reading extension function code in GIF image", NULL );
/*
interp->result =
"error reading extension function code in GIF image";
*/
goto error;
}
if (DoExtension(f, buf[0], &transparent, &delay, &loop) < 0) {
Tcl_AppendResult( interp,
"error reading extension in GIF image", NULL );
/*
interp->result = "error reading extension in GIF image";
*/ goto error;
}
continue;
}
if (buf[0] == '\0') {
/*
* Not a valid start character; ignore it.
*/
#ifdef TKANIM_DEBUG
fprintf(stderr, "0", buf[0]);
prevpos = ftell(f);
#endif
continue;
}
if (buf[0] != ',') {
/*
* Not a valid start character; ignore it.
*/
#ifdef TKANIM_DEBUG
fprintf(stderr, "?(%c)", buf[0]);
prevpos = ftell(f);
#endif
continue;
}
if (fread(buf, 1, 9, f) != 9) {
Tcl_AppendResult( interp,
"couldn't read left/top/width/height in GIF image", NULL );
/*
interp->result = "couldn't read left/top/width/height in GIF image";
*/
goto error;
}
useGlobalColormap = ! BitSet(buf[8], LOCALCOLORMAP);
bitPixel = 1<<((buf[8]&0x07)+1);
imageLeftPos= LM_to_uint(buf[0], buf[1]);
imageTopPos= LM_to_uint(buf[2], buf[3]);
imageWidth= LM_to_uint(buf[4], buf[5]);
imageHeight= LM_to_uint(buf[6], buf[7]);
block.width = imageWidth;
block.height = imageHeight;
block.pixelSize = 3;
block.pitch = 3 * imageWidth;
block.offset[0] = 0;
block.offset[1] = 1;
block.offset[2] = 2;
block.offset[3] = 3;
nBytes = imageHeight * block.pitch;
block.pixelPtr = (unsigned char *) ckalloc((unsigned) nBytes);
sprintf(widthbuf, "%d", imageWidth);
sprintf(heightbuf, "%d", imageHeight);
/* save result */
{
#if (TK_MAJOR_VERSION >= 8 && TK_MINOR_VERSION >= 1)
Tcl_Obj *argv[7];
int i;
argv[0] = Tcl_NewStringObj("image", -1);
argv[1] = Tcl_NewStringObj("create", -1);
argv[2] = Tcl_NewStringObj("photo", -1);
argv[3] = Tcl_NewStringObj("-width", -1);
argv[4] = Tcl_NewStringObj(widthbuf, -1);
argv[5] = Tcl_NewStringObj("-height", -1);
argv[6] = Tcl_NewStringObj(heightbuf, -1);
for(i=0; i<7; i++){ Tcl_IncrRefCount(argv[i]); }
if( Tk_ImageObjCmd((ClientData) winPtr, interp,
/* "image create photo -width <imageWidth>
-height <imageHeight>" */
7, argv) == TCL_ERROR ){
return TCL_ERROR;
}
for(i=0; i<7; i++){ Tcl_DecrRefCount(argv[i]); }
#else
char *argv[7] = {"image", "create", "photo", "-width", NULL,
"-height", NULL};
argv[4] = widthbuf;
argv[6] = heightbuf;
#ifdef TKANIM_DEBUG
fprintf(stderr, "\n\t\timage creation (%s %s %s %s %s %s %s)",
argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]);
#endif
if( Tk_ImageCmd((ClientData) winPtr, interp,
/* "image create photo -width <imageWidth>
-height <imageHeight>" */
7, argv) == TCL_ERROR ){
return TCL_ERROR;
}
#endif
#ifdef TKANIM_DEBUG
fprintf(stderr, " done ");
#endif
}
imageName = interp->result;
#if (TK_MAJOR_VERSION < 8)
photoHandle = Tk_FindPhoto(interp->result);
#else
photoHandle = Tk_FindPhoto(interp, interp->result);
#endif
if (!useGlobalColormap) {
if (!ReadColorMap(f, bitPixel, localColorMap)) {
Tcl_AppendResult(interp, "error reading color map",
(char *) NULL);
goto error;
}
if (ReadImage(interp, (char *) block.pixelPtr, f, imageWidth,
imageHeight, localColorMap, BitSet(buf[8], INTERLACE),
transparent) != TCL_OK) {
goto error;
}
} else {
if (ReadImage(interp, (char *) block.pixelPtr, f, imageWidth,
imageHeight, colorMap, BitSet(buf[8], INTERLACE),
transparent) != TCL_OK) {
goto error;
}
}
Tk_PhotoPutBlock(photoHandle, &block, 0, 0,
imageWidth, imageHeight);
#ifdef TKANIM_DEBUG
fprintf(stderr, " Retrieving result\n");
#endif
/* retrieve result */
sprintf(newresbuf, "{%s %d %d %d %d %d} ",
imageName, imageWidth, imageHeight, imageLeftPos, imageTopPos,
delay);
#ifdef TKANIM_DEBUG
fprintf(stderr, " newresbuf = %s\n", newresbuf);
#endif
ckfree((char *) block.pixelPtr);
#ifdef TKANIM_DEBUG
fprintf(stderr, " free done (now append result)");
#endif
Tcl_DStringAppend( &resultbuf, newresbuf, -1 );
#ifdef TKANIM_DEBUG
fprintf(stderr, "\n\t\tFrame done (%d)", ftell(f) - prevpos);
prevpos = ftell(f);
#endif
}
sprintf( widthbuf, "%d", loop );
Tcl_DStringAppend( &resultbuf, "} ", -1 );
resultptr = Tcl_DStringAppend( &resultbuf, widthbuf, -1 );
#ifdef TKANIM_DEBUG
fprintf(stderr, "\nResult = %s\n", resultptr);
#endif
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, resultptr, NULL);
Tcl_DStringFree(&resultbuf);
return TCL_OK;
error:
Tcl_DStringFree(&resultbuf);
ckfree((char *) block.pixelPtr);
return TCL_ERROR;
}
static int
DoExtension(fd, label, transparent, delay, loop)
FILE *fd;
int label;
int *transparent;
int *delay;
int *loop;
{
static unsigned char buf[256];
int count = 0;
switch (label) {
case 0x01: /* Plain Text Extension */
break;
case 0xff: /* Application Extension */
count = GetDataBlock(fd, (unsigned char*) buf);
if( count < 0){
return 1;
}
if( !strncmp (buf, "NETSCAPE", 8) ) {
/* we ignore check of "2.0" */
count = GetDataBlock (fd, (unsigned char*) buf);
if( count < 0){
return 1;
}
if( buf[0] != 1 ){
fprintf(stderr, "??? %d", buf[0]);
}
*loop = LM_to_uint(buf[1], buf[2]);
}
do {
count = GetDataBlock(fd, (unsigned char*) buf);
} while (count > 0);
return count;
break;
case 0xfe: /* Comment Extension */
do {
count = GetDataBlock(fd, (unsigned char*) buf);
} while (count > 0);
return count;
case 0xf9: /* Graphic Control Extension */
count = GetDataBlock(fd, (unsigned char*) buf);
if (count < 0) {
return 1;
}
if ((buf[0] & 0x1) != 0) {
*transparent = buf[3];
}
/* Delay time */
*delay = LM_to_uint(buf[1],buf[2]);
do {
count = GetDataBlock(fd, (unsigned char*) buf);
} while (count > 0);
return count;
}
do {
count = GetDataBlock(fd, (unsigned char*) buf);
} while (count > 0);
return count;
}
/*
*----------------------------------------------------------------------
*
* ReadGIFHeader --
*
* This procedure reads the GIF header from the beginning of a
* GIF file and returns the dimensions of the image.
*
* Results:
* The return value is 1 if file "f" appears to start with
* a valid GIF header, 0 otherwise. If the header is valid,
* then *widthPtr and *heightPtr are modified to hold the
* dimensions of the image.
*
* Side effects:
* The access position in f advances.
*
*----------------------------------------------------------------------
*/
static int
ReadGIFHeader(f, widthPtr, heightPtr)
FILE *f; /* Image file to read the header from */
int *widthPtr, *heightPtr; /* The dimensions of the image are
* returned here. */
{
unsigned char buf[7];
if ((fread(buf, 1, 6, f) != 6)
|| ((strncmp("GIF87a", (char *) buf, 6) != 0)
&& (strncmp("GIF89a", (char *) buf, 6) != 0))) {
return 0;
}
if (fread(buf, 1, 4, f) != 4) {
return 0;
}
*widthPtr = LM_to_uint(buf[0],buf[1]);
*heightPtr = LM_to_uint(buf[2],buf[3]);
return 1;
}
/*
*-----------------------------------------------------------------
* The code below is copied from the giftoppm program and modified
* just slightly.
*-----------------------------------------------------------------
*/
static int
ReadColorMap(fd,number,buffer)
FILE *fd;
int number;
unsigned char buffer[3][MAXCOLORMAPSIZE];
{
int i;
unsigned char rgb[3];
for (i = 0; i < number; ++i) {
if (! ReadOK(fd, rgb, sizeof(rgb)))
return 0;
buffer[CM_RED][i] = rgb[0] ;
buffer[CM_GREEN][i] = rgb[1] ;
buffer[CM_BLUE][i] = rgb[2] ;
}
return 1;
}
static int ZeroDataBlock = 0;
static int
GetDataBlock(fd, buf)
FILE *fd;
unsigned char *buf;
{
unsigned char count;
if (! ReadOK(fd,&count,1)) {
return -1;
}
ZeroDataBlock = count == 0;
if ((count != 0) && (! ReadOK(fd, buf, count))) {
return -1;
}
return count;
}
static int
ReadImage(interp, imagePtr, fd, len, height, cmap, interlace, transparent)
Tcl_Interp *interp;
char *imagePtr;
FILE *fd;
int len, height;
unsigned char cmap[3][MAXCOLORMAPSIZE];
int interlace;
int transparent;
{
unsigned char c;
int v;
int xpos = 0, ypos = 0, pass = 0;
char *colStr;
/*
* Initialize the Compression routines
*/
if (! ReadOK(fd,&c,1)) {
Tcl_AppendResult(interp, "error reading GIF image: ",
Tcl_PosixError(interp), (char *) NULL);
return TCL_ERROR;
}
if (LWZReadByte(fd, 1, c) < 0) {
interp->result = "format error in GIF image";
return TCL_ERROR;
}
if (transparent!=-1 &&
(colStr = Tcl_GetVar(interp, "TRANSPARENT_GIF_COLOR", 0L))) {
XColor *colorPtr;
colorPtr = Tk_GetColor(interp, Tk_MainWindow(interp),
Tk_GetUid(colStr));
if (colorPtr) {
/*
printf("color is %d %d %d\n",
colorPtr->red >> 8,
colorPtr->green >> 8,
colorPtr->blue >> 8);
*/
cmap[CM_RED][transparent] = colorPtr->red >> 8;
cmap[CM_GREEN][transparent] = colorPtr->green >> 8;
cmap[CM_BLUE][transparent] = colorPtr->blue >> 8;
Tk_FreeColor(colorPtr);
}
}
while ((v = LWZReadByte(fd,0,c)) >= 0 ) {
imagePtr[ (xpos*3) + (ypos *len*3)] = cmap[CM_RED][v];
imagePtr[ (xpos*3) + (ypos *len*3) +1] = cmap[CM_GREEN][v];
imagePtr[ (xpos*3) + (ypos *len*3) +2] = cmap[CM_BLUE][v];
++xpos;
if (xpos == len) {
xpos = 0;
if (interlace) {
switch (pass) {
case 0:
case 1:
ypos += 8; break;
case 2:
ypos += 4; break;
case 3:
ypos += 2; break;
}
if (ypos >= height) {
++pass;
switch (pass) {
case 1:
ypos = 4; break;
case 2:
ypos = 2; break;
case 3:
ypos = 1; break;
default:
return TCL_OK;
}
}
} else {
++ypos;
}
}
if (ypos >= height)
break;
}
return TCL_OK;
}
static int
LWZReadByte(fd, flag, input_code_size)
FILE *fd;
int flag;
int input_code_size;
{
static int fresh = 0;
int code, incode;
static int code_size, set_code_size;
static int max_code, max_code_size;
static int firstcode, oldcode;
static int clear_code, end_code;
static int table[2][(1<< MAX_LWZ_BITS)];
static int stack[(1<<(MAX_LWZ_BITS))*2], *sp;
register int i;
if (flag) {
set_code_size = input_code_size;
code_size = set_code_size+1;
clear_code = 1 << set_code_size ;
end_code = clear_code + 1;
max_code_size = 2*clear_code;
max_code = clear_code+2;
GetCode(fd, 0, 1);
fresh = 1;
for (i = 0; i < clear_code; ++i) {
table[0][i] = 0;
table[1][i] = i;
}
for (; i < (1<<MAX_LWZ_BITS); ++i) {
table[0][i] = table[1][0] = 0;
}
sp = stack;
return 0;
} else if (fresh) {
fresh = 0;
do {
firstcode = oldcode = GetCode(fd, code_size, 0);
} while (firstcode == clear_code);
return firstcode;
}
if (sp > stack)
return *--sp;
while ((code = GetCode(fd, code_size, 0)) >= 0) {
if (code == clear_code) {
for (i = 0; i < clear_code; ++i) {
table[0][i] = 0;
table[1][i] = i;
}
for (; i < (1<<MAX_LWZ_BITS); ++i) {
table[0][i] = table[1][i] = 0;
}
code_size = set_code_size+1;
max_code_size = 2*clear_code;
max_code = clear_code+2;
sp = stack;
firstcode = oldcode = GetCode(fd, code_size, 0);
return firstcode;
} else if (code == end_code) {
int count;
unsigned char buf[260];
if (ZeroDataBlock)
return -2;
while ((count = GetDataBlock(fd, buf)) > 0)
;
if (count != 0)
return -2;
}
incode = code;
if (code >= max_code) {
*sp++ = firstcode;
code = oldcode;
}
while (code >= clear_code) {
*sp++ = table[1][code];
if (code == table[0][code]) {
return -2;
fprintf(stderr, "circular table entry BIG ERROR\n");
/*
* Used to be this instead, Steve Ball suggested
* the change to just return.
printf("circular table entry BIG ERROR\n");
*/
}
code = table[0][code];
}
*sp++ = firstcode = table[1][code];
if ((code = max_code) <(1<<MAX_LWZ_BITS)) {
table[0][code] = oldcode;
table[1][code] = firstcode;
++max_code;
if ((max_code>=max_code_size) && (max_code_size < (1<<MAX_LWZ_BITS))) {
max_code_size *= 2;
++code_size;
}
}
oldcode = incode;
if (sp > stack)
return *--sp;
}
return code;
}
static int
GetCode(fd, code_size, flag)
FILE *fd;
int code_size;
int flag;
{
static unsigned char buf[280];
static int curbit, lastbit, done, last_byte;
int i, j, ret;
unsigned char count;
if (flag) {
curbit = 0;
lastbit = 0;
done = 0;
return 0;
}
if ( (curbit+code_size) >= lastbit) {
if (done) {
/* ran off the end of my bits */
return -1;
}
buf[0] = buf[last_byte-2];
buf[1] = buf[last_byte-1];
if ((count = GetDataBlock(fd, &buf[2])) == 0)
done = 1;
last_byte = 2 + count;
curbit = (curbit - lastbit) + 16;
lastbit = (2+count)*8 ;
}
ret = 0;
for (i = curbit, j = 0; j < code_size; ++i, ++j)
ret |= ((buf[ i / 8 ] & (1 << (i % 8))) != 0) << j;
curbit += code_size;
return ret;
}
int Tk_AnimationCmd(clientData, interp, argc, argv)
ClientData clientData; /* Main window associated with interpreter. */
Tcl_Interp *interp; /* Current interpreter. */
int argc; /* Number of arguments. */
char **argv; /* Argument strings. */
{
char c;
int length;
if (argc < 2) {
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" option ?arg arg ...?\"", (char *) NULL);
return TCL_ERROR;
}
c = argv[1][0];
length = strlen(argv[1]);
if((c == 'c') && (length >= 2)
&& (strncmp(argv[1], "create", length) == 0)) {
char * realFileName;
Tcl_DString buffer;
FILE *f;
#ifdef TKANIM_DEBUG
fprintf(stderr, "AnimationCmd => create ");
#endif
if ( argc != 3 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" create GifFile\"", (char *) NULL);
return TCL_ERROR;
}
#ifdef TKANIM_DEBUG
fprintf(stderr, "\n\tRealFileName = ");
#endif
realFileName = Tcl_TranslateFileName(interp, argv[2],
&buffer);
if(realFileName == NULL) {
Tcl_DStringFree(&buffer);
return TCL_ERROR;
}
#ifdef TKANIM_DEBUG
fprintf(stderr, "%s ", realFileName);
#endif
#ifdef TKANIM_DEBUG
fprintf(stderr, "\n\tOpen ", realFileName);
#endif
f = fopen(realFileName, "rb");
Tcl_DStringFree(&buffer);
if (f == NULL ){
Tcl_AppendResult(interp, "couldn't read image file \"",
argv[2], "\": ", Tcl_PosixError(interp),
(char *) NULL);
return TCL_ERROR;
}
#ifdef TKANIM_DEBUG
fprintf(stderr, "success ", realFileName);
#endif
#ifdef TKANIM_DEBUG
fprintf(stderr, "\n\tRead ", realFileName);
#endif
if( FileReadGIF(interp, f, argv[2], "gif") != TCL_OK ){
#ifdef TKANIM_DEBUG
fprintf(stderr, "\n\tRead failed", realFileName);
#endif
return TCL_ERROR;
}
fclose(f);
#ifdef TKANIM_DEBUG
fprintf(stderr, "\n\tRead done", realFileName);
#endif
#ifdef TKANIM_DEBUG
fprintf(stderr, "done\n");
#endif
}
return TCL_OK;
}
void
TkDeleteTkAnim(clientData)
ClientData clientData;
{
#ifdef TKANIM_DEBUG
fprintf(stderr, "TkDeleteTkAnim\n");
#endif
}
int Tkanim_Init(interp)
Tcl_Interp *interp;
{
#ifdef TKANIM_DEBUG
fprintf(stderr, "Tkanim initialize...");
#endif
Tcl_CreateCommand(interp, "animation", Tk_AnimationCmd,
(ClientData) NULL,
(Tcl_CmdDeleteProc *) TkDeleteTkAnim);
#ifdef TKANIM_DEBUG
fprintf(stderr, "done\n");
#endif
return Tcl_PkgProvide(interp, "Tkanim", TKANIM_VERSION );
}