/************************************************************************\
* Portable pixmap to C64 FLI converter 26-Sep-92                         *
* By Pasi 'Albert' Ojala ) 1991,1992                                     *
*    po87553@cs.tut.fi  albert@cc.tut.fi                                 *
*                                                                        *
* Compile with SAS/C: lc -. -ctf -L -ba -r -v -y -NODEBUG ppmtoFLI.c     *
*                                                                        *
* 26-Sep-92	Changed the palette a bit                                    *
* 02-Jan-92 Color error propagation to next line also (when dithering)   *
*                                                                        *
\************************************************************************/
 
/* #define AMIGA */
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef	AMIGA

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/libraries.h>
#include <exec/interrupts.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>

#include <proto/dos.h>
#include <proto/exec.h>

#else

typedef unsigned char UBYTE;

#endif

char *vers="\0$VER: ppmtoFLI 1.1 02-Jan-93\n";

UBYTE	pic[200][160];	/* here we store the C64 color values */

const UBYTE col[16][3]=	/* RGB values for C64 colors */
{
	0,0,0,
	240,240,240,
	240,0,0,
	0,240,240,
	240,0,240,
	0,240,0,
	0,0,240,
	240,240,64,
	240,128,0,
	128,64,0,
	240,128,128,
	64,64,64,
	128,128,128,
	128,240,128,
	128,128,240,
	192,192,192
};

const UBYTE merge[16]=
{			/* 01 -> 10 */
	0x00,	/* 0000 -> 0000 */
	0x02,	/* 0001 -> 0010 */
	0x02,	/* 0010 -> 0010 */
	0x03,	/* 0011 -> 0011 */
	0x08,	/* 0100 -> 1000 */
	0x0a,	/* 0101 -> 1010 */
	0x0a,	/* 0110 -> 1010 */
	0x0b,	/* 0111 -> 1011 */
	0x08,	/* 1000 -> 1000 */
	0x0a,	/* 1001 -> 1010 */
	0x0a,	/* 1010 -> 1010 */
	0x0b,	/* 1011 -> 1011 */
	0x0c,	/* 1100 -> 1100 */
	0x0e,	/* 1101 -> 1110 */
	0x0e,	/* 1110 -> 1110 */
	0x0f	/* 1111 -> 1111 */
};

const char *colornames[]=
{
	"black",
	"white",
	"red",
	"cyan",
	"purple",
	"green",
	"blue",
	"yellow",
	"orange",
	"brown",
	"light red",
	"dark gray",
	"gray",
	"light green",
	"light blue",
	"light gray"
};

#define MODE_USEBGCOLOR			1	/* will use bgcolor */
#define MODE_USEWHOLESCREEN		2	/* will convert the leftmost 12 pixels too */
#define MODE_MERGE				4	/* will merge FLI colors, if it means less error */

#define STAT_ERROR				32
#define STAT_USAGE				64

int  Convert(char *name,int mode);

#ifdef AMIGA
int  CXBRK(void)	{return 0;}
void chkabort(void) {}
void getline(long handle,char *buf);
#else
void getline(FILE *handle,char *buf);
#endif

