/* ppm2ilbm module                 */
/* written by Günther Röhrich      */
/* this is version 1.5             */

/* this code is tested with Aztec C 5.2a and GNU C 2.5.8 */
/* NOTE: you will need the newiff V37 or higher package */
/* (V37 is available on Fish-disk 705) */


#include <clib/exec_protos.h>
#include <libraries/iffparse.h>
#include <dos/dos.h>
#include <iffp/ilbmapp.h>
#include <iffp/packer.h>
#include <stdio.h>
#include <graphics/modeid.h>

#include "ppm2AGA.h"

#ifndef __GNUC__
#include <pragmas/exec_pragmas.h>
#include <pragmas/iffparse_pragmas.h>
#endif



/* needed when compiling with newiff V37 package */
/* (not needed with V39 or higher) */

#ifndef BMHDF_CMAPOK
#define BMHDF_CMAPOK (1 << 7)
#endif


/* externals used by this module */


extern int AbortCheck(void);
extern void PLProgress(ULONG Value, ULONG MaxValue);
extern volatile unsigned short MaxError, MaxErrorPos;
extern void EncodeHAM(UBYTE *yorig, UBYTE *yham, char *ColorTable, 
                       short NumColors, short xsize);
extern int MapColorASM(colorhist_vector colormap, pixval r1, pixval g1, pixval b1, 
             int NumColors);
extern int ExactColor;
extern int GfxEnable;
extern int VGAenable;
extern int jpegAGA;
extern char *ILBMfile;
extern char *BaseName;
extern ULONG SMR;
extern ULONG SMR_DisplayID;

/* definitions for the display routines */

extern int InitDisplay(int cols, int rows, ULONG Mode, int NumPlanes);
extern void SetDisplayColor(int ColorNumber, UBYTE r, UBYTE g, UBYTE b);
extern void DisplayRow(char *array, int cols, int row);
extern void CloseDisplay(void);

/* globals defined in this module */

#ifdef DEBUG_ASM
int MapColor(colorhist_vector colormap, pixval r1, pixval g1, pixval b1, 
             int NumColors);
#endif

unsigned short Mult_Table[2*256];
unsigned long  Mult_Table32[2*256];
jmp_buf ErrorEnv;
int cols, rows, rowcnt;
pixel **pixels;
pixel *pixrow;
pixval maxval;
int ppmformat;
char ColorTable[64*3] = {0};
int ColorRegMax; 
unsigned short ConvertMode;
char *ColorCache; /* a 256K cache for EncodeHAM() */

/* these are needed by Floyd-Steinberg-routines */
long  *thisrerr;
long  *nextrerr;
long  *thisgerr;
long  *nextgerr;
long  *thisberr; 
long  *nextberr;     
extern int floyd; 
int fs_direction = 1;
int Convert4096;
int Convert262144;
#define FS_SCALE 1024

/* locals defined in this module */

static FILE *fppm;
static FILE *ColorMapFile;
static struct IFFHandle *iff;
static struct ILBMInfo ilbm;
static int error;
static LONG IFFError;
static unsigned char *coded_rowbuf;
static unsigned char *hamarray;
static int returnvalue;
static void ppmCleanUp(void);
static int readall;
static colorhist_vector chv, colormap;
static BYTE *CompressBuffer;
static void encode_row(UBYTE *row, int cols, int nPlanes);
static int ColorShift, NumColors;
static gray *pgmrow;

static int lumcompare(colorhist_vector ch1, colorhist_vector ch2)
{
 return (int)((double)PPM_LUMIN(ch1->color) - (double)PPM_LUMIN(ch2->color));
}


