/* Copyright (C) 1989 by Ken C.M. Lau.
** Permission to use, copy, and distribute this software and its
** documentation for any purpose and without fee is hereby granted.
**
** Author   : Ken Lau. June 89
** Purpose  : Convert GIF file to IFF ham file
** Compiler : Aztec C 4.3a
**             cc hamsharp.c
**             cc hamsharp2.c
**             cc hamsharp3.c
**             cc hamsharp4.c
**             ln hamsharp.o hamsharp2.o hamsharp3.o hamsharp4.o -lc -lm
*/

#include <stdio.h>

struct rgb {int r, g, b;};
struct remapelement {int pal, dist;};

long colorerr [256];       /* Histogram of errors */
long imageerr = 0L;        /* Total error */
int colors;                /* Colors in source */
int pals;                  /* Number of palette colors currently defined */
int planes;                /* Planes in destination */
int hamflag;               /* Set if picture requires HAM */
int picstep;               /* Horizontal source pixels per dest pixel */
int picwidth;              /* Destination pixels per line */
int scanstep = 2;          /* Increase for faster processing */
int hamonly = 0;           /* Set to process skip processing non-HAM */
int compression = -1;      /* Set to 0 to switch off IFF compression */
int filenote = 0;          /* Set to -1 to note error */
int edge = 1;              /* Set to 0 for edge smoothing algorithm */
int update = 0;            /* Set to 1 for skip existing files */
short iffbufsize = 16384;  /* Default output file buffer size */
struct rgb palette [32];   /* Destination palette */
unsigned char **pic = NULL; /* Pointer to decompressed image in RAM */
struct remapelement remap [256]; /* Closest palette color to given color */
char gifname [80], iffname [80]; /* Source and destination files */
FILE *gif, *iff;
char *iffbuf = NULL;       /* Pointer to output buffer */

/* GIF header info */
char signature [6];
struct {
   unsigned width;
   unsigned height;
   unsigned char pixel;
   unsigned char background;
   unsigned char reserved;
}  screen;
struct rgb cmap [256];
struct {
   unsigned char seperator;
   unsigned left;
   unsigned top;
   unsigned width;
   unsigned height;
   unsigned char pixel;
}  image;

/* Main. Source and destination filenames may be specified in command line */
main(argc, argv)
char *argv[];
{
   int bestcolor;

   getargs(argc, argv);   /* Set filenames */
   initvars();   /* Allocate and initialize tables and variables */
   while (getlist(gifname, iffname)) {
      printf("Converting %s-->%s\n", gifname, iffname);
      if (!readgif())        /* Decompress file into RAM pointed by pic */
         goto nextfile;
      palette [0] = cmap [0]; /* Background color is always assigned */
      if (hamflag) {
         if (edge) {
            for (pals = 1; pals < 16; pals++) {
               calcremap();  /* Calculate closest palette colors to remap to */
               scanpic();    /* Scan pic and set colorerr [] */
               bestcolor = findbest();   /* Pick a color to assign to palette */
               palette [pals] = cmap [bestcolor];
               printf("Palette %2d = colour %3d\x0D", pals, bestcolor);
               printf("\x1B[40CImageErr = %7ld\n\x1BM", imageerr);
            }
            printf("\x1B[80K");
         }
         else
            setpalette();
      }
      else {
         for (pals = 1; pals < colors; pals++)
            palette [pals] = cmap [pals];
      }
      calcremap();
      if (!writeiff())    /* Use palette [] and remap [] */
         goto nextfile;
      printf("\x1B[1F\x1B[40K"); /* Erase image size stats */
      printf("\x1BM\x1B[56C");
      printf("ImageErr=%ld\n", imageerr);
nextfile:
      freepic(); /* Free memory if required */
   }
   freevars();
}

/* Allocate and initialize tables and variables */
initvars()
{
   char *calloc();

   /* Allocate memory for file buffers */
   iffbuf = calloc(iffbufsize, sizeof(char));
   if (iffbuf == NULL)
      fatalerr("Out of memory");
   initrgberr();
}

