// (C) Antony Searle 1998
// Legal: Do whatever you like with this code!
// I accept no responsibilty for damage or loss incurred by using this program.

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>

long Target;

#define TARGET_NONE 0
#define TARGET_TGA  1
#define TARGET_SGI  2
#define TARGET_RGBA 3
#define TARGET_RGB  4

class iColour {
    // Integer colour representation (used for storage)
    public:
    unsigned char b, g, r, a;
    };

class Bitmap {
    // Bitmaps in B-G-R-A format, as in 32 bit TARGA
    public:
    unsigned long uMax, vMax;
    iColour *Pixel;
    int Create(unsigned long u, unsigned long v);
	int Destroy(void);
    int LoadTGA(char *Name);
    int SaveTGA(char *Name);
    int LoadRGBA(char *Name);
    int SaveRGBA(char *Name);
    int LoadRGB(char *Name);
    int SaveRGB(char *Name);
    };

int Bitmap::Create(unsigned long u, unsigned long v) {
    uMax = u;
    vMax = v;

    Pixel = new iColour[uMax * vMax];
    if(Pixel == NULL) {
        printf("Bitmap::Create: Can't allocate %li bytes of memory for new bitmap (%li x %li)\n", 4 * uMax * vMax, uMax, vMax);
		return 0;
        }

    memset(Pixel, 0, 4 * uMax * vMax);
	
	return 1;

    }

int Bitmap::Destroy(void) {
	uMax = 0;
	vMax = 0;
	delete Pixel;
	return 1;
}

int Bitmap::LoadTGA(char *Name) {
    FILE *Handle;
    unsigned char Header[18];

    Handle = fopen(Name, "rb");
    if(Handle == NULL) {
        printf("Bitmap::LoadTGA: Can't open %s\n", Name);
        return 0;
        }

    fseek(Handle, 0, 0);
    fread(Header, 1, 18, Handle);

    if(Header[2] != 2) {
        printf("Bitmap::LoadTGA: %s is not a valid TARGA file\n", Name);
        return 0;
        }

    if(Header[16] != 32) {
        printf("Bitmap::LoadTGA: %s is a %i bit file.\n    Only 32 bit images (24 bit colour + 8 bit alpha channel) are supported.\n    Please convert this file\n", Name, Header[16]);
        exit(EXIT_FAILURE);
        }

    uMax = ((unsigned long) Header[13] << 8) + Header[12];
    vMax = ((unsigned long) Header[15] << 8) + Header[14];

    Pixel = new iColour[uMax * vMax];
    if(Pixel == NULL) {
        printf("Bitmap::LoadTGA: Can't allocate %li bytes of memory for %s (%li x %li)\n", 4 * uMax * vMax, Name, uMax, vMax);
        return 0;
        }

    fseek(Handle, 18, 0);
    fread(Pixel, 4, uMax*vMax, Handle);
    fclose(Handle);

    printf("Bitmap::LoadTGA: %s (%li x %li) successfully loaded\n", Name, uMax, vMax);

	return 1;
    }

int Bitmap::SaveTGA(char *Name) {
    FILE *Handle;
    unsigned char Header[18];

    Header[ 0] = 0;
    Header[ 1] = 0;
    Header[ 2] = 2;     // Uncompressed, uninteresting
    Header[ 3] = 0;
    Header[ 4] = 0;
    Header[ 5] = 0;
    Header[ 6] = 0;
    Header[ 7] = 0;
    Header[ 8] = 0;
    Header[ 9] = 0;
    Header[10] = 0;
    Header[11] = 0;
    Header[12] = (unsigned char) uMax;  // Dimensions
    Header[13] = (unsigned char) ((unsigned long) uMax >> 8);
    Header[14] = (unsigned char) vMax;
    Header[15] = (unsigned char) ((unsigned long) vMax >> 8);
    Header[16] = 32;    // Bits per pixel
    Header[17] = 8;

    Handle = fopen(Name, "wb");
    if(Handle == NULL) {
        printf("Bitmap::SaveTGA: Can't open %s\n", Name);
        return 0;
        }
    fseek(Handle, 0, 0);
    fwrite(Header, 1, 18, Handle);
    fseek(Handle, 18, 0);
    fwrite(Pixel, 4, uMax * vMax, Handle);
    fclose(Handle);

    printf("Bitmap::SaveTGA: %s (%li x %li) successfully saved\n", Name, uMax, vMax);

	return 1;
    }

