[comp.graphics] generate GIF file source code

jeff@minnow.sp.unisys.com (Jeff Sampson) (02/25/89)

   There were several requests for a program to create GIF pictures lately.
   I just finished this one and it seems to work on my files. I'm sure
   there are bugs, but some people sounded quite interested in source
   code. So if anyone is still interested...

 - cut here - cut here -

/* This is my interpretation of the GIF compression algorithim. I first
 * wrote it in BASIC, because I am not a C programmer. This is my first
 * C program, so I don't want to here complaints about my style and the fact
 * that I don't use structures and my variables are all global. If you can
 * live with that, here is a program that works. I wrote it using Borland's
 * Turbo-C, but it does compile on big machines.
 *
 * This was done from an example by Bob Montgomery. Most of the variables
 * that are important have the same names as his example.
 *
 * Feel free to hack this up all you want. I tried to comment it to the point
 * where (if you have Bob Montgomery's example and the spec from
 * Compu-Serve) it makes sense.
 *
 * Input file format:
 *
 *  I used a simple x by y array of 8 bit pixels for my input file.
 *  This is because my Matrox PIP512a digitizer board saves pictures in
 *  this format. Each byte is a pixel, if you use one bit or all 8 bits.
 *  This way I didn't have to unpack different formats. You can either
 *  hack this program to read your format or write a translator from your
 *  format to mine.
 *
 * Palette data:
 *
 *  Again, since my Matrox card uses a 256 level grey scale, that is what
 *  my program generates. If you have your own palette, you could read the
 *  palette data from a file.
 *
 * I have only tried this for 8 bit per pixel data. The header data and
 * the palette data will probably need to be changed for smaller bit sizes.
 *
 * If you are tempted to use the string functions in C to improve this,
 * keep in mind C uses null terminated strings. I didn't think this would
 * work because the data produced can contain nulls.
 *
 *					Jeff Sampson - Unisys Corp.
 *					(612)-635-7469
 */

#include <stdio.h>

#define STRSIZE 256
#define TABSIZE 24000

/* If you get forced clear codes, you can make TABSIZE bigger
 * if your system can handle it, however turbo-c will compile
 * a size of 24000 from the environment. If you make it bigger,
 * keep in mind pointers and tree pointer tables may need to be
 * long ints.
 */


/* function declarations */

void sendbits();
void makestream();
void gif();
int tabcomp();

/* variables */

char infile[30], outfile[30], tmpchar;

unsigned char strbuf[STRSIZE], table[TABSIZE];

int xsize, ysize, bits, colors, nextent, nextstr, codesize,
    i, code, inpchar, suffix, x, y, eofcode, clearcode,
    j, offset, ch, buf[256], bufpnt, prefix, nodepnt,
    strpnt, val, maxcode;

unsigned int ltbuf[4096], eqbuf[4096], gtbuf[4096], tabcnt[4096];

FILE *iptr,*optr;