/* Free memory for lookup tables */
freevars ()
{
   if (iffbuf != NULL) {
      free (iffbuf);
      iffbuf = NULL;
   }
   freergberr ();
}

/* Print title page, get arguments from user if required */
getargs (argc, argv)
char *argv [];
{
   int i;

   printf ("\n");
   printf ("        HAMSHARP 1.5, GIF to IFF HAM converter, KL\n");
   printf ("        ------------------------------------------\n");
   printf ("\n");

   initlist ();
   gifname[0] = iffname[0] = '\0';
   for (i = 1; i < argc; i++)
      if (argv[i][0] == '-') {
         switch (tolower (argv[i][1])) {
         case 's':
            sscanf (argv[i], "%*c%*c%d", &scanstep);
            break;
         case 'h':
            hamonly = -1;
            break;
         case 'c':
            compression = 0;
            break;
         case 'b':
            sscanf (argv [i], "%*c%*c%d", &iffbufsize);
            break;
         case 'f':
            filenote = -1;
            break;
         case 'd':
            edge = 0;
            break;
         case 'e':
            edge = -1;
            break;
         case 'u':
            update = -1;
            break;
         default:
            fatalerr ("Illegal option");
            break;
         }
      }
      else {
         if (gifname[0] == '\0') strcpy (gifname, argv[i]);
         else if (iffname [0] == '\0') strcpy (iffname, argv[i]);
      }
   printf ("Source GIF filespec? ");
   if (gifname [0] == '\0')
      scanf ("%s", gifname);
   else
      printf ("%s\n", gifname);

   printf ("Destn  IFF filespec? ");
   if (iffname [0] == '\0')
      scanf ("%s", iffname);
   else
      printf ("%s\n", iffname);

   if (scanstep <= 0)
      fatalerr ("Scan step must be greater than 0");

   getwild (); /* Read filenames matching wildcard from disk */
               /* and add to list */
   printf ("\n");
}

/* Decompress GIF file into RAM. Return error status */
readgif ()
{
   if ((gif = fopen (gifname, "r")) == NULL) {
      printf ("GIF file not found\n");
      return (0);
   }

   if (!readheader ())
      return (0);
   if (hamonly && !hamflag) {
      fclose (gif);
      printf ("\x1BM\x1B[40K\x1BM\x1B[56C");
      printf ("Not HAM\n");
      return (0);
   }
   readpic ();
   fclose (gif);
   return (-1);
}

/* Print error message and halt */
fatalerr (s)
char *s;
{
   printf (s);
   printf ("\n");
   freepic ();
   freelist ();
   freevars ();
   exit (20);
}

/* Read header. Return error status */
readheader ()
{
   int   color;

   fread (signature, 1, 6, gif);
   if (strncmp (signature, "GIF87a", 6) != 0) {
      printf ("Not GIF file\n");
      return (0);
   }

   screen.width = readint ();
   screen.height = readint ();
   screen.pixel = getc (gif);
   screen.background = getc (gif);
   screen.reserved = getc (gif);

   colors = 1 << ((screen.pixel & 0x07) + 1);
   for (color = 0; color < colors; color++) {
      cmap [color].r = getc (gif) >> 4;
      cmap [color].g = getc (gif) >> 4;
      cmap [color].b = getc (gif) >> 4;
   }

   image.seperator = getc (gif);
   image.left = readint ();
   image.top = readint ();
   image.width = readint ();
   image.height = readint ();
   image.pixel = getc (gif);

   if (image.seperator != ',') {
      printf ("Missing seperator\n");
      return (0);
   }
   if ((image.pixel & 0x80) != 0) {
      printf ("Cannot handle local color map\n");
      return (0);
   }
   printf ("%ux%ux%u\n", image.width, image.height, colors);

   picstep = 1;
   picwidth = image.width;
   hamflag = (colors > 32) || ((colors > 16) && (image.width > 320));
   if (hamflag && (image.width > 320)) {
      picstep = 2;
      picwidth = image.width / picstep;
   }
   planes = hamflag ? 6 : (screen.pixel & 7) + 1;
   return (-1);
}