int Bitmap::LoadRGBA(char *Name) {

	// As storage and file layout differ, we have to use a different method.

	FILE *Handle;
    unsigned char Header[512];
    unsigned char *Buffer;
    unsigned long i, size;

    Handle = fopen(Name, "rb");
    if(Handle == NULL) {
        printf("Bitmap::LoadRGBA: Can't open file %s\n", Name);
		return 0;
        }

    fseek(Handle, 0, 0);
    fread(Header, 1, 512, Handle);

    if( (((unsigned short) Header[0] << 8) + Header[1] )!= 474 ) {
        printf("Bitmap::LoadRGBA: %s is not an SGI image file\n", Name);
        return 0;
        }

    if(Header[2] != 0) {
        printf("Bitmap::LoadRGBA: %s is RLE (compressed).    \nOnly uncompressed images are supported.\n    Please convert this file\n", Name);
        return 0;
        }

    if(Header[3] != 1) {
		printf("Bitmap::LoadRGBA: %s contains more than one byte per colour channel per pixel\n", Name);
		return 0;
		}

    if(Header[11] != 4) {
    	printf("Bitmap::LoadRGBA: %s is not a four-channel (RGBA) image\n", Name);
		return 0;
		}

    uMax = ((unsigned long) Header[6] << 8) + Header[7];
    vMax = ((unsigned long) Header[8] << 8) + Header[9];

    Pixel  = new iColour[uMax * vMax];
    Buffer = new unsigned char[uMax * vMax * 4];

    if(Pixel == NULL) {
        printf("LoadRGBA: Can't allocate %li bytes of memory for %s (%li x %li)\n", 4 * uMax * vMax, Name, uMax, vMax);
        return 0;
        }
    if(Buffer == NULL) {
        printf("LoadRGBA: Can't allocate %li bytes of memory to unpack %s (%li x %li)\n", 4 * uMax * vMax, Name, uMax, vMax);
        return 0;
        }

    fseek(Handle, 512, 0);
    fread(Buffer, 4, uMax*vMax, Handle);
    fclose(Handle);

    // Unfortunately, RGBA and TGA images store colour channels according to
    // different conventions, so we have to unpack:

    size = uMax*vMax;
    
	i = 0;
    do {
		Pixel[i].r = Buffer[i];
		Pixel[i].g = Buffer[i + size];
		Pixel[i].b = Buffer[i + (size * 2)];
		Pixel[i].a = Buffer[i + (size * 3)];
		} while(++i < size);
	
    delete Buffer;

    printf("Bitmap::LoadRGBA: %s (%li x %li) successfully loaded\n", Name, uMax, vMax);

	return 1;
}

int Bitmap::SaveRGBA(char *Name) {
    FILE *Handle;
    unsigned char Header[512];
    unsigned char *Buffer;
    unsigned long i, size;

    memset(Header, 0, 512);
    
    Header[0] = 474 >> 8;   	// Magic number
    Header[1] = 474 & 255; 	// ...
    Header[2] = 0; 		// Verbatim
    Header[3] = 1; 		// BPC
    Header[4] = 0;		// Dimension
    Header[5] = 3;		// ...
    Header[6] = (unsigned char) ((unsigned long) uMax >> 8);	// X size
    Header[7] = (unsigned char) ((unsigned long) uMax & 255);	// ...
    Header[8] = (unsigned char) ((unsigned long) vMax >> 8);	// Y size
    Header[9] = (unsigned char) ((unsigned long) vMax & 255);	// ...
    Header[10] = 0;		// Z Size (4 channel RGBA)
    Header[11] = 4;		// ...
    				// 4 byte pixel min 00
    Header[16] = 0;		// 4 byte pixel max FF
    Header[17] = 0;		// ...
    Header[18] = 0;		// ...
    Header[19] = 255;		// ...
    				// And the rest is irrelevant...

    Handle = fopen(Name, "wb");
    if(Handle == NULL) {
    	printf("Bitmap::SaveRGBA: Can't open %s\n", Name);
		return 0;
    	}
    fseek(Handle, 0, 0);
    fwrite(Header, 1, 512, Handle);
    
    // Unfortunately, RGBA and TGA images store colour channels according to
    // different conventions, so we have to unpack:

    Buffer = new unsigned char[4*uMax*vMax];

    if(Buffer == NULL) {
        printf("LoadRGBA: Can't allocate %li bytes of memory to pack %s (%li x %li)\n", 4 * uMax * vMax, Name, uMax, vMax);
        return 0;
        }
    
    size = uMax*vMax;
	i = 0;
    do {
		Buffer[i] = Pixel[i].r;
		Buffer[i + size] = Pixel[i].g;
		Buffer[i + (size * 2)] = Pixel[i].b;
		Buffer[i + (size * 3)] = Pixel[i].a;
		} while(++i < size);
    
    // Save new data
    
    fseek(Handle, 512, 0);
    fwrite(Buffer, 4, uMax*vMax, Handle);
    fclose(Handle);
    
    delete Buffer;
    
    printf("Bitmap::SaveRGBA: %s (%li x %li) successfully saved\n", Name, uMax, vMax);
	
	return 1;
}