int ppm2ilbm(char *PPMfile, ULONG Mode, int NumPlanes, ULONG MaxMem)
{
 int i, colors;
 pixel *pP;
 unsigned short AbsMaxError = 0;
 unsigned short MaxErrorXPos = 0;
 unsigned short MaxErrorYPos = 0;

 /* initialize everything                            */
 /* this is needed because we may call this function */
 /* multiple times                                   */
 returnvalue = 0;
 fppm = NULL;
 ColorMapFile = NULL;
 readall = 0;
 pixrow = NULL;
 pixels = NULL;
 hamarray = NULL; coded_rowbuf = NULL; ColorCache=NULL; 
 memset(&ilbm, 0, sizeof(struct ILBMInfo)); 
 chv = NULL;
 colormap = NULL;
 CompressBuffer = NULL;
 pgmrow = NULL;


 /* multiplication table is needed by assembler subroutines */
 for(i=-255; i<256; i++)
 {
   Mult_Table[i+255] = i*i;
   Mult_Table32[i+255] = i*i;
 }

 
 /* if we encounter an error we longjmp() to this location       */
 /* the ppmCleanUp() function will free all resources  allocated */
 /* in this module                                               */

 error = setjmp(ErrorEnv);
 if(error != 0) 
 {
  ppmCleanUp();
  return error;
 }  
 

 /* try to open the ppm file */
 fppm = fopen(PPMfile, "r");
 if(!fppm) pm_error("Could not open file %s\n", PPMfile);
 
 /* read the header of the ppm file */
 ppm_readppminit(fppm, &cols, &rows, &maxval, &ppmformat); 

 #ifdef DEBUG
 pm_message("Cols: %d, Rows: %d, MaxVal: %d, Format: %d\n",
             cols, rows, maxval, ppmformat);
 #else
 pm_message("Cols: %d, Rows: %d, MaxVal: %d\n", cols, rows, maxval);
 #endif

 /* Initialize Floyd-Steinberg error vectors. */
 thisrerr = (long*) pm_allocrow( cols + 2, sizeof(long) );
 nextrerr = (long*) pm_allocrow( cols + 2, sizeof(long) );
 thisgerr = (long*) pm_allocrow( cols + 2, sizeof(long) );
 nextgerr = (long*) pm_allocrow( cols + 2, sizeof(long) );
 thisberr = (long*) pm_allocrow( cols + 2, sizeof(long) );
 nextberr = (long*) pm_allocrow( cols + 2, sizeof(long) );
 srandom( (int) ( time( 0 ) ^ getpid(0) ) );
 for ( i = 0; i < cols + 2; ++i )
 {
   thisrerr[i] = random( ) % ( FS_SCALE * 2 ) - FS_SCALE;
   thisgerr[i] = random( ) % ( FS_SCALE * 2 ) - FS_SCALE;
   thisberr[i] = random( ) % ( FS_SCALE * 2 ) - FS_SCALE;
   /* (random errors in [-1 .. 1]) */ 
 }
 
 /* try to open the output file */
 
 if(!jpegAGA)
 {
   if(!(ilbm.ParseInfo.iff = AllocIFF()))
     pm_error("Could not do AllocIFF()");

   iff = ilbm.ParseInfo.iff;

   IFFError = openifile((struct ParseInfo *)&ilbm, (UBYTE *)ILBMfile, IFFF_WRITE);
   if(IFFError) pm_error(""); /* openifile() will print error messages... */
 }

 if(Mode == HAM8 || Mode == HAM6 || Mode == COLORMAP)
 {
   int Clustering;
   if(Mode == HAM8)
   {
     NumColors = NumPlanes;
     ColorRegMax  = 63;
     ColorShift   = 2; /* we only have 256K colors */
     NumPlanes    = 8;
     ConvertMode  = 1; /* Needed by EncodeHAM() */
     Clustering   = 0;
     Convert4096  = 0; /* No 4096-FS conversion */
     if(floyd) Convert262144 = 1; /* 262144 conversion */
    
   }
   else if(Mode == HAM6)
   {
     NumColors = NumPlanes;
     ColorRegMax = 15; 
     ColorShift  = 4; /* converting to 4096 colors will be done by FS dithering */
     NumPlanes   = 6;
     ConvertMode = 0; /* Needed by EncodeHAM() */
     Clustering  = 0;
     if(floyd)
       Convert4096 = 1; /* Do 4096-FS conversion */
     Convert262144 = 0;
   }
   else /* COLORMAP */
   { 
     NumColors = 1 << NumPlanes;      
     ColorRegMax = 255;
     ColorShift = 0; /* we will handle full 16M colors */
     if(ppmformat == PGM_FORMAT || ppmformat == RPGM_FORMAT || ExactColor == 1)
     {
       Clustering = 0;
     }
     else
     {
       Clustering = 2;
     }
     Convert4096 = 0; /* No 4096-FS conversion */
     Convert262144 = 0;
   } 
       
   if( cols*rows*3 <= (int)MaxMem)
   {     
     /* read the complete picture into memory */
     int row;
     readall = 1;
     pm_message("Reading complete picture into memory...\n");
     pixels = ppm_allocarray(cols, rows);     
     for(row = 0; row < rows; ++row)
     {
       if(AbortCheck()) pm_error("^C\n");
       ppm_readppmrow(fppm, pixels[row], cols, maxval, ppmformat);
       if(maxval != ColorRegMax)
       {
         if(maxval == 255)
         {
           for(i=0, pP=pixels[row]; i < cols; ++i, ++pP)
           {
             pP->r = pP->r >> ColorShift;
             pP->g = pP->g >> ColorShift;
             pP->b = pP->b >> ColorShift;
           }
         }
         else
         {
           for(i=0, pP=pixels[row]; i < cols; ++i, ++pP)
             PPM_DEPTH(*pP, *pP, maxval, ColorRegMax);
         }
       }
     }
   }
   else
   {
     rowcnt = 0;
     if(!(pixrow = ppm_allocrow(cols)))
       pm_error("Out of memory.\n");     
    }


   /* allocate enough room for one row of chunky HAM pixels  */
   /* (this array will also be used for colormap conversion) */
   if(!(hamarray = malloc(RowBits(cols))))
     pm_error("Out of memory.\n");

   /* we only have to clear the space between cols and RowBits(cols)    */
   /* hamarray will be larger in most cases because of special alignment*/
   for(i = cols; i < RowBits(cols); i++) hamarray[i] = 0;
   pm_message("Computing colormap...\n");

 
   /* now we make a histogram of the picture...          */
   /* if we increase Clustering we will find less colors */
   /* we allow a maximum of 10000 colors                 */
   /* ColorShift is the shift needed for HAM encoding    */
   for(i=Clustering; i<5; i++)
   {
     if((chv = ppm_fcomputecolorhist(fppm, cols, rows, 10000, &colors, ColorShift, i)))
       break;
     if(AbortCheck()) pm_error("^C\n");
     pm_message("Could not compute colormap, trying again...\n");
     rowcnt=0;
     if(!readall)
     { 
       rewind(fppm); /* re-initialise the ppm pointers for reading again */
       ppm_readppminit(fppm, &cols, &rows, &maxval, &ppmformat);       
     }
   } 

   pm_message("Found %d colors.\n", colors);


   /* now we try to find a suitable colormap... */
   colormap = mediancut(chv, colors, rows*cols, ColorRegMax, NumColors);
   ppm_freecolorhist(chv); /* we don't need the histogram any more */
   chv = NULL;
   /* sort the colors in ascending luminance order...  */ 
   /* (the first color will be the background color)      */
   if(NumColors < colors)
   {
     qsort((char *)colormap, NumColors, sizeof(struct colorhist_item), lumcompare);
   }
   else
   {
     qsort((char *)colormap, colors, sizeof(struct colorhist_item), lumcompare);
   }

   
   if(!jpegAGA)
   {   
     /* the CompressBuffer is needed by the cmpByteRun1 compressor */
     if(!(CompressBuffer = malloc(MaxPackedSize(RowBytes(cols)))))
       pm_error("Out of memory allocating the compress buffer.\n");

     /* the ColorCache is needed by EncodeHAM() for higher speed */
     /* (for HAM6 encoding only 4097 bytes are needed...)        */
     if(!(ColorCache = malloc(262145)))
       pm_error("Out of memory allocating the color cache.\n");

     /* the coded_rowbuf is needed by the IFF encoder */
     if(!(coded_rowbuf = (unsigned char *)malloc(RowBytes(cols))))
       pm_error("Out of memory allocating coded_rowbuf.\n");
     for(i=RowBytes(cols)-1; i>=0; i--) coded_rowbuf[i]=0;

     /* write the FORM chunk */
     IFFError = PushChunk(iff, ID_ILBM, ID_FORM, IFFSIZE_UNKNOWN);
     if(IFFError) pm_error("Error writing FORM chunk.\n");
     
     /* initialize the BMHD chunk */
     ilbm.Bmhd.w = cols;
     ilbm.Bmhd.h = rows;             /* Width, height in pixels */
     ilbm.Bmhd.x = 0;
     ilbm.Bmhd.y = 0;                /* x, y position for this bitmap  */
     ilbm.Bmhd.nPlanes = NumPlanes;  /* # of planes (not including mask) */
     ilbm.Bmhd.masking = mskNone;   
     ilbm.Bmhd.compression = cmpByteRun1;   
     ilbm.Bmhd.flags = BMHDF_CMAPOK;        /* CMAP has 8 significant bits */
     ilbm.Bmhd.transparentColor = 0; 
     ilbm.Bmhd.xAspect = cols;         
     ilbm.Bmhd.yAspect = rows;       /* xAspect, yAspect */
     ilbm.Bmhd.pageWidth = cols;
     ilbm.Bmhd.pageHeight = rows;    /* pageWidth, pageHeight */
  
     /* write the BMHD chunk */   
     IFFError = putbmhd(iff, &ilbm.Bmhd);
     if(IFFError) pm_error("Error writing BMHD chunk.\n");
  
     if(GfxEnable) InitDisplay(cols, rows, Mode, NumPlanes);
   }
   
   if(Mode == HAM8 || Mode == HAM6)
   { 
     /* fill in the ColorTable in B R G order */
     /* (this is better for HAM encoding)     */       
     for(i=0; i<NumColors; i++)
     {
       ColorTable[i*3]   = PPM_GETB(colormap[i].color);
       ColorTable[i*3+1] = PPM_GETR(colormap[i].color);
       ColorTable[i*3+2] = PPM_GETG(colormap[i].color);
       if(!jpegAGA)
         SetDisplayColor(i, PPM_GETR(colormap[i].color) << ColorShift,
                            PPM_GETG(colormap[i].color) << ColorShift,
                            PPM_GETB(colormap[i].color) << ColorShift);
     }
     free(colormap); /* we don't need the colormap any more */
     colormap = NULL;

     if(jpegAGA)
     {
       char *MapDir;
       unsigned short MagicNumber = 0x1203; /* guess what this means! */
       int Reserved=0; 
       unsigned char color;
       MapDir = getenv("MAPDIR");  

       ColorMapFile = fopen(ILBMfile, "w");
       
       if(!ColorMapFile && !BaseName)
       {
         if(MapDir)
          if(strlen(MapDir) != 0)
         {
           char *MapDirName;
           int pos,i;
           MapDirName = malloc(strlen(MapDir)+strlen(ILBMfile)+5); /* worst case */
           if(!MapDirName) pm_error("Out of memory.\n");
           strcpy(MapDirName, MapDir);

           i = strlen(MapDirName);
           if(MapDirName[i-1] != '/' && MapDirName[i-1] != ':')
           {
             strcat(MapDirName, "/");
             i++;
           }
           i = strlen(ILBMfile);
           while(i > 0 && ILBMfile[i-1] != '/' && ILBMfile[i-1] != ':') i--;
           strcat(MapDirName, &ILBMfile[i]);
           ColorMapFile = fopen(MapDirName, "w");
           if(ColorMapFile) pm_message("Writing mapfile: %s\n", MapDirName);
         }
       }
       else if(!ColorMapFile && BaseName && MapDir)
         if(strlen(MapDir) != 0)
       {
         char *NewName;
         int i;
         NewName = malloc(strlen(MapDir)+strlen(BaseName)+strlen(ILBMfile)); /* worst case */
         if(!NewName) pm_error("Out of memory.\n");
         strcpy(NewName, MapDir);
         i=strlen(NewName);
         if(NewName[i-1] != '/' && NewName[i-1] != ':')
         {
           strcat(NewName, "/");
           i++;
         }
         strcat(NewName, BaseName);
         i=strlen(ILBMfile);
         while(i > 0 && ILBMfile[i-1] != '/' && ILBMfile[i-1] != ':') i--;
         if(strlen(ILBMfile) > i+3)
         {
           strcat(NewName, &ILBMfile[i+3]);
           i = strlen(NewName) - 4;
           if(i < 1) pm_error("Wrong mapfile name.\n");
           while(i > 0 && NewName[i-1] != '.') i--;
           if(NewName[i-1] == '.')
           {
             strcpy(&NewName[i], "map");
             printf("%s\n", NewName);
             ColorMapFile = fopen(NewName, "w");
           }
         }
         free(NewName);
       }
         

       if(!ColorMapFile) pm_error("Could not open color map file for jpegAGA.\n");

       if(fwrite(&MagicNumber, 2, 1, ColorMapFile) != 1) pm_error("Write error.\n");
       if(fwrite(&Reserved,    4, 1, ColorMapFile) != 1) pm_error("Write error.\n");     
       if(fwrite(ColorTable,  64*3, 1, ColorMapFile) != 1) pm_error("Write error.\n");
       pm_message("Colormap file for jpegAGA/PhotoCDAGA created.\n");
       ppmCleanUp();
       return 0;
     }

     memset(ColorCache, 0, 262145); /* clear the color cache */          

     do
     {
       int y;
       rowcnt=0;
       
       /* if we don't need an additional color the color cache */
       /* will remain valid and should not be cleared          */

       memset(ColorCache, 0, 262145); /* clear the color cache */
   

       if(!readall)
       {
         rewind(fppm);
         ppm_readppminit(fppm, &cols, &rows, &maxval, &ppmformat);
       }
 
       /* if we have all colors we can write the CMAP chunk */
       if(NumColors == ColorRegMax+1)
       {
         IFFError = PushChunk(iff, 0, ID_CMAP, (ColorRegMax+1)*3);
         if(IFFError) pm_error("Error writing CMAP header.\n");

         for( i = 0; i <= ColorRegMax; i++) 
         {
           ColorRegister cmapReg;
           cmapReg.red   = ColorTable[i*3+1] << ColorShift;   /* red */
           cmapReg.green = ColorTable[i*3+2] << ColorShift;   /* green */
           cmapReg.blue  = ColorTable[i*3]   << ColorShift;   /* blue */
           IFFError = WriteChunkBytes(iff, (UBYTE *)&cmapReg, 3);
           if(IFFError!=3) pm_error("Error writing CMAP chunk.\n");
         }
         IFFError = PopChunk(iff); /* close CMAP */
         if(IFFError) pm_error("Error closing CMAP.\n");

         /* write the CAMG chunk */
         if(SMR)
         {
           ilbm.camg = SMR_DisplayID;
         }
         else
         {
           if(VGAenable)
           {
             if(cols > 370 || rows > 260)
             {
               ilbm.camg = VGAPRODUCTHAM_KEY;
             }
             else
             {
               ilbm.camg = VGALORESHAMDBL_KEY;
             }
           }
           else
           {
             ilbm.camg = HAM;
             if(cols > 370) ilbm.camg = ilbm.camg | HIRES;
             if(rows > 280) ilbm.camg = ilbm.camg | LACE;
           }
         }
         IFFError = putcamg(iff, &ilbm.camg);
         if(IFFError) pm_error("Error writing CAMG chunk.\n");

         /* start with the BODY chunk */
         IFFError = PushChunk(iff, 0, ID_BODY, IFFSIZE_UNKNOWN);
         if(IFFError) pm_error("Error writing BODY header.\n");
       }
 
       /* set a dummy value for AbsMaxError                          */
       /* it will contain the maximum error for the complete picture */ 
       AbsMaxError=0;
  
       for(y=0; y<rows; y++)
       {
         pixel *nextrow;          

         MaxError = 0;         
           
         nextrow = next_pixrow(fppm, y, ColorShift);
          
         if(AbortCheck()) pm_error("^C\n");

         EncodeHAM(nextrow, hamarray, ColorTable, NumColors*3, (short)cols);

         
         /* remember the color of the pixel with the maximum error */
         /* even if MaxError == 0 we will get a valid entry in the */
         /* colormap but it will not be used by the picture        */           
         if(MaxError > AbsMaxError)
         {
           MaxErrorXPos = MaxErrorPos;
           MaxErrorYPos = y;
           AbsMaxError = MaxError;

           /* use the color of the pixel with the maximum error as */
           /* a ColorTable entry                                   */
           /* we will have to recompute the picture if we want to  */
           /* use the additional color                             */
           if(NumColors <= ColorRegMax)
           {
             ColorTable[NumColors*3]   = PPM_GETB(pixrow[MaxErrorXPos]);
             ColorTable[NumColors*3+1] = PPM_GETR(pixrow[MaxErrorXPos]);
             ColorTable[NumColors*3+2] = PPM_GETG(pixrow[MaxErrorXPos]);
           }        
         }

         /* if all colors are set we can write our row to the BODY chunk */
         if(NumColors == ColorRegMax+1) 
         {
           encode_row(hamarray, cols, NumPlanes);
         }
         DisplayRow(hamarray, cols, y);
       }

       /* this is only for debugging purposes */
       if(NumColors <= ColorRegMax)
       {
         #ifdef DEBUG
         pm_message("Max Error: %hd ", AbsMaxError);
         #endif
         if(AbsMaxError == 0)
         {
           printf("\n");
         }
         else
         {
           #ifdef DEBUG
           pm_message("Color %hd, XPos: %hd, YPos: %hd\n", NumColors, MaxErrorXPos, MaxErrorYPos);
           #endif
         }
       }
       SetDisplayColor(NumColors, ColorTable[NumColors*3+1] << ColorShift,
                                  ColorTable[NumColors*3+2] << ColorShift,
                                  ColorTable[NumColors*3]   << ColorShift);
       NumColors = NumColors + 1; 
    
     /* finish if we have set all colors */
     } while(NumColors <= ColorRegMax+1);


     #ifdef DEBUG
     pm_message("Max Error: %hd, XOffset: %hd YOffset: %hd\n",
               AbsMaxError, MaxErrorXPos, MaxErrorYPos);

     pm_message("Colortable: ");
     for(i=0; i<=ColorRegMax; i++)
     {
       pm_message("%hd,%hd,%hd ",ColorTable[i*3],
                   ColorTable[i*3+1],ColorTable[i*3+2]);
     }
     pm_message("\n");
     #endif

     /* close the BODY chunk */
     IFFError = PopChunk(iff); 
     if(IFFError) pm_message("Error closing the BODY chunk.\n");
   }
   

   else if(Mode == COLORMAP)
   { 
     /* if floyd is nonzero we will use Floyd-Steinberg dithering */
     int row, fs_direction;
     register int col, limitcol, ind;
     register pixel *pP;
     register long sg, sr, sb, err;
     long *temperr;

     /*
     ** map the colors in the image to their closest match in the
     ** new colormap, and write 'em out.
     */
       
     pm_message( "Mapping image to new colors...\n" );

     /* write the CMAP chunk */
     IFFError = PushChunk(iff, 0, ID_CMAP, NumColors*3);
     if(IFFError) pm_error("Error writing CMAP header.\n");
     for( i = 0; i < NumColors; i++) 
     {
       ColorRegister cmapReg;
       cmapReg.red   = PPM_GETR( colormap[i].color );   /* red   */
       cmapReg.green = PPM_GETG( colormap[i].color );   /* green */
       cmapReg.blue  = PPM_GETB( colormap[i].color );   /* blue  */
       SetDisplayColor(i, cmapReg.red, cmapReg.green, cmapReg.blue);
       IFFError = WriteChunkBytes(iff, (UBYTE *)&cmapReg, 3);
       if(IFFError!=3) pm_error("Error writing CMAP chunk.\n");
     }
     IFFError = PopChunk(iff); /* close CMAP */
     if(IFFError) pm_error("Error closing CMAP.\n");

     /* write the CAMG chunk */
     if(SMR)
     {
       ilbm.camg = SMR_DisplayID;
     }
     else
     {
       ilbm.camg = 0;
       if(VGAenable)
       {
         if(cols > 370 || rows > 260)
           ilbm.camg = VGAPRODUCT_KEY;
         else
           ilbm.camg = VGALORESDBL_KEY;
       }
       else
       {
         if(cols > 370) ilbm.camg = ilbm.camg | HIRES;
         if(rows > 280) ilbm.camg = ilbm.camg | LACE;
       }
     }
     IFFError = putcamg(iff, &ilbm.camg);
     if(IFFError) pm_error("Error writing CAMG chunk.\n");

     /* start with the BODY chunk */
     IFFError = PushChunk(iff, 0, ID_BODY, IFFSIZE_UNKNOWN);
     if(IFFError) pm_error("Error writing BODY header.\n");

     rowcnt=0;
     if(!readall)
     {
       rewind(fppm);
       ppm_readppminit(fppm, &cols, &rows, &maxval, &ppmformat);       
     }

     if ( floyd )
     {
       fs_direction = 1;
     }
     for ( row = 0; row < rows; ++row )
     {
       /* PLProgress(row, rows); */
       if(AbortCheck()) pm_error("^C\n");
       if ( floyd )
         for ( col = 0; col < cols + 2; ++col )
           nextrerr[col] = nextgerr[col] = nextberr[col] = 0;
       if ( ( ! floyd ) || fs_direction )
       {
         col = 0;
         limitcol = cols;
         /* pP = pixels[row]; */
         pP = next_pixrow(fppm, row, ColorShift);
       }
       else
       {
         col = cols - 1;
         limitcol = -1;
         /* pP = &(pixels[row][col]); */
         pP = &(next_pixrow(fppm, row, ColorShift)[col]);
       }
       do
       {
         if ( floyd )
         {
           /* Use Floyd-Steinberg errors to adjust actual color. */
           sr = PPM_GETR(*pP) + thisrerr[col + 1] / FS_SCALE;
           sg = PPM_GETG(*pP) + thisgerr[col + 1] / FS_SCALE;
           sb = PPM_GETB(*pP) + thisberr[col + 1] / FS_SCALE;
           if ( sr < 0 ) sr = 0;
           else if ( sr > maxval ) sr = maxval;
           if ( sg < 0 ) sg = 0;
           else if ( sg > maxval ) sg = maxval;
           if ( sb < 0 ) sb = 0;
           else if ( sb > maxval ) sb = maxval;
           PPM_ASSIGN( *pP, sr, sg, sb );
         }

         /* use the assembler subroutine for the colormap search */
         /* this will consume most of the CPU time               */                     
         ind = MapColorASM(colormap, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP),
                           NumColors);
           
         /* compare the results of the ASM versus the C routine */
         /* if they differ the ASM routine has a bug...         */
         /* (we don't need them because they are the same now)  */
         #ifdef DEBUG_ASM
         if(ind != MapColor(colormap, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP),
                            NumColors))
           pm_message("Diff: row %d col %d  r %d g %d b %d\n", row,col,
                       (int)PPM_GETR(*pP), (int)PPM_GETG(*pP), (int)PPM_GETB(*pP));
         #endif

         if ( floyd )
         {
           /* Propagate Floyd-Steinberg error terms. */
           if ( fs_direction )
           {
             err = ( sr - (long) PPM_GETR( colormap[ind].color ) ) * FS_SCALE;
             thisrerr[col + 2] += ( err * 7 ) / 16;
             nextrerr[col    ] += ( err * 3 ) / 16;
             nextrerr[col + 1] += ( err * 5 ) / 16;
             nextrerr[col + 2] += ( err     ) / 16;
             err = ( sg - (long) PPM_GETG( colormap[ind].color ) ) * FS_SCALE;
             thisgerr[col + 2] += ( err * 7 ) / 16;
             nextgerr[col    ] += ( err * 3 ) / 16;
             nextgerr[col + 1] += ( err * 5 ) / 16;
             nextgerr[col + 2] += ( err     ) / 16;
             err = ( sb - (long) PPM_GETB( colormap[ind].color ) ) * FS_SCALE;
             thisberr[col + 2] += ( err * 7 ) / 16;
             nextberr[col    ] += ( err * 3 ) / 16;
             nextberr[col + 1] += ( err * 5 ) / 16;
             nextberr[col + 2] += ( err     ) / 16;
           }
           else
           {
             err = ( sr - (long) PPM_GETR( colormap[ind].color ) ) * FS_SCALE;
             thisrerr[col    ] += ( err * 7 ) / 16;
             nextrerr[col + 2] += ( err * 3 ) / 16;
             nextrerr[col + 1] += ( err * 5 ) / 16;
             nextrerr[col    ] += ( err     ) / 16;
             err = ( sg - (long) PPM_GETG( colormap[ind].color ) ) * FS_SCALE;
             thisgerr[col    ] += ( err * 7 ) / 16;
             nextgerr[col + 2] += ( err * 3 ) / 16;
             nextgerr[col + 1] += ( err * 5 ) / 16;
             nextgerr[col    ] += ( err     ) / 16;
             err = ( sb - (long) PPM_GETB( colormap[ind].color ) ) * FS_SCALE;
             thisberr[col    ] += ( err * 7 ) / 16;
             nextberr[col + 2] += ( err * 3 ) / 16;
             nextberr[col + 1] += ( err * 5 ) / 16;
             nextberr[col    ] += ( err     ) / 16;
           }
         }

         /* *pP = colormap[ind].color; */
         hamarray[col] = (unsigned char)ind;

         if ( ( ! floyd ) || fs_direction )
         {
           ++col;
           ++pP;
         }
         else
         {
           --col;
           --pP;
         }
       }
       while ( col != limitcol );

       if ( floyd )
       {
         temperr = thisrerr;
         thisrerr = nextrerr;
         nextrerr = temperr;
         temperr = thisgerr;
         thisgerr = nextgerr;
         nextgerr = temperr;
         temperr = thisberr;
         thisberr = nextberr;
         nextberr = temperr;
         fs_direction = ! fs_direction;
       }

       /* ppm_writeppmrow( stdout, pixels[row], cols, maxval, 0 ); */
       /* write one row to the BODY chunk */
       encode_row(hamarray, cols, NumPlanes);
       DisplayRow(hamarray, cols, row);
     }
     IFFError = PopChunk(iff); /* close the BODY */
     if(IFFError) pm_error("Error closing the BODY chunk.\n");
   }       
 }

 closeifile((struct ParseInfo *)&ilbm);
 ppmCleanUp();
 return 0;
}
 

