/************************************************************************\
* Portable pixmap to C64 Koala converter 7-Jun-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 ppmtokoala.c   *
*                                                                        *
* 26-Sep-92	Changed the palette a bit                                    *
* 03-Jan-93 Added color error propagation to the next line (in 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: ppmtokoala 1.1 03-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 char *colornames[]=
{
	"black",
	"white",
	"red",
	"cyan",
	"purple",
	"green",
	"blue",
	"yellow",
	"orange",
	"brown",
	"light red",
	"dark gray",
	"gray",
	"light green",
	"light blue",
	"light gray"
};

#ifdef AMIGA
void getline(long handle,char *buf);
int  CXBRK(void)	{return 0;}
void chkabort(void) {}
#else
void getline(FILE *handle,char *buf);
#endif
int  Convert(char *nimi,int height,int width,int init0,int init1,int init2);

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;
#ifdef AMIGA
	long handle;
#else
	FILE *handle;
#endif

	if(argc==2 || argc==3 || argc==4 || argc==5 || argc==6)
	{
		if(argc==4)
		{
			if(!(dither=atoi(argv[3])))
				dither=2;
			fprintf(stderr,"Using dither divider %d\n",dither);
		}

#ifdef AMIGA
		if(handle=Open(argv[1],MODE_OLDFILE))
#else
		if(handle=fopen(argv[1],"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(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 || height>200)
						fprintf(stderr,"Output picture will be sized 160 x 200\n");

					w=width;
					h=height;
					if(w>160)	w=160;
					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]=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,SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C)
						{
							fprintf(stderr,"*** Break: %s\n",argv[0]);
							FreeMem(buffer,3*width*height);
							exit(20);
						}
						fprintf(stderr,"\rLine: %ld Image error: %ld",y,imerr);
#endif
						fprintf(stderr,"\rLine: %ld Image error: %ld",y,imerr);
					}
					fprintf(stderr,"\n");
#ifdef AMIGA
					FreeMem(buffer,3*width*height);
#else
					free(buffer);
#endif
					if(argc==2)
						error=	Convert(NULL,h,w,0,0,0);
					else
					if(argc==3 || argc==4)
						error=	Convert(argv[2],h,w,0,0,0);
					else
					if(argc==5)
						error=	Convert(NULL,h,w,atoi(argv[3]),atoi(argv[4]),atoi(argv[5]));
					else
					if(argc==6)
						error=	Convert(argv[2],h,w,atoi(argv[3]),atoi(argv[4]),atoi(argv[5]));
					exit(error);
				}
				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[1]);
			exit(10);
		}
	}
	else
	{
		fprintf(stderr,"%s\n",vers+7);
		fprintf(stderr,"Usage: ppmtokoala ppmfile [koalafile] [colinit lowinit highinit]\n");
		fprintf(stderr,"       ppmtokoala ppmfile koalafile <dither-value>\n");
		exit(5);
	}
}

#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 *nimi,int height,int width,int init0,int init1,int init2)
{	int x,y,error,ind,pos,bits,d[4],e,g[4],approx[256],appr=0;
	UBYTE b,c,colbits;
	UBYTE	*BASE;
	UBYTE	*colmemhist[16];/* [16][1024]*/
	UBYTE	*gfxmem;		/* [8192]    */
	UBYTE	*chrmem;		/* [1024]	 */
	UBYTE	*colmem;		/* [1024]    */
#ifdef AMIGA
	long handle;
#else
	FILE *handle;
#endif

#define ALLOC_SIZE	26*1024

	const UBYTE bitmask[4]={0x3f,0xcf,0xf3,0xfc};
	const UBYTE bitshift[4]={0x40,0x10,0x04,0x01};

