/* TGA to HAR conversion program */

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <conio.h>

#define LINE_SIZE 320
#define TOT_LINES 200
#define VGA_MEM 0xa0000000
#define BIOSVID 0x10
#define VGA256 0x13
#define TEXT 0x3
#define SETMODE 0

/* avg takes the average of two values a and b */
#define avg(a,b) ((a + b) / 2)
/* diff takes the difference of two values a and b, modulo 256 */
#define diff(a,b) (a - b)

/* Declare a variable type that holds the image information */
typedef struct {
	char identsize;
	char colormaptype;
	char imagetype;
	unsigned int colormapstart;
	unsigned int colormaplength;
	char colormapbits;
	unsigned int xstart;
	unsigned int ystart;
	unsigned int width, depth;
	char bits;
	char descriptor;
} TGAHEAD;

/* Declare variable types for various buffers */
typedef char linebuffer[LINE_SIZE];
typedef unsigned char avgbuffer[LINE_SIZE];
typedef char palbuffer[768];

/* Function declarations */
void graphics (void);
void text (void);
void convert (FILE *fp, FILE *out);
void paletteset (char *buffer);
void putline(char *buffer, unsigned int n, int width);
void getline(char *buffer, unsigned int n, int width);
void error (char *message);

void main (int argc, char *argv[]) {
	FILE *fp, *out;

	/* Make sure two filenames were entered */
	if(argc > 2) {
		/* Open the files */
		if((fp = fopen (argv[1], "rb")) != NULL) {
			if((out = fopen (argv[2], "wb")) != NULL) {
				graphics();
				/* Convert the file to Haar format */
				convert(fp, out);
				/* Close the files */
				fclose(fp);
				fclose(out);
				getch();
				text();
				puts("Done.\n");
			} else printf("Error opening %s for output\n", argv[2]);
		} else printf("Error opening %s for input\n", argv[1]);
	} else puts("Must specify a filename for both input and output\n");
}

/* The graphics function puts the screen in graphics mode */
void graphics (void) {
	union REGS regs;
	regs.h.ah = SETMODE;
	regs.h.al = VGA256;
	int86(BIOSVID, &regs, &regs);
}

/* The text function returns the screen to text mode */
void text (void) {
	union REGS regs;
	regs.h.ah = SETMODE;
	regs.h.al = TEXT;
	int86(BIOSVID, &regs, &regs);
}