void main(int argc,char *argv[])
{	long imerr=0L;
	UBYTE *a,*buffer=0L,color,apu[100],*b;
	int i=0,x,y,error,temp,width,height,w,h,dither=0,status=0,inf=0,ouf=0,shift=0;
#ifdef AMIGA
	long handle;
#else
	FILE *handle;
#endif

	for(i=1;i<argc;i++)
	{
		if(argv[i][0]=='-')
		{
			x=1;
			while(argv[i][x])
			{
				switch(argv[i][x])
				{
				/* dither -d<value> , default value is 5 */
				case 'd':
					if(!(dither=atoi(argv[i]+x+1)))
						dither=5;
					x=strlen(argv[i])-1;
					break;
				/* use the whole screen */
				case 'w':
					status |= MODE_USEWHOLESCREEN;
					break;
				/* use the background color */
				case 'b':
					status |= MODE_USEBGCOLOR;
					break;
				/* use merge mode */
				case 'm':
					status |= MODE_MERGE;
					break;
				/* shift the picture */
				case 's':
					shift=12;
					break;
				default:
					status |= STAT_ERROR;
					break;
				}
				x++;
			}
			if(status & STAT_ERROR)
			{	fprintf(stderr,"Error in the options!\n");
				break;
			}
		}
		else
		if(!inf)
		{	inf=i;
		}
		else
		if(!ouf)
		{	ouf=i;
		}
		else
		{	status |= STAT_ERROR;
			fprintf(stderr,"Too many filenames!\n");
			break;
		}
	}

	if(!ouf)
		fprintf(stderr,"No output filename - outputting to stdout\n");

	if(!(status & STAT_ERROR) && inf)
	{
#ifdef AMIGA
		if(handle=Open(argv[inf],MODE_OLDFILE))
#else
		if(handle=fopen(argv[inf],"rb"))
#endif
		{
			getline(handle,(char *)apu);
			if(!strcmp((char *)apu,"P6"))
			{
				getline(handle,(char *)apu);
				width=atoi((char *)apu);
				i=0;
				while(apu[i]!=' ' && apu[i]!='\t' && apu[i])	i++;
				height=atoi((char *)apu+i);
				fprintf(stderr,"Input file seems to be %d x %d\n",width,height);
				getline(handle,(char *)apu);	/* just get the maxval..*/

#ifdef AMIGA
				if(buffer=(char *)AllocMem(3*width*height,MEMF_PUBLIC | MEMF_CLEAR))
				{
					if(Read(handle,buffer,3*width*height)!=(3*width*height))
						fprintf(stderr,"Short file! Result may be quite original.. \n");

					Close(handle);
#else
				if(buffer=(UBYTE *)calloc(3*width*height,sizeof(UBYTE)))
				{
					if(fread((char *)buffer,3*width*height,sizeof(UBYTE),handle)!=(3*width*height))
						fprintf(stderr,"Short file! Result may be quite original.. \n");

					fclose(handle);
#endif
					if(width>160-shift || height>200)
						fprintf(stderr,"Output picture will be sized %d x 200\n",160-shift);

			/*		for(a=&pic[0];a<&pic[0]+200*160;a++)	*a=0; */

					w=width;
					h=height;
					if(w>160-shift)	w=160-shift;
					if(h>200)	h=200;
					fprintf(stderr,"Converting colors\n");
					for(y=0;y<h;y++)
					{
						a=buffer+3*width*y;
						for(x=0;x<w;x++)
						{
							error=255*255*255;
							for(i=0;i<16;i++)
							{	temp=abs(col[i][0]-*a)+abs(col[i][1]-*(a+1))+abs(col[i][2]-*(a+2));
								if(temp<error)
								{	color=i;
									error=temp;
									if(error<8)	/* we are very satisfied if the error is less than .. */
										break;
								}
							}
							pic[y][x+shift]=color;

							if(dither && x<w-1)
							{
								if(y+1<h)
								{
									b=a+3;
									temp=(*a-col[color][0])/dither/2+*b;
									if(temp>=0 && temp<256)		*b=temp;
									b++;
									temp=(*(a+1)-col[color][1])/dither/2+*b;
									if(temp>=0 && temp<256)		*b=temp;
									b++;
									temp=(*(a+2)-col[color][0])/dither/2+*b;
									if(temp>=0 && temp<256)		*b=temp;

									b=a+3*width;
									temp=(*a-col[color][0])/dither/2+*b;
									if(temp>=0 && temp<256)		*b=temp;
									b++;
									temp=(*(a+1)-col[color][1])/dither/2+*b;
									if(temp>=0 && temp<256)		*b=temp;
									b++;
									temp=(*(a+2)-col[color][0])/dither/2+*b;
									if(temp>=0 && temp<256)		*b=temp;
								}
								else
								{
									temp=(*(a+0)-col[color][0])/dither+*(a+3);
									if(temp>=0 && temp<256)
										*(a+3)=temp;
									temp=(*(a+1)-col[color][1])/dither+*(a+4);
									if(temp>=0 && temp<256)
										*(a+4)=temp;
									temp=(*(a+2)-col[color][0])/dither+*(a+5);
									if(temp>=0 && temp<256)
										*(a+5)=temp;
								}
							}

							imerr+=error;
							a+=3;
						}
#ifdef AMIGA
						if(SetSignal(0L,0L) & SIGBREAKF_CTRL_C)
						{
							fprintf(stderr,"*** Break: %s\n",argv[0]);
							FreeMem(buffer,3*width*height);
							exit(20);
						}
						fprintf(stderr,"\rLine %d Color error: %ld",y,imerr);
					}
					fprintf(stderr,"\n");
					FreeMem(buffer,3*width*height);
#else
						fprintf(stderr,"\rLine %d Color error: %ld",y,imerr);
					}
					fprintf(stderr,"\n");
					free(buffer);
#endif

					if(shift)
						fprintf(stderr,"Shifting the picture by %d pixels\n",shift);

					if(ouf)
						exit(Convert(argv[ouf],status));
					else
						exit(Convert((char *)NULL,status));
				}
				else
				{	fprintf(stderr,"Memory allocation error (%d bytes)\n",3*width*height);
#ifdef AMIGA
					Close(handle);
#else
					fclose(handle);
#endif
					exit(42);
				}
			}
			else
			{	fprintf(stderr,"Don't know how to handle this type of ppm ..(%s)\n",apu);
#ifdef AMIGA
				Close(handle);
#else
				fclose(handle);
#endif
				exit(24);
			}
		}
		else
		{	fprintf(stderr,"Could not access %s\n",argv[inf]);
			exit(10);
		}
	}
	else
	{
		if(!inf)
			fprintf(stderr,"This program needs at least input filename!\n");

		fprintf(stderr,"%s\n",vers+7);
		fprintf(stderr,"Usage: ppmtofli  [-mbswd<dither-value>] ppmfile [flifile]\n");
		fprintf(stderr,"       d = do a simple dither in the color conversion\n");
		fprintf(stderr,"       w = use the 12 leftmost pixels too (will use approximation)\n");
		fprintf(stderr,"       s = shift the picture before converting\n");
		fprintf(stderr,"       b = use the background colors in the conversion\n");
		fprintf(stderr,"       m = merge FLI colors if it means less errors\n");
		exit(5);
	}
	exit(0);
}

