/*
** pdt10topnm ver 2.1
**
** (C) 2000 ROYALPANDA
*/

#include<stdio.h>
#include<errno.h>
#include<string.h>
#include"pdt.h"

char *Myname;

#define SYSERR		1
#define BADMAGIC	2
#define TOOSHORT	3
#define NOALPHA		4
#define NOIMAGE		5
#define INVALID		6

#define MAXERRNO	6

char *Err_msg[] = {
	"OK",
	"SYSTEMCALL ERROR",
	"not a  PDT10 file.",
	"unexpected EOF.",
	"no alpha channel is included.",
	"no image is loaded.",
	"invalid data.",
};

/*
** read PDT10 header
*/

int pdt_read_header( pdt_t *dat )
{
	unsigned char buf[32];
	int err;

	/* read header */

	if( (err = fread(buf,32,1,dat->fp)) < 1 ){
		if( err < 0 )
			return -SYSERR;
		else
			return -TOOSHORT;
	}
	if( strncmp(buf,"PDT10",5) != 0 ){
		return -BADMAGIC;
	}
	dat->width = (int)buf[12] | ((int)buf[13] << 8);
	dat->height = (int)buf[16] | ((int)buf[17] << 8);
	dat->offset_alpha = (int)buf[28] | (int)buf[29] << 8
			| (int)buf[30] << 16 | (int)buf[31] << 24;
	dat->count = 32;
	return 0;
}

/*
** open PDT file
*/

int pdt_open( pdt_t *dat, char *file )
{
	int err;

	dat->image = NULL;
	dat->alpha = NULL;

	if( file == NULL ){
		dat->fp = stdin;
		if( (err = pdt_read_header(dat)) < 0 )
			return err;
	}else{
		dat->fp = fopen(file,"r");
		if( dat->fp == NULL )
			return -SYSERR;
		if( (err = pdt_read_header(dat)) < 0 ){
			fclose(dat->fp);
			return err;
		}
	}
	return 0;
}

/*
** close PDT file
*/

int pdt_close( pdt_t *dat )
{
	if( dat->fp != NULL && dat->fp != stdin )
		fclose(dat->fp);
	if( dat->image != NULL )
		free(dat->image);
	if( dat->alpha != NULL )
		free(dat->alpha);

	dat->fp = NULL;
	dat->image = dat->alpha = NULL;
	return 0;
}


/*
** seek to alpha
*/

int pdt_seek_alpha( pdt_t *dat )
{
	int c;

	if( dat->offset_alpha == 0 )
		return -NOALPHA;
	if( dat->offset_alpha == dat->count )
		return 0;

	if( fseek(dat->fp,dat->offset_alpha,SEEK_SET) < 0 ){
		if( errno != EBADF )
			return -SYSERR;
		c = dat->offset_alpha - dat->count;
		if( c < 0 )
			return -INVALID;
		while( c-- ){
			if( fgetc(dat->fp) == EOF ){
				if( feof(dat->fp) )
				return -TOOSHORT;
			}
		}
	}
	return 0;
}



/*
** create RGB image data
*/

int pdt_image( pdt_t *dat )
{
	unsigned char *p,*dic;
	unsigned char buf[32];
	int size;
	int i,count;
	unsigned char flag,mask;

	size = dat->width * dat->height;

	/* to prevent buffer-overflow, make buffer-size be big */
	if( (p = (unsigned char*)malloc((size+4096)*3)) == NULL )
		return -SYSERR;

	dat->image = p;

	/* create image (LZ decompression) */

	mask = 0;

	while( size > 0 ){

		if( mask == 0  ){
			if( fread(&flag,1,1,dat->fp) < 1 )
				break;
			dat->count++;
			mask = 0x80;
		}

		if( flag & mask ){

			/* if bit is 1, new 3-bytes date from input */
			if( fread(buf,3,1,dat->fp) < 1 )
				break;
			dat->count += 3;
			p[2] = buf[0];
			p[1] = buf[1];
			p[0] = buf[2];
			size--;
			p+=3;

		}else{

			/* if bit is 0, read from dictionary (repeat backward data) */
			if( fread(buf,2,1,dat->fp) < 1 )
				break;
			dat->count += 2;
			i = (int)buf[0] | ((int)buf[1] << 8);
			count = (i & 0x0f)+1;
			size -= count;
			count *= 3;
			dic = p - (i >> 4)*3 - 3;

			while( count-- )
				*p++ = *dic++;
		}

		mask >>= 1;
	}

	if( feof(dat->fp) )
		return -TOOSHORT;
	if( ferror(dat->fp) )
		return -SYSERR;

	return 0;
}

