/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * filename: m-gif.c                                                       *
 *                                                                         *
 * UTIL C-source: Medical Image Conversion Utility                         *
 *                                                                         *
 * purpose      : read and write GIF files                                 *
 *                                                                         *
 * project      : (X)MedCon by Erik Nolf                                   *
 *                                                                         *
 * Functions    : MdcCheckGIF()              - Check for GIF format        *
 *                MdcReadGIF()               - Read GIF file               *
 *                MdcDoExtension()           - Handle extensions           *
 *                MdcReadGifHeader()         - Read header block           *
 *                MdcReadGifImageBlk()       - Read image block            *
 *                MdcReadGifControlBlk()     - Read control block          *
 *                MdcReadGifPlainTextBlk()   - Read plain text block       *
 *                MdcReadGifApplicationBlk() - Read application block      *
 *                MdcUnpackImage()           - Unpack LZW compressed image *
 *                MdcPutGifLine()            - Copy line to memory buffer  *
 *                MdcWriteGIF()              - Write GIF file              *
 *                MdcGetGifOpt()             - Get specific GIF options    *
 *                MdcWriteGifHeader()        - Write gif header            *
 *                MdcWriteControlBlock()     - Write control block         *
 *                MdcWriteImageBlock()       - Write image block           *
 *                MdcWriteImage()            - Write LZW compressed image  *
 *                MdcWriteCommentBlock()     - Write comment block         *
 *                MdcWriteLoopBlock()        - Write loop block            *
 *                MdcWriteApplicationBlock() - Write application block     *
 *                MdcInitTable()             - Initialize compression table*
 *                MdcFlush()                 - Flush code the code buffer  *
 *                MdcWriteCode()             - Write code to code buffer   *
 *                                                                         *
 * Notes        : Code fragments addapted from Alchemy Mindworks, Inc.     *
 *                Original code GIF reader/writer copyright (c) 1991       *
 *                                                                         *
 *                "Supercharged bitmapped graphics"                        *
 *                written by Steve Rimmer                                  *
 *                published by Windcrest(r)/McGraw-Hill                    *
 *                ISBN: 0-8306-3788-5                                      *
 *                                                                         *
 *                We only write GIF89a animated gifs                       *
 *                                                                         *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
 */

/*
   Copyright (C) 1997-2026 by Erik Nolf

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any later
   version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
   Public License for more details.

   You should have received a copy of the GNU General Public License along
   with this program; if not, write to the Free Software Foundation, Inc.,
   59 Place - Suite 330, Boston, MA 02111-1307, USA.  */

/****************************************************************************
                              H E A D E R S
****************************************************************************/

#include "m-depend.h"

#include <stdio.h>
#ifdef LIBMDC_HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef LIBMDC_HAVE_STRING_H
#include <string.h>
#endif
#ifdef LIBMDC_HAVE_STRINGS_H
#ifndef _WIN32
#include <strings.h>
#endif
#endif

#include "medcon.h"

/****************************************************************************
                              D E F I N E S
****************************************************************************/

#define largest_code    4095            /* largest possible code */
#define table_size      5003            /* table dimensions */

static Uint8 code_buffer[259];         /* where the codes go */
static Int16 oldcode[table_size];      /* the table */
static Int16 currentcode[table_size];
static Uint8 newcode[table_size];

static Int16 code_size;
static Int16 clear_code;
static Int16 eof_code;
static Int16 bit_offset;
static Int16 byte_offset;
static Int16 bits_left;
static Int16 max_code;
static Int16 free_code;


/****************************************************************************
                            F U N C T I O N S
****************************************************************************/

int MdcCheckGIF(FILEINFO *fi)
{

  MDC_GIFHEADER gh;

  memset(&gh,0,MDC_GIF_GH_SIZE);

  if ( fread((char *)&gh,1,MDC_GIF_GH_SIZE,fi->ifp) != MDC_GIF_GH_SIZE )
    return(MDC_BAD_READ);
  if ( memcmp(gh.sig,MDC_GIF_SIG,3) )
    return(MDC_FRMT_NONE);

  return(MDC_FRMT_GIF);
}

