/* pcx2jif sourceFile targetFile [-b]
 produced a raw binary file
 source.pcx is 8 colors PCX file in 8bits per pixel (1 octet)
 target.jif is the produced compressed image file.

 To create a correct PCX file as source, use ImageMagick tool:
- to generate 8 colors image (8bits per pixel) with filtering:
convert 1942.gif -channel red -threshold 112 -channel green -threshold 112 -channel blue -threshold 112 1942_8.gif

- to generate automatically 8 colors image:
convert 1942.gif -dither none -posterize 2 1942_8.gif

- to generate 2 colors (black & white) image (8bits per pixel) with specific dithering:
convert 1942.gif -scale 100%x50% -ordered-dither o2x2 -colors 2 -monochrome 1942.pcx
convert 1942.pcx -dither none -posterize 2 1942.pcx
(the second command is required to build a 8bits per pixel file)

 The target fil has an 10 bytes header:
 3 octets : JIF
 1 octet : version number
 1 octet : width in a number of 8 pixels (*8 for the real width)
 1 octet : heigth
 2 octets : Data Length
 1 octet	: flag (bit 0 used for color or monochrome mode)
 1 octets : reserved
 
 Note that the width of the PCX file has to be a multiple of
 	- 8 for color file
 	- 16 for monochrome file
 
*/
#include <string.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <math.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>

struct header_JIF
{
	char check[3];
	unsigned char version;
	unsigned short width;
	unsigned short heigth;
	unsigned short Length;
	unsigned char Flag;
	unsigned char Reserved;
	char *image;
};

struct header_JIF *read_pcx(char *);
int  encoder_COLOR_jif(struct header_JIF *);
int  encoder_BW_jif(struct header_JIF *);
int  save_jif(char *, struct header_JIF *,int);
int ManageArguments(int argc, char *argv[]);
void erreur(int);

int color=1;
char *source;
char *target;

int main (int argc, char *argv[])
{
	int length_coding=0;
	struct header_JIF *hdjif;

	printf("Pcx2JIF 2010.01 (c)Jester\n");
	if (ManageArguments(argc,argv))	exit(1);
	
  hdjif=read_pcx(source);
  if (hdjif==NULL)
  {
  	erreur(2);
  	exit(2);
  }
	printf ("%d x %d\n",hdjif->width,hdjif->heigth);

  if (color) length_coding=encoder_COLOR_jif(hdjif);
	else length_coding=encoder_BW_jif(hdjif);
  
	if (length_coding<8)
	{
		erreur(9);
		free(hdjif->image);
		exit(9);
	}
	printf("Taille du fichier JIF: %d\n",length_coding);
	
	if (!save_jif(target,hdjif,length_coding)) erreur(3);
	
	free(hdjif->image);
	exit(0);
}

int ManageArguments(int argc, char *argv[])
{	int c;
	
	while ((c = getopt (argc, argv, "hb")) != -1)
		switch (c)
		{
			case 'h':
				fprintf(stdout,"Usage: pcx2jif sourceFile targetFile [-b]\n");	
				return 1;
			case 'b':
				color=0;
				break;
			case '?':
				if (isprint (optopt))
					fprintf (stderr, "Unknown option `-%c'.\n", optopt);
				else
					fprintf (stderr,"Unknown option character `\\x%x'.\n",optopt);
				return 1;
			default: return 1;
		}

	if (argc-optind<2 || argc-optind>2)
		{
		fprintf (stderr,"Two arguments required: sourceFile TargetFile.\n");
		return 1; }
		
	source=argv[optind];
	target=argv[optind+1];
	if (!strcmp(source,target)) {erreur(12); return 1;}
	return 0;
}

struct header_JIF *read_pcx(char *filename)
{
	FILE *fd;
	char *buffer;
	fd=fopen(filename,"rb");
	if (fd==NULL)
	{
		 erreur(5);
		 return NULL;
	}
	buffer=(char *)malloc(65535);
	if (buffer==NULL)
	{
		erreur(5);
		fclose(fd);
		return NULL;
	}
	int lg=fread(buffer,1, 65535,fd);
	if (lg==0)
	{
		erreur(6);
		fclose(fd);
		free(buffer);
		return NULL;
	}
	fclose(fd);
	if (buffer[0]!=0x0A || buffer[2]!=1 || buffer[3]!=8 || buffer[65]!=1)
	{
		erreur(7);
		free(buffer);
		return NULL;
	}
	int xmin=(unsigned char)buffer[5]*256+(unsigned char)buffer[4];
	int ymin=(unsigned char)buffer[7]*256+(unsigned char)buffer[6];
	int xmax=(unsigned char)buffer[9]*256+(unsigned char)buffer[8];
	int ymax=(unsigned char)buffer[11]*256+(unsigned char)buffer[10];
	if ((color &(xmax-xmin+1>320 || ymax-ymin+1>250 || (xmax-xmin+1)%8)) ||
		(!color &(xmax-xmin+1>640 || ymax-ymin+1>250 || (xmax-xmin+1)%16)))
	{
		erreur(8);
		free(buffer);
		return NULL;
	}
	