int Bitmap::LoadRGB(char *Name) {

	// As storage and file layout differ, we have to use a different method.

	FILE *Handle;
    unsigned char Header[512];
    unsigned char *Buffer;
    unsigned long i, size;

    Handle = fopen(Name, "rb");
    if(Handle == NULL) {
        printf("Bitmap::LoadRGB: Can't open file %s\n", Name);
		return 0;
        }

    fseek(Handle, 0, 0);
    fread(Header, 1, 512, Handle);

    if( (((unsigned short) Header[0] << 8) + Header[1] )!= 474 ) {
        printf("Bitmap::LoadRGB: %s is not an SGI image file\n", Name);
        return 0;
        }

    if(Header[2] != 0) {
        printf("Bitmap::LoadRGB: %s is RLE (compressed).    \nOnly uncompressed images are supported.\n    Please convert this file\n", Name);
        return 0;
        }

    if(Header[3] != 1) {
		printf("Bitmap::LoadRGB: %s contains more than one byte per colour channel per pixel\n", Name);
		return 0;
		}

    if(Header[11] != 3) {
    	printf("Bitmap::LoadRGB: %s is not a three-channel (RGB) image\n", Name);
		return 0;
		}

    uMax = ((unsigned long) Header[6] << 8) + Header[7];
    vMax = ((unsigned long) Header[8] << 8) + Header[9];

    Pixel  = new iColour[uMax * vMax];
    Buffer = new unsigned char[uMax * vMax * 3];

    if(Pixel == NULL) {
        printf("LoadRGB: Can't allocate %li bytes of memory for %s (%li x %li)\n", 4 * uMax * vMax, Name, uMax, vMax);
        return 0;
        }
    if(Buffer == NULL) {
        printf("LoadRGB: Can't allocate %li bytes of memory to unpack %s (%li x %li)\n", 3 * uMax * vMax, Name, uMax, vMax);
        return 0;
        }

    fseek(Handle, 512, 0);
    fread(Buffer, 3, uMax*vMax, Handle);
    fclose(Handle);

    // Unfortunately, RGBA and TGA images store colour channels according to
    // different conventions, so we have to unpack:

    size = uMax*vMax;
    
	i = 0;
    do {
		Pixel[i].r = Buffer[i];
		Pixel[i].g = Buffer[i + size];
		Pixel[i].b = Buffer[i + (size * 2)];
		Pixel[i].a = 0;						// Dummy alpha
		} while(++i < size);
	
    delete Buffer;

    printf("Bitmap::LoadRGB: %s (%li x %li) successfully loaded\n", Name, uMax, vMax);

	return 1;
}

int Bitmap::SaveRGB(char *Name) {
    FILE *Handle;
    unsigned char Header[512];
    unsigned char *Buffer;
    unsigned long i, size;

    memset(Header, 0, 512);
    
    Header[0] = 474 >> 8;   	// Magic number
    Header[1] = 474 & 255; 		// ...
    Header[2] = 0; 		// Verbatim
    Header[3] = 1; 		// BPC
    Header[4] = 0;		// Dimension
    Header[5] = 3;		// ...
    Header[6] = (unsigned char) ((unsigned long) uMax >> 8);	// X size
    Header[7] = (unsigned char) ((unsigned long) uMax & 255);	// ...
    Header[8] = (unsigned char) ((unsigned long) vMax >> 8);	// Y size
    Header[9] = (unsigned char) ((unsigned long) vMax & 255);	// ...
    Header[10] = 0;		// Z Size (3 channel RGB)
    Header[11] = 3;		// ...
    					// 4 byte pixel min 00
    Header[16] = 0;		// 4 byte pixel max FF
    Header[17] = 0;		// ...
    Header[18] = 0;		// ...
    Header[19] = 255;	// ...
    				// And the rest is irrelevant...

    Handle = fopen(Name, "wb");
    if(Handle == NULL) {
    	printf("Bitmap::SaveRGB: Can't open %s\n", Name);
		return 0;
    	}
    fseek(Handle, 0, 0);
    fwrite(Header, 1, 512, Handle);
    
    // Unfortunately, RGBA and TGA images store colour channels according to
    // different conventions, so we have to unpack:

    Buffer = new unsigned char[3*uMax*vMax];

    if(Buffer == NULL) {
        printf("LoadRGB: Can't allocate %li bytes of memory to pack %s (%li x %li)\n", 3 * uMax * vMax, Name, uMax, vMax);
        return 0;
        }
    
    size = uMax*vMax;
	i = 0;
    do {
		Buffer[i] = Pixel[i].r;
		Buffer[i + size] = Pixel[i].g;
		Buffer[i + (size * 2)] = Pixel[i].b;
		} while(++i < size);
    
    // Save new data
    
    fseek(Handle, 512, 0);
    fwrite(Buffer, 3, uMax*vMax, Handle);
    fclose(Handle);
    
    delete Buffer;
    
    printf("Bitmap::SaveRGB: %s (%li x %li) successfully saved\n", Name, uMax, vMax);
	
	return 1;
}