/* unpack a GIF file */
char *MdcReadGIF(FILEINFO *fi)
{
  FILE *fp=fi->ifp;
  MDC_GIFHEADER gh;
  MDC_GIFIMAGEBLOCK iblk;
  Uint32 b, c, img=0, number=0;
  char *err=NULL;
  Uint32 bytes;

  if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_BEGIN,0.,"Reading GIF:");

  if (MDC_VERBOSE) MdcPrntMesg("GIF  Reading <%s> ...",fi->ifname);

  if (MDC_ECHO_ALIAS == MDC_YES) {
    MdcEchoAliasName(fi); return(NULL); /* a little pointless for GIF */
  }

  /* initialize structures */
  memset(&gh,0,MDC_GIF_GH_SIZE);
  memset(&iblk,0,MDC_GIF_IBLK_SIZE);

  /* put some defaults we use */
  fi->endian=MDC_FILE_ENDIAN=MDC_LITTLE_ENDIAN; /* always for a GIF */
  fi->dim[0] = 4; fi->dim[4]=1;

  /* make sure it's a GIF file */
  if ( MdcReadGifHeader(fp,&gh) != MDC_YES ) return("GIF  Bad read gifheader");
  if ( memcmp(gh.sig, MDC_GIF_SIG, 3) ) return("No GIF file");

  if (MDC_INFO) {
    MdcPrntScrn("GIFHEADER (%d bytes)\n",MDC_GIF_GH_SIZE);
    MdcPrintLine('-',MDC_HALF_LENGTH);
  }

  fi->bits=8; /* (gh.flags & 0x0007) + 1; */

  if (MDC_INFO) {
    MdcPrntScrn("signature: %.6s\n",gh.sig);
    MdcPrntScrn("screen width: %d\nscreen height: %d\n",
                          gh.screenwidth,gh.screenheight);
    MdcPrntScrn("global palette: ");
  }

  /* get colour map if there is one */
  if (gh.flags & 0x80) {
    if (fi->map < 2) fi->map=MDC_MAP_PRESENT;
    c = 3 * (1 << ((gh.flags & 0x0007) + 1));
    if (fread(fi->palette,1,c,fp) != c)
      return("GIF  Bad read global palette");

    if (MDC_INFO) {
      MdcPrntScrn("Yes\n");
      MdcPrntScrn("bits: %hd\n",(gh.flags & 7)+1 );
      MdcPrntScrn("colors: %hd\n",(1 << ((gh.flags & 0x0007)+1)));
      MdcPrntScrn("sorted: ");
      if (gh.flags > 0x0008 ) MdcPrntScrn("Yes\n");
      else MdcPrntScrn("No\n");
      MdcPrntScrn("background: %hd\n", (int)gh.background);
      MdcPrntScrn("aspect: %hd\n\n",(int)gh.aspect);
    }

  } else if (MDC_INFO) MdcPrntScrn("No\n");

  /* step through the blocks */
  while((c=(Int16)fgetc(fp))==',' || c=='!' || c==0) {

    /* if it's an image block... */
    if (c == ',') {

      if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_SET,0.0,NULL);

      if (MDC_INFO) {
        MdcPrntScrn("\n");
        MdcPrintLine('=',MDC_HALF_LENGTH);
        MdcPrntScrn("\nIMAGEBLOCK %03d (%d bytes)\n",number+1
                                                    ,MDC_GIF_IBLK_SIZE);
        MdcPrintLine('-',MDC_HALF_LENGTH);
      }

      /* get the start of the image block */
      if (MdcReadGifImageBlk(fp,&iblk) != MDC_YES)
        return("GIF  Bad read imageblock");

      number+=1;

      if (!MdcGetStructID(fi,number)) return("GIF  Bad malloc IMG_DATA struct");

      /* fill in the IMG_DATA struct */
      fi->image[img].width=(Uint32) iblk.width;
      fi->image[img].height=(Uint32) iblk.height;

      if (MDC_INFO) {
        MdcPrntScrn("image left: %hu\n",iblk.left);
        MdcPrntScrn("image top: %hu\n",iblk.top);
        MdcPrntScrn("image width: %hu\n",iblk.width);
        MdcPrntScrn("image height: %hu\n",iblk.height);
        MdcPrntScrn("interlaced: ");
        if ( iblk.flags & 0x0040 ) MdcPrntScrn("Yes\n");
        else MdcPrntScrn("No\n");
        MdcPrntScrn("local palette: ");
      }
      /* get the local colour map if there is one */
      if (iblk.flags & 0x80) {
        if (fi->map < 2) fi->map=MDC_MAP_PRESENT;
        if (MDC_INFO) {
          MdcPrntScrn("Yes\n");
          MdcPrntScrn("sorted: ");
          if ( iblk.flags & 0x0020 ) MdcPrntScrn("Yes\n");
          else MdcPrntScrn("No\n");
        }
        b = 3*(1<<((iblk.flags & 0x0007) + 1));
        if (fread(fi->palette,1,b,fp) != b)
          return("GIF  Bad read local palette");
      } else if (MDC_INFO) MdcPrntScrn("No\n");

      /* get the initial code size */
      if ((c=(Int16)fgetc(fp))==EOF) return("GIF  Bad read initial code");

      fi->image[img].bits = c;
      fi->image[img].flags = iblk.flags;

      /* get an image buffer */

      bytes=MdcPixels2Bytes(fi->image[img].width*fi->image[img].height*8);

      if ( (fi->image[img].buf=MdcGetImgBuffer(bytes)) == NULL )
        return("GIF  Bad malloc image buffer");

      if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_SET,0.5,NULL);

      /* unpack the image */
      err = MdcUnpackImage(fi,img);

      if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_SET,1.0,NULL);

      /* fill in the FILEINFO struct */
      fi->bits=fi->image[img].bits=8;
      fi->image[img].type=BIT8_U;
      fi->dim[3] = number;
      img = number;

      /* warning if there was an error */
      if (err != NULL) {
        MdcPrntWarn(err);
        err = MdcHandleTruncated(fi,fi->number,MDC_NO);
        if (err != NULL) return(err);
        break;
      }

    }else if (c == '!') MdcDoExtension(fi);

  }

  if (fi->number == 0) return("GIF  No valid images found");

  MdcCloseFile(fi->ifp);

  if (fi->truncated) return("GIF  Truncated image file");

  return NULL;
}
/* this function is called when the GIF decoder encounters an extension */
void MdcDoExtension(FILEINFO *fi)
{
  FILE *fp=fi->ifp;
  MDC_GIFPLAINTEXT pt;
  MDC_GIFCONTROLBLOCK cb;
  MDC_GIFAPPLICATION ap;
  Int16 c,n,i;

  memset(&pt,0,MDC_GIF_TBLK_SIZE);
  memset(&cb,0,MDC_GIF_CBLK_SIZE);
  memset(&ap,0,MDC_GIF_ABLK_SIZE);

  if (MDC_INFO) { MdcPrntScrn("\n"); MdcPrintLine('=',MDC_HALF_LENGTH); }

  switch (c=(Int16)fgetc(fp)) {

   case 0x0001: /* plain text descriptor */
    if (MdcReadGifPlainTextBlk(fp,&pt) == MDC_YES) {

      if (MDC_INFO) {
        MdcPrntScrn("\nPLAIN TEXT BLOCK\n");
          MdcPrintLine('-', MDC_HALF_LENGTH);
        MdcPrntScrn("This block requires %u bytes\n",pt.blocksize);
        MdcPrntScrn("Text location at (%u,%u)\n",pt.left,pt.top);
        MdcPrntScrn("Grid dimensions are %u by %u\n",pt.gridwidth
                                                       ,pt.gridheight);
        MdcPrntScrn("Cell dimensions are %u by %u\n",pt.cellwidth
                                                       ,pt.cellheight);
        MdcPrntScrn("Foregound colour is %u\n",pt.forecolour);
        MdcPrntScrn("Background colour is %u\n",pt.backcolour);
      }
      do {
        if ((n=(Int16)fgetc(fp)) != EOF) {
          for (i=0;i<n;++i) {
             c = (Int16)fgetc(fp);
             if (MDC_INFO) MdcPrintChar(c);
          }
        }
      }while (n > 0 && n != EOF);
    }else{
      MdcPrntWarn("GIF  Bad read plain text block");
    }
    break;

   case 0x00f9: /* graphic control block */
    if (MdcReadGifControlBlk(fp,&cb) == MDC_YES) {

      if (MDC_INFO) {
        MdcPrntScrn("\nCONTROL BLOCK\n"); MdcPrintLine('-',MDC_HALF_LENGTH);
        MdcPrntScrn("This block requires %u bytes\n",cb.blocksize);
        switch((cb.flags >> 2) & 0x0007) {
          case 0:
            MdcPrntScrn("No disposal specified\n");
            break;
          case 1:
            MdcPrntScrn("Do not dispose\n");
            break;
          case 2:
            MdcPrntScrn("Dispose to background colour\n");
            break;
          case 3:
            MdcPrntScrn("Dispose to previous graphic\n");
            break;
          default:
            MdcPrntScrn("Unknown disposal procedure\n");
            break;
        }
        if (cb.flags & 0x0002)
          MdcPrntScrn("User input required - delay for %g seconds\n"
                                      ,(float)cb.delay/100.);
        else
          MdcPrntScrn("No user input required\n");

        if (cb.flags & 0x0001)
          MdcPrntScrn("Transparent colour: %u\n",cb.transparent_colour);
        else
          MdcPrntScrn("No transparent_colour\n");
      }
    }else{
      MdcPrntWarn("GIF  Bad read control block");
    }
    break;

   case 0x00fe: /* comment extension */

    if (MDC_INFO) {
      MdcPrntScrn("\nCOMMENT BLOCK\n"); MdcPrintLine('-',MDC_HALF_LENGTH);
    }
    do {
      if ((n=(Int16)fgetc(fp)) != EOF) {
        if (n > 0) {
          fi->comment = (char *)MdcRealloc(fi->comment,fi->comm_length+n+2);
          if (fi->comment == NULL) {
            MdcPrntWarn("Couldn't allocate comment buffer");
          }else if (fi->comm_length==0) fi->comment[fi->comm_length] = '\0';
        }
        for (i=0;i<n;++i) {
           c = (Int16)fgetc(fp);
           if (MDC_INFO) MdcPrintChar(c);
           if (fi->comment != NULL) {
             fi->comment[fi->comm_length++] = c;
           }
        }
        if ((n <= 0) && (fi->comment !=  NULL)) {
          fi->comment[fi->comm_length++] = '\n';
          fi->comment[fi->comm_length]   = '\0';
        }
      }
    }while (n > 0 && n != EOF);
    break;

   case 0x00ff: /* application extension */
    if (MdcReadGifApplicationBlk(fp,&ap) == MDC_YES) {
      if (MDC_INFO) {
        MdcPrntScrn("\nAPPLICATION BLOCK\n"); MdcPrintLine('-',MDC_HALF_LENGTH);
        MdcPrntScrn("This block requires %d bytes\n",ap.blocksize);
        MdcPrntScrn("Identification string: %.8s\n",ap.applstring);
        MdcPrntScrn("Authentication string: %.3s\n",ap.authentication);
      }
      do {
        if ((n=(Int16)fgetc(fp)) != EOF) {
          if (MDC_INFO) MdcPrntScrn("\nSub-block requires %d bytes:\n",n);
          for (i=0;i<n;++i) {
             c = (Int16)fgetc(fp);
             if (MDC_INFO) MdcPrintChar(c);
          }
        }
      }while (n > 0 && n != EOF);
    }else{
      MdcPrntWarn("GIF  Bad read application block");
    }
    break;

   default: /* something else */
    MdcPrntWarn("GIF  Unknown extension 0x%02.2x\n",c & 0x00ff);
    n=(Int16)fgetc(fp);
    for (i=0;i<n;++i) fgetc(fp);
    break;
  }

}