/* Read 16 bit quantity from GIF file, low byte first */
readint ()
{
   int temp;

   temp = getc (gif);
   temp |= getc (gif) << 8;
   return (temp);
}

unsigned char codesize;  /* Used by readpic() and readcode() */

/* Read from gif file and write to iff file */
readpic()
{
   int   i;
   int   code, maxcode, clearcode, eofcode, curcode, oldcode, incode;
   int   freecode, firstfree;
   unsigned char initcodesize, finchar, bitmask, *outptr;
   static int prefix [4096];
   static unsigned char suffix [4096], outcode [1025];

   openadd ();
   openreadcode ();

   outptr = outcode;
   bitmask = (1 << ((screen.pixel & 7) + 1)) - 1;

   codesize = getc (gif);
   clearcode = (1 << codesize);
   eofcode = clearcode + 1;
   freecode = firstfree = clearcode + 2;
   
   codesize++;
   initcodesize = codesize;
   maxcode = (1 << codesize);

   code = readcode ();
   while (code != eofcode)
   {
      if (code == clearcode)
      {
         codesize = initcodesize;
         maxcode = (1 << codesize);
         freecode = firstfree;
         curcode = oldcode = code = readcode ();
         finchar = curcode;
         addpixel (finchar);
      }
      else
      {
         curcode = incode = code;

         if (curcode >= freecode)
         {
            curcode = oldcode;
            *outptr++ = finchar;
         }

         while (curcode > bitmask)
         {
#ifdef CHECK
            if (outptr > outcode + 1024)
               fatalerr ("Corrupt GIF file");
#endif
            *outptr++ = suffix [curcode];
            curcode = prefix [curcode];
         }

         finchar = curcode;
         addpixel (finchar);

         while (outptr != outcode)
            addpixel (*--outptr);

         prefix [freecode] = oldcode;
         suffix [freecode] = finchar;
         oldcode = incode;

         if (++freecode >= maxcode)
         {
#ifdef CHECK
            if (codesize < 12)
            {
#endif
               codesize ++;
               maxcode <<= 1;
#ifdef CHECK
            }
#endif
         }
      }
      code = readcode ();
   }

   if (getc (gif) != 0)
      printf ("Warning: unaligned packet\n");

   closereadcode ();
   closeadd ();
}

static unsigned char bitmask = 0x00;
static int bytecount = 0;

/* Prepare to call readcode */
openreadcode ()
{
   bytecount = bitmask = 0;
}

/* Fetch the next few bit from the source gif file */
readcode ()
{
   static unsigned dsttbl [13] =
      {1, 2, 4, 8, 0x10, 0x20, 0x40, 0x80,
       0x0100, 0x0200, 0x0400, 0x0800, 0x1000 };
   static unsigned dstmasked = 0x0800;
   static unsigned dstmask = 0x0001;
   static int bytebuf = 0;
   static unsigned temp;

   temp = 0;
   dstmasked = dsttbl [codesize];
   for (dstmask = 0x0001; dstmask != dstmasked; dstmask <<= 1)
   {
      if (!bitmask)
      {
         bitmask = 0x01;
         if (bytecount == 0)
            bytecount = getc (gif);
         bytebuf = getc (gif);
         bytecount--;
      }
      if (bytebuf & bitmask)
         temp |= dstmask;
      bitmask <<= 1;
   }
   return (temp);
}

/* Finish reading code */
closereadcode ()
{
}

int xpic, ypic, xstate, leavepass;
unsigned char *linestart;