	struct header_JIF *jif=(struct header_JIF*)malloc(sizeof(struct header_JIF));
	jif->check[0]='J';
	jif->check[1]='I';
	jif->check[2]='F';
	jif->version=1;
	jif->width=xmax-xmin+1;
	jif->heigth=ymax-ymin+1;
	jif->Flag=color;
  
  lg=jif->width * jif->heigth;
  jif->image=(char *)malloc(lg);
  if (jif->image==NULL)
  {
  	erreur(4);
  	free(buffer);
  	return NULL;
  }
  int pb=128;
  int pi=0;
  int count;
  unsigned char val;
  int i;
  while (pi<lg)
  {
  	if (buffer[pb] & 0xC0)
  	{
  		count=buffer[pb] & 0x3F;
  		pb++;
  		val=buffer[pb];
  		for(i=0;i<count;i++)
  		{
  			jif->image[pi]=val;
  			pi++;
  		}
  		pb++;
  	}
  	else
  	{
  		jif->image[pi]=buffer[pb];
  		pb++;
  		pi++;
  	}
  }

	FILE *fd2=fopen("truc.raw","wb");
	fwrite(jif->image,1,pb,fd2);

  return jif;
}

int  encoder_BW_jif(struct header_JIF *jif)
{
	char *source=jif->image;
	char *dest=(char *)malloc(65535);
	
	int i;
	int ps=0;
	int pd=0;
	int count=0;
	unsigned char last=0;
	unsigned char val=0;
	unsigned char rept=0;
	int pos_dest=0;
	int bluegreen=0;
	unsigned char blue=0, green=0;
	int lg=jif->width * jif->heigth;
	last=(source[ps] & 0x01);
	count++;
	ps++;
	
	while (ps<lg)
	{
		val=(source[ps] & 0x01);
		ps++;
	
		if (val!=last || count==1024 || ps==lg)
		{
			if (ps==lg) count++;
			if (count>=16)
			{
				rept=count/16-1;
				rept=rept << 1;
				rept=rept | last;
				dest[pd]=rept;
				pd++;
				pos_dest=0;
				if (count%16)
				{
					bluegreen=0;
					for(i=0;i<count%16;i++)
					{
						if (!bluegreen) { blue=blue << 1; blue=blue | last; }
						else { green=green << 1; green=green | last; }
						bluegreen=~bluegreen;
					}
					if (!bluegreen) { blue=blue << 1; blue=blue | val; }
					else { green=green << 1; green=green | val; }
					bluegreen=~bluegreen;
					for(i=0;i<15-count%16;i++)
					{
						val=(source[ps] & 0x01);
						ps++;
						if (!bluegreen) { blue=blue << 1; blue=blue | val; }
						else { green=green << 1; green=green | val; }
						bluegreen=~bluegreen;
					}
					pos_dest=pd+1;
					dest[pos_dest-1]=0x80;
					pd++;
					dest[pd]=blue;
					pd++;
					dest[pd]=green;
					pd++;
					blue=green=0;
					val=(source[ps] & 0x01);
					ps++;
				}
				count=1;
			}
			else 
			{
					bluegreen=0;
					for(i=0;i<count;i++)
					{
						if (!bluegreen) { blue=blue << 1; blue=blue | last; }
						else { green=green << 1; green=green | last; }
						bluegreen=~bluegreen;
					}
					if (!bluegreen) { blue=blue << 1; blue=blue | val; }
					else { green=green << 1; green=green | val; }
					bluegreen=~bluegreen;
					for(i=0;i<15-count;i++)
					{
						val=(source[ps] & 0x01);
						ps++;
						if (!bluegreen) { blue=blue << 1; blue=blue | val; }
						else { green=green << 1; green=green | val; }
						bluegreen=~bluegreen;
					}
					if (pos_dest)	dest[pos_dest-1]=(unsigned char) dest[pos_dest-1] +1;
					else
					{
						pos_dest=pd+1;
						dest[pos_dest-1]=0x80;
						pd++;
					}
					dest[pd]=blue;
					pd++;
					dest[pd]=green;
					pd++;
					val=(source[ps] & 0x01);
					ps++;
					count=1; blue=green=0;
					if ((unsigned char)dest[pos_dest-1]==0xFF) pos_dest=0;
			}
		}
		else count++;

		last=val;		
	}

	jif->image=dest;
	free(source);
	return pd;

}