int TypeImage(char *Name) {
	FILE *Handle;
	unsigned char Header[12];

	Handle = fopen(Name, "rb");
	if(Handle == NULL) {
		printf("Can't open %s - ignoring\n", Name);
		return 0;
		}
	
    fseek(Handle, 0, 0);
    fread(Header, 1, 12, Handle);
	fclose(Handle);

	switch(Header[0]) {
		case 0:
			// TGA
			return 1;
		case 1:
			// SGI
			switch(Header[11]) {
				case 3:
					// RGB
					return 2;
				case 4:
					// RGBA
					return 3;
				}
		default:
			printf("%s is not a convertable image\n", Name);
			return 0;
		}
	
	}

char *MakeName(char *Name, long t) {
	long k, l;
	char *Write;

	k = 0;
	l = 0;
	while(Name[k] != 0) {
		if(Name[k] == '.') { 
			l++ ;
			}
		k++;
		}
	if(l > 0) {
		// We have an extension, so prune it off
		while(Name[k] != '.') {
			k--;
			}
		// We are on the spot (literally)
		Name[k] = 0;
		
		Write = new char[k + 5];

		if(Write == NULL) {
			printf("Out of memory\n");
			exit(EXIT_FAILURE);
			}

		switch(t) {
			case TARGET_TGA:
				sprintf(Write, "%s.tga", Name);
				break;
			case TARGET_SGI:
				sprintf(Write, "%s.sgi", Name);
				break;
			}

		Name[k] = '.';

		}
	else {
		// No extension, so add one

		Write = new char[k + 5];

		if(Write == NULL) {
			printf("Out of memory\n");
			exit(EXIT_FAILURE);
			}

		switch(t) {
			case TARGET_TGA:
				sprintf(Write, "%s.tga", Name);
				break;
			case TARGET_SGI:
				sprintf(Write, "%s.sgi", Name);
				break;
			}

		Name[k] = '.';
	
		}

	return Write;

	}