int MdcReadGifHeader(FILE *fp, MDC_GIFHEADER *gh)
{
  char bb[MDC_GIF_GH_SIZE];

  if (fread(bb,1,MDC_GIF_GH_SIZE,fp) != MDC_GIF_GH_SIZE ) return(MDC_NO);

  memcpy(gh->sig          ,bb  ,6);
  memcpy(&gh->screenwidth ,bb+6,2); MdcSWAP(gh->screenwidth);
  memcpy(&gh->screenheight,bb+8,2); MdcSWAP(gh->screenheight);
  gh->flags      = (Uint8) bb[10];
  gh->background = (Uint8) bb[11];
  gh->aspect     = (Uint8) bb[12];

  return(MDC_YES);
}

int MdcReadGifImageBlk(FILE *fp, MDC_GIFIMAGEBLOCK *ib)
{
  char bb[MDC_GIF_IBLK_SIZE];

  if (fread(bb,1,MDC_GIF_IBLK_SIZE,fp) != MDC_GIF_IBLK_SIZE) return(MDC_NO);

  memcpy(&ib->left  ,bb  ,2); MdcSWAP(ib->left);
  memcpy(&ib->top   ,bb+2,2); MdcSWAP(ib->top);
  memcpy(&ib->width ,bb+4,2); MdcSWAP(ib->width);
  memcpy(&ib->height,bb+6,2); MdcSWAP(ib->height);
  ib->flags = (Uint8) bb[8];

  return(MDC_YES);
}