#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;
		gfxmem=BASE+16*1024;
		chrmem=gfxmem+8192;
		colmem=chrmem+1024;

		if(y=init1+init2*16)
			for(x=0;x<1024;chrmem[x++]=y);
		if(init0)
			for(x=0;x<1024;colmem[x++]=init0);

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

		if(!init0 && !init1 && !init2)
		{
			fprintf(stderr,"Making histograph (matrix)\n");
			for(y=0;y<height;y++)
			{
				for(x=0;x<width;x++)
				{
					ind=x/4+(y/8)*40;
					++colmemhist[pic[y][x]][ind];
				}
			}

			fprintf(stderr,"Selecting colors\n");
			for(y=0;y<1000;y++)
			{
				c=b=0;
				for(x=1;x<16;x++)
				{
					/* we take the color that is mostly used */
					/* 0=black don't count */
					if(colmemhist[x][y]>c)
					{
						c=colmemhist[b=x][y];
					}
				}
				colmem[y]=b;
				colmemhist[b][y]=0;

				c=b=0;
				for(x=1;x<16;x++)
				{
					/* we take the color that is mostly used */
					/* 0=black don't count */
					if(colmemhist[x][y]>c)
					{
						c=colmemhist[b=x][y];
					}
				}
				chrmem[y]=b;
				colmemhist[b][y]=0;

				c=b=0;
				for(x=1;x<16;x++)
				{
					/* we take the color that is mostly used */
					/* 0=black don't count */
					if(colmemhist[x][y]>c)
					{
						c=colmemhist[b=x][y];
					}
				}
				chrmem[y]=chrmem[y]+16*b;
				/* colmemhist[b][y]=0; */
			}
		}

		fprintf(stderr,"Converting picture\n");
		for(y=0;y<height;y++)
		{
			for(x=0;x<width;x++)
			{
				ind=x/4+(y/8)*40;			/* color memory index */
				pos=y%8+(x/4)*8+(y/8)*320;	/* grafix memory byte */
				bits=(x%4);					/* bit numbers */

				c=pic[y][x];
				if(c==0)	/* == background */
				{	colbits=0;
				}
				else
				if(colmem[ind]==c)
				{	colbits=3;
				}
				else
				if((chrmem[ind]%16)==c)
				{	colbits=2;
				}
				else
				if((chrmem[ind]/16)==c)
				{	colbits=1;
				}
				else
				{	if(colmem[ind]==0)
					{	colbits=3;
						colmem[ind]=c;
					}
					else
					if((chrmem[ind]%16)==0)
					{	colbits=2;
						chrmem[ind]=(chrmem[ind] & 0xf0) | c;
					}
					else
					if((chrmem[ind]/16)==0)
					{	colbits=1;
						chrmem[ind]=(chrmem[ind] & 0x0f) | (c*16);
					}
					else
					{	g[0]=0;
						d[0]=abs(col[c][0])+abs(col[c][1])+abs(col[c][2]);
						g[1]=e=chrmem[ind]/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=chrmem[ind]%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;
						colbits=0;
						for(e=0;e<4;e++)
							if(d[e]<error)
							{	colbits=e;
								error=d[e];
							}

						appr=-1;
						++approx[c*16+g[colbits]];
					}
				}

				gfxmem[pos]=(gfxmem[pos] & bitmask[bits]) | (bitshift[bits]*colbits);
			}
		}

		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(nimi)
		{
			fprintf(stderr,"Saving koalapainter format to %s\n",nimi);

#ifdef AMIGA
			if(handle=Open(nimi,MODE_NEWFILE))
			{	Write(handle,"\000\140",2);	/* load address $6000 */
				Write(handle,gfxmem,8000);	/* graphics memory */
				Write(handle,chrmem,1000);	/* video matrix */
				Write(handle,colmem,1000);	/* color memory */
				Write(handle,"\000",1);		/* background */
				Close(handle);
				FreeMem(BASE,ALLOC_SIZE);
				return 0;
			}
			else
			{	fprintf(stderr,"Could not open %s\n",nimi);
				FreeMem(BASE,ALLOC_SIZE);
				return 10;
			}
#else
			if(handle=fopen(nimi,"wb"))
			{	fwrite("\000\140",2,sizeof(char),handle);
				fwrite(gfxmem,8000,sizeof(UBYTE),handle);
				fwrite(chrmem,1000,sizeof(UBYTE),handle);
				fwrite(colmem,1000,sizeof(UBYTE),handle);
				fwrite("\000",1,sizeof(UBYTE),handle);
				fclose(handle);
				return 0;
			}
			else
			{	fprintf(stderr,"Write failed!\n");
				free(BASE);
				return 10;
			}
			free(BASE);
			return 0;
#endif
		}
		else
		{	/* output to stdout */
			fwrite("\000\140",2,sizeof(char),stdout);	/* load address $6000 */
			fwrite(gfxmem,8000,sizeof(UBYTE),stdout);
			fwrite(chrmem,1000,sizeof(UBYTE),stdout);
			fwrite(colmem,1000,sizeof(UBYTE),stdout);
			fwrite("\000",1,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;
	}
}

