/****************************\
* Bitmap mit Farbtabelle als *
* Graphik-Datei speichern		 *
* Autor: Gabriel Schmidt		 *
* (c) 1992 by MAXON-Computer *
* Modifiziert von Sebastian	 *
* Bieber, Dez. 1994					 *
* -> Programmcode						 *
\****************************/

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

#include "bitmfile.h"

/* --- (X) IMG-Implementation ----------------- */

#define IMG_COMPRESSED

typedef struct
	{
	UWORD img_version;
	UWORD img_headlen;
	UWORD img_nplanes;
	UWORD img_patlen;
	UWORD img_pixw;
	UWORD img_pixh;
	UWORD img_w;
	UWORD img_h;
	} IMG_HEADER;

typedef enum {NONE, SOLID0, SOLID1, PATRUN, BITSTR} IMG_MODE;

typedef UBYTE IMG_SOLID;

typedef enum { RGB=0, CMY=1, Pantone=2 } XIMG_COLMODEL;

typedef struct
	{
	ULONG img_ximg;
	XIMG_COLMODEL img_colmodel;
	} XIMG_HEADER;

typedef struct RGB XIMG_RGB;


int bitmap_to_img(FILE_TYP typ,	int ww, int wh,
									unsigned int pixw, unsigned int pixh,
									unsigned int planes, unsigned int colors,
									const char *filename,
									void(*get_color)(unsigned int colind, struct RGB *rgb),
									void(*get_pixel)(int x, int y, unsigned int *colind) )
	{
	int file, error, cnt;
	IMG_HEADER header;
	XIMG_HEADER xheader;
	XIMG_RGB xrgb;
	IMG_MODE mode;
	UBYTE *line_buf, *write_buf;
	register UBYTE *startpnt, *bufpnt;
	unsigned int colind, line_len, line, bit;
	register unsigned int byte;
	register UBYTE count;

	/* fill in (X) IMG-Header */

	header.img_version = 1;
	header.img_headlen = (UWORD) sizeof(header) /2;
	if (typ == XIMG)
		header.img_headlen += (UWORD)(sizeof(xheader)+colors*sizeof(xrgb))/2;

	header.img_nplanes	= planes;
	header.img_patlen		= 2;
	header.img_pixw			= pixw;
	header.img_pixh			= pixh;
	header.img_w				= ww;
	header.img_h				= wh;

	xheader.img_ximg		= XIMG_MAGIC;
	xheader.img_colmodel= RGB;

	/* calculate linelength, allocate buffer. */

	line_len 	= (ww+7)/8;

	line_buf 	= malloc((size_t)planes*line_len);
	if (line_buf == NULL)
		return(ENOMEM);

	/* Worst case: the bufferd line could grow to max. 3 times the length */
	/* of the original!																	*/

	write_buf = malloc((size_t)3*line_len);
	if (write_buf == NULL)
		{
		free(line_buf);
		return(ENOMEM);
		};

	/* open file */

	file = open(filename, O_WRONLY | O_CREAT | O_TRUNC);
	if (file<0)
		{
		error = errno;
		free(line_buf);
		free(write_buf);
		return(error);
		};

	/* write Header */

	if (write (file, &header, sizeof(header)) != sizeof(header) ||
	 	  (typ == XIMG &&	write (file, &xheader, sizeof(xheader) ) != sizeof(xheader)))
			{
			error = errno;
			close(file);
			free(line_buf);
			free(write_buf);
			return(error);
			};

	/* save the colortable if possible */

	if ( typ == XIMG )
	for (cnt=0; cnt<colors; cnt++)
		{
		get_color(cnt,&xrgb);
		if (write(file,&xrgb,sizeof(xrgb)) != sizeof(xrgb))
			{
			error = errno;
			close(file);
			free(line_buf);
			free(write_buf);
			return(error);
			};
		};

	/* And now line by line ... */

	for (line=0; line<wh; line++)
		{
		/* get pixel, split it up and */
		/* store it as planes in buffer */

		for (byte=0; byte<line_len; byte++)
			{
			for (cnt=0; cnt<planes; cnt++)
				line_buf[cnt*line_len+byte] = 0x00;

			for (bit=0; bit<8; bit++)
				{
				if (8*byte+bit < ww)
					get_pixel(8*byte+bit, line, &colind);

				for (cnt=0; cnt<planes; cnt++)
					{
					line_buf[cnt*line_len+byte] <<= 1;
					line_buf[cnt*line_len+byte]  |= colind & 0x01;
					colind >>= 1;
					};
				};
			};

		/* compress bitstrings in buffer */
		/* and write it to file        */

		for (cnt=0; cnt<planes; cnt++)
			{
			/* Bitstringpointer to start of plane */

			startpnt = &line_buf[cnt*line_len];
			bufpnt = write_buf;

			while (startpnt < &line_buf[(cnt+1)*line_len])
				{
				/*********************************************/
				/* Which _new_ compression-mode "fits" the	*/
				/* the current byte?									*/
				/* Note: the compressing modes get choosen	*/
				/* "positive". The non compressing BITSTR-	*/
				/* mode is choosen only if nothing else 		*/
				/* "fits" ...											*/
				/*********************************************/

				switch (*startpnt)
					{
					case 0x00:
						mode = SOLID0;
						break;
					case 0xFF:
						mode = SOLID1;
						break;
					default:
						if ( startpnt	<  &line_buf[(cnt+1)*line_len-3] &&
								*(startpnt)   == *(startpnt+2)						 &&
								*(startpnt+1) == *(startpnt+3)						 )
							mode = PATRUN;
						else
							mode = BITSTR;
					};

				/************************************************/
				/* The mode is choosen, now work with it.			*/
				/* The compressing modi stay current as long as	*/
				/* possible.												*/
				/************************************************/

				count = 0;

				switch (mode)
					{
					case SOLID0:
						while (	(startpnt < &line_buf[(cnt+1)*line_len])	&&
									 	(*(startpnt)==0x00)											&&
									 	(count < 0x7F) 				 										)
									{
									startpnt++;
									count++;
									};
						*(bufpnt++) = count;
						break;

					case SOLID1:
						while (	(startpnt < &line_buf[(cnt+1)*line_len])	&&
									 	(*(startpnt)==0xFF)											&&
									 	(count < 0x7F) 				 										)
									{
									startpnt++;
									count++;
									};
						*(bufpnt++) = 0x80 | count;
						break;

					case PATRUN:
						*(bufpnt++) = 0x00;
						startpnt += 2;
						count = 1;
						while (	startpnt < &line_buf[(cnt+1)*line_len-1]	&&
										*(startpnt)		== *(startpnt-2)						&&
										*(startpnt+1)	== *(startpnt-1)						&&
										(count < 0xFF)														)
									{
									count++;
									startpnt += 2;
									};
						*(bufpnt++) = count;
						*(bufpnt++) = *(startpnt-2);
						*(bufpnt++) = *(startpnt-1);
						break;

					/************************************************/
					/* The while Condition is ment as follows:		*/
					/*																							*/
					/* while ( NOT(2-Byte-Solidrun possible)	&&		*/
					/*			 NOT(6-Byte-Patternrun possible)	&&		*/
					/*				 count < 0xFF										&&		*/
					/*			 still Bytes remaining			)			*/
					/*																							*/
					/* As soon as a _compressing_ alternative	shows	*/
					/* up, BITSTR gets cancelled!							*/
					/************************************************/

					case BITSTR:
						*(bufpnt++) = 0x80;
						while ( !(((startpnt+count)<&line_buf[(cnt+1)*line_len-1])&&
											(((*(startpnt+count)==0xFF) && (*(startpnt+count+1)==0xFF))||
											 ((*(startpnt+count)==0x00) && (*(startpnt+count+1)==0x00))))		&&
										!(((startpnt+count)<&line_buf[(cnt+1)*line_len-5])&&
											(*(startpnt+count)   == *(startpnt+count+2))&&
											(*(startpnt+count+1) == *(startpnt+count+3))&&
											(*(startpnt+count)   == *(startpnt+count+4))&&
											(*(startpnt+count+1) == *(startpnt+count+5)))												&&
										(count < 0xFF)																												&&
										((startpnt+count) < &line_buf[(cnt+1)*line_len]) 											)
										count++;
						*(bufpnt++) = count;
						for(; count>0; count--)
							*(bufpnt++) = *(startpnt++);
						break;
					};
				};

			if (write(file,write_buf,bufpnt-write_buf) != (bufpnt-write_buf))
				{
				error = errno;
				close(file);
				free(line_buf);
				free(write_buf);
				return(error);
				};

			};
		};

	/*close file, free buffer. */

	close(file);
	free(line_buf);
	free(write_buf);
	return(0);

}

/*---filetype-dispatcher--------------------*/

const char *get_file_ext(FILE_TYP typ)
	{
	switch (typ)
		{
		case IMG:
		case XIMG:
			return("IMG");
		default:
			return("");
		};
}


int bitmap_to_file(FILE_TYP typ, int ww, int wh,
									 unsigned int pwx, unsigned int pwy,
									 unsigned int planes, unsigned int colors,
									 const char *filename,
									 void (*get_color)(unsigned int colind, struct RGB *rgb),
									 void (*get_pixel)(int x, int y, unsigned int *colind))
	{

	switch (typ)
		{
		case IMG:
		case XIMG:
			return(bitmap_to_img(typ,ww,wh,pwx,pwy,planes,colors,filename,get_color,get_pixel));
		default:
			return(-1);
		};
}