int MdcReadGifControlBlk(FILE *fp, MDC_GIFCONTROLBLOCK *cb)
{
  char bb[MDC_GIF_CBLK_SIZE];

  if (fread(bb,1,MDC_GIF_CBLK_SIZE,fp) != MDC_GIF_CBLK_SIZE) return(MDC_NO);

  cb->blocksize = (Uint8) bb[0];
  cb->flags     = (Uint8) bb[1];
  memcpy(&cb->delay,bb+2,2); MdcSWAP(cb->delay);
  cb->transparent_colour = (Uint8) bb[4];
  cb->terminator = (Uint8) bb[5];

  return(MDC_YES);
}

int MdcReadGifPlainTextBlk(FILE *fp, MDC_GIFPLAINTEXT *pt)
{
  char bb[MDC_GIF_TBLK_SIZE];

  if (fread(bb,1,MDC_GIF_TBLK_SIZE,fp) != MDC_GIF_TBLK_SIZE) return(MDC_NO);

  pt->blocksize = (Uint8) bb[0];
  memcpy(&pt->left      ,bb+1,2); MdcSWAP(pt->left);
  memcpy(&pt->top       ,bb+3,2); MdcSWAP(pt->top);
  memcpy(&pt->gridwidth ,bb+5,2); MdcSWAP(pt->gridwidth);
  memcpy(&pt->gridheight,bb+7,2); MdcSWAP(pt->gridheight);
  pt->cellwidth  = (Uint8) bb[9];
  pt->cellheight = (Uint8) bb[10];
  pt->forecolour = (Uint8) bb[11];
  pt->backcolour = (Uint8) bb[12];

  return(MDC_YES);
}

int MdcReadGifApplicationBlk(FILE *fp, MDC_GIFAPPLICATION *ap)
{
  char bb[MDC_GIF_ABLK_SIZE];

  if (fread(bb,1,MDC_GIF_ABLK_SIZE,fp) != MDC_GIF_ABLK_SIZE) return(MDC_NO);

  ap->blocksize = (Uint8) bb[0];
  memcpy(ap->applstring    ,bb+1,8);
  memcpy(ap->authentication,bb+9,3);

  return(MDC_YES);
}