/* free all resources used by this module */
void ppmCleanUp(void)
{
 CloseDisplay();
 if(colormap) free(colormap);
 if(pgmrow) pgm_freerow(pgmrow); 
 if(coded_rowbuf) free(coded_rowbuf);
 if(CompressBuffer) free(CompressBuffer);
 if(ColorCache) free(ColorCache);
 if(chv) free(chv);
 if(hamarray) free(hamarray);
 if(pixels)
 {
   ppm_freearray(pixels, rows);
 } 
 else
 {
   if(pixrow) pbm_freerow(pixrow);
 }
 if(fppm) fclose(fppm);

 if(jpegAGA)
 {
   if(ColorMapFile)
   {
     fclose(ColorMapFile);
     ColorMapFile = NULL;
   }
 }
 else
 {
   if(ilbm.ParseInfo.opened)
   {
     closeifile((struct ParseInfo *)&ilbm);
     remove(ILBMfile);
   }
   if(ilbm.ParseInfo.iff) FreeIFF(ilbm.ParseInfo.iff);
 }
}



/* convert one row from chunky to planar, compress it, */
/* and write it to the BODY chunk                      */

/* encode algorithm by Johan Widen (jw@jwdata.se) */
/* modified by Günther Röhrich */

const unsigned char bit_mask[] = {1, 2, 4, 8, 16, 32, 64, 128};