main()
{
  printf("Generic raster file to GIF compression program by Jeff Sampson.\n");
  printf("\nEnter file name to convert ");
  scanf("%s",infile);
  printf("\nEnter output file name ");
  scanf("%s",outfile);
  printf("\nEnter X dimension ");
  scanf("%d",&xsize);
  printf("\nEnter Y dimension ");
  scanf("%d",&ysize);
  printf("\nEnter number of bits per pixel ");
  scanf("%d",&bits);
  printf("\n\n");
/*
  strcpy(infile,"jeff.mat");
  strcpy(outfile,"jeff.gif");
  xsize=512;
  ysize=480;
  bits=8;
*/
/* set up GIF variables */
  colors=1 << bits;
  clearcode=colors;
  eofcode=colors + 1;
  codesize=bits+1;
  maxcode=colors << 1;
/* init output buffer pointers */
  offset=0;
  ch=0;
  bufpnt=0;
/* open files (Note: turbo-c needs the "b" on opens to specify "binary") */
  iptr=fopen(infile,"rb");
  optr=fopen(outfile,"wb");
/* set up binary tree pointers */
  nextent=colors+2;
  nextstr=1;   /* start buffer at location 1, value of 0 = empty */
  eqbuf[nextent]=0;
/* build GIF header in output file */
  fputs("GIF87a",optr);

  tmpchar=xsize & 255;
  fputc(tmpchar,optr);
  tmpchar=xsize >> 8;
  fputc(tmpchar,optr);

  tmpchar=ysize & 255;
  fputc(tmpchar,optr);
  tmpchar=ysize >> 8;
  fputc(tmpchar,optr);
/* The fwrites worked on Turbo-C but didn't work on a 32 bit UNIX machine
  fwrite(&xsize,2,1,optr);
  fwrite(&ysize,2,1,optr);
*/
  fputc(151,optr); /* some flags that worked for me */
  fputc(0,optr);
  fputc(0,optr);

/* build color table ( I just create a grey scale now, this could be
   loaded from a file) */

  for (i=0; i<colors; i++) {
    fputc(i,optr);
    fputc(i,optr);
    fputc(i,optr);
  }

/* add rest of header */
  fputs(",",optr);
  fputc(0,optr);
  fputc(0,optr);
  fputc(0,optr);
  fputc(0,optr);

  tmpchar=xsize & 255;
  fputc(tmpchar,optr);
  tmpchar=xsize >> 8;
  fputc(tmpchar,optr);

  tmpchar=ysize & 255;
  fputc(tmpchar,optr);
  tmpchar=ysize >> 8;
  fputc(tmpchar,optr);
/*
  fwrite(&xsize,2,1,optr);
  fwrite(&ysize,2,1,optr);
*/
  fputc(0,optr);
  fputc(bits,optr);
/* done with header */

  sendbits(clearcode); /* send out clearcode*/

/* pre load first character */

  inpchar=fgetc(iptr);
  suffix=inpchar;
  strbuf[0]=suffix;
  strpnt=1;

/* read characters one at a time and do compression routine for each one */

  for ( y=0; y<ysize; y++ ) {
    printf(".");
    for ( x=0; x<xsize; x++ ) {
      inpchar=fgetc(iptr);
      gif(inpchar);
    }
  }

/* output GIF EOF code */

  sendbits(eofcode);

/* flush output buffer */

  if ( offset != 0 ) {
    buf[bufpnt]=ch;
    bufpnt=bufpnt+1;
  }

  if ( bufpnt !=0 ) {
    fputc(bufpnt,optr); /* write block size */
    for (j=0; j<bufpnt; j++ )
      fputc(buf[j],optr); /* write buffer entry */

/* some GIF terminating stuff that I forgot what it does */

    fputc(0,optr);
    fputs(";",optr);
  }
}

/* function sendbits() - output 'codesize' number of bits in current number */
void sendbits(num)
int num;
{
int temp;

  for (i=1; i<=codesize; i++ ) {
    temp=num & 1;
    makestream(temp);
    num=num >> 1;
  }
}

/* function makestream() - add current bit to bitstream */
void makestream(num1)
int num1;
{

/* slide this bit over to the right spot and OR it into current byte */

  for (j=0; j<offset; j++ )
    num1=num1 << 1;
  ch=ch | num1;
  offset=offset + 1;

/* if this byte is full, then write it to the output buffer */

  if (offset == 8 ) {
    offset = 0;
    buf[bufpnt]=ch;
    ch=0;
    bufpnt=bufpnt+1;

/* if buffer is full, write buffer size to output file, then write buffer */

    if (bufpnt==254) {
      fputc(bufpnt,optr); /* write block size */
      for (j=0; j<bufpnt; j++ ) {
        fputc(buf[j],optr); /* write buffer entry */
      }
      bufpnt=0;
    }
  }
}