/* unpack an LZW compressed image */
char *MdcUnpackImage(FILEINFO *fi, Uint32 nr )
{
  FILE *fp=fi->ifp;
  Int16 bits=fi->image[nr].bits;
  IMG_DATA *id=&fi->image[nr];
  Uint8 pix;

  Int16 bits2;         /* Bits plus 1 */
  Int16 codesize;      /* Current code size in bits */
  Int16 codesize2;     /* Next codesize */
  Int16 nextcode;      /* Next available table entry */
  Int16 thiscode;      /* Code being expanded */
  Int16 oldtoken;      /* Last symbol decoded */
  Int16 currentcode;   /* Code just read */
  Int16 oldcode;       /* Code read before this one */
  Int16 bitsleft;      /* Number of bits left in *p */
  Int16 blocksize;     /* Bytes in next block */
  Int16 line=0;        /* next line to write */
  Int16 nxtbyte=0;     /* next byte to write */
  Int16 pass=0;        /* pass number for interlaced pictures */

  Uint8 *p;           /* Pointer to current byte in read buffer */
  Uint8 *q;           /* Pointer past last byte in read buffer */
  Uint8 b[255];       /* Read buffer */
  Uint8 *u;           /* Stack pointer into firstcodestack */
  Uint8 *linebuffer;  /* place to store the current line */

  static Uint8 firstcodestack[4096];  /* Stack for first codes */
  static Uint8 lastcodestack[4096];   /* Statck for previous code */
  static Int16 codestack[4096];      /* Stack for links */

  static Int16 wordmasktable[] = {
    0x0000,0x0001,0x0003,0x0007,
    0x000f,0x001f,0x003f,0x007f,
    0x00ff,0x01ff,0x03ff,0x07ff,
    0x0fff,0x1fff,0x3fff,0x7fff
  };

  static Int16 inctable[] = { 8,8,4,2,0 };   /* interlace increments */
  static Int16 startable[] = { 0,4,2,1,0 };  /* interlace starts */

  p=q=b;
  bitsleft = 8;

  if (bits < 2 || bits > 8) return("GIF  Bad symbolsize");
  bits2 = 1 << bits;
  nextcode = bits2 + 2;
  codesize2 = 1 << (codesize = bits + 1);
  oldcode=oldtoken=MDC_NO_CODE;

  if ((linebuffer=(Uint8 *)malloc(id->width)) == NULL)
    return("GIF  Bad malloc linebuffer");

  /* loop until something breaks */
  for (;;) {
    if (bitsleft==8) {
      if (++p >= q && (((blocksize = (Int16)fgetc(fp)) < 1) ||
            (q=(p=b)+fread(b,1,(unsigned)blocksize,fp))< (b+blocksize))) {
        MdcFree(linebuffer);
        return("GIF  Unexpected EOF (1)");
      }
      bitsleft = 0;
    }
    thiscode = *p;
    if ((currentcode=(codesize+bitsleft)) <= 8) {
      *p >>= codesize;
      bitsleft = currentcode;

    }else {
      if (++p >= q && (((blocksize = (Int16)fgetc(fp)) < 1) ||
            (q=(p=b)+fread(b,1,(unsigned)blocksize,fp)) < (b+blocksize))) {
        MdcFree(linebuffer);
        return("GIF  Unexpected EOF (2)");
      }
      thiscode |= *p << (8 - bitsleft);
      if (currentcode <= 16) *p >>= (bitsleft=currentcode-8);
      else {
        if (++p >= q && (((blocksize = (Int16)fgetc(fp)) < 1) ||
              (q=(p=b) + fread(b,1,(unsigned)blocksize,fp)) < (b+blocksize))) {
          MdcFree(linebuffer);
          return("GIF  Unexpected EOF (3)");
        }
        thiscode |= *p << (16 - bitsleft);
        *p >>= (bitsleft = currentcode - 16);
      }
    }
    thiscode &= wordmasktable[codesize];
    currentcode = thiscode;

    if (thiscode == (bits2+1)) break; /* found EOI */
    if (thiscode > nextcode) {
      MdcFree(linebuffer);
      return("GIF  Bad compression code");
    }

    if (thiscode == bits2) {
      nextcode = bits2 + 2;
      codesize2 = 1 << (codesize = (bits + 1));
      oldtoken = oldcode = MDC_NO_CODE;
      continue;
    }

    u = firstcodestack;

    if (thiscode==nextcode) {
      if (oldcode==MDC_NO_CODE) {
        MdcFree(linebuffer);
        return("GIF  Bad first code");
      }
      *u++ = oldtoken;
      thiscode = oldcode;
    }

    while (thiscode >= bits2) {
      *u++ = lastcodestack[thiscode];
      thiscode = codestack[thiscode];
    }

    oldtoken = thiscode;

    do {

      pix = (Uint8)thiscode;

      linebuffer[nxtbyte++]=pix;
      if (nxtbyte >= id->width) {
        MdcPutGifLine(id,linebuffer,line);
        nxtbyte=0;

        /* check for interlaced image */
        if (id->flags & 0x40) {
          line+=inctable[pass];
          if (line >= id->height) line=startable[++pass];
        }else ++line;
      }

      if (u <= firstcodestack) break;
      thiscode = *--u;

    }while(1);

    if (nextcode < 4096 && oldcode != MDC_NO_CODE) {
      codestack[nextcode] = oldcode;
      lastcodestack[nextcode] = oldtoken;
      if (++nextcode >= codesize2 && codesize < 12)
        codesize2 = 1 << ++codesize;
    }
    oldcode = currentcode;
  }

  MdcFree(linebuffer);
  return(NULL);
}

/* save one line to memory */
void MdcPutGifLine(IMG_DATA *id, Uint8 *p, Int16 n)
{

  if ( n >= 0  && n < id->height )
    memcpy(id->buf+((Uint32)n*id->width),p,id->width);
}