void main(int argc, char *argv[], char *envp[]) {
	long i, j;
	char *Write;

	Bitmap Image;

	printf("\n    Converts TARGA and SGI images (c) Antony Searle 1998\n\n");

	if(argc == 1) {
		// We have no input: give help
		printf("Syntax: Convert [options] [file1] [file2]...\n\n");
		printf("Options:\n");
		printf("    NONE    Defaults to changing all TGA to (24 bit) SGI, and all SGI to TGA\n");
		printf("    -tga    Changes all SGI images to TGA                                   \n");
		printf("    -sgi    Changes all TGA images to (24 bit) SGI images                   \n");
		printf("    -rgb    Changes all images to (24 bit) SGI images                       \n");
		printf("    -rgba   Changes all images to (32 bit) SGI images                       \n\n");

		printf("Default behaviour will cause the loss of any TGA alpha channels\n");
		printf("Files with incorrect extensions will be copied to their correct\n    extensions (.tga, .sgi)\n\n");
		
		return;
	
		}

	Target = TARGET_NONE;

	i = 1;
	while((argv[i])[0] == '-') {
		// We have an option, not a file
		switch((argv[i])[1]) {
			case 't':
				Target = TARGET_TGA;
				printf("Converting all files to TARGA (.tga)\n");
				break;
			case 's':
				Target = TARGET_SGI;
				printf("Converting all TARGA files to (24 bit) SGI (.sgi)\n");
				break;
			case 'r':
				switch((argv[i])[4]) {
					case 0:
						Target = TARGET_RGB;
						printf("Converting all files to (24 bit) SGI (.sgi)\n");
						break;
					case 'a':
						Target = TARGET_RGBA;
						printf("Converting all files to (32 bit) SGI (.sgi)\n");
						break;
					default:
						printf("Unknown option %s\n", argv[i]);
						return;
					}
				break;
			default:
				printf("Unknown option [%s]\n", argv[i]);
				return;
			}
		if(++i == argc) {
			printf("No files\n");
			return;
			}
		}
	
	// Have parsed options: Now, we go to work on the images.

	do {

		// First, determine the type of the file:
		j = TypeImage(argv[i]);
		
		if(j != 0) {
			
			// We have a recognised format

			switch(Target) {
				case TARGET_NONE:
					// Take SGI->TGA, TGA->RGB
					switch(j) {
						case 1:
							//TGA->RGB
							Write = MakeName(argv[i], TARGET_SGI);
							if(Image.LoadTGA(argv[i])) { Image.SaveRGB(Write); Image.Destroy(); }
							delete Write;
							break;
						case 2:
							//RGB->TGA
							Write = MakeName(argv[i], TARGET_TGA);
							if(Image.LoadRGB(argv[i])) { Image.SaveTGA(Write); Image.Destroy(); }
							delete Write;
							break;
						case 3:
							//RGBA->TGA
							Write = MakeName(argv[i], TARGET_TGA);
							if(Image.LoadRGBA(argv[i])) { Image.SaveTGA(Write); Image.Destroy(); }
							delete Write;
							break;
						}
					break;
				case TARGET_TGA:
					// Take SGI->TGA
					switch(j) {
						case 1:
							//TGA->TGA
							Write = MakeName(argv[i], TARGET_TGA);
							if(Image.LoadTGA(argv[i])) { Image.SaveTGA(Write); Image.Destroy(); }
							delete Write;
							break;
						case 2:
							//RGB->TGA
							Write = MakeName(argv[i], TARGET_TGA);
							if(Image.LoadRGB(argv[i])) { Image.SaveTGA(Write); Image.Destroy(); }
							delete Write;
							break;
						case 3:
							//RGBA->TGA
							Write = MakeName(argv[i], TARGET_TGA);
							if(Image.LoadRGBA(argv[i])) { Image.SaveTGA(Write); Image.Destroy(); }
							delete Write;
							break;
						}
					break;
				case TARGET_SGI:
					// Take TGA->RGB
					switch(j) {
						case 1:
							//TGA->RGB
							Write = MakeName(argv[i], TARGET_SGI);
							if(Image.LoadTGA(argv[i])) { Image.SaveRGB(Write); Image.Destroy(); }
							delete Write;
							break;
						case 2:
							//RGB->RGB
							Write = MakeName(argv[i], TARGET_SGI);
							if(Image.LoadRGB(argv[i])) { Image.SaveRGB(Write); Image.Destroy(); }
							delete Write;
							break;
						case 3:
							//RGBA->RGBA
							Write = MakeName(argv[i], TARGET_SGI);
							if(Image.LoadRGBA(argv[i])) { Image.SaveRGBA(Write); Image.Destroy(); }
							delete Write;
							break;
						}
					break;
				case TARGET_RGB:
					// Take TGA->RGB, RGBA->RGB
					switch(j) {
						case 1:
							//TGA->RGB
							Write = MakeName(argv[i], TARGET_SGI);
							if(Image.LoadTGA(argv[i])) { Image.SaveRGB(Write); Image.Destroy(); }
							delete Write;
							break;
						case 2:
							//RGB->RGB
							Write = MakeName(argv[i], TARGET_SGI);
							if(Image.LoadRGB(argv[i])) { Image.SaveRGB(Write); Image.Destroy(); }
							delete Write;
							break;
						case 3:
							//RGBA->RGB
							Write = MakeName(argv[i], TARGET_SGI);
							if(Image.LoadRGBA(argv[i])) { Image.SaveRGB(Write); Image.Destroy(); }
							delete Write;
							break;
						}
					break;
				case TARGET_RGBA:
					// Take TGA->RGBA, RGB->RGBA
					switch(j) {
						case 1:
							//TGA->RGBA
							Write = MakeName(argv[i], TARGET_SGI);
							if(Image.LoadTGA(argv[i])) { Image.SaveRGBA(Write); Image.Destroy(); }
							delete Write;
							break;
						case 2:
							//RGB->RGBA
							Write = MakeName(argv[i], TARGET_SGI);
							if(Image.LoadRGB(argv[i])) { Image.SaveRGBA(Write); Image.Destroy(); }
							delete Write;
							break;
						case 3:
							//RGBA->RGB
							Write = MakeName(argv[i], TARGET_SGI);
							if(Image.LoadRGBA(argv[i])) { Image.SaveRGBA(Write); Image.Destroy(); }
							delete Write;
							break;
						}
					break;
				}

			}

		} while(++i < argc);

	} 