/* THIS PROGRAM IS WRITTEN BY KLAUS PEDERSEN - micro@imada.ou.dk *\
|* The program NEEDS a ST with High rez monitor, and serverves as*|
\* demostration ONLY.____________________________________________*/

#include <tos.h>
#include <stdlib.h>
#include <portab.h>
  
#define ERRWIDTH 1024
#define PLANES 3
#define WORDLINE 40 /* words/line */

#define FIXEDNUM 4  /* size of the fixed point numbers */

typedef WORD ERRTYP; /* if FIXEDNUM>4 then ERRTYP must be LONG */

#define MAXGRAY (( (ERRTYP)256 << FIXEDNUM ) - 1)

static ERRTYP Levels[4] = {0, MAXGRAY/3, 2*MAXGRAY/3, MAXGRAY}; 


WORD	Beta, Width, Height, MaxRandom, Flicker, Zoom, n_levels;
ERRTYP Error1Array[ERRWIDTH], Error2Array[ERRWIDTH], *Error1, *Error2;

UWORD Screens[(LONG)PLANES*16000+128]; /* reserve screens */


WORD myRandom(WORD max)
{static LONG Seed = 0;
	if (max == 0) return 0;
	Seed = (5*Seed + 37);  
	return ((WORD)Seed % max);
}



WORD Laplace(char *Pic)
/* This routine returns 2^FIXEDNUM times the pixel value,
 * Beta is fixed point (x.4 bits) 
 *  The formular is :
 *  (2^FIXEDNUM)*lp
 *  	= (2^FIXEDNUM) * P(0,0) 
 *    + ( (2^FIXEDNUM)*BETA/4 ) ; this is the 'Beta' you can set.
 *    * ( 4*P(0,0) - P(-1,0)-P(1,0)-P(0,1)-P(0,-1) ) 
 */
{
return 
	((ERRTYP)*Pic<<FIXEDNUM)
  	+ Beta*
  	( (ERRTYP)
  	(*Pic<<2) - *(Pic-1) - *(Pic+1) - *(Pic+Width) - *(Pic-Width) );
}



int GenConvPixel(char *pic, WORD x, WORD y)
/*  This rutine converts a point in the 'real' image to
 *	a point in the screen image. The procedure uses error-
 *	diffusion to determine the state of the new pixel.
 *	The Error filter is that of Floyd & Steinberg :
 *				/ 1  5  3 \  / 
 *				\ 7  X    / / 16
 * This rutine uses a table of levels to make the convertion
 */
{WORD	i, level;
 ERRTYP q, p, err, diffr, *P_levels;
 	if (x == 0 || x == Width-1)
		p = (ERRTYP)*pic<<FIXEDNUM;
	else
	{	p = 7*Error1[x-1] + Error2[x-1] + 5*Error2[x] + 3*Error2[x+1];
		if (y == 0 || y == Height-1 || Beta == 0)
			p += (ERRTYP)*pic<<FIXEDNUM;
		else
			p += Laplace(pic);
	}
	
	/*	add dynamic noise, set the initial difference to infinite */
	diffr = MAXGRAY<<2;
	q = p + (ERRTYP)myRandom(MaxRandom);
	P_levels = &Levels[0];

	/* find the level that introduces least error */
	for(i = 0; i < n_levels; i++)
	{	err = q - *P_levels++;
		if (err < 0)
			err = -err;
		if (err < diffr)
		{	diffr = err;
			level = i;
		}
	}
	
	/* Now, calculate the error made by quantisation (without noise) */
	Error1[x] = (p - Levels[level]) >> 4;

	return level;
}


void ClearError(void)
{int i;

	Error1 = Error1Array;
	Error2 = Error2Array;
 	for(i = 0; i < ERRWIDTH; i++)
 		Error1Array[i] = Error2Array[i] = 0;
}


void NextError(void)
{ERRTYP *tmp;

	tmp = Error1; Error1 = Error2; Error2 = tmp;
}

	
void Convert2Gray(char *pic)
{LONG	yoffset, wordpos;
 WORD x, y, i, q, NextPix;
 UWORD pix[PLANES], *screen[PLANES];
 char *p, *oldphys;
	Cconws("Converting picture to flicker...\r\n");
	/* initialize screens */
	oldphys = Physbase();
	Setscreen((VOID *)-1, (Screens + 128L), -1);
	screen[0] = Physbase();
	for(i=1; i<PLANES; i++)
		screen[i] = screen[i-1] + 16000L;
	
	yoffset = 0;
	Levels[1] = MAXGRAY/3; /* reinsert 2. element of array */
	ClearError();
	NextPix = 0;
		
	for(y = 0; y < Height; y++)
	{	p = pic + (LONG)y*Width;
		wordpos = yoffset;

		for(x = 0; x < Width; x++)
		{	/* first clear first pixel in all planes */
			for (i=0; i<PLANES; pix[i++] <<= 1);

			q = GenConvPixel(p + (LONG)x, x, y);

			while (q--)  /* update all planes */
			{ 	/* set the pixel in that plane */
				pix[NextPix]++;
				if (++NextPix >= PLANES) NextPix = 0;
			}
			
			/* When 16 pixels is made, then put them on the screen */
			if ((x & 0xf) == 0xf)
			{	for(i = 0; i<PLANES; i++)
					*(screen[i] + wordpos) = pix[i];
				wordpos++;
			}
		}
		yoffset += WORDLINE;
		NextError();
		if (Bconstat(2)) break;
	}
	
	/* animate the screens */
	do
	{	for (i=0; i<PLANES; i++)
		{	Vsync();
			Setscreen((VOID *)-1,screen[i],-1);
		}
	} while (!Bconstat(2)); /* until a key have been pressed */
	
	Setscreen((VOID *)-1,oldphys,-1);
}