char *MdcWriteGIF(FILEINFO *fi)
{
  Uint32 nr;
  Uint8 *buf8;
  MDC_GIFOPT opt;

  MDC_FILE_ENDIAN = MDC_LITTLE_ENDIAN; /* always for a gif */

  /* check supported */
  if (fi->type == COLRGB) return("GIF  True color files unsupported");

  memset(&opt,0,sizeof(MDC_GIFOPT));

  if ((MDC_GIF_OPTIONS == MDC_YES) && (XMDC_GUI == MDC_NO)) {
    MdcGetGifOpt(fi,&opt);
  }else {
    opt.transp = MDC_NO;
    opt.loop   = MDC_YES;
    opt.delay  = GIF_DELAY;
  }

  if (XMDC_GUI == MDC_NO) {
    MdcDefaultName(fi,MDC_FRMT_GIF,fi->ofname,fi->ifname);
  }

  if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_BEGIN,0.,"Writing GIF:");

  if (MDC_VERBOSE) MdcPrntMesg("GIF  Writing <%s> ...",fi->ofname);

  if (MDC_FILE_STDOUT == MDC_YES) {
    fi->ofp = stdout;
  }else{
    if (MdcKeepFile(fi->ofname))
      return("GIF  File exists!!");
    if ( (fi->ofp=fopen(fi->ofname,"wb")) == NULL )
      return ("GIF  Couldn't open file");
  }

  if (MDC_FORCE_INT != MDC_NO) {
    if (MDC_FORCE_INT != BIT8_U) {
      MdcPrntWarn("GIF  Only Uint8 pixels supported");
    }
  }

  /* check supported things */
  if (MDC_QUANTIFY || MDC_CALIBRATE) {
    MdcPrntWarn("GIF  Normalization loses quantified values!");
  }

  if ( MdcWriteGifHeader(fi,&opt) )
    return("GIF  Bad write screen description");

  if ( MdcWriteCommentBlock(fi,MDC_LIBVERS) )
     return("GIF  Bad write comment block");

  if ( fi->acquisition_type != MDC_ACQUISITION_UNKNOWN )
  if ( MdcMakeScanInfoStr(fi) ) {
    if ( MdcWriteCommentBlock(fi,mdcbufr) ) {
      return("GIF  Bad write scan info comment block");
    }
  }

  if ( (fi->number > 1) && (opt.loop == MDC_YES) ) {
    if ( MdcWriteLoopBlock(fi,"NETSCAPE","2.0") )
      return ("GIF  Bad write loop block");
  }

  for ( nr=0; nr<fi->number; nr++) {

     if (MDC_PROGRESS) MdcProgress(MDC_PROGRESS_INCR,1./(float)fi->number,NULL);

     if ( (fi->number > 1) || (opt.transp == MDC_YES))
       if ( MdcWriteControlBlock(fi,&opt,nr) )
         return("GIF  Bad write control block");

     if ( MdcWriteImageBlock(fi,nr) )  return("GIF  Bad write image block");

     if (fi->image[nr].type != BIT8_U) {
        if ( (buf8=MdcGetImgBIT8_U(fi, nr)) == NULL)
          return("GIF  Bad malloc new image buffer");
        if ( MdcWriteImage(buf8,fi,nr) ) {
          MdcFree(buf8);  return("GIF  Bad compression (1)");
        }else{
          MdcFree(buf8);
        }
     }else{
        if ( MdcWriteImage(fi->image[nr].buf,fi,nr) )
          return("GIF  Bad compression (2)");
     }
  }

  if ( MdcWriteApplicationBlock(fi,LIBMDC_PACKAGE_NAME,"NLF"))
    return("GIF  Bad write application block");

  if ( fputc(';',fi->ofp) == EOF ) return("GIF  Bad write terminator code");

  MdcCloseFile(fi->ofp);

  return NULL;

}

void MdcGetGifOpt(FILEINFO *fi, MDC_GIFOPT *opt)
{
 opt->loop   = MDC_YES;
 opt->transp = MDC_YES;

 if (MDC_FILE_STDIN == MDC_YES) return; /* stdin already in use */

 MdcPrintLine('-',MDC_FULL_LENGTH);
 MdcPrntScrn("\tGIF OPTIONS\t\tORIG FILE: %s\n",fi->ifname);
 MdcPrintLine('-',MDC_FULL_LENGTH);
 MdcPrntScrn("\n\tSelect color map:\n\n");
 MdcPrntScrn("\t\t%d  ->  present\n",MDC_MAP_PRESENT);
 MdcPrntScrn("\t\t%d  ->  gray\n",MDC_MAP_GRAY);
 MdcPrntScrn("\t\t%d  ->  rainbow\n",MDC_MAP_RAINBOW);
 MdcPrntScrn("\t\t%d  ->  combined\n",MDC_MAP_COMBINED);
 MdcPrntScrn("\t\t%d  ->  hotmetal\n",MDC_MAP_HOTMETAL);
 MdcPrntScrn("\t\t%d  ->  loaded LUT\n",MDC_MAP_LOADED);
 MdcPrntScrn("\n\tYour choice [%d]? ",(int)fi->map);

 if (!MdcPutDefault(mdcbufr)) {
   fi->map = (Uint8)atoi(mdcbufr);
   MdcGetColorMap((int)fi->map,fi->palette);
 }

 if (fi->number > 1) {
   MdcPrntScrn("\n\tInsert a display loop    [yes]? ");
   mdcbufr[0]='y';
   if (!MdcPutDefault(mdcbufr))
     if (mdcbufr[0]=='n' || mdcbufr[0]=='N') opt->loop = MDC_NO;
   MdcPrntScrn("\n\tDelay 1/100ths of a second [%3d]? ", GIF_DELAY);
   if (!MdcPutDefault(mdcbufr)) {
     opt->delay = (Uint16)atoi(mdcbufr);
   }else{
     opt->delay = GIF_DELAY;
   }
 }
 MdcPrntScrn("\n\tInsert transparent color [yes]? ");
 mdcbufr[0]='y';
 if (!MdcPutDefault(mdcbufr))
   if (mdcbufr[0]=='n' || mdcbufr[0]=='N') opt->transp = MDC_NO;
 if (opt->transp == MDC_YES) {
   MdcPrntScrn("\n\tTransparent color [%u]? ",opt->transp_color);
   if (!MdcPutDefault(mdcbufr)) opt->transp_color = (Uint8)atoi(mdcbufr);
 }
 MdcPrntScrn("\n\tBackground  color [%u]? ",opt->bground_color);
 if (!MdcPutDefault(mdcbufr))   opt->bground_color= (Uint8)atoi(mdcbufr);
 MdcPrntScrn("\n");
 MdcPrintLine('-',MDC_FULL_LENGTH);
}