/*
** create Alpha-channel data
*/

int pdt_alpha( pdt_t *dat )
{
	unsigned char *p,*dic,*dst;
	unsigned char buf[32];
	int size;
	int i,count;
	unsigned char flag,mask;
	int err;

	if( (err = pdt_seek_alpha(dat)) < 0 )
		return err;

	size = dat->width * dat->height;

	/* to prevent buffer-overflow, make buffer-size be big */
	if( (p = (unsigned char*)malloc(size+257)) == NULL )
		return -SYSERR;
	dat->alpha = p;

	/* create image (LZ decompression) */

	mask = 0;

	while( size > 0 ){

		if( mask == 0  ){
			if( fread(&flag,1,1,dat->fp) < 1 )
				break;
			mask = 0x80;
		}

		if( flag & mask ){

			/* if bit is 1, new byte date from input */
			if( fread(p,1,1,dat->fp) < 1 )
				break;
			p++;
			size--;

		}else{

			/* if bit is 0, read from dictionary (repeat backward data) */
			if( fread(buf,2,1,dat->fp) < 1 )
				break;
			count = (int)buf[0] + 2;
			size -= count;
			dic = p - (int)buf[1] - 1;
			while( count-- )
				*p++ = *dic++;
		}

		mask >>= 1;
	}

	if( feof(dat->fp) )
		return -TOOSHORT;
	if( ferror(dat->fp) )
		return -SYSERR;

	return 0;
}

/*
** chroma key
*/

int pdt_chromakey( pdt_t *base, pdt_t *pic )
{
	int x,y;
	int xx,yy;
	int pitch;
	unsigned char *p,*ip,*ap;

	xx = base->width < pic->width ? base->width : pic->width;
	yy = base->height < pic->height ? base->height : pic->height;
	pitch = base->width - pic->width;
	p = base->image;
	ip = pic->image;
	ap = pic->alpha;

	if( pic->offset_alpha == 0 ){
		for( y = 0 ; y < yy ; y++ ){
			memcpy(p,ip,xx*3);
			p += base->width*3;
			ip += pic->width*3;
		}
		return 0;
	}

	for( y = 0 ; y < yy ; y++ ){
		for( x = 0 ; x < xx ; x++ ){
			*p = ((unsigned)*ip * (unsigned)*ap
			    + (unsigned)*p * (unsigned)(255-*ap)) / 255;
			p++;
			ip++;
			*p = ((unsigned)*ip * (unsigned)*ap
			    + (unsigned)*p * (unsigned)(255-*ap)) / 255;
			p++;
			ip++;
			*p = ((unsigned)*ip * (unsigned)*ap
			    + (unsigned)*p * (unsigned)(255-*ap)) / 255;
			p++;
			ip++;

			ap++;
		}
		if( pitch < 0 ){
			ip -= pitch * 3;
			ap -= pitch;
		}else if( pitch > 0 ){
			p += pitch * 3;
		}
	}
	return 0;
}


/*
** chroma key with white background
*/

int pdt_chromakey_white( pdt_t *dat )
{
	int len;
	unsigned char *p,*ap;

	if( dat->alpha == NULL )
		return 0;

	len = dat->width * dat->height;
	p = dat->image;
	ap = dat->alpha;

	while( len-- ){
		*p = ((unsigned)*p * (unsigned)*ap
			+ 255 * (unsigned)(255-*ap)) / 255;
		p++;
		*p = ((unsigned)*p * (unsigned)*ap
			+ 255 * (unsigned)(255-*ap)) / 255;
		p++;
		*p = ((unsigned)*p * (unsigned)*ap
			+ 255 * (unsigned)(255-*ap)) / 255;
		p++;

		ap++;
	}
	return 0;
}


/*
** put error message
*/

int pdt_puterr( int err, char *file )
{
	char *str;

	if( err == -SYSERR )
		str = strerror(errno);
	else if( err < 0 && err >= -MAXERRNO )
		str = Err_msg[-err];
	else
		str = "unknown error.";

	file = file ? file : "(stdin)";

	fprintf(stderr,"%s: %s: %s\n",Myname,file,str);

	return 0;
}


/*
** load from some files
*/