static void
encode_row(UBYTE *row, int cols, int nPlanes)
{
 register int plane, col;
 int bytes;
 LONG packedRowBytes;
 BYTE *source;
 BYTE *destination;

 bytes = RowBytes(cols);

 /* Encode and write raw bytes in plane-interleaved form. */
 for( plane = 0; plane < nPlanes; plane++ ) 
 {
   int mask, cbit, wr;
   unsigned char *cp;
   UBYTE *rp;

   mask = 1 << plane;
   cbit = -1;
   cp = coded_rowbuf-1;
   rp = row;
   for( col = 0; col < cols; col++, cbit--, rp++ ) 
   {
     if( cbit < 0 ) 
     {
       cbit = 7;
       *++cp = 0;
     }
     if( *rp & mask )
       *cp |= bit_mask[cbit];
   }
   /* wr = fwrite(coded_rowbuf, 1, bytes, stdout); */

   /* compress the row */
   source = coded_rowbuf;
   destination = CompressBuffer;
   packedRowBytes = packrow(&source, &destination, bytes);

   /* write the compressed row to the BODY chunk */  
   IFFError = WriteChunkBytes(iff, CompressBuffer, packedRowBytes); 
   if(IFFError!=packedRowBytes) pm_error("Error writing BODY chunk.\n");
 }
}


#ifdef DEBUG_ASM
/* this is the C version of the colormap search routine */
/* (it is now replaced by the ASM version)              */

/* search colormap for closest match. */
int MapColor(colorhist_vector colormap, pixval r, pixval g, pixval b, 
             int newcolors)
{ 
 int ind;
 register int i, r1, g1, b1, r2, g2, b2;
 register long dist, newdist;
 r1 = r;
 g1 = g;
 b1 = b;
 dist = 2000000000;
 for ( i = 0; i < newcolors; ++i )
 {
   r2 = PPM_GETR( colormap[i].color );
   g2 = PPM_GETG( colormap[i].color );
   b2 = PPM_GETB( colormap[i].color );
   newdist = ( r1 - r2 ) * ( r1 - r2 ) +
             ( g1 - g2 ) * ( g1 - g2 ) +
             ( b1 - b2 ) * ( b1 - b2 );
   if ( newdist < dist )
   {
     ind = i;
     dist = newdist;
   }
 }
 return ind;
}
#endif