/* write the header */
int MdcWriteGifHeader(FILEINFO *fi, MDC_GIFOPT *opt)
{
  MDC_GIFHEADER gh;
  unsigned int bits = 8;  /* we only write 8-bit gifs */

  /* fill the header struct */
  memset(&gh,0,MDC_GIF_GH_SIZE);
  memcpy(gh.sig,MDC_GIF89_SIG,6);
  gh.screenwidth=(Int16)fi->mwidth;
  gh.screenheight=(Int16)fi->mheight;
  gh.background=opt->bground_color;
  gh.aspect=0;
  gh.flags=0;

  /* set up the global flags */
  if (fi->map < MDC_MAP_PRESENT) {
    /* never reached; just show possibility */
    gh.flags=(((bits-1) & 0x07)<<4);
  }else{
    /* we always add a global colour map */ 
    gh.flags = (0x80 | ((bits-1)<<4) | ((bits-1) & 0x07));
  }

  MdcSWAP(gh.screenwidth); MdcSWAP(gh.screenheight);

  /* write the header */
  fwrite((char *)&gh,1,MDC_GIF_GH_SIZE,fi->ofp);

  /* write the colour map */
  fwrite(fi->palette,1U,3U*(1U<<bits),fi->ofp);

  return(ferror(fi->ofp));
}


int MdcWriteControlBlock(FILEINFO *fi, MDC_GIFOPT *opt, Uint32 n)
{
  MDC_GIFCONTROLBLOCK cb;

  memset(&cb,0,MDC_GIF_CBLK_SIZE);

  fputc('!',fi->ofp);             /* say it's an extension block */
  fputc(0xf9,fi->ofp);            /* say it's a control block    */

  cb.blocksize=0x04;
  cb.flags=0x00;

  if (fi->number > 1 ) {
    cb.flags^=0x02; cb.flags<<=2; /* dispose to background       */
    cb.flags^=0x02;               /* wait for user input         */
    cb.delay = opt->delay;        /* delay to dispose 1/100 sec  */
  }
  if (opt->transp == MDC_YES) {   /* transparent color           */
    cb.flags^=0x01;
    cb.transparent_colour = opt->transp_color;
  }

  MdcSWAP(cb.delay);

  fwrite((char *)&cb,1,MDC_GIF_CBLK_SIZE,fi->ofp);

  return(ferror(fi->ofp));
}


/* write an image descriptor block */
int MdcWriteImageBlock(FILEINFO *fi, Uint32 n)
{
  MDC_GIFIMAGEBLOCK ib;
  Uint8 *palette=NULL;
  int bits = 8; /* we only write 8-bit gifs */

  memset(&ib,0,MDC_GIF_IBLK_SIZE);

  /* fill the image block struct */
  fputc(',',fi->ofp);
  ib.left=0;
  ib.top=0;
  ib.width=fi->image[n].width;
  ib.height=fi->image[n].height;

  /* set the local flags */
  if(palette==NULL) ib.flags=bits-1;
  else ib.flags=((bits-1) & 0x07) | 0x80;

  MdcSWAP(ib.left);  MdcSWAP(ib.top);
  MdcSWAP(ib.width); MdcSWAP(ib.height);

  /* write the block */
  fwrite((char *)&ib,1,MDC_GIF_IBLK_SIZE,fi->ofp);

  return(ferror(fi->ofp));
}


/* compress an image */
int MdcWriteImage(Uint8 * buffer, FILEINFO *fi, Uint32 n)
{
  FILE *fp=fi->ofp;
  Uint8 *pix=buffer;
  Int16 prefix_code;
  Int16 suffix_char;
  Int16 hx,d;
  Uint16 min_code_size=8;
  Uint32 i, width=fi->image[n].width, height=fi->image[n].height;


  /* make sure the initial code size is legal */
  if (min_code_size < 2 || min_code_size > 9) {
    /* monochrome images have two bits in LZW compression */
    if (min_code_size == 1) min_code_size = 2;
    else return(EOF);
  }

  /* write initial code size */
  fputc(min_code_size,fp);

  /* initialize the encoder */
  bit_offset=0;
  MdcInitTable(min_code_size);
  MdcWriteCode(fp,clear_code);

  if (pix == NULL) return(EOF);

  suffix_char=(Int16)pix[0];

  /* initialize the prefix */
  prefix_code = suffix_char;

  /* get a character to compress */
  for ( i=1; i<(width*height); i++) {

    if ( i == (width*height) ) break;

    suffix_char = (Int16)pix[i];

    /* derive an index into the code table */
    hx=(prefix_code ^ (suffix_char << 5)) % table_size;
    d=1;

    for (;;) {

       /* see if the code is in the table */
       if (currentcode[hx] == 0) {

         /* if not, put it there */
         MdcWriteCode(fp,prefix_code);
         d = free_code;

         /* find the next free code */
         if (free_code <= largest_code) {
           oldcode[hx] = prefix_code;
           newcode[hx] = suffix_char;
           currentcode[hx] = free_code;
           free_code++;
         }

         /* expand the code size or scrap the table */
         if (d == max_code) {
           if (code_size < 12) {
             code_size++;
             max_code <<= 1;
           }else{
             MdcWriteCode(fp,clear_code);
             MdcInitTable(min_code_size);
           }
         }
         prefix_code = suffix_char;
         break;
       }
       if (oldcode[hx] == prefix_code &&
           newcode[hx] == suffix_char) {
         prefix_code = currentcode[hx];
         break;
       }
       hx += d;
       d += 2;
       if(hx >= table_size) hx -= table_size;
     }
   }

   /* write the prefix code */
   MdcWriteCode(fp,prefix_code);

   /* and the end of file code */
   MdcWriteCode(fp,eof_code);

   /* MdcFlush the buffer */
   if(bit_offset > 0) MdcFlush(fp,(bit_offset+7)/8);

   /* write a zero length block */
   MdcFlush(fp,0);

   return(ferror(fp));
}