void SimpleConvert2Gray(char *pic)
{LONG yoffset, wordpos;
 WORD x, y;
 UWORD pix, *screen, *oldphys;
 char *p;
 
	Cconws("Converting picture to mono...\r\n");

	/* initialize screen */
	oldphys = Physbase();
	Setscreen((VOID *)-1, (Screens + 128L), -1);
	screen = Physbase();

	Levels[1] = MAXGRAY; /* reinsert 2. element of array */
	ClearError();
	yoffset = 0;
	pix = 0;
	
	for(y = 0; y < Height; y++)
	{	p = pic + (LONG)y*Width;
		wordpos = yoffset;
	
		for(x = 0; x < Width; x++)
		{	pix <<= 1;
			if (GenConvPixel(p + (LONG)x, x, y))
				pix++;
	
			/* When 16 pixels is made, then put them on the screen */
			if ((x & 0xf) == 0xf)
				screen[wordpos++] = pix;
		}
		yoffset += WORDLINE;
		NextError();
		if (Bconstat(2)) break;
	}
	
	/* wait until a key have been pressed */
	while (!Bconstat(2));

	Setscreen((VOID *)-1,oldphys,-1);
}



int LoadPicture(const char *Name, char *Picture)
{int f;
	Cconws("Loading picture\r\n");
	if ( (f = Fopen(Name, 0) ) > 0)
	{	Fseek(82l,f,0);  /* ...skip TIFF header... */
		Fread(f, (LONG)Width*Height, Picture);
		Fclose(f);
		return 0;
	}
	else
		return 1;
}


/*		1 long          resolution (0=low, 1=medium, 2=high)
 *		16 words        palette
 *		23 longs        reserved for expansion
 *		16000 words     picture data (screen memory)
 */

int LoadNEO(const char *Name, char *pic)
{int f,color,gray,i,j,pix;
 WORD Palette[16];
 UWORD Line[80], *LinePoint, plane1,plane2,plane3,plane4;
	Cconws("Loading NEO-picture\r\n");

	if ( (f = Fopen(Name, 0) ) > 0)
	{	Fseek(4, f, 0);
		Fread(f, 32, Palette);
		
		/* convert all palette entrys to grayscale */
		for (i=0; i<16; i++)
		{	color = Palette[i];
			gray =   26*((color     ) & 7)
					+ 151*((color >> 4) & 7)
					+  77*((color >> 8) & 7);
			Palette[i] = 255 - gray/7;
		}

		Fseek(4*23, f, 1);
		for(i=0; i<200; i++)
		{	Fread(f, 160, Line);
			LinePoint = Line;
			for(j=0; j<320; j++)
			{	if ((j & 15) == 0)
				{	plane1 = *LinePoint++;
					plane2 = *LinePoint++;
					plane3 = *LinePoint++;
					plane4 = *LinePoint++;
				}
				pix = 0;
				if (plane1 & 0x8000) pix |= 1;
				if (plane2 & 0x8000) pix |= 2;
				if (plane3 & 0x8000) pix |= 4;
				if (plane4 & 0x8000) pix |= 8;
				gray  = Palette[pix];
				if (Zoom)
				{	*(pic + (LONG)Width) = gray;
					*pic++ = gray;
					*(pic + (LONG)Width) = gray;
				}
				*pic++ = gray;
					
				plane1 <<= 1; plane2 <<= 1;
				plane3 <<= 1; plane4 <<= 1;
			}
			if (Zoom)
				pic += (LONG)Width;
		}
		Fclose(f);
		return 0;
	}
	else
		return 1;
}



main(int argc, const char *argv[])
{char	*ScreenBuf;
 const char *p;
 
	Beta = 2;
	MaxRandom =	10;
	Height = 400; Width = 575;
	Flicker = 0;  n_levels = 2;
	
	while (--argc)
	{	switch( *(p = *++argv) )
		{	case 'W':
				Width = atoi(p+1); break;
			
			case 'H':
				Height = atoi(p+1); break;
			
			case 'R':
				MaxRandom = atoi(p+1); break;
			
			case 'L':
				Beta = atoi(p+1); break;
			
			case 'F':
				Flicker = atoi(p+1); 
				if (Flicker)
					n_levels = 4;
				else
					n_levels = 2;
				break;
				
			case 'N': /* Load 320x200 pics NEO */
				Width = 320;
				Height = 200;
				Zoom = 0;
				
			case 'Z': /* Load NEO and zoom to full screen */
				if (*p == 'Z')
				{	Width = 640;
					Height = 400;
					Zoom = 1;
				}
				if ((ScreenBuf = Malloc((LONG)Width*Height)) <= 0)
					goto Error;
				if (LoadNEO(p + 1, ScreenBuf))
					goto Error1; 
				if (Flicker)
					Convert2Gray(ScreenBuf);
				else
					SimpleConvert2Gray(ScreenBuf);
				Mfree(ScreenBuf);
				break;
				
			case 'T': /* Load TIFF picture */	
				if ((ScreenBuf = Malloc((LONG)Width*Height)) <= 0)
					goto Error;
				if (LoadPicture(p + 1, ScreenBuf))
					goto Error1;
				if (Flicker)
					Convert2Gray(ScreenBuf);
				else
					SimpleConvert2Gray(ScreenBuf);
				Mfree(ScreenBuf);
				break;
		}
	}		

	Mfree(ScreenBuf);
	return 0;
Error1:
	Cconws("Can't Load picture!\r\n");
	Mfree(ScreenBuf);
	return 0;
Error:
	Cconws("Can't allocate memory! BUY MORE!\r\n");
	return 0;
}
