/* * whirlgif.c * * Copyright (c) 1997,1998,1999 by Hans Dinsen-Hansen (dino@danbbs.dk) * Copyright (c) 1995,1996 by Kevin Kadow (kadokev@msg.net) * Based on txtmerge.c * Copyright (c) 1990,1991,1992,1993 by Mark Podlipec (podlipec@BayNetworks.com). * All rights reserved. * * This software may be freely copied, modified and redistributed * without fee provided that above copyright notices are preserved * intact on all copies and modified copies. * * There is no warranty or other guarantee of fitness of this software. * It is provided solely "as is". The author(s) disclaim(s) all * responsibility and liability with respect to this software's usage * or its effect upon hardware or computer systems. * * The Graphics Interchange format (c) is the Copyright property of * Compuserve Incorporated. Gif(sm) is a Service Mark property of * Compuserve Incorporated. * */ /* * Description: * * This program reads in a sequence of single-image Gif format files and * outputs a single multi-image Gif file, suitable for use as an animation. * * TODO: * * More options for dealing with the colormap * */ /* * Rev 3.04 21Feb99 Hans Dinsen-Hansen * RunLength & Amiga. * Rev 3.03 02Feb99 Hans Dinsen-Hansen * Published as a patch. Better error messages. * Rev 3.02 01Oct98 Hans Dinsen-Hansen * Loop. Verbose -> DEBUG. Further minimizing. * Rev 3.01 Oct98 Hans Dinsen-Hansen * Never published. Various experiments with Windows versions. * Rev 3.00 29jul98 Hans Dinsen-Hansen * Gif-repacking; unification of color map; only output diff. * Rev 2.02 09Sep97 Hans Dinsen-Hansen * Gif89a input; use global colormap whenever possible; background index * Rev 2.01 31Aug96 Kevin Kadow * disposal * Rev 2.00 05Feb96 Kevin Kadow * transparency, gif comments, * Rev 1.10 29Jan96 Kevin Kadow * first release of whirlgif * * txtmerge: * Rev 1.01 08Jan92 Mark Podlipec * use all colormaps, not just 1st. * Rev 1.00 23Jul91 Mark Podlipec * creation * * */ #include "whirlgif.h" /* * Set some defaults, these can be changed on the command line */ unsigned int loop=DEFAULT_LOOP, loopcount=0, useColormap=DEFAULT_USE_COLORMAP, debugFlag=0, globmap=0, minimize=0; int imagex = 0, imagey = 0, imagec = 0, GifBgcolor=0, count=0; /* global settings for offset, transparency */ Global global; GifColor gifGmap[256], gifCmap[256]; GifScreenHdr globscrn, gifscrn; GifImageHdr gifimage, gifimageold; extern ULONG gifMask[]; extern int picI; UBYTE *pixold=NULL; ULONG gifMask[16]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,0,0}, obits; ULONG gifPtwo[16]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,0,0}; char gifFileName[BIGSTRING]; FILE *ff; long sq(UBYTE i,UBYTE j) { return((i-j)*(i-j)); } int main(argc, argv) int argc; char *argv[]; { FILE * infile, *fout; char temp[BIGSTRING], *cmt; int i; fprintf(stderr, "whirlgif Rev %2.2f (c) 1997-1999 by %s\n%s\n%s\n", DA_REV,"Hans Dinsen-Hansen", " (c) 1995-1996 by Kevin Kadow", " (c) 1990-1993 by Mark Podlipec"); cmt = temp; /* if there is no comment option, let cmt point at the final message */ if (argc < 2) Usage(); /* set global values */ global.trans.type = TRANS_NONE; global.trans.valid = 0; global.time = DEFAULT_TIME; global.left = 0; global.top = 0; global.disposal = DEFAULT_DISPOSAL; fout = stdout; i = 1; while( i < argc) { char *p; p = argv[i]; if (debugFlag > 1) fprintf(stderr, "Option: %s\n", p); if ( (p[0] == '-') || (p[0] == '+') ) { ++p; /* strip off the - */ switch(p[0]) { case 'v': /* Give lots of information */ debugFlag++; i++; fprintf(stderr, "Verbose output; debug level: %d\n", debugFlag); break; case 'g': /* Use the global colormap throughout */ globmap++; i++; if ( debugFlag > 1) fprintf(stderr, "globmap\n"); break; case 'm': /* minimize output */ minimize++; globmap++; i++; if ( debugFlag > 1) fprintf(stderr, "minimize \n"); break; case 'd': /* disposal setting */ i++; p = argv[i++]; if(!strcmp("not", p)) global.disposal = DISP_NOT; else if(!strcmp("back", p)) global.disposal = DISP_BACK; else if(!strcmp("prev", p)) global.disposal = DISP_PREV; else if(!strcmp("none", p)) global.disposal = DISP_NONE; else global.disposal = DEFAULT_DISPOSAL; if(debugFlag) fprintf(stderr, "Disposal method set to %s = %d\n", p, global.disposal); break; case 'D': /* Debug setting */ i++; debugFlag = 2; fprintf(stderr, "DEBUG: Debug Level %d\n", debugFlag); break; case 'c': /* set comment pointer */ i++; cmt = argv[i++]; if(debugFlag) fprintf(stderr, "Comment: '%s'\n", cmt); break; case 'b': /* set Background color index */ i++; GifBgcolor = atoi(argv[i++]) | 0x100; if (debugFlag) fprintf(stderr, "BACKGROUND = %d\n", GifBgcolor & 0xff); break; case 'l': /* Enable looping */ loop = TRUE; i++; if(*argv[i] != '-') { /* a loop count was perhaps given */ loopcount = atoi(argv[i]); if(debugFlag) { fprintf(stderr, loopcount != 0 ? "Loop %d times\n" : "Loop forever, count = %d\n", loopcount); } if( (loopcount > 0) | ((loopcount == 0) & (*argv[i] == '0'))) i++; } else { /* default to infinite loop */ loopcount = 0; if(debugFlag) fprintf(stderr, "Looping enabled\n"); } break; case 't': /* either time or transparent */ i++; if(!strncmp("time", p, 2)) { /* Delay time in 1/100's of a second */ global.time = atoi(argv[i++]); } else if(!strncmp("trans", p, 2)) CalcTrans(argv[i++]); break; case 'o': /* Output file - send output to a given filename */ i++; if(!strncmp("off", p, 2)) SetOffset(argv[i]); else if(NULL == (fout = fopen(argv[i], WRIBIN))) { /* It must be 'output, so we do that */ fprintf(stderr, "Cannot open %s for output\n", argv[i]); exit(1); } i++; break; case 'i': /* input file - file with a list of images */ i++; if(NULL != (infile = fopen(argv[i], REATXT))) { while(fgets(gifFileName, BIGSTRING, infile)) { strtok(gifFileName, "\n"); GifReadFile(fout, gifFileName, count++ == 0); } fclose(infile); global.left = global.top = 0; } else fprintf(stderr, "Cannot read list file %s\n", argv[i]); i++; break; default: Usage(); exit(1); break; } continue; } /* Not an option, must be the name of an input file */ GifReadFile(fout, argv[i], count++ == 0); global.left = global.top = 0; i++; } /* We're done with all options and file names, finish up */ if(count >0) { sprintf(temp, "whirlgif %2.2f (c) %s\r\n%d %s", DA_REV, "dino@danbbs.dk", count, count == 1 ? "image" : "images"); /* Either output above std. mess. or a possible user defined comment */ GifComment(fout, cmt); } fputc(';', fout); /* End of Gif file */ fclose(fout); fprintf(stderr, "Processed %d files.\n", count); return 0; } /* * Read a Gif file. */ void GifReadFile(FILE *fout, char *fname, int firstImage) { FILE *fp; UBYTE *pix; int i, gifBlockSize; if ( (fp = fopen(fname, REABIN)) == 0) { fprintf(stderr, "Can't open %s for reading.\n", fname); TheEnd(); } GifScreenHeader(fp, fout, firstImage); /* read until , separator */ do { switch ( i = Xgetc(fp)) { case ',': case '\0': break; case '!': Xgetc(fp); /* the extension code */ for ( i = Xgetc(fp); i > 0; i-- ) Xgetc(fp); while ( ( i = Xgetc(fp) ) > 0 ) { for ( i = i ; i > 0; i-- ) Xgetc(fp); } break; default: fclose(fp); if ( feof(fp) || i == ';' ) TheEnd1("GifReadHeader: Unexpected End of File\n"); TheEnd1("GifReadHeader: Unknown block type\n"); } } while(i != ','); if(firstImage) { globscrn.m = gifscrn.m; globscrn.pixbits = gifscrn.pixbits; globscrn.bc = gifscrn.bc; if ( globscrn.m ) { for (i = gifMask[1+globscrn.pixbits]; i >= 0; i--) { gifGmap[i].cmap.red = gifCmap[i].cmap.red; gifGmap[i].cmap.green = gifCmap[i].cmap.green; gifGmap[i].cmap.blue = gifCmap[i].cmap.blue; } } if(loop) GifLoop(fout, loopcount); } ReadImageHeader(fp); /*** ACTION for IMAGE */ if ( ( gifimage.m != 0 && globmap !=0 ) || minimize !=0 ) { UBYTE translator[256], *p, *po; int left, right, top, bot, i, j, k, l, hi, wi; long dsquare, dsquare1; left = 0, right = 0, top = 0, bot = 0; hi = gifimage.height; wi = gifimage.width; if (( pix = (UBYTE *)malloc(wi * hi * sizeof(UBYTE)) ) == NULL ) TheEnd1("No memory for image\n"); if (debugFlag) fprintf(stderr, " decoding picture no %d\n", count); GifDecode(fp, pix, gifimage); gifimage.i = 0; k = gifMask[1+globscrn.pixbits]; l = gifMask[1+gifscrn.pixbits]; for (j = 0; j <= l; j++) { dsquare = 256*256*3; for (i = 0; i <= k; i++) { dsquare1 = sq(gifGmap[i].cmap.red, gifCmap[j].cmap.red) + sq(gifGmap[i].cmap.green, gifCmap[j].cmap.green) + sq(gifGmap[i].cmap.blue, gifCmap[j].cmap.blue); if ( dsquare1 < dsquare ) { dsquare = dsquare1; translator[j]=i; if ( dsquare == 0 ) break; } } } gifimage.m = 0; gifscrn.pixbits = globscrn.pixbits; if (debugFlag) fprintf(stderr, " translating picture no %d\n", count); for (i = wi * hi -1; i>=0; i--) pix[i]=translator[pix[i]]; if ( minimize != 0 && pixold != NULL && hi == gifimageold.height && wi == gifimageold.width && gifimage.top == gifimageold.top && gifimage.left == gifimageold.left ) { gifimageold = gifimage; /* First test from left to right, top to bottom */ p = pix; po = pixold; for (i = 0; i < hi; i++ ) { for (j = 0; j < wi; j++ ) { if ( *p++ != *po++ ) { left = j; top=i; goto done; } } } if (FALSE) { done: /* i.e. a preliminary left and top found */ ; } else goto alike; /* Then test from right to left, bottom to top */ k=hi*wi-1; p = &pix[k]; po = &pixold[k]; for (i = hi-1; i >= top; i-- ) { for (j = wi -1; j >= 0; j-- ) { if ( *p-- != *po-- ) { right = j; bot=i; goto botfound; } } } botfound: /* The form of the differing area (not rectangle) may be slanted */ if ( right < left ) { i = right; right = left; left = i; } /* Now test between top and bottom at the left hand side */ for (i = top+1; i <= bot; i++ ) { k= i * wi; p = &pix[k]; po = &pixold[k]; for (j = 0; j < left; j++ ) { if ( *p++ != *po++ ) { left = j; break; } } } /* Finally test between bottom and top at the right hand side */ for (i = bot-1; i >= top; i-- ) { k= (i+1) * wi-1; p = &pix[k]; po = &pixold[k]; for (j = wi-1; j > right; j-- ) { if ( *p-- != *po-- ) { right = j; break; } } } gifimage.left += left; gifimage.top += top; gifimage.width = right-left+1; gifimage.height = bot-top+1; WriteImageHeader(fout); /* The rectangle containing diffs is transferred to the mem area of pixold */ po = pixold; for (i = top; i <= bot; i++ ) { p = &pix[i * wi+left]; for (j = left; j <= right; j++ ) { *po++ = *p++; } } GifEncode(fout, pixold, gifscrn.pixbits+1, gifimage.height * gifimage.width); if (debugFlag) fprintf(stderr, " encoded: width= %d, height = %d, left = %d, top = %d\n", gifimage.width, gifimage.height, gifimage.left, gifimage.top); } else { alike: WriteImageHeader(fout); gifimageold = gifimage; GifEncode(fout, pix, gifscrn.pixbits+1, gifimage.height * gifimage.width); if (debugFlag) fprintf(stderr, " picture re-encoded\n"); /* Undocumented feature: If two subsequent images are alike, then send the whole image to the output stream (to keep the timing between frames, and not confuse the viewer with empty images) */ } free(pixold); pixold = pix; fputc(0, fout); /* block count of zero */ } else { WriteImageHeader(fout); i = Xgetc(fp); fputc(i, fout); /* the LZW code size */ while ( ( gifBlockSize = Xgetc(fp) ) > 0 ) { fputc(gifBlockSize, fout); while ( gifBlockSize-- > 0 ) fputc(Xgetc(fp),fout); } if ( gifBlockSize == 0 ) fputc(gifBlockSize, fout); else TheEnd1("GifPassing: Unexpected End of File\n"); } fclose(fp); } /* * read Gif header */ void GifScreenHeader(FILE *fp, FILE *fout, int firstTime) { int temp, i; for(i = 0; i < 6; i++) { temp = Xgetc(fp); if(i == 4 && temp == '7') temp = '9'; if (firstTime) fputc(temp, fout); } gifscrn.width = GifGetShort(fp); gifscrn.height = GifGetShort(fp); temp = Xgetc(fp); if (firstTime) { GifPutShort(gifscrn.width, fout); GifPutShort(gifscrn.height, fout); fputc(temp, fout); } gifscrn.m = temp & 0x80; gifscrn.cres = (temp & 0x70) >> 4; gifscrn.pixbits = temp & 0x07; gifscrn.bc = Xgetc(fp); if (firstTime) { if (debugFlag) fprintf(stderr, "First Time ... "); if(GifBgcolor) gifscrn.bc = GifBgcolor & 0xff; fputc(gifscrn.bc, fout); } temp = Xgetc(fp); if (firstTime) { fputc(temp, fout); if ( minimize && gifscrn.bc == 0 ) { /* Set a pseudo screen filled with the background color. This is only done for background color index == 0 because of Netscape and I.E.'s strange handling of backgrounds not covered by an image. */ temp = gifscrn.width * gifscrn.height; if (( pixold = (UBYTE *)malloc(temp * sizeof(UBYTE)) ) == NULL ) TheEnd1("No memory for image\n"); if (debugFlag) fprintf(stderr, "BACKGROUND = %d\n", gifscrn.bc); while (temp > 0) pixold[--temp] = 0; /* gifscrn.bc; */ gifimageold.left = gifimageold.top = 0; gifimageold.width = gifscrn.width; gifimageold.height = gifscrn.height; gifimageold.pixbits = gifscrn.pixbits; } } imagec = gifPtwo[(1+gifscrn.pixbits)]; if (debugFlag) fprintf(stderr, "Screen #%d: %dx%dx%d m=%d cres=%d bkgnd=%d pix=%d\n", count, gifscrn.width, gifscrn.height, imagec, gifscrn.m, gifscrn.cres, gifscrn.bc, gifscrn.pixbits); if (gifscrn.m) { for(i = 0; i < imagec; i++) { gifCmap[i].cmap.red = temp = Xgetc(fp); if (firstTime) fputc(temp, fout); gifCmap[i].cmap.green = temp = Xgetc(fp); if (firstTime) fputc(temp, fout); gifCmap[i].cmap.blue = temp = Xgetc(fp); if (firstTime) fputc(temp, fout); if(firstTime && (global.trans.type==TRANS_RGB && global.trans.valid==0) ) { if (global.trans.red == gifCmap[i].cmap.red && global.trans.green == gifCmap[i].cmap.green && global.trans.blue == gifCmap[i].cmap.blue) { if(debugFlag > 1) fprintf(stderr, " Transparent match at %d\n", i); global.trans.map = i; global.trans.valid = 1; } else if(debugFlag > 1) fprintf(stderr, "No transp. RGB=(%x,%x,%x)\n", gifCmap[i].cmap.red, gifCmap[i].cmap.green, gifCmap[i].cmap.blue); } } } } void ReadImageHeader(FILE *fp) { int tnum, i, flag; gifimage.left = GifGetShort(fp); if(global.left) gifimage.left += global.left; gifimage.top = GifGetShort(fp); if(global.top) gifimage.top += global.top; gifimage.width = GifGetShort(fp); gifimage.height = GifGetShort(fp); flag = Xgetc(fp); gifimage.i = flag & 0x40; gifimage.pixbits = flag & 0x07; gifimage.m = flag & 0x80; imagex = gifimage.width; imagey = gifimage.height; tnum = gifPtwo[(1+gifimage.pixbits)]; if (debugFlag > 1) fprintf(stderr, "Image: %dx%dx%d (%d,%d) m=%d i=%d pix=%d \n", imagex, imagey, tnum, gifimage.left, gifimage.top, gifimage.m, gifimage.i, gifimage.pixbits); /* if there is an image cmap then read it */ if (gifimage.m) { if(debugFlag>1) fprintf(stderr, "DEBUG:Transferring colormap of %d colors\n", imagec); /* * note below assignment, it may make the subsequent code confusing */ gifscrn.pixbits = gifimage.pixbits; for(i = 0; i < tnum; i++) { gifCmap[i].cmap.red = Xgetc(fp); gifCmap[i].cmap.green = Xgetc(fp); gifCmap[i].cmap.blue = Xgetc(fp); } } gifimage.m = 0; if ( globscrn.m && globscrn.pixbits == gifscrn.pixbits ) { for (i = gifMask[1+globscrn.pixbits]; i >= 0; i--) { if (gifGmap[i].cmap.red != gifCmap[i].cmap.red || gifGmap[i].cmap.green != gifCmap[i].cmap.green || gifGmap[i].cmap.blue != gifCmap[i].cmap.blue ) { gifimage.m = 0x80; break; } } } else gifimage.m = 0x80; return; } void WriteImageHeader(FILE *fout) { int temp, i, flag; /* compute a Gif_GCL */ fputc(0x21, fout); fputc(0xF9, fout); fputc(0x04, fout); flag = global.disposal <<2; if(global.time) flag |= 0x80; if(global.trans.type == TRANS_RGB && global.trans.valid == 0) gifimage.m = 0x80; temp = global.trans.map; if (gifimage.m != 0 && global.trans.type == TRANS_RGB ) { temp = 0; /* set a default value, in case nothing found */ for (i = gifMask[1+gifscrn.pixbits]; i >= 0; i--) { if(global.trans.red == gifCmap[i].cmap.red && global.trans.green == gifCmap[i].cmap.green && global.trans.blue == gifCmap[i].cmap.blue) { if(debugFlag > 1) fprintf(stderr, " Transparent match at %d\n", i); temp = i; flag |= 0x01; } } } else if(global.trans.valid) flag |= 0x01; fputc(flag, fout); GifPutShort(global.time, fout); /* the delay speed - 0 is instantaneous */ fputc(temp, fout); /* the transparency index */ if(debugFlag > 1) { fprintf(stderr, "GCL: delay %d", global.time); if(flag && 0x1) fprintf(stderr, " Transparent: %d", temp); fputc('\n', stderr); } fputc(0, fout); /* end of Gif_GCL */ fputc(',', fout); /* image separator */ GifPutShort(gifimage.left , fout); GifPutShort(gifimage.top , fout); GifPutShort(gifimage.width , fout); GifPutShort(gifimage.height, fout); fputc(gifscrn.pixbits | gifimage.i | gifimage.m, fout); if ( gifimage.m ) { for(i = 0; i < imagec; i++) { fputc(gifCmap[i].cmap.red, fout); fputc(gifCmap[i].cmap.green, fout); fputc(gifCmap[i].cmap.blue, fout); } if(debugFlag) fprintf(stderr, "Local %d color map for picture #%d\n", imagec, count); } } void GifComment(FILE *fout, char *string) { int len; if( (len = strlen(string)) > 254 ) fprintf(stderr, "GifComment: String too long ; dropped\n"); else if ( len > 0 ) { /* Undocumented feature: Empty comment string means no comment block in outfile */ fputc(0x21, fout); fputc(0xFE, fout); fputc(len, fout); fputs(string, fout); fputc(0, fout); } return; } /* * Write a Netscape loop marker. */ void GifLoop(FILE *fout, unsigned int repeats) { fputc(0x21, fout); fputc(0xFF, fout); fputc(0x0B, fout); fputs("NETSCAPE2.0", fout); fputc(0x03, fout); fputc(0x01, fout); GifPutShort(repeats, fout); /* repeat count */ fputc(0x00, fout); /* terminator */ if(debugFlag) fprintf(stderr, "Wrote loop extension\n"); } void CalcTrans(char *string) { if(string[0] != '#') { global.trans.type = TRANS_MAP; global.trans.map = atoi(string); global.trans.valid = 1; } else { /* it's an RGB value */ unsigned int r, g, b; string++; if (debugFlag > 1) fprintf(stderr, "String is %s\n", string); if(3 == sscanf(string, "%2x%2x%2x", &r, &g, &b)) { global.trans.red = r; global.trans.green = g; global.trans.blue = b; global.trans.type = TRANS_RGB; global.trans.valid = 0; if(debugFlag > 1) fprintf(stderr, "Transparent RGB=(%x,%x,%x) = (%d,%d,%d)\n", r, g, b, r, g, b); } } if(debugFlag > 1) fprintf(stderr, "DEBUG:CalcTrans is %d\n", global.trans.type); } void SetOffset(char *string) { int npar, offX, offY; char sep; if ( (npar = sscanf(string, "%d%c%d", &offX, &sep, &offY)) == 3 ) { /* set the offset */ global.left = offX; global.top = offY; if(debugFlag > 1) fprintf(stderr, "Offset set to %d,%d\n", global.left, global.top); return; } fprintf(stderr, "Offset string: '%s'; fields = %d\n", string, npar); TheEnd1("Couldn't parse offset values.\n"); } void TheEnd() { exit(0); } void TheEnd1(char *p) { fprintf(stderr, "Image #%d: %s", count, p); TheEnd(); } void Usage() { fprintf(stderr, "\nUsage: whirlgif %s\n %s\n %s\n", "[-o outfile] [-loop [count]] [-time #delay]", "\t-disp [ none | back | prev | not ]", "\t[ -i listfile] file1 [ -time #delay] file2 ..."); exit(1); } UBYTE Xgetc(FILE *fin) { int i; if ( ( i = fgetc(fin) ) == EOF ) { TheEnd1("Unexpected EOF in input file\n"); } return(i & 0xff); }