/* function gif() - actual GIF compression routine */
void gif(num)
int num;
{
  prefix=suffix;
  suffix=num;           /* load new number into suffix */
  strbuf[strpnt]=num;   /* add new number onto existing string */
  strpnt=strpnt+1;      /* inc pointer */

/* make sure strbuf does not over flow */

  if ( strpnt > STRSIZE ) {
    printf("strbuf overflow, increase STRSIZE");
    exit();
  }

/* search string table for the entry in strbuf */

  nodepnt=colors+2; /* start at beginning of table */
  if (eqbuf[nodepnt] != 0 ) {  /* if 0, first entry and skip table search */
    val=1;
    while ( val != 0 ) { /* loop until found or added */
      val=tabcomp();     /* compare entry in table with strbuf */

      if (val < 0 ) {
        if ( ltbuf[nodepnt] != 0 ) /* if less than and not null entry, */
          nodepnt=ltbuf[nodepnt];  /* then load new node pointer, try again */
        else {
          ltbuf[nodepnt]=nextent;  /* if less than and null entry, */
          break;                   /* then add new entry at this point */
        }
      }

      if ( val > 0 ) {
        if ( gtbuf[nodepnt] != 0 )
          nodepnt=gtbuf[nodepnt];
        else {
          gtbuf[nodepnt]=nextent;
          break;
        }
      }

      if ( val == 0 ) {     /* if found, */
        suffix=nodepnt;     /* load suffix with entry number and return */
        return;
      }
    }
  }

/* add entry in strbuf to the string table - first copy strbuf to the
 * next spot in the table.
 */
  for ( i=0; i<strpnt; i++ )
    table[nextstr+i]=strbuf[i];
  tabcnt[nextent]=strpnt; /* Put the length of this entry in tabcnt. */
  eqbuf[nextent]=nextstr; /* Put the table array pointer in eqbuf. */
/* Set ltbuf and gtbuf to 0 to indicate this is last entry in tree. */
  ltbuf[nextent]=0;
  gtbuf[nextent]=0;
/* Advanced table array pointer to next available spot. */
  nextstr=nextstr+strpnt;
  nextent=nextent+1; /* Advance to next open slot in pointer table. */
  code=nextent; /* This indicates how many entries have been used */
  sendbits(prefix); /* output this code */
  strbuf[0]=num; /* start with new nuber in strbuf */
  strpnt=1; /* set strbuf pointer to second location */

/* If at end of table, issue clear code and reset for original code size */

  if (code > 4095) {
    printf("normal clearcode\n");
    sendbits(clearcode);
    codesize=bits+1;
    nextent=colors+2;
    nextstr=1;
    eqbuf[nextent]=0;
    codesize=bits+1;
    maxcode=colors << 1;
    code=colors+2;
  }

/*  Note: this is a trick to allow a small table size if running on a small
 *  machine. If running on a large machine and you get this message, you
 *  can increase the size of TABSIZE
 */

  if (nextstr > (TABSIZE-1000)) {
    printf("forced clearcode - nextstr=%d\n",nextstr);
    sendbits(clearcode);
    codesize=bits+1;
    nextent=colors+2;
    nextstr=1;
    eqbuf[nextent]=0;
    codesize=bits+1;
    maxcode=colors << 1;
    code=colors+2;
  }

/* When size of numbers goes beyond current output length, this adds on more
 * bit to the current size
 */

  if (code > maxcode) {
    codesize=codesize+1;
    maxcode=maxcode << 1;
  }
}

/* function tabcomp() - compare entry in table with strbuf
 * This doesn't do a real value compare, just enough to select a spot
 * in the binary tree. Also notice if one entry has more bytes than another,
 * I consider it to be larger.
 *
 * Returns  1 - table > strbuf
 *	    0 - table = strbuf
 *	   -1 - table < strbuf
 */

int tabcomp()
{
int temp;

/* check for table entry shorter than strbuf, if so return (-1) */
  if (tabcnt[nodepnt] < strpnt )
    return(-1);
/* check for table entry longer than strbuf, if so return (1) */
  if (tabcnt[nodepnt] > strpnt )
    return(1);
/* now scan through and compare each byte */
  for ( i=0; i<strpnt; i++ ) {
    temp=table[eqbuf[nodepnt]+i];
    if ( temp < strbuf[i] )
      return(-1);
    if ( temp > strbuf[i] )
      return(1);
  }
  return(0);
}

 - cut here - cut here -

Jeff Sampson			UUCP: ...!pyramid!pwcs!minnow!jeff
Unisys Corporation		      ...!amdahl!ems!minnow!jeff
Phone: (612) 635-7469	       CSNET: jeff@minnow.SP.Unisys.Com