/* Prepare to add pixels to pic */
openadd ()
{
   char *calloc ();
   int y;

   xpic = ypic = xstate = leavepass = 0;
   if (image.pixel & 0x40)
      leavepass = 2; /* GIF interleaved */
   /* Allocate memory for the whole picture */
   pic = (unsigned char **) calloc (image.height, sizeof (unsigned char *));
   if (pic == NULL)
      fatalerr ("Out of memory");
   for (y = 0; y < image.height; y++) {
      pic [y] = (unsigned char *) calloc (picwidth, sizeof (unsigned char));
      if (pic [y] == NULL)
         fatalerr ("Out of memory");
   }
   linestart = pic [0];
   printf ("Decompressing line\n");
}

/* Add pixels to pic in RAM */
addpixel (pix)
{
   static step [8]  = {1, 0, 8, 8, 4, 2, 0};  /* GIF interleaved consts */
   static first [8] = {0, 0, 0, 4, 2, 1, 0};

   if (!xstate) {
      xstate = picstep;
      *linestart++ = pix;
   }
   xstate--;
   xpic++;
   if (xpic >= image.width) {
      printf ("%3d\n\x1BM", ypic);
      xpic = 0;
      ypic += step [leavepass];
      if (ypic >= image.height)
         ypic = first [++leavepass];
      linestart = pic [ypic];
   }
}

/* Stop add pixels to RAM */
closeadd ()
{
   printf ("   \x1B[1F\x1B[40K\n\x1BM");
}

/* Free memory for picture */
freepic ()
{
   int y;

   if (pic == NULL)
      return;
   for (y = 0; y < image.height; y++)
      free (pic [y]);
   free (pic);
   pic = NULL;
}

/* Calculate nearest palette color to given color */
calcremap ()
{
   int color, pal, minpal, mindistance, distance, r, g, b;
   int maxdistance = rgberr (0, 0, 0, 15, 15, 15);
   struct rgb *c, *p;

   for (color = 0; color < colors; color++) {
      r = (c = & cmap [color]) -> r;
      g = c->g;
      b = c->b;
      minpal = 0;
      mindistance = maxdistance;
      for (pal = 0; pal < pals; pal++) {
         p = & palette [pal];
         distance = rgberr (r, g, b, p->r, p->g, p->b);
         if (distance < mindistance) {
            mindistance = distance;
            minpal = pal;
         }
      }
      remap [color].pal = minpal;
      remap [color].dist = mindistance;
   }
}

/* Accumulate the error due to each color in the source image */
scanpic ()
{
   unsigned char *linestart, *lineend;
   int color, y, pix, select, gunerr, gunerr2, gunerr3;
   struct rgb gun, want;
   struct remapelement *remapp;

   for (color = 0; color < colors; color++)
      colorerr [color] = 0L;
   for (y = 0; y < image.height; y += scanstep) {
      gun = palette [0];
      lineend = (linestart = pic [y]) + picwidth;
      while (linestart != lineend) {
         pix = *linestart++;
         if (! (remapp = & remap [pix]) -> dist)
            gun = palette [remapp->pal];
         else {
            want = cmap [pix];
            gunerr  = rgberr (want.r, gun.g, gun.b, want.r, want.g, want.b);
            gunerr2 = rgberr (gun.r, want.g, gun.b, want.r, want.g, want.b);
            gunerr3 = rgberr (gun.r, gun.g, want.b, want.r, want.g, want.b);
            select = 0;
            if (gunerr2 < gunerr) {
               gunerr = gunerr2;
               select = 1;
            }
            if (gunerr3 < gunerr) {
               gunerr = gunerr3;
               select = 2;
            }
            *(&gun.r + select) = *(&want.r + select);

            if (remapp->dist < gunerr) {
               gunerr = remapp->dist;
               gun = palette [remapp->pal];
            }
            colorerr [pix] += gunerr;
         }
      }
   }
}

/* Select colour to assign to palette given colorerr [] */
findbest ()
{
   int bestcolor = 0;
   long besterror = 0L;
   int color;

   imageerr = 0L; /* Estimated */
   for (color = 0; color < colors; color++) {
      if (colorerr [color] >= besterror) {
         besterror = colorerr [color];
         bestcolor = color;
      }
      imageerr += colorerr [color];
   }
   imageerr = (imageerr - besterror) * scanstep;
   return (bestcolor);
}