int pdt_load_images( pdt_t *dat, int num_file, char **filelist, int white )
{
	pdt_t pic;
	int err;

	for( ; num_file > 0 ; num_file--, filelist++ ){
		if( (err = pdt_open(dat,*filelist)) < 0 ){
			pdt_puterr(err,*filelist);
			continue;
		}
		if( (err = pdt_image(dat)) < 0 ){
			pdt_puterr(err,*filelist);
			pdt_close(dat);
			continue;
		}
		if( white && dat->offset_alpha != 0 ){
			if( (err = pdt_alpha(dat)) < 0 ){
				pdt_puterr(err,*filelist);
				pdt_close(dat);
				continue;
			}
		}
		break;
	}

	if( num_file <= 0 ){
		fprintf(stderr,"%s: %s\n",Myname,Err_msg[NOIMAGE]);
		return -NOIMAGE;
	}

	if( white )
		pdt_chromakey_white(dat);

	num_file--;
	filelist++;

	fclose(dat->fp);
	dat->fp = NULL;

	for( ; num_file > 0 ; num_file--, filelist++ ){
		if( (err = pdt_open(&pic,*filelist)) < 0 ){
			pdt_puterr(err,*filelist);
			continue;
		}
		if( pic.offset_alpha == 0 ){
			pdt_puterr(-NOALPHA,*filelist);
			pdt_close(&pic);
			continue;
		}
		if( (err = pdt_image(&pic)) < 0 ){
			pdt_puterr(err,*filelist);
			pdt_close(&pic);
			continue;
		}
		if( (err = pdt_alpha(&pic)) < 0 ){
			pdt_puterr(err,*filelist);
			pdt_close(&pic);
			continue;
		}
		pdt_chromakey(dat,&pic);
		pdt_close(&pic);
	}
	return 0;
}

/*
** load from a file or stdin
*/

int pdt_load_file( pdt_t *dat, char *file, int alpha, int white )
{
	int err;

	if( (err = pdt_open(dat,file)) < 0 ){
		pdt_puterr(err,NULL);
		return err;
	}
	if( alpha ){
		 if( (err = pdt_alpha(dat)) < 0 ){
			pdt_puterr(err,file);
			pdt_close(dat);
			return err;
		}
	}else{
		if( (err = pdt_image(dat)) < 0 ){
			pdt_puterr(err,file);
			pdt_close(dat);
			return err;
		}
		if( white ){
			if( (err = pdt_alpha(dat)) < 0 ){
				pdt_puterr(err,file);
				pdt_close(dat);
				return err;
			}
			pdt_chromakey_white(dat);
		}
	}
	return 0;
}


/*
** output image data
*/

void pdt_output_image( pdt_t *dat )
{
	printf("P6 %d %d 255\n",dat->width,dat->height);
	fwrite(dat->image,dat->width*3,dat->height,stdout);
	fflush(stdout);
}


/*
** output alpha-channel data
*/

void pdt_output_alpha( pdt_t *dat )
{
	printf("P5 %d %d 255\n",dat->width,dat->height);
	fwrite(dat->alpha,dat->width,dat->height,stdout);
	fflush(stdout);
}


/*
** usage
*/

void usage( void )
{
	fprintf(stderr,"usage:\n");
	fprintf(stderr,"\t%s [-nocheck] [-white] [file ..]\n",Myname);
	fprintf(stderr,"\t%s -alpha [-nocheck] [file]\n",Myname);
}



int main( int argc, char **argv )
{
	int alpha;
	int white;
	int nocheck;
	pdt_t pic;
	int err;

	alpha = 0;
	white = 0;
	nocheck = 0;
	Myname = argv[0];

	while( argc > 1 && argv[1][0] == '-' ){
		if( argv[1][1] == '\0' ){
			argc--;
			argv++;
			break;
		}else if( strcmp(argv[1],"-alpha") == 0 ){
			alpha = 1;
			white = 0;
			argc--;
			argv++;
		}else if( strcmp(argv[1],"-white") == 0 ){
			alpha = 0;
			white = 1;
			argc--;
			argv++;
		}else if( strcmp(argv[1],"-nocheck") == 0 ){
			nocheck = 1;
			argc--;
			argv++;
		}else{
			usage();
			return 1;
		}
	}

	if( !nocheck ){
		if( isatty(fileno(stdout)) ){
			fprintf(stderr,"%s: output is a tty.\n",Myname);
			return 1;
		}
	}

	if( alpha ){
		if( argc > 2 ){
			usage();
			return 1;
		}
		err = pdt_load_file(&pic,(argc==2 ? argv[1] : NULL),1,0);
	}else if( argc < 2 ){
		err = pdt_load_file(&pic,(argc==2 ? argv[1] : NULL),0,white);
	}else{
		err = pdt_load_images(&pic,argc-1,argv+1,white);
	}

	if( err < 0 )
		return 1;

	if( alpha )
		pdt_output_alpha(&pic);
	else
		pdt_output_image(&pic);

	pdt_close(&pic);
	return 0;
}