int MdcWriteCommentBlock(FILEINFO *fi, const char *comment)
{
        int n;

        n=strlen(comment);

        fputc('!',fi->ofp);          /* say it's an extension block */
        fputc(0xfe,fi->ofp);         /* say it's a comment */

        do {
                if(n > 255) {
                        fputc(255,fi->ofp);
                        fwrite(comment,1,255,fi->ofp);
                        comment +=255;
                        n-=255;
                }
                else {
                        fputc(n,fi->ofp);
                        fwrite(comment,1,(unsigned)n,fi->ofp);
                        fputc(0,fi->ofp);
                        n=0;
                }
        } while(n);
        return(ferror(fi->ofp));
}


int MdcWriteLoopBlock(FILEINFO *fi, const char *applstr, const char *auth)
{

  MDC_GIFAPPLICATION ap;

  memset(&ap,0,MDC_GIF_ABLK_SIZE);

  fputc('!',fi->ofp);
  fputc(0xff,fi->ofp);

  ap.blocksize=0x0b;
  memcpy(ap.applstring,applstr,8);
  memcpy(ap.authentication,auth,3);

  fwrite((char *)&ap,1,MDC_GIF_ABLK_SIZE,fi->ofp);

  fputc(0x03,fi->ofp);
  fputc(0x01,fi->ofp);
  fputc(0xe8,fi->ofp);
  fputc(0x03,fi->ofp);

  fputc(0,fi->ofp);

  return(ferror(fi->ofp));
}


int MdcWriteApplicationBlock(FILEINFO *fi, const char *applstr, const char *auth)
{
  MDC_GIFAPPLICATION ap;

  memset(&ap,0,MDC_GIF_ABLK_SIZE);

  fputc('!',fi->ofp);
  fputc(0xff,fi->ofp);

  ap.blocksize=0x0b;
  memcpy(ap.applstring,applstr,7);
  memcpy(ap.authentication,auth,3);

  fwrite((char *)&ap,1,MDC_GIF_ABLK_SIZE,fi->ofp);

  fputc(0,fi->ofp);

  return(ferror(fi->ofp));

}

/* initialize the code table */
void MdcInitTable(Int16 min_code_size)
{
        Int16 i;

        code_size=min_code_size+1;
        clear_code=(1<<min_code_size);
        eof_code=clear_code+1;
        free_code=clear_code+2;
        max_code=(1<<code_size);

        for(i=0;i<table_size;i++) currentcode[i]=0;
}

/* MdcFlush the code buffer */
void MdcFlush(FILE *fp, Int16 n)
{
        fputc(n,fp);
        fwrite(code_buffer,1,(unsigned)n,fp);
}

/* write a code to the code buffer */
void MdcWriteCode(FILE *fp, Int16 code)
{
        Int32 temp;

        byte_offset = bit_offset >> 3;
        bits_left = bit_offset & 7;

/* eNlf: BUG?? Problem:                                      */
/* eNlf: BUG?? We found one image that didn't compress well  */
/* eNlf: BUG?? with the original code of (byte_offset>=254)  */
/* eNlf: BUG?? Decoding gave error "GIF Unexpected EOF (2)"  */
/* eNlf: BUG?? Solution:                                     */
/* eNlf: BUG?? We changed de code into   (byte_offset>=253)  */
/* eNlf: BUG?? and that image got compressed (others too)    */
/* eNlf: BUG?? Perhaps our solution implicates a new BUG??   */
/* eNlf: BUG?? for other images! Let's hope it doesn't ...   */
/* eNlf:if(byte_offset >= 254) {     original code fragment  */
        if(byte_offset >= 253) {  /* new      code fragment  */
                MdcFlush(fp,byte_offset);
                code_buffer[0] = code_buffer[byte_offset];
                bit_offset = bits_left;
                byte_offset = 0;
        }

        if(bits_left > 0) {
                temp = ((Int32)code << bits_left) | code_buffer[byte_offset];
                code_buffer[byte_offset]=temp;
                code_buffer[byte_offset+1]=(temp >> 8);
                code_buffer[byte_offset+2]=(temp >> 16);
        }
        else {
                code_buffer[byte_offset] = code;
                code_buffer[byte_offset+1]=(code >> 8);
        }
        bit_offset += code_size;
}
