/* PCXtoIFF.c - Converts IBM .PCX picture files to IFF format
 *
 * Written by David Godshall
 * Released into the Public Domain by David Godshall
 *
 * This little utility was written as the request of another BBS caller who
 * wanted to be able to view IBM .PCX type graphic files on his Amiga.  He
 * uploaded the source to an IBM .PCX viewer which I used to determine the
 * format of PCX files in order to be able to write this.  I was going to
 * write a PCX viewer, but decided it would be better to be able to convert
 * PCX files to IFF so more could be done with them then just viewing.
 */

#include <exec/types.h>
#include <stdio.h>
#include <string.h>

#define COMPRESS 1         /* Remove this line to disable compression */

typedef struct {
   UBYTE red, green, blue;
} TRIPLET;

typedef struct {              /* 128 byte header in PCX files */
   UBYTE maker;               /* ??? */
    UBYTE version;             /* PCX version */
   UBYTE code;                /* ??? */
   UBYTE bpp;                 /* ??? */
   WORD  x1,y1;               /* Top, Left corner */
   WORD  x2,y2;               /* Bottom, Right corner */
   WORD  hres, vres;          /* Dots-Per-Inch */
   TRIPLET triple[16];        /* palette */
   UBYTE vmode;               /* ??? */
   UBYTE nplanes;             /* Number of bitplanes */
   UWORD bpl;                 /* Number of bytes per row */
   UBYTE pad[128-68];
} PCXHDR;

UBYTE Buffer[1024];
int WriteSize,BodyPosition,BodySize;

PCXHDR PCXHeader;
int PCXWide,PCXHigh,PCXDeep,PCX_BPL,IFF_BPL;

#define SwapWord(x) ((x) = ((x) & 0x00FF)<<8 | ((x) & 0xFF00)>>8)


UBYTE ReadByte(fin)

FILE *fin;
{
   UBYTE data;
   
   if (fread((char *)&data,1,1,fin) != 1)
   {
      printf ("%% Error reading PCX file\n");
      exit(20);
   }
   return data;
}


void WriteLong(fout,data)

FILE *fout;
ULONG data;
{
   if (fwrite((char *)&data,4,1,fout) != 1)
   {
      printf ("%% Error writing IFF file\n");
      exit(20);
   }
   WriteSize += 4;
}


void WriteWord(fout,data)

FILE *fout;
UWORD data;
{
   if (fwrite((char *)&data,2,1,fout) != 1)
   {
      printf ("%% Error writing IFF file\n");
      exit(20);
   }
   WriteSize += 2;
}


void WriteByte(fout,data)

FILE *fout;
UBYTE data;
{
   if (fwrite((char *)&data,1,1,fout) != 1)
   {
      printf ("%% Error writing IFF file\n");
      exit(20);
   }
   WriteSize += 1;
}


void WriteData(fout,data,size)

FILE *fout;
APTR data;
int size;
{
   if (fwrite((char *)data,size,1,fout) != 1)
   {
      printf ("%% Error writing IFF file\n");
      exit(20);
   }
   WriteSize += size;
}


void WriteIFFRow(fout,data,size)

FILE *fout;
UBYTE *data;
int size;
{
#ifdef COMPRESS
   UBYTE *ptr,*maxptr;
   UBYTE ob,b;
   int len,pos;
   
   pos = WriteSize;
   ptr = data;
   maxptr = data+size;
   ob = *ptr++;
   while (data < maxptr)
   {
      b = *ptr++;
      while (b == ob && ptr < maxptr)
         b = *ptr++;
      len = ptr - data;
      if (ptr < maxptr)
         len--;
      while (len >= 128)
      {
         WriteByte(fout,257-128);
         WriteByte(fout,ob);
         len -= 128;
         data += 128;
      }
      if (len > 2)
      {
         WriteByte(fout,257-len);
         WriteByte(fout,ob);
         data += len;
      }
      else
      {
         ob = *data;
         while (ptr < maxptr)
         {
            if (b != ob)
            {
               ob = b;
               b = *ptr++;
            }
            else
               if (ptr+1 < maxptr)
               {
                  if (*ptr == b)
                     break;
                  ob = b;
                  b = *ptr++;
               }
               else
                  break;
         }
         len = ptr-data-1;
         if (ptr < maxptr)
            len--;
         while (len > 128)
         {
            WriteByte(fout,127);
            WriteData(fout,data,128);
            len -= 128;
            data += 128;
         }
         if (len > 0)
         {
            WriteByte(fout,len-1);
            WriteData(fout,data,len);
            data += len;
         }
      }
      ob = *data;
   }
/* if (WriteSize-pos & 1)
      WriteByte(fout,0);  */
#else
   WriteData(fout,data,size);
#endif
}