int encoder_COLOR_jif(struct header_JIF *jif)
{
	char *source=jif->image;
	char *dest=(char *)malloc(65535);
	
	int i;
	int ps=0;
	int pd=0;
	int count=0;
	unsigned char last=0;
	unsigned char val=0;
	unsigned char rept=0;
	int pos_dest=0;
	unsigned char blue=0, green=0, red=0;
	int lg=jif->width * jif->heigth;
	last=source[ps];
	count++;
	ps++;
	
	while (ps<lg)
	{
		val=source[ps];
		ps++;
	
		if (val!=last || count==128 || ps==lg)
		{
			if (ps==lg) count++;
			if (count>=8)
			{
				rept=count/8-1;
				rept=rept << 3;
				rept=rept | last;
				dest[pd]=rept;
				pd++;
				pos_dest=0;
				if (count%8)
				{
					for(i=0;i<count%8;i++)
					{
						blue=blue | ((last & 4) !=0);
						green=green | ((last & 2)!=0);
						red=red | ((last & 1)!=0);
						blue=blue << 1;
						green=green << 1;
						red=red << 1;
					}
					blue=blue | ((val & 4) !=0);
					green=green | ((val & 2)!=0);
					red=red | ((val & 1)!=0);
					for(i=0;i<7-count%8;i++)
					{
						blue=blue << 1;
						green=green << 1;
						red=red << 1;
						val=source[ps];
						ps++;
						blue=blue | ((val & 4) !=0);
						green=green | ((val & 2)!=0);
						red=red | ((val & 1)!=0);				
					}
					pos_dest=pd+1;
					dest[pos_dest-1]=0x80;
					pd++;
					dest[pd]=blue;
					pd++;
					dest[pd]=green;
					pd++;
					dest[pd]=red;
					pd++;
					blue=green=red=0;
					val=source[ps];
					ps++;
				}
				count=1;
			}
			else 
			{
					for(i=0;i<count;i++)
					{
						blue=blue | ((last & 4)!=0);
						green=green | ((last & 2)!=0);
						red=red | ((last & 1)!=0);
						blue=blue << 1;
						green=green << 1;
						red=red << 1;
					}
					blue=blue | ((val & 4)!=0);
					green=green | ((val & 2)!=0);
					red=red | ((val & 1)!=0);
					for(i=0;i<7-count;i++)
					{
						blue=blue << 1;
						green=green << 1;
						red=red << 1;
						val=source[ps];
						ps++;
						blue=blue | ((val & 4)!=0);
						green=green | ((val & 2)!=0);
						red=red | ((val & 1)!=0);				
					}
					if (pos_dest)	dest[pos_dest-1]=(unsigned char) dest[pos_dest-1] +1;
					else
					{
						pos_dest=pd+1;
						dest[pos_dest-1]=0x80;
						pd++;
					}
					dest[pd]=blue;
					pd++;
					dest[pd]=green;
					pd++;
					dest[pd]=red;
					pd++;
					val=source[ps];
					ps++;
					count=1; blue=green=red=0;
					if ((unsigned char)dest[pos_dest-1]==0xFF) pos_dest=0;
			}
		}
		else count++;

		last=val;		
	}

	jif->image=dest;
	free(source);
	return pd;
}

int  save_jif(char *filename, struct header_JIF *jif,int length)
{
	FILE *fd;
	int lg;
	char *fichier;
		
	fd=fopen(filename,"wb");
	if (fd==NULL)
	{
		 erreur(10);
		 fclose(fd);
		 return 0;
	}
	
	fichier=malloc(10);
	if (fichier==NULL) erreur(4);
	fichier[0]=jif->check[0];
	fichier[1]=jif->check[1];
	fichier[2]=jif->check[2];
	fichier[3]=jif->version;
	if (color) fichier[4]=jif->width/8;
	else fichier[4]=jif->width/16;
	fichier[5]=jif->heigth;
	fichier[6]=length/256;
	fichier[7]=length & 0xFF;
	fichier[8]=jif->Flag;
	fichier[9]=0;
	lg=fwrite(fichier,1,10,fd);
	if (lg<8)
	{
		erreur(11);
		fclose(fd);
		return 0;
	}
	lg=fwrite(jif->image,1,length,fd);
	if (lg<length)
	{
		erreur(11);
		fclose(fd);
		return 0;
	}

	fclose(fd);
	fprintf(stderr,"%s file written.\n",filename);
	return lg;
}

void erreur(int err)
{ fprintf(stderr,"ERROR: ");
	switch (err)
	{
		case 1: fprintf(stderr,"Erreur de syntaxe: pcx2jif source target\n");break;
		case 2: fprintf(stderr,"When loading source file.\n");break;
		case 3: fprintf(stderr,"When saving target file.\n");break;
		case 4: fprintf(stderr,"Memory problem\n");break;
		case 5: fprintf(stderr,"Cannot open source file.\n");break;
		case 6: fprintf(stderr,"When loading source file.\n");break;
		case 7: fprintf(stderr,"Bad PCX source format.\n");break;
		case 8: fprintf(stderr,"Bad image size.\n");break;
		case 9: fprintf(stderr,"Encoding problem.\n");break;
		case 10: fprintf(stderr,"Cannot open source file.\n");break;
		case 11: fprintf(stderr,"When saving target file.\n");break;
		case 12: fprintf(stderr,"Same filename for source and target files.\n");break;

		default: fprintf(stderr,"Error %i\n",err);
	}
}