#ifdef AMIGA
void getline(long ha,char *bu)
{	int j;

	j=0;
	do
	{	Read(ha,bu+j,1);
		j++;
	}
	while(*(bu+j-1)!='\n' && j<20);
	bu[j-1]=0;	/* remove newline.. just to be neat.. */
}
#else
void getline(FILE *ha,char *bu)
{	int i,j;

	j=0;
	do
	{	if((i=fgetc(ha))<0)
			break;
		bu[j++]=i;
	}
	while(*(bu+j-1)!='\n' && j<20);
	bu[j-1]=0;	/* remove newline.. just to be neat.. */
}
#endif

int Convert(char *name,int mode)
{	int x,y,ind,pos,bits,d[4],e,error,g[4],approx[256],appr=0,start=12;
	register UBYTE *mempos;
	UBYTE	*BASE,a,b,c;
	UBYTE	*colmemhist[16];/* [16][1024]*/
	UBYTE	*linehist[16];	/* [16][256] */
	UBYTE	*gfxmem;		/* [8192]    */
	UBYTE	*chrmems;		/* [8192]	 */
	UBYTE	*colmem;		/* [1024]    */
	UBYTE	*bgcol;			/* [256]	 */
#ifdef AMIGA
	long handle;
#else
	FILE *handle;
#endif
	const UBYTE bitmask[4]={0x3f,0xcf,0xf3,0xfc};
	const UBYTE bitshift[4]={0x40,0x10,0x04,0x01};

	if(mode & MODE_USEWHOLESCREEN)
		start=0;

#define ALLOC_SIZE	37*1024+256		/* size of the allocation */

#ifdef AMIGA
	if(BASE=(UBYTE *)AllocMem(ALLOC_SIZE,MEMF_CLEAR))
#else
	if(BASE=(UBYTE *)calloc(ALLOC_SIZE,sizeof(UBYTE)))
#endif
	{
		/* initialize the pointers */
		for(x=0;x<16;x++)	colmemhist[x]=BASE+x*1024;
		for(x=0;x<16;x++)	linehist[x]=BASE+16*1024+x*256;
		gfxmem=BASE+16*1024+16*256;
		chrmems=gfxmem+8192;
		colmem=chrmems+8192;
		bgcol=colmem+1024;

		for(y=0;y<256;approx[y++]=0);

		fprintf(stderr,"Making histograph (matrix)\n");
		for(y=0;y<200;y++)
		{	for(x=0;x<40;x++)
			{	error=1;
				a=pic[y][4*x+0];
				b=pic[y][4*x+1];
				c=pic[y][4*x+2];
				e=pic[y][4*x+3];

				if(b!=a)					error++;
				if(c!=a && c!=b)			error++;
				if(e!=a && e!=b && e!=c)	error++;
				/* if there is more than 2 colors in the same line, count them. */
				/* if there is only two colors, we can use the FLI colors and   */
				/* there is no need to use color memory ! */
				if(error>2)
				{	ind=x+(y/8)*40;
					colmemhist[a][ind]+=1;
					colmemhist[b][ind]+=1;
					colmemhist[c][ind]+=1;
					colmemhist[e][ind]+=1;
				}
			}
		}

		fprintf(stderr,"Selecting colors for color memory.\n");
		for(y=0;y<1000;y++)
		{
			if(y%40<3)
			{
				/* FLI has a 'feature' in the first three char columns (12 pixels) */
				/* bitpair 11 will be brown, 01 and 10 will be light gray          */
				/* here we select those colors - conversion will try to use them   */
				colmem[y]=0x09;
				chrmems[y]=chrmems[y+1024]=chrmems[y+2048]=chrmems[y+3072]=chrmems[y+4096]=chrmems[y+5120]=chrmems[y+6144]=chrmems[y+7168]=0xff;
			}
			else
			{
				a=b=0;
				for(x=1;x<16;x++)
				{
					/* we take the color that is used mostly as the third color  */
					/* or the best possible in that respect, 0=black don't count */
					if(colmemhist[x][y]>a)
					{
						a=colmemhist[b=x][y];
					}
				}
				colmem[y]=b;
				chrmems[y]=chrmems[y+1024]=chrmems[y+2048]=chrmems[y+3072]=chrmems[y+4096]=chrmems[y+5120]=chrmems[y+6144]=chrmems[y+7168]=17*b;
			}
		}

		if(mode & MODE_USEBGCOLOR)
		{	fprintf(stderr,"Making histograph (line)\n");
			for(y=0;y<200;y++)
			{
				/* if the color is not in color memory, count it to line history */
				/* Even if it is black */
				for(x=0;x<160;x++)
				{	if(colmem[x/4+(y/8)*40]!=pic[y][x])
						linehist[pic[y][x]][y]++;
				}
			}

			fprintf(stderr,"Selecting background colors\n");
			for(y=0;y<200;y++)
			{	a=b=0;
				/* we pick the most used color in the line */
				for(x=0;x<16;x++)
				{	if(linehist[x][y]>a)
					{	b=x;
						a=linehist[x][y];
					}
				}
				bgcol[y+6]=b;
			}
		}

		fprintf(stderr,"Creating FLI-picture\n");
		for(y=0;y<200;y++)
		{
			for(x=start;x<160;x++)
			{
				ind=x/4+(y/8)*40;	/* color memory index */
				pos=y%8+ind*8;		/* grafix memory byte */
				bits=(x%4);			/* bit numbers */
				mempos=chrmems+(y%8)*1024+ind;
				a=*mempos;

				c=pic[y][x];
				if(c==bgcol[y+6])
				{	b=0;
				}
				else
				if(c==colmem[ind])	/* this MUST BE here, not after those b=1 and 2 !!! */
				{	b=3;
				}
				else
				if(c==a%16)
				{	b=2;
				}
				else
				if(c==a/16)
				{	b=1;
				}
				else
				{	if((a%16)==colmem[ind])
					{	b=2;
						*mempos=(a & 0xf0) | c;
					}
					else
					if((a/16)==colmem[ind])
					{	b=1;
						*mempos=(a & 0x0f) | (16*c);
					}
					else
					{	g[0]=e=bgcol[y+6];
						d[0]=abs(col[c][0]-col[e][0])+abs(col[c][1]-col[e][1])+abs(col[c][2]-col[e][2]);
						g[1]=e=a/16;
						d[1]=abs(col[c][0]-col[e][0])+abs(col[c][1]-col[e][1])+abs(col[c][2]-col[e][2]);
						g[2]=e=a%16;
						d[2]=abs(col[c][0]-col[e][0])+abs(col[c][1]-col[e][1])+abs(col[c][2]-col[e][2]);
						g[3]=e=colmem[ind];
						d[3]=abs(col[c][0]-col[e][0])+abs(col[c][1]-col[e][1])+abs(col[c][2]-col[e][2]);

						error=256*256*256;
						b=0;
						for(e=0;e<4;e++)
							if(d[e]<error)
							{	b=e;
								error=d[e];
							}

						if(x>12 && (mode & MODE_MERGE))
						{
							/* check if FLI color merge will cause less error */
							e=abs(col[a/16][0]-col[a%16][0])+abs(col[a/16][1]-col[a%16][1])+abs(col[a/16][2]-col[a%16][2]);
							if(e<error)
							{
								gfxmem[pos]=merge[gfxmem[pos]/16]*16+merge[gfxmem[pos]%16];
								b=1;
								*mempos=(a & 0x0f) | (16*c);
								fprintf(stderr,"Merged %s -> %s\n",colornames[a/16],colornames[a%16]);
							}
							else
							{
								appr=-1;
								++approx[c*16+g[b]];
							}
						}
						else
						{
							appr=-1;
							++approx[c*16+g[b]];
						}
					}
				}
				gfxmem[pos]=(gfxmem[pos] & bitmask[bits]) | (bitshift[bits] * b);
			}
#ifdef AMIGA
			if(SetSignal(0L,0L) & SIGBREAKF_CTRL_C)
			{	fprintf(stderr,"*** Break\n");
				FreeMem(BASE,ALLOC_SIZE);
				return 20;
			}
#endif
		}

		if(appr)
		{
			fprintf(stderr,"Approximations:\n");
			for(e=0;e<256;e++)
				if(approx[e])
					fprintf(stderr,"%4d %s -> %s\n",approx[e],colornames[e/16],colornames[e%16]);
		}

		if(name)
		{
			fprintf(stderr,"Saving %s\n",name);
#ifdef AMIGA
			if(handle=Open(name,MODE_NEWFILE))
			{	Write(handle,"\000\073",2);	/* load address $3b00 */
				Write(handle,bgcol,256);
				Write(handle,colmem,1024);
				Write(handle,chrmems,8192);
				Write(handle,gfxmem,8000);
				Close(handle);
			}
			else
			{	fprintf(stderr,"Write failed (%d)!\n",IoErr());
				FreeMem(BASE,ALLOC_SIZE);
				return 21;
			}
			FreeMem(BASE,ALLOC_SIZE);
			return 0;
#else
			if(handle=fopen(name,"wb"))
			{	fwrite("\000\073",2,sizeof(char),handle);	/* load address $3b00 */
				fwrite(bgcol,256,sizeof(UBYTE),handle);
				fwrite(colmem,1024,sizeof(UBYTE),handle);
				fwrite(chrmems,8192,sizeof(UBYTE),handle);
				fwrite(gfxmem,8000,sizeof(UBYTE),handle);
				fclose(handle);
			}
			else
			{	fprintf(stderr,"Write failed!\n");
				free(BASE);
				return 21;
			}
			free(BASE);
			return 0;
#endif
		}
		else
		{	/* output to stdout */
			fwrite("\000\073",2,sizeof(char),stdout);	/* load address $3b00 */
			fwrite(bgcol,256,sizeof(UBYTE),stdout);
			fwrite(colmem,1024,sizeof(UBYTE),stdout);
			fwrite(chrmems,8192,sizeof(UBYTE),stdout);
			fwrite(gfxmem,8000,sizeof(UBYTE),stdout);
#ifdef AMIGA
			FreeMem(BASE,ALLOC_SIZE);
#else
			free(BASE);
#endif
			return 0;
		}
	}
	else
	{	fprintf(stderr,"AllocMem failed! (%d bytes)\n",ALLOC_SIZE);
		return 42;
	}
}