/* The decode function reads and displays the TGA file */
void convert (FILE *fp, FILE * out) {
	TGAHEAD tga;
	int bytes, i, n, startline, endline, incline;
	char *ptr;
	char temp;
	linebuffer buf1, buf2, diffbuf;
	avgbuffer avgbuf, avgbuf1, avgbuf2;
	palbuffer buffer;

	/* Read the header for the file */
	if (fread((char *)&tga, 1, sizeof(TGAHEAD), fp)==sizeof(TGAHEAD)) {
		if(tga.imagetype == 0x01 ||
		   tga.imagetype == 0x02 ||
                   tga.imagetype == 0x03) {

			/* Change the image type so that the Haar reader will
			   know that the file it is reading is a Haar file */
			tga.imagetype = 128 + tga.imagetype;

			/* Make sure the image in the output file is 0 bytes away */
			tga.identsize = 0;

			/* Write the header to the output file */
			fwrite((char *)&tga, 1, sizeof(TGAHEAD), out);
			/* Make sure the dimensions of the image are all right */
			if(tga.width > LINE_SIZE) error("Can only handle images of dimension 320x200\n");
			if(tga.depth > TOT_LINES) error("Can only handle images of dimension 320x200\n");

			/* Read until the beginning of the image is reached */
			fseek(fp, (long)tga.identsize, SEEK_CUR);

			/* See if the file has an valid number of colors */
			if(tga.colormaptype) {
				switch(tga.colormapbits) {
					case 24:
						for(i = tga.colormapstart; i < tga.colormaplength; i++) {
							if(i >= 256) break;
							buffer[i * 3 + 2] = fputc(fgetc(fp), out);
							buffer[i * 3 + 1] = fputc(fgetc(fp), out);
							buffer[i * 3]     = fputc(fgetc(fp), out);
						}
						break;
					case 16:
						for(i = tga.colormapstart; i < tga.colormaplength; i++) {
							if(i >= 256) break;
							n = ((fputc(fgetc(fp), fp) & 0xff) + ((fputc(fgetc(fp), fp) & 0xff) << 8));
							buffer[i * 3 + 2] = ((n >> 10) & 0x1f) << 3;
							buffer[i * 3 + 1] = ((n >> 5 ) & 0x1f) << 3;
							buffer[i * 3]     = (n & 0x1f) << 3;

						}
						break;
				}
				paletteset(buffer);
			} else {
                                if(tga.imagetype==3) {
                                        for(i=0; i<=255; i++) {
                                                buffer[i * 3] = i;
                                                buffer[i * 3 + 1] = i;
                                                buffer[i * 3 + 2] = i;
                                        }
                                        paletteset(buffer);
                                }
                                error("Image must be greyscale or have a palette\n");
			}

			/* Set up the options for what is to be converted */
			startline = 0;
			endline = tga.depth;
			incline = 2;

			/* Convert the image */
			for(i = startline; i < endline; i += incline) {
				fread(&buf1, 1, tga.width, fp);
				fread(&buf2, 1, tga.width, fp);
				for(n = 0; n < tga.width; n++) {
					avgbuf1[n] = avg(buf1[n], buf2[n]);
					diffbuf[n] = diff(buf1[n], buf2[n]);
					if(((buf1[n] + buf2[n]) < 0) && (diffbuf[n] & 1))
						avgbuf1[n]--;
					if((diff(buf1[n], buf2[n]) < -127) ||
					   (diff(buf1[n], buf2[n]) > 128))
						avgbuf1[n] = avgbuf1[n] + 128;
				}
				putline(diffbuf, i / (incline * 2) + tga.depth / 2, tga.width);

				i += incline;
				fread(&buf1, 1, tga.width, fp);
				fread(&buf2, 1, tga.width, fp);
				for(n = 0; n < tga.width; n++) {
					avgbuf2[n] = avg(buf1[n], buf2[n]);
					diffbuf[n] = diff(buf1[n], buf2[n]);
					if(((buf1[n] + buf2[n]) < 0) && (diffbuf[n] & 1))
						avgbuf2[n]--;
					if((diff(buf1[n], buf2[n]) < -127) ||
					   (diff(buf1[n], buf2[n]) > 128))
						avgbuf2[n] = avgbuf2[n] + 128;
				}
				putline(diffbuf, i / (incline * 2) + 3 * tga.depth / 4, tga.width);

				for(n = 0; n < tga.width; n++) {
					avgbuf[n] = avg(avgbuf1[n], avgbuf2[n]);
					diffbuf[n] = diff(avgbuf1[n], avgbuf2[n]);
					if(((avgbuf1[n] + avgbuf2[n]) < 0) && (diffbuf[n] & 1))
						avgbuf[n]--;
					if((diff(avgbuf1[n], avgbuf2[n]) < -127) ||
					   (diff(avgbuf1[n], avgbuf2[n]) > 128))
						avgbuf[n] = avgbuf[n] + 128;
				}
				putline(avgbuf, (i - incline) / (incline * 2), tga.width);
				putline(diffbuf, (i - incline) / (incline * 2) + tga.depth / 4, tga.width);
			}

			/* Finally, write the converted image to disk */
			for(i = startline; i < endline; i++) {
				getline(buf1, i, tga.width);
				fwrite(&buf1, 1, tga.width, out);
			}
		}
	}
}

/* The paletteset function sets the color palette */
void paletteset (char *buffer) {
	union REGS regs;
	int i;

	outp(0x3c6, 0xff);
	for(i = 0; i < 255; i++) {
		outp(0x3c8, i);
		outp(0x3c9, (*buffer++) >> 2);
		outp(0x3c9, (*buffer++) >> 2);
		outp(0x3c9, (*buffer++) >> 2);
	}
}

/* The putline function displays one line of the image */
void putline(char *buffer, unsigned int n, int width) {
	char far *ScreenMemory = (char far *)(VGA_MEM + n * LINE_SIZE);
	int j;

	for(j = 0; j < width; j++) *(ScreenMemory + j) = buffer[j];
}

/* The getline function reads a line of the image from screen memory */
void getline(char *buffer, unsigned int n, int width) {
	char far *ScreenMemory = (char far *)(VGA_MEM + n * LINE_SIZE);
	int j;

	for(j = 0; j < width; j++) *(buffer + j) = *(ScreenMemory + j);
}

/* The error function displays an error message and halts the program */
void error (char *message) {
	puts(message);
	exit(1);
}