int main(argc,argv)

int argc;
char *argv[];
{
   FILE *fin,*fout;
   int x,y,n,b;
   int AllRows,oldperc,newperc;
   UBYTE *ptr;

   if (argc == 3)
   {
      fin = fopen(argv[1],"r");
      if (fin)
      {
         if (fread((char *)&PCXHeader,1,128,fin) == 128)
         {
            PCXWide  = SwapWord(PCXHeader.x2)+1;
            PCXHigh  = SwapWord(PCXHeader.y2)+1;
            PCXWide -= SwapWord(PCXHeader.x1);
            PCXHigh -= SwapWord(PCXHeader.y1);
            SwapWord(PCXHeader.hres);
            SwapWord(PCXHeader.vres);
            PCX_BPL  = SwapWord(PCXHeader.bpl);
            IFF_BPL  = PCX_BPL;
            if (IFF_BPL & 1)
               IFF_BPL++;
            PCXDeep  = PCXHeader.nplanes;

            printf ("Converting %dx%dx%d from PCX to IFF... ",PCXWide,PCXHigh,PCXDeep);

            fout = fopen(argv[2],"w");
            if (fout)
            {
               WriteSize = 0;
               WriteData(fout,"FORM    ILBMBMHD",16);
               WriteLong(fout,20);
               WriteWord(fout,PCXWide);         /* Width */
               WriteWord(fout,PCXHigh);         /* Height */
               WriteWord(fout,PCXHeader.x1);    /* Xoffset */
               WriteWord(fout,PCXHeader.y1);    /* Yoffset */
               WriteByte(fout,PCXDeep);         /* Depth */
               WriteByte(fout,0);               /* Masking */
#ifdef COMPRESS
               WriteByte(fout,1);               /* Compression on */
#else
               WriteByte(fout,0);               /* Compression off */
#endif
               WriteByte(fout,0);               /* pad */
               WriteWord(fout,0);               /* Transparent Color */
               WriteByte(fout,10);              /* XAspect, YAspect */
               WriteByte(fout,PCXHeader.hres*10 / PCXHeader.vres);
               WriteWord(fout,PCXWide);         /* PageWidth */
               WriteWord(fout,PCXHigh);         /* PageHeight */
               WriteData(fout,"CMAP",4);
               WriteLong(fout,(1<<PCXDeep)*3);
               WriteData(fout,&PCXHeader.triple,(1<<PCXDeep)*3);
               WriteData(fout,"BODY    ",8);
               BodyPosition = WriteSize-4;

               AllRows = PCXHigh*PCXDeep;
               n = 0;
               ptr = Buffer;
               oldperc = -1;
               for (y=0; y<AllRows; y++)
               {
                  newperc = (y*100)/AllRows;
                  if (newperc != oldperc)
                     printf ("%3d%%\b\b\b\b",newperc);
                  oldperc = newperc;
                  for (x=n; x<PCX_BPL; x+=n)
                  {
                     n = ReadByte(fin);
                     if ((n & 0xC0) == 0xC0)
                     {
                        n &= 0x3F;
                        b = ReadByte(fin);
                        memset(ptr,b,n);
                        ptr += n;
                     }
                     else
                     {
                        *ptr++ = n;
                        n = 1;
                     }
                  }
                  WriteIFFRow(fout,Buffer,IFF_BPL);
                  ptr = Buffer;
                  n = x-PCX_BPL;
                  if (n > 0)
                  {
                     memset(ptr,b,n);
                     ptr += n;
                  }
               }
               BodySize = WriteSize - BodyPosition - 4;
               if (BodySize & 1)
               {
                  WriteByte(fout,0);
                  BodySize++;
               }
               fseek(fout,BodyPosition,0);
               WriteLong(fout,BodySize);
               fseek(fout,4,0);
               WriteLong(fout,BodyPosition+BodySize-4);
               printf ("Done!\n");
               
               return 0;
            }
            else
               printf ("%%%% Unable to open %s for output as IFF file\n",argv[2]);
         }
         else
            printf ("%%%% Invalid PCX file\n");
      }
      else
         printf ("%%%% Unable to open %s for input from PCX file\n",argv[1]);
   }
   else
      printf ("\nUsage:\n\n   PCXTOIFF <sourcepcxfile> <destifffile>\n\n");

   return 20;
}