/* Write IFF file from image in RAM given pic and remap [] */
/* Return error status */
writeiff ()
{
   static char noteline [120];

   if ((iff = fopen (iffname, "w")) == NULL) {
      printf ("Cannot write to IFF file\n");
      return (0);
   }

   /* Set up buffer for output */
   if (iff->_buff)
      fatalerr ("IFF buffer already allocated"); /* Should never occur */
   iff->_buff = iffbuf;
   iff->_buflen = iffbufsize;

   writeheader ();
   writepic ();      /* with optional compression */
   fixupheader ();   /* body and form lengths fixups */
   fclose (iff);

   sprintf (noteline, "Err=%ld", imageerr);
   if (filenote)
      SetComment (iffname, noteline);
   return (-1);
}

long formlenpsn, bodylenpsn;

/* Write header to IFF file */
writeheader ()
{
   long ftell ();
   int pal;

   fputs ("FORM", iff); formlenpsn = ftell (iff); writelong (0L);
   fputs ("ILBM", iff);

   fputs ("BMHD", iff); writelong (20L);
   writeint (picwidth); writeint (image.height);   /* w, h */
   writeint (image.left / picstep);                /* x, y */
   writeint (image.top);
   putc (planes, iff);                 /* bitplanes */
   putc (0, iff);                      /* masking */
   putc (compression ? 1 : 0, iff);    /* compression */
   putc (0, iff);                      /* pad1 */
   writeint (0);                       /* transparent color */
   putc (10, iff); putc (11, iff);     /* xAspect, yAspect */
   writeint (screen.width / picstep);  /* pagewidth, pageheight */
   writeint (screen.height);

   if (hamflag) {
      fputs ("CAMG", iff); writelong (4L);
      writelong (screen.height > 200 ? 0x4804L : 0x4800L);
   }

   fputs ("CMAP", iff); writelong ((long)(pals * 3));
   for (pal = 0; pal < pals; pal++) {
      putc (palette [pal].r << 4, iff);
      putc (palette [pal].g << 4, iff);
      putc (palette [pal].b << 4, iff);
   }
   fputs ("BODY", iff); bodylenpsn = ftell (iff); writelong (0L);
}

/* Write long integer to IFF file, high byte first */
writelong (a)
long a;
{
   writeint ((unsigned) (a >> 16));
   writeint ((unsigned) a);
}

/* Write unsigned integer to IFF file, high byte first */
writeint (a)
unsigned a;
{
   putc (a >> 8, iff);
   putc (a & 0xFF, iff);
}

/* Fixup IFF header */
fixupheader ()
{
   long ftell ();
   long bodylen = ftell (iff) - bodylenpsn - 4L;
   long formlen = ftell (iff) - formlenpsn - 4L;

   fseek (iff, bodylenpsn, 0); writelong (bodylen);
   fseek (iff, formlenpsn, 0); writelong (formlen);
   fseek (iff, 0L, 2);
}

