/******************************************************************************
*
* project name:    TIGCC Tools Suite
* file name:       ttbmp2bin.c
* initial date:    29/03/2001
* author:          thomas.nussbaumer@gmx.net
* description:     converts 4 grayscale to bin file (ImageStudio format)
*
*
* $Id$
*
******************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "ttversion.h"
#include "revtools.h"
#include "tt.h"

#ifdef CVS_FILE_REVISION
#undef CVS_FILE_REVISION
#endif
//-----------------------------------------------------------------------------
// DON'T EDIT THE NEXT REVISION BY HAND! THIS IS DONE AUTOMATICALLY BY THE
// CVS SYSTEM !!!
//-----------------------------------------------------------------------------
#define CVS_FILE_REVISION "$Revision: 1.0 $"


//=============================================================================
// outputs usage information of this tool
//=============================================================================
void PrintUsage() {
    fprintf(USAGE_OUT, "Usage: ttbmp2bin [flags] <infile> <outfile>\n\n"             \
                       "       -m          ....  treat input as monochrome BMP\n"    \
                       "       -v          ....  verbose mode\n"                     \
                       "       -bmp        ....  write again BMP file\n\n"           \
                       "       converts monochrome BMP or 4 grayscale BMP file to\n" \
                       "       ImageStudio-like binary file\n\n"                     \
                       "NOTE:  grayscale BMP  - only the first 4 color indices are used\n"\
                       "NOTE2: monochrome BMP - only the first 2 color indices are used\n"\
                       "NOTE3: the width of the input BMP have to be a multiple of 8\n\n");
}


//=============================================================================
// how a palette entry of a BMP file looks like ...
//=============================================================================
typedef union {
    unsigned int  udata;
    unsigned char bdata[4];
} PALETTE_ENTRY;


//=============================================================================
// general structure of a BMP file ...
//=============================================================================
typedef struct {
    int            w, h, colors;
    unsigned char* data;
    PALETTE_ENTRY* pal;
} IMAGE_DATA;


//=============================================================================
// important BMP constants and the BMP header structure ...
//=============================================================================
#define BMP_WINHEADERSIZE    0x28
#define BMP_COMPRESSION_NONE 0

// NOTE: Due to the standard 4-byte alignment for structures on the PC
// I have excluded the type entry from the structure below!

typedef struct {
    //unsigned char type[2];        // 'BM' == Windows Bitmap
    unsigned int  filesize;       // complete filesize
    unsigned int  reserved;
    unsigned int  dataoffset;   // data offset
    unsigned int  headersize;   // size of header (0x28 for Windows Bitmap)  ... idx 14,15
    unsigned int  width;        // horizontal width in pixels
    unsigned int  height;       // vertical height in pixels
    unsigned int  planes:16;
    unsigned int  bitsperpixel:16;
    unsigned int  compression;  // 0 == none
    unsigned int  datasize;     // rounded to the next 4 byte boundary
    unsigned int  hres;         // horizontal resolution in pixels per meter
    unsigned int  vres;         // vertical   resolution in pixels per meter
    unsigned int  colors;       // number of colors used
    unsigned int  sigcolors;    // number of important colors
} BMPHEADER;


BMPHEADER bh;


//=============================================================================
// outputs palette of BMP to given filestream
//=============================================================================
void PrintPalette(FILE* fp,PALETTE_ENTRY* pal, int number) {
    int i;

    for (i=0;i<number;i++) {
        fprintf(fp,"entry[%03d] = (%02x|%02x|%02x|%02x)\n",
                i,pal[i].bdata[0],pal[i].bdata[1],pal[i].bdata[2],pal[i].bdata[3]);
    }
}


//=============================================================================
// outputs header information of BMP to given filestream
//=============================================================================
void PrintHeaderInfo(FILE* fp) {
    fprintf(fp,"type         = BMP\n");
    fprintf(fp,"filesize     = %lu\n",bh.filesize);
    fprintf(fp,"dataoffset   = %lu\n",bh.dataoffset);
    fprintf(fp,"headersize   = %lu\n",bh.headersize);
    fprintf(fp,"width        = %ld\n",bh.width);
    fprintf(fp,"height       = %ld\n",bh.height);
    fprintf(fp,"planes       = %u\n", bh.planes);
    fprintf(fp,"bitsperpixel = %u\n", bh.bitsperpixel);
    fprintf(fp,"compression  = %lu\n",bh.compression);
    fprintf(fp,"datasize     = %lu\n",bh.datasize);
    fprintf(fp,"hres         = %ld\n",bh.hres);
    fprintf(fp,"vres         = %ld\n",bh.vres);
    fprintf(fp,"colors       = %lu\n",bh.colors);
    fprintf(fp,"sigcolors    = %lu\n",bh.sigcolors);
}


//=============================================================================
// utility function which frees allocated parts of a IMAGE_DATA structure
//=============================================================================
void FreeImageData(IMAGE_DATA* img) {
    if (img) {
        if (img->data) {
            free(img->data);
            img->data=NULL;
        }
        if (img->pal) {
            free(img->pal);
            img->pal = NULL;
        }
        free(img);
    }
}


//=============================================================================
// loads BMP from a given file (out == used error outputstream)
//=============================================================================
IMAGE_DATA* LoadImage(FILE* out,char* filename,int btrace) {
    FILE          *fp = fopen(filename, "rb");
    IMAGE_DATA    *img;
    long           i;
    long           size;
    short          valid = 0;

    if (!fp) {
        fprintf(out,"ERROR: file %s not found\n", ((filename) ? filename : "NULL"));
        return NULL;
    }


    if (fgetc(fp)==0x42) {
       if (fgetc(fp) == 0x4d) {
           valid = 1;
       }
    }

    //-----------------------------
    // check type of BMP ....
    //-----------------------------
    if (!valid) {
        fprintf(out,"ERROR: %s is not a Windows BMP file\n", ((filename) ? filename : "NULL"));
        fclose(fp);
        return NULL;
    }

    fread(&bh,sizeof(BMPHEADER),1,fp); //bytes are reversed !!


    //-----------------------------
    // check headersize ....
    //-----------------------------
    if (bh.headersize != BMP_WINHEADERSIZE) {
        fprintf(out,"ERROR: %s contains an invalid header size (%lu)\n", ((filename) ? filename : "NULL"),bh.headersize);
        fclose(fp);
        return NULL;
    }

    //-----------------------------
    // check compression ....
    //-----------------------------
    if (bh.compression != BMP_COMPRESSION_NONE) {
        fprintf(out,"ERROR: compressed BMP's are not supported\n");
        fclose(fp);
        return NULL;
    }

    //-----------------------------
    // check bits-per-pixel ....
    //-----------------------------
    if (bh.bitsperpixel > 8 || bh.colors > 256) {
        fprintf(out,"ERROR: only 256-color BMPs are supported (8 bits-per-pixel)\n");
        fclose(fp);
        return NULL;
    }

    if (bh.colors < bh.sigcolors) {
        fprintf(out,"ERROR: sigcolors larger than colors\n");
        fclose(fp);
        return NULL;
    }

    if (!(img = (IMAGE_DATA*)malloc(sizeof(IMAGE_DATA)))) {
        fprintf(out,"ERROR: cannot allocate structure for image\n");
        fclose(fp);
        return NULL;
    }

    memset(img,0,sizeof(IMAGE_DATA));

    if (!(img->pal = (PALETTE_ENTRY*)malloc(bh.colors*sizeof(PALETTE_ENTRY)))) {
        fprintf(out,"ERROR: cannot allocate structure for palette\n");
        FreeImageData(img);
        fclose(fp);
        return NULL;
    }

    fread(img->pal, sizeof(PALETTE_ENTRY),bh.colors, fp);

    size = bh.width*bh.height;

    if (!(img->data = (unsigned char*)malloc(size))) {
        fprintf(out,"ERROR: cannot allocate memory for pixel data\n");
        FreeImageData(img);
        fclose(fp);
        return NULL;
    }

    img->w      = bh.width;
    img->h      = bh.height;
    img->colors = bh.colors;

    if (fread(img->data,size,1,fp)!=1) {
        fprintf(out,"ERROR: not enough data bytes in BMP\n");
        FreeImageData(img);
        fclose(fp);
        return NULL;
    }

    fclose(fp);

    if (btrace) {
        PrintHeaderInfo(out);
        PrintPalette(out,img->pal,bh.colors);
    }

    return img;
}

//=============================================================================
// checks first 4 entries of palette for grayscales
//=============================================================================
int IsGrayScale(IMAGE_DATA* img) {
    int i;

    for (i=0;i<img->colors;i++) {
        PALETTE_ENTRY* p = &(img->pal[i]);

        if (p->bdata[0] == p->bdata[1] && p->bdata[0] == p->bdata[2]) {
            continue;
        }
        return 0;
    }

    return 1;
}


//=============================================================================
// replaces one lookup entry in the image with another one
//=============================================================================
void ReplacePalEntry(IMAGE_DATA* img,unsigned char entry1, unsigned char entry2) {
    long size = img->w * img->h;
    long i;
    PALETTE_ENTRY* pal = img->pal;
    unsigned int tmp;

    //printf("replacing %d with %d ... ",entry1,entry2);


    for (i=0;i<size;i++) {
        if (img->data[i] == entry1)      img->data[i] = entry2;
        else if (img->data[i] == entry2) img->data[i] = entry1;
    }

    //printf("%08x/%08x",pal[entry1].udata,pal[entry2].udata);
    tmp = pal[entry1].udata;
    pal[entry1].udata = pal[entry2].udata;
    pal[entry2].udata = tmp;
    //printf("->%08x/%08x\n",pal[entry1].udata,pal[entry2].udata);
}



//=============================================================================
// sort the indices of the lookup table
//=============================================================================
void SortIndicesAscending(FILE* out,IMAGE_DATA* img) {
    int  i;
    int  found;
    int  tmp;
    int  val_new[256];
    int  idx_new[256];

    for (i=0;i<bh.colors;i++) {
        idx_new[i] = i;
        val_new[i] = img->pal[i].bdata[0];
    }

    // simple bubblesort ...
    do {
        found = 0;
        for (i=0;i<bh.colors-1;i++) {
            if (val_new[i] > val_new[i+1]) {
                // swap values
                tmp = val_new[i];
                val_new[i] = val_new[i+1];
                val_new[i+1] = tmp;
                // swap indices
                tmp = idx_new[i];
                idx_new[i] = idx_new[i+1];
                idx_new[i+1] = tmp;
                found++;
            }
        }
    }
    while(found);

    for (i=0;i<bh.colors;i++) {
        if (idx_new[i] != i) {
            ReplacePalEntry(img,i,idx_new[i]);
            idx_new[idx_new[i]] = idx_new[i];  // ! IMPORTANT mark the destination as already swapped
        }
    }
}



//=============================================================================
// as usual: our main
//=============================================================================
int main(int argc,char *argv[]) {
    char*          infile    = NULL;
    char*          outfile   = NULL;
    FILE*          ofp;
    int            n;
    int            use_monochrome = 0;
    int            verbose        = 0;
    int            output_bmp     = 0;
    IMAGE_DATA*    img;

    PRINT_ID("TTBmp2Bin");

    // check for too less arguments
    if (argc < 3) {
        PrintUsage();
        return 1;
    }

    // parse arguments
    for (n=1; n<argc; n++) {
        if (!strcmp(argv[n], "-m"))        use_monochrome = 1;
        else if (!strcmp(argv[n], "-v"))   verbose        = 1;
        else if (!strcmp(argv[n], "-bmp")) output_bmp     = 1;
        else if (argv[n][0] == '-') {
            fprintf(stderr,"ERROR: invalid option %s",argv[n]);
            return 1;
        }
        else if (!infile)  infile  = argv[n];
        else if (!outfile) outfile = argv[n];
        else {
            PrintUsage();
            return 1;
        }
    }

    if (!infile || !outfile) {
        PrintUsage();
        return 1;
    }


    if (!(img = LoadImage(stdout,infile,verbose))) return 1;

    if (!IsGrayScale(img)) {
        fprintf(stderr,"ERROR: not a grayscale image\n");
        FreeImageData(img);
        return 1;
    }

    SortIndicesAscending(stdout,img);

    printf("=============== converted ==============\n");
    PrintPalette(stdout,img->pal,bh.colors);


    if (!(ofp = fopen(outfile,"w"))) {
        fprintf(stderr,"ERROR: cannot open outputfile %s\n",outfile);
        return 1;
    }


    fclose(ofp);
    FreeImageData(img);

    return 0;
}

//#############################################################################
//###################### NO MORE FAKES BEYOND THIS LINE #######################
//#############################################################################
//
//=============================================================================
// Revision History
//=============================================================================
//
// $Log$
//