/* Write out body of IFF file with optional compression */
writepic ()
{
   long ftell ();
   unsigned char *linestart, *lineend;
   int y, pix, select, gunerr, gunerr2, gunerr3, colmask, planemask;
   struct rgb gun, want;
   struct remapelement *remapp;
   int cols = (picwidth + 7) / 8;
   static char pl[6][256];
   static bitpsn, col, plane;
   static compbuf [128];
   static compbufi, compbufi1, compbufi2, state, disp, data;
   static hambit [3] = {0x20, 0x30, 0x10};

   imageerr = 0L;
   printf ("Writing line\n");
   for (y = 0; y < image.height; y++) {
      printf ("%3d\n\x1BM", y);
      col = 0;
      colmask = 0x80;
      gun = palette [0];
      lineend = (linestart = pic [y]) + picwidth;
      while (linestart != lineend) {
         pix = *linestart++;
         if (! (remapp = & remap [pix]) -> dist)
            gun = palette [data = remapp->pal];
         else {
            want = cmap [pix];
            gunerr  = rgberr (want.r, gun.g, gun.b, want.r, want.g, want.b);
            gunerr2 = rgberr (gun.r, want.g, gun.b, want.r, want.g, want.b);
            gunerr3 = rgberr (gun.r, gun.g, want.b, want.r, want.g, want.b);
            select = 0;
            if (gunerr2 < gunerr) {
               gunerr = gunerr2;
               select = 1;
            }
            if (gunerr3 < gunerr) {
               gunerr = gunerr3;
               select = 2;
            }
            data = hambit [select]
                 | (*(&gun.r + select) = *(&want.r + select));
            if (remapp->dist < gunerr) {
               gunerr = remapp->dist;
               gun = palette [data = remapp->pal];
            }
            imageerr += gunerr;
         }
         planemask = 0x01;
         for (plane = 0; plane < planes; plane++) {
            if (data & planemask)
               pl [plane][col] |= colmask;
            planemask <<= 1;
         }
         if (colmask == 0x01) {
            colmask = 0x80;
            col++;
         }
         else
            colmask >>= 1;
      }

      if (!compression) {
         for (plane = 0; plane < planes; plane++) {
            for (col = 0; col < cols; col++) {
               putc (pl [plane][col], iff);
               pl [plane][col] = 0;
            }
            if (ftell (iff) & 1L)
               putc (0, iff);
         }
         goto nextline;
      }
      for (plane = 0; plane < planes; plane ++) {
         compbufi = 0;
         state = 0;
         data = pl[plane][0];
         for (col = 1; col < cols; col ++)
            switch (state) {
               case 0:
                  if (data == pl[plane][col]) {
                     state = 1;
                     compbuf [compbufi] = col - 2 - compbufi;
                     compbufi = col - 1;
                  }
                  else
                     data = pl[plane][col];
                  break;
               case 1:
                  if (data != pl[plane][col]) {
                     state = 0;
                     compbuf [compbufi] = compbufi - col + 1;
                     compbufi = col;
                     data = pl[plane][col];
                  }
                  break;
            }
         compbuf [compbufi] =
            (state==0) ? cols-compbufi-1 : compbufi-cols+1;
         state = 0;
         compbufi = 0;
         while (compbufi < cols) {
            disp = compbuf [compbufi];
            switch (state) {
               case 0:
                  if (disp >= 0) {
                     state = 1;
                     compbufi1 = compbufi;
                  }
                  break;
               case 1:
                  if (disp < 0) {
                     state = 2;
                     compbufi2 = compbufi;
                  }
                  else
                     compbufi1 = compbufi;
                  break;
               case 2:
                  if (disp >= 0) {
                     state = 1;
                     if (compbuf [compbufi2] == -1) {
                        compbuf [compbufi1] += 3 + disp;
                        compbuf [compbufi2] = 0;
                        compbuf [compbufi] = 0;
                     }
                     else
                        compbufi1 = compbufi;
                  }
                  else
                     state = 0;
                  break;
            }
            compbufi += (disp >= 0) ? disp + 1 : -disp + 1;
         }
         compbufi = 0;
         while (compbufi < cols) {
            disp = compbuf [compbufi];
            putc (disp, iff);
            if (disp < 0) {
               putc (pl [plane][compbufi], iff);
               for (col = compbufi; col <= (compbufi - disp); col++)
                  pl [plane][col] = 0;
            }
            else
               for (col = compbufi; col <= (compbufi + disp); col++) {
                  putc (pl [plane][col], iff);
                  pl [plane][col] = 0;
               }
            compbufi += (disp >= 0) ? disp + 1 : -disp + 1;
         }
      }
      nextline:;
   }
   if (ftell (iff) & 1L)
      putc (0, iff); /* Pad to word boundary */
   printf ("   \x1B[1F\x1B[40K\n\x1BM");
}

