/*
 *  This source is free writen by M'dJ at 17/05/2011
 *  Use open source FreeImage and gnu gcc
 *  Thank to freeimage, libsb and opencpn
 *
 *	imgkap.c - Convert kap a file from/to a image file and kml to kap
 */

#define VERS   "1.11"

#include <stdint.h>
#include <math.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>		/* for malloc() */
#include <string.h>		/* for strncmp() */

#include <time.h>       /* for date in kap */

#include <FreeImage.h>

/* color type and mask */

typedef union
{
    RGBQUAD  q;
    uint32_t p;
} Color32;

#define RGBMASK 0x00FFFFFF


/* --- Copy this part in header for use imgkap functions in programs and define LIBIMGKAP*/

#define METTERS     0
#define FATHOMS     1

#define NORMAL     0
#define OLDKAP     1

#define COLOR_NONE  1
#define COLOR_IMG   2
#define COLOR_MAP   3
#define COLOR_KAP   4

#define FIF_KAP 1024
#define FIF_NO1 1025
#define FIF_TXT 1026
#define FIF_KML 1027


int imgtokap(int typein,char *filein, double lat0, double lon0, double lat1, double lon1, int optkap,int color,char *title, int units, char *sd,char *fileout);
int imgheadertokap(int typein,char *filein,int typeheader,int optkap,int color,char *title,char *fileheader,char *fileout);
int kaptoimg(int typein,char *filein,int typeheader,char *fileheader,int typeout,char *fileout,char *optionpal);

int findfiletype(char *file);
int writeimgkap(FILE *out,FIBITMAP **bitmap,int optkap,int colors,Color32 *pal,uint16_t widthin,uint16_t heightin,uint16_t widthout,uint16_t heightout);

int bsb_uncompress_row(int typein, FILE *in, uint8_t *buf_out, uint16_t bits_in,uint16_t bits_out,uint16_t width);
int bsb_compress_row(const uint8_t *buf_in, uint8_t *buf_out, uint16_t bits_out, uint16_t line, uint16_t widthin,uint16_t widthout);

/* --- End copy */


static const double WGSinvf                           = 298.257223563;       /* WGS84 1/f */
static const double WGSexentrik                       = 0.081819;             /* e = 1/WGSinvf; e = sqrt(2*e -e*e) ;*/

struct structlistoption
{
    char const *name;
    int val;
} ;

static struct structlistoption imagetype[] =
{
    {"KAP",FIF_KAP},
    {"NO1",FIF_NO1},
    {"KML",FIF_KML},
    {"TXT",FIF_TXT},
    {NULL, FIF_UNKNOWN},
} ;

static struct structlistoption listoptcolor[] =
{
    {"NONE",COLOR_NONE},
    {"KAP",COLOR_KAP},
    {"IMG",COLOR_IMG},
    {"MAP",COLOR_MAP},
    {NULL,COLOR_NONE}
} ;

int findoptlist(struct structlistoption *liste,char *name)
{
    while (liste->name != NULL)
    {
        if (!strcasecmp(liste->name,name)) return liste->val;
        liste++;
    }
    return liste->val;
}

int findfiletype(char *file)
{
    char *s ;

    s = file + strlen(file)-1;
    while ((s > file) && (*s != '.')) s--;
    s++;
    return findoptlist(imagetype,s);
}

static double postod(double lat0, double lon0, double lat1, double lon1)
{
    double x,v,w;

    lat0 = lat0 * M_PI / 180.;
    lat1 = lat1 * M_PI / 180.;
    lon0 = lon0 * M_PI / 180.;
    lon1 = lon1 * M_PI / 180.;

    v = sin((lon0 - lon1)/2.0);
    w = cos(lat0) * cos(lat1) * v * v;
    x = sin((lat0 - lat1)/2.0);
    return (180. * 60. / M_PI) * 2.0 * asinl(sqrtl(x*x + w));
}

static inline double lontox(double l)
{
    return l*M_PI/180;
}

static inline double lattoy_WS84(double l)
{
    double e = WGSexentrik;
    double s = sinl(l*M_PI/180.0);

    return log(tan(M_PI/4 + l * M_PI/360)*pow((1. - e * s)/(1. + e * s), e/2.));
}


/*------------------ Single Memory algorithm ------------------*/

#define MYBSIZE 1572880

typedef struct smymemory
{
    struct smymemory *next;
    uint32_t size;
} mymemory;

static mymemory *mymemoryfirst = 0;
static mymemory *mymemorycur = 0;

void * myalloc(int size)

{
    void *s = NULL;
    mymemory *mem = mymemorycur;

    if (mem && ((mem->size + size) > MYBSIZE)) mem = 0;

    if (!mem)
    {
        mem = (mymemory *)calloc(MYBSIZE,1);
        if (mem == NULL) return 0;
        mem->size = sizeof(mymemory);

        if (mymemorycur) mymemorycur->next = mem;
        mymemorycur = mem;
        if (!mymemoryfirst) mymemoryfirst = mem;
    }

    s = ((int8_t *)mem + mem->size);
    mem->size += size;
    return s;
}

void myfree(void)

{
    struct smymemory *mem, *next;

    mem = mymemoryfirst;
    while (mem)
    {
        next = mem->next;
        free(mem);
        mem = next;
    }
    mymemoryfirst = 0;
    mymemorycur = 0;
}

/*------------------ Histogram algorithm ------------------*/

typedef struct
{
    Color32 color;
    uint32_t count;
    int16_t num;
} helem;

typedef struct shistogram
{
    Color32 color;
    uint32_t count;
    int16_t num;
    int16_t used;
    struct shistogram *child ;
} histogram;


#define HistIndex2(p,l) ((((p.q.rgbRed >> l) & 0x03) << 4) | (((p.q.rgbGreen >> l) & 0x03) << 2) |	((p.q.rgbBlue >> l) & 0x03) )
#define HistSize(l) (l?sizeof(histogram):sizeof(helem))
#define HistInc(h,l) (histogram *)(((char *)h)+HistSize(l))
#define HistIndex(h,p,l) (histogram *)((char *)h+HistSize(l)*HistIndex2(p,l))

static histogram *HistAddColor (histogram *h, Color32 pixel )
{
    char level;

    for (level=6;level>=0;level -=2)
    {
        h = HistIndex(h,pixel,level) ;

        if (h->color.p == pixel.p) break;
        if (!h->count && !h->num)
        {
            h->color.p = pixel.p;
            break;
        }
        if (!h->child)
        {
            h->child = (histogram *)myalloc(HistSize(level)*64);
            if (h->child == NULL) return 0;
        }
        h = h->child;
    }

    h->count++;
    return h;
}

static int HistGetColorNum (histogram *h, Color32 pixel)
{
    char level;

    for (level=6;level>=0;level -=2)
    {
        /* 0 < index < 64 */
        h = HistIndex(h,pixel,level) ;
        if (h->color.p == pixel.p) break;
        if (!level)
            break; // erreur
        if (!h->child) break;
        h = h->child;
    }
    if (h->num < 0) return -1-h->num;
    return h->num-1;
}

#define HistColorsCount(h) HistColorsCountLevel(h,6)

static int32_t HistColorsCountLevel (histogram *h,int level)
{
    int i;
    uint32_t count = 0;

    for (i=0;i<64;i++)
    {
        if (h->count) count++;
        if (level)
        {
            if(h->child) count += HistColorsCountLevel(h->child,level-2);
        }
        h = HistInc(h,level);
    }
    return count;
}


/*--------------- reduce begin -------------*/

typedef struct
{
    histogram   *h;

    int32_t 	nbin;
    int32_t 	nbout;

    int32_t     colorsin;
    int32_t     colorsout;

    int         nextcote;
    int         maxcote;
    int         limcote[8];

    uint64_t    count;
    uint64_t    red;
    uint64_t    green;
    uint64_t    blue;

} reduce;

static inline int HistDist(Color32 a, Color32 b)
{
   int c,r;

   c = a.q.rgbRed - b.q.rgbRed;
   r = c*c;

   c = a.q.rgbGreen - b.q.rgbGreen;
   r += c*c;

   c = a.q.rgbBlue - b.q.rgbBlue;
   r += c*c;

   return sqrt(r);
}

static int HistReduceDist(reduce *r, histogram *h, histogram *e, int cote, int level)
{
    int i;
    int used = 1;
    int curcote;
    int limcote = r->limcote[level];

    for (i=0;i<64;i++)
    {
        if (h->count && !h->num)
        {

            curcote = HistDist((Color32)e->color,(Color32)h->color);

            if (curcote <= cote)
            {
                    uint64_t c;

                    c = h->count;

                    r->count += c;
                    r->red += c * (uint64_t)((Color32)h->color).q.rgbRed ;
                    r->green +=  c * (uint64_t)((Color32)h->color).q.rgbGreen;
                    r->blue +=  c * (uint64_t)((Color32)h->color).q.rgbBlue;

                    h->num = r->nbout;
                    h->count = 0;
                    r->nbin++;
            }
            else
            {
                    if (r->nextcote > curcote)
                        r->nextcote = curcote;
                    used = 0;
            }
        }
        if (level && h->child && !h->used)
        {
            limcote += cote ;

            curcote = HistDist((Color32)e->color,(Color32)h->color);

            if (curcote <= limcote)
                h->used = HistReduceDist(r,h->child,e,cote,level-2);
            if (!h->used)
            {
                if ((curcote > limcote) && (r->nextcote > limcote))
                    r->nextcote = curcote ;
                used = 0;
            }
            limcote -= cote ;
        }
        h = HistInc(h,level);
    }
    return used;
}

static void HistReduceLevel(reduce *r, histogram *h, int level)
{
    int i;

    for (i=0;i<64;i++)
    {
        if (level && h->child && !h->used)
        {
            HistReduceLevel(r, h->child,level-2);
            if (!r->colorsout) break;
        }

        if (h->count && !h->num)
        {
            int32_t cote = 0;
            int32_t nbcolors;
            int32_t curv;

            r->count = r->red = r->green = r->blue = 0;
            r->nbin = 0;
            r->nextcote = 0;
            r->nbout++;

            cote = (int32_t)(pow((double)((1<<24)/(double)r->colorsout),1.0/3.0)/2); //-1;
            r->maxcote = sqrt(3*cote*cote);

            curv = 0;
            nbcolors = (r->colorsin +r->colorsout -1)/r->colorsout;

            while (r->nbin < nbcolors)
            {
                curv += nbcolors - r->nbin;
                cote = (int32_t)(pow(curv,1.0/3.0)/2); // - 1;
                cote = sqrt(3*cote*cote);

                if (r->nextcote > cote)
                    cote = r->nextcote;

                r->nextcote = r->maxcote+1;

                if (cote >= r->maxcote)
                        break;

                h->used = HistReduceDist(r,r->h,h,cote,6);

                if (!r->count)
                {
                    fprintf(stderr,"Erreur quantize\n");
                    return;
                }
            }

            r->colorsin -= r->nbin;
            r->colorsout--;
            {
                histogram *e;
                Color32 pixel ;
                uint64_t c,cc;

                c = r->count; cc = c >> 1 ;
                pixel.q.rgbRed = (uint8_t)((r->red + cc) / c);
                pixel.q.rgbGreen = (uint8_t)((r->green + cc) / c);
                pixel.q.rgbBlue = (uint8_t)((r->blue + cc) / c);
                pixel.q.rgbReserved = 0;

                e = HistAddColor(r->h,pixel);
                e->count = r->count;
                e->num = -r->nbout;

            }

            if (!r->colorsout) break;
        }
        h = HistInc(h,level);
    }

}

static int HistReduce(histogram *h, int colorsin, int colorsout)
{
    reduce r;

    r.h = h;

    r.nbout = 0;

    if (!colorsout || !colorsin) return 0;

    if (colorsout > 0x7FFF) colorsout = 0x7FFF;
    if (colorsout > colorsin) colorsout = colorsin;
    r.colorsin = colorsin;
    r.colorsout = colorsout;

    r.limcote[2] = sqrt(3*3*3) ;
    r.limcote[4] = sqrt(3*15*15) ;
    r.limcote[6] = sqrt(3*63*63) ;

    HistReduceLevel(&r,h,6);

    return r.nbout;
}

/*--------------- reduce end -------------*/

static int _HistGetList(histogram *h,helem **e,int nbcolors,char level)
{
    int i;
    int nb;

    nb = 0;
    for (i=0;i<64;i++)
    {
        if (h->count && (h->num < 0))
        {
            e[-1-h->num] = (helem *)h;
            nb++;
        }

        if (level && h->child) nb += _HistGetList(h->child,e,nbcolors-nb,level-2);
        if (nb > nbcolors)
                return 0;
        h = HistInc(h,level);
    }
    return nb;
}


static int HistGetPalette(uint8_t *colorskap,uint8_t *colors,Color32 *palette,histogram *h,int nbcolors, int nb, int optcolors, Color32 *imgpal,int maxpal)
{
    int i,j;
    helem *t,*e[128];
    uint8_t numpal[128];

    /* get colors used */
    if ((i= _HistGetList(h,e,nbcolors,6)) != nbcolors)
        return 0;

    /* load all color in final palette */
    memset(numpal,0,sizeof(numpal));
    if (!imgpal)
    {
        for (i=0;i<nbcolors;i++)
        {
            if (!(palette[i].q.rgbReserved & 1)) palette[i].p = e[i]->color.p;
            palette[i].q.rgbReserved |= 1;
            colors[i] = i;
            numpal[i] = i;
        }
        palette->q.rgbReserved |= 8;
        maxpal = nbcolors;
    }
    else
    {
        for (i=maxpal-1;i>=0;i--)
        {
            j = HistGetColorNum(h,imgpal[i]);
            if (j>=0)
            {
                if (!(palette[i].q.rgbReserved & 1))
                    palette[i].p = imgpal[i].p;
                palette[i].q.rgbReserved |= 1;
                numpal[j] = i;
                colors[i] = j;
            }
        }
        palette->q.rgbReserved |= 8;
    }

    /* sort palette desc count */
    for (i=0;i<nbcolors;i++)
    {
        for (j=i+1;j<nbcolors;j++)
            if (e[j]->count > e[i]->count)
            {
                t =  e[i];
                e[i] = e[j];
                e[j] = t;
            }
    }
    /* if palkap 0 put first in last */
    if (nb)
    {
        nb=1;
        t =  e[0];
        e[0] = e[nbcolors-1];
        e[nbcolors-1] = t;
    }

    /* get kap palette colors */
    colorskap[0] = 0;
    for (i=0;i<nbcolors;i++)
        colorskap[i+nb] = numpal[-1-e[i]->num];

    /* get num colors in kap palette */
    for (i=0;i<maxpal;i++)
    {
        for (j=0;j<nbcolors;j++)
            if (colors[i] == (-1 - e[j]->num))
                break;
        colors[i] = j+nb;
    }

    /* taitement img && map sur colorskap */
    if ((optcolors == COLOR_IMG) || (optcolors == COLOR_MAP))
    {
        for(i=0;i<maxpal;i++)
        {

            palette[256+i].q.rgbRed = palette[i].q.rgbRed ;
            palette[256+i].q.rgbGreen = palette[i].q.rgbGreen ;
            palette[256+i].q.rgbBlue = palette[i].q.rgbBlue ;
            palette[512+i].q.rgbRed = (palette[i].q.rgbRed)/2 ;
            palette[512+i].q.rgbGreen = (palette[i].q.rgbGreen)/2 ;
            palette[512+i].q.rgbBlue = (palette[i].q.rgbBlue)/2 ;
            palette[768+i].q.rgbRed = (palette[i].q.rgbRed)/4 ;
            palette[768+i].q.rgbGreen = (palette[i].q.rgbGreen)/4 ;
            palette[768+i].q.rgbBlue = (palette[i].q.rgbBlue)/4 ;
            palette[768+i].q.rgbReserved = palette[512+i].q.rgbReserved = palette[256+i].q.rgbReserved = palette[i].q.rgbReserved ;
        }

        if ((optcolors == COLOR_MAP) && (nbcolors < 64))
        {
            Color32 *p = palette+768;

            for(i=0;i<maxpal;i++)
            {
                if ((p->q.rgbRed <= 4) && (p->q.rgbGreen <= 4) && (p->q.rgbBlue <= 4))
                    p->q.rgbRed = p->q.rgbGreen = p->q.rgbBlue = 55;

                if ((p->q.rgbRed >= 60) && (p->q.rgbGreen >= 60) && (p->q.rgbBlue >= 60))
                    p->q.rgbRed = p->q.rgbGreen = p->q.rgbBlue = 0;
                p++;
            }


        }
    }
/*
    for (i=0;i<nbcolors;i++)
    {
        printf("eorder %d rgb %d %d %d\n",i,e[i]->color.q.rgbRed,e[i]->color.q.rgbGreen,e[i]->color.q.rgbBlue);
    }
    for (i=0;i<maxpal;i++)
    {
        printf("palette %d rgb %d %d %d\n",i,palette[i].q.rgbRed,palette[i].q.rgbGreen,palette[i].q.rgbBlue);
    }
    for (i=0;i<nbcolors+nb;i++)
    {
        j = colorskap[i];
        printf("palkap %d rgb %d %d %d\n",i,palette[j].q.rgbRed,palette[j].q.rgbGreen,palette[j].q.rgbBlue);
    }
    for (i=0;i<maxpal;i++)
    {
        printf("indexcol %i colors : %d\n",i,colors[i]);
    }
*/
    nbcolors += nb;
    return nbcolors;
}

#define HistFree(h) myfree()


/*------------------ End of Histogram ------------------*/

typedef struct shsv
{
    double hue;
    double sat;
    double val;
} HSV;


/* read in kap file */

//* si size < 0 lit jusqu'a \n (elimine \r) et convertie NO1 int r = (c - 9) & 0xFF; */

static inline int fgetkapc(int typein, FILE *in)
{
    int c;


    c = getc(in);
    if (typein == FIF_NO1) return (c - 9) & 0xff;
    return c;
}

static int fgetkaps(char *s, int size, FILE *in, int typein)
{
    int i,c;

    i = 0;
    while (((c = getc(in)) != EOF) && size)
    {
        if (typein == FIF_NO1) c = (c - 9) & 0xff ;
        if (size > 0)
        {
            s[i++] = (char)c ;
            size--;
            continue;
        }
        if (c == '\r') continue;
        if (c == '\n')
        {
            s[i] = 0;
            size++;
            break;
        }
        s[i++] = (char)c;
        size++;
        if (!c) break;
    }
    return i;
}

/* function read and write kap index */

static int bsb_write_index(FILE *fp, uint16_t height, uint32_t *index)
{
    uint8_t l;

	/* Write index table */
	while (height--)
	{
		/* Indices must be written as big-endian */
        l = (*index >> 24) & 0xff;
        fputc(l, fp);
        l = (*index >> 16) & 0xff;
        fputc(l, fp);
		l = (*index >> 8) & 0xff;
        fputc(l, fp);
		l = *index & 0xff;
        fputc(l, fp);
        index++;
	}
	return 1;
}

static uint32_t *bsb_read_index(int typein,FILE *in,uint16_t height)
{
    uint32_t l,end;
    uint32_t *index;
    int i;

    index = NULL;

    fseek(in,-4,SEEK_END);
    end = ftell(in);

    l = ((uint32_t)fgetkapc(typein,in)<<24)+((uint32_t)fgetkapc(typein,in)<<16)+((uint32_t)fgetkapc(typein,in)<<8)+((uint32_t)fgetkapc(typein,in));

    if (((end - l)/4) != height) return NULL;

    index = (uint32_t *)malloc(height*sizeof(uint32_t));
    if (index == NULL) return NULL;


    fseek(in,l,SEEK_SET);

	/* Read index table */
	for (i=0; i < height; i++)
	{
        index[i] = ((uint32_t)fgetkapc(typein,in)<<24)+((uint32_t)fgetkapc(typein,in)<<16)+((uint32_t)fgetkapc(typein,in)<<8)+((uint32_t)fgetkapc(typein,in));
	}
	return index;
}

/* bsb compress number, not value 0 at first write */

static uint16_t bsb_compress_nb(uint8_t *p, uint16_t nb, uint8_t pixel, uint16_t max)
{
    uint16_t count = 0;

    if (nb > max)
    {
        count = bsb_compress_nb(p,nb>>7,pixel|0x80,max);
        p[count++] = (nb & 0x7F) | (pixel & 0x80);
        return count;
    }
    pixel |= nb ;
    if (!pixel) p[count++] = 0x80;
    p[count++] = pixel ;
    return count;
}


/* write line bsb */

int bsb_compress_row(const uint8_t *buf_in, uint8_t *buf_out, uint16_t bits_out, uint16_t line, uint16_t widthin, uint16_t widthout)
{
	uint16_t	ibuf,run_length ;
	uint16_t    ipixelin,ipixelout,xout;
	uint8_t	    last_pix;
	uint16_t    dec, max;

	dec = 7-bits_out;
	max = (1<<dec) -1;

    /*      write the line number */
    ibuf = bsb_compress_nb(buf_out,line,0,0x7F);

 	ipixelin = ipixelout = 0;

	while ( ipixelin < widthin )
	{
		last_pix = buf_in[ipixelin];
		ipixelin++;
		ipixelout++;

		/* Count length of same pixel */
		run_length = 0;
		if (ipixelin == 1592)
            ipixelin = 1592;
		while ( (ipixelin < widthin) && (buf_in[ipixelin] == last_pix) )
		{
			ipixelin++;
			ipixelout++;
			run_length++;
		}

		/* Extend, like but faster (total time/2) than xout = round((double)ipixelin*widthout/widthin); */
        xout = ((uint32_t)((ipixelin<<1)+1)*widthout)/((uint32_t)widthin<<1);
        if (xout > ipixelout)
        {
            run_length += xout - ipixelout;
            ipixelout = xout;
        }

        /* write pixel*/
        ibuf += bsb_compress_nb(buf_out+ibuf,run_length,last_pix<<dec,max);
	}
	buf_out[ibuf++] = 0;
	return ibuf;
}

/* bsb uncompress number */

static uint16_t bsb_uncompress_nb(int typein,FILE *in, uint8_t *pixel, uint8_t decin, uint8_t maxin)
{
    uint8_t c;
    uint16_t count;

    c = fgetkapc(typein,in);

    count = (c & 0x7f);
    *pixel = count >> decin;
    count &= maxin;
    while (c & 0x80)
    {
        c = fgetkapc(typein,in);
        count = (count << 7) + (c & 0x7f);
    }
    return count+1;
}

/* read line bsb */

int bsb_uncompress_row(int typein, FILE *in, uint8_t *buf_out, uint16_t bits_in,uint16_t bits_out, uint16_t width)
{
	uint16_t    count;
	uint8_t     pixel;
	uint8_t     decin, maxin;
    uint16_t    xout = 0;

	decin = 7-bits_in;
	maxin = (1<<decin) - 1;

    /* read the line number */
    count = bsb_uncompress_nb(typein, in,&pixel,0,0x7F);

    /* no test count = line number */
    switch (bits_out)
    {
        case 1:
            while ( width )
            {
                count = bsb_uncompress_nb(typein,in,&pixel, decin,maxin);
                if (count > width) count = width;
                width -= count;
                while (count)
                {
                    buf_out[xout>>3] |= pixel<<(7-(xout&0x7));
                    xout++;
                    count--;
                }
            }
            break;
        case 4:
             while ( width )
             {
                count = bsb_uncompress_nb(typein,in,&pixel, decin,maxin);
                if (count > width) count = width;
                width -= count;
                while (count)
                {
                    buf_out[xout>>1] |= pixel<<(4-((xout&1)<<2));
                    xout++;
                    count--;
                }
            }
            break;
        case 8:
            while ( width )
            {
                count = bsb_uncompress_nb(typein,in,&pixel, decin,maxin);
                if (count > width) count = width;
                width -= count;
                while (count)
                {
                    *buf_out++ = pixel;
                    count--;
                }
            }
            break;
    }
	/* read last byte (0) */
	getc(in);
	return 0;
}

static void read_line(uint8_t *in, uint16_t bits, int width, uint8_t *colors, histogram *hist, uint8_t *out)
{
    int i;
    uint8_t c = 0;

    switch (bits)
    {
        case 1:
            for (i=0;i<width;i++)
            {
                out[i] = colors[(in[i>>3] >> (7-(i & 0x7))) & 1];
            }
            return;
        case 4:
            for (i=0;i<width;i++)
            {
                c = in[i >> 1];
                out[i++] = colors[(c>>4) & 0xF];
                out[i] = colors[c & 0xF];
            }
            return;
        case 8:
            for (i=0;i<width;i++)
            {
                out[i] = colors[in[i]];
            }
            return;
        case 24:
        {
            Color32 cur, last;
            last.p = 0xFFFFFFFF;

            for (i=0;i<width;i++)
            {
                cur.p = ( *(uint32_t *)in & RGBMASK);
                if (last.p != cur.p)
                {
                    c = colors[HistGetColorNum(hist, cur)];
                    last = cur;
                }
                out[i] = c;
                in += 3;
            }
        }
    }
}


static uint32_t GetHistogram(FIBITMAP *bitmap,uint32_t bits,uint16_t width,uint16_t height,Color32 *pal,histogram *hist)
{
    uint32_t    i,j;
    Color32     cur;
    uint8_t     *line,k;
    histogram   *h = hist;

    switch (bits)
    {
        case 1:
            HistAddColor (hist, pal[0]);
            h = HistAddColor (hist, pal[1]);
            h->count++;
            break;

        case 4:
            for (i=0;i<height;i++)
            {
                line = FreeImage_GetScanLine(bitmap, i);
                cur.p = (width+1)>>1;
                for (j=0;j<cur.p;j++)
                {
                    k = (*line++)>>4;
                    if (h->color.p == pal[k].p)
                    {
                        h->count++;
                        continue;
                    }
                    h = HistAddColor (hist, pal[k]);
                }
                line = FreeImage_GetScanLine(bitmap, i);
                cur.p = width >> 1;
                for (j=0;j<cur.p;j++)
                {
                    k = (*line++)&0x0F;
                    if (h->color.p == pal[k].p)
                    {
                        h->count++;
                        continue;
                    }
                    h = HistAddColor (hist, pal[k]);
                }
            }
            break;

        case 8:
            for (i=0;i<height;i++)
            {
                line = FreeImage_GetScanLine(bitmap, i);
                for (j=0;j<width;j++)
                {
                    k = *line++ ;
                    if (h->color.p == pal[k].p)
                    {
                        h->count++;
                        continue;
                    }
                    h = HistAddColor (hist, pal[k]);
                }
            }
            break;

        case 24:
            for (i=0;i<height;i++)
            {
                line = FreeImage_GetScanLine(bitmap, i);
                for (j=0;j<width;j++)
                {
                    cur.p = *(uint32_t *)(line) & RGBMASK;
                    line += 3;
                    if (h->color.p == cur.p)
                    {
                        h->count++;
                        continue;
                    }
                    h = HistAddColor (hist, cur);
                }
            }
            break;
    }

    return HistColorsCount(hist);
}

static const char *colortype[] = {"RGB","DAY","DSK","NGT","NGR","GRY","PRC","PRG"};

int writeimgkap(FILE *out,FIBITMAP **bitmap,int optkap, int optcolors, Color32 *palette, uint16_t widthin,uint16_t heightin, uint16_t widthout, uint16_t heightout)
{
	uint16_t	 i,cpt,len,cur,last;
    int          num_colors;

	uint32_t    *index;
	uint8_t		*buf_in,*buf_out;
    int         bits_in,bits_out;

    uint8_t     colorskap[128];
    uint8_t     colors[256];
    histogram   hist[64];

    memset(hist,0,sizeof(hist));
    memset(colors,0,sizeof(colors));
    memset(colorskap,0,sizeof(colorskap));

    bits_in = FreeImage_GetBPP(*bitmap);

    /* make bitmap 24, accept only 1 4 8 24 bits */
    if ((bits_in > 8) && (bits_in != 24))
	{
        FIBITMAP *bitmap24;

	    bitmap24 = FreeImage_ConvertTo24Bits(*bitmap);
        if (bitmap24 == NULL)
        {
            fprintf(stderr,"ERROR - bitmap PPP is incorrect\n");
            return 2;
        }
        FreeImage_Unload(*bitmap);
        *bitmap = bitmap24;
        bits_in = 24;
    }

    /* read histogram */
    num_colors = GetHistogram(*bitmap,bits_in,widthin,heightin,(Color32 *)FreeImage_GetPalette(*bitmap),hist);
    if (!num_colors)
    {
        fprintf(stderr,"ERROR - No Colors or bitmap bits %d\n",num_colors);
        return 2;
    }

    /* reduce colors */
    num_colors = HistReduce(hist,num_colors,(optkap == NORMAL)?128:127);

    bits_out = ceil(log2(num_colors + ((optkap == NORMAL)?0:1)));

    /* if possible do not use colors 0 */
    len = ((1<<bits_out) > num_colors)?1:0;

    if (optcolors != COLOR_KAP )
        memset(palette,0,sizeof(Color32)*256*8);

    /* sort palette with 0 blank and get index */
    num_colors = HistGetPalette(colorskap,colors,palette,hist,num_colors,len,optcolors,(Color32 *)FreeImage_GetPalette(*bitmap),FreeImage_GetColorsUsed(*bitmap));
    if (!num_colors)
    {
        fprintf(stderr,"ERROR - internal GetPalette\n");
        return 2;
    }

    fputs("OST/1\r\n", out);
	fprintf(out, "IFM/%d\r\n",bits_out);

    /* Write RGB tags for colormap */
    for (cpt=0;cpt<8;cpt++)
    {
        if (! palette[256*cpt].q.rgbReserved) continue;
        for (i = len; i < num_colors; i++)
        {
                fprintf(out, "%s/%d,%d,%d,%d\r\n",
                    colortype[cpt],
                    i,
                    palette[256*cpt+colorskap[i]].q.rgbRed,
                    palette[256*cpt+colorskap[i]].q.rgbGreen,
                    palette[256*cpt+colorskap[i]].q.rgbBlue
                    );
        }
    }

	fputc(0x1a, out);
	fputc('\0', out);
	fputc(bits_out, out);



	buf_in = (uint8_t *)malloc((widthin + 4)/4*4);
	/* max space bsb encoded line can take */
	buf_out = (uint8_t *)malloc((widthout*2 + 8)/4*4);
	index = (uint32_t *)malloc((heightout + 1) * sizeof(uint32_t));
	if ((buf_in == NULL) || (buf_out == NULL) || (index == NULL))
	{
		fprintf(stderr,"ERROR - mem malloc\n");

        return 2;
 	}

    last = -1;
  	for (i = 0; i<heightout; i++)
	{
	    /* Extend on height */
	    cur = round((double)i * heightin / heightout);
        if (cur != last)
	    {
	        last = cur;
            read_line(FreeImage_GetScanLine(*bitmap, heightin-cur-1), bits_in, widthin, colors, hist,buf_in);
	    }

        /* Compress raster and write to BSB file */

        len = bsb_compress_row(buf_in, buf_out, bits_out, i, widthin,widthout);

		/* Record index table */
		index[i] = ftell(out);

		/* write data*/
        fwrite(buf_out, len, 1, out);
	}

	free(buf_in);
	free(buf_out);
    HistFree(hist);

	/* record start-of-index-table file tion in the index table */
	index[heightout] = ftell(out);

	bsb_write_index(out, heightout+1, index);

	free(index);
    return 0;
}


static int readkapheader(int typein,FILE *in,int typeout, FILE *out, char *date,char *title,int optcolor,int *widthout, int *heightout,  double *rx, double *ry, int *depth, RGBQUAD *palette)

{
	char    *s;
    int     result =  0;
	char    line[1024];

    /* lit l entete kap  y compris RGB et l'écrit dans un fichier sauf RGB*/
    *widthout = *heightout = 0;
    if (depth != NULL) *depth = 0;
    if (palette != NULL) memset(palette,0,sizeof(RGBQUAD)*128);

    while (fgetkaps(line,-1024,in,typein) > 0)
	{
		if (line[0] == 0x1a)
			break;
		if ((s = strstr(line, "RA=")))
		{
			unsigned x0, y0;

			/* Attempt to read old-style NOS (4 parameter) version of RA= */
			/* then fall back to newer 2-argument version */
			if ((sscanf(s,"RA=%d,%d,%d,%d",&x0,&y0,widthout,heightout)!=4) &&
				(sscanf(s,"RA=%d,%d", widthout, heightout) != 2))
			{
				result = 1;
				break;
			}
		}
        if (palette != NULL)
        {
            int index,r,g,b;
            RGBQUAD *p = NULL;

            if (sscanf(line, "RGB/%d,%d,%d,%d", &index, &r, &g, &b) == 4) p = palette;
            if (sscanf(line, "DAY/%d,%d,%d,%d", &index, &r, &g, &b) == 4) p = palette+256;
            if (sscanf(line, "DSK/%d,%d,%d,%d", &index, &r, &g, &b) == 4) p = palette+256*2;
            if (sscanf(line, "NGT/%d,%d,%d,%d", &index, &r, &g, &b) == 4) p = palette+256*3;
            if (sscanf(line, "NGR/%d,%d,%d,%d", &index, &r, &g, &b) == 4) p = palette+256*4;
            if (sscanf(line, "GRY/%d,%d,%d,%d", &index, &r, &g, &b) == 4) p = palette+256*5;
            if (sscanf(line, "PRC/%d,%d,%d,%d", &index, &r, &g, &b) == 4) p = palette+256*6;
            if (sscanf(line, "PRG/%d,%d,%d,%d", &index, &r, &g, &b) == 4) p = palette+256*7;

            if (p != NULL)
            {
                if ((unsigned)index > 127)
                {
                    result = 2;
                    break;
                }
                p[0].rgbReserved |= 8;
                p[index].rgbReserved |= 1;
                p[index].rgbRed = r;
                p[index].rgbGreen = g;
                p[index].rgbBlue = b;
            }
		}
		if (depth != NULL) sscanf(line, "IFM/%d", depth);

		if ( (rx != NULL) && (s = strstr(line, "DX=")) ) sscanf(s, "DX=%lf", rx);
		if ( (ry != NULL) && (s = strstr(line, "DY=")) ) sscanf(s, "DY=%lf", ry);

		if ((out != NULL) && (typeout != FIF_UNKNOWN))
		{
		    if (typeout != FIF_TXT)
		    {
                if (!strncmp(line,"RGB/",4)) continue;
                if (!strncmp(line,"DAY/",4)) continue;
                if (!strncmp(line,"DSK/",4)) continue;
                if (!strncmp(line,"NGT/",4)) continue;
                if (!strncmp(line,"NGR/",4)) continue;
                if (!strncmp(line,"GRY/",4)) continue;
                if (!strncmp(line,"PRC/",4)) continue;
                if (!strncmp(line,"PRG/",4)) continue;
		    }

            if ((*line == '!') && strstr(line,"M'dJ")) continue;
            if ((*line == '!') && strstr(line,"imgkap")) continue;

            if (!strncmp(line,"VER/",4) && ((optcolor == COLOR_IMG) || (optcolor == COLOR_MAP)))
            {
                fprintf(out,"VER/3.0\r\n");
                continue;
            }

            if (!strncmp(line,"OST/",4)) continue;
            if (!strncmp(line,"IFM/",4)) continue;

            if ((s = strstr(line, "ED=")) && (date != NULL))
            {
                *s = 0;
                while (*s && (*s != ',')) s++;
                fprintf(out,"%sED=%s%s\r\n",line,date,s);
                continue;

            }
            if ((s = strstr(line, "NA=")) && (title != NULL) && *title)
            {
                *s = 0;
                while (*s && (*s != ',')) s++;
                fprintf(out,"%sNA=%s%s\r\n",line,title,s);
                continue;
            }
            fprintf(out,"%s\r\n",line);
		}
	}
    return result;
}

int kaptoimg(int typein,char *filein,int typeheader,char *fileheader,int typeout, char *fileout, char *optionpal)

{
    int result;
    int cpt, width, height;
    int bits_in,bits_out;
    RGBQUAD palette[256*8];
    RGBQUAD *bitmappal;
    FIBITMAP *bitmap;
    FILE *in, *header = NULL ;
    header = NULL;
    double rx,ry;
    uint8_t *line = NULL;
    uint32_t *index;

    memset(palette,0,sizeof(palette));

    if (optionpal && !strcasecmp(optionpal,"ALL") && (typeout != (int)FIF_TIFF) && (typeout != (int)FIF_GIF))
	{
	    typeout = FIF_TIFF;

		fprintf(stderr,"ERROR - Palette ALL accepted with only TIF or GIF %s\n",fileout);
		return 2;
	}

    in = fopen(filein, "rb");
	if (in == NULL)
	{
		fprintf(stderr,"ERROR - Can't open KAP file %s\n",filein);
		return 2;
	}
    if (fileheader != NULL)
    {
        header = fopen(fileheader, "wb");
        if (header == NULL)
        {
            fprintf(stderr,"ERROR - Can't create header KAP file %s\n",fileheader);
            fclose(in);
            return 2;
        }
    }
    if (typeheader == FIF_KAP) typeheader = FIF_TXT;
    result = readkapheader(typein,in,typeheader,header,NULL,NULL,COLOR_NONE,&width,&height,&rx,&ry,&bits_in,palette);
    if (header != NULL) fclose(header);
    if (result)
    {

        fprintf(stderr,"ERROR - Invalid Header file %s\n",fileheader);
        fclose(in);
        return result;
    }
    if ((fileout == NULL) || !*fileout) return 0;

    bits_out = bits_in;
    if (bits_in > 1)
    {
        if (bits_in > 4)  bits_out = 8;
        else bits_out = 4;
    }

    if ((typeout != (int)FIF_TIFF) && (typeout != (int)FIF_ICO) && (typeout != (int)FIF_PNG) && (typeout != (int)FIF_BMP))
        bits_out = 8;

    index = bsb_read_index(typein,in,height);
    if (index == NULL)
    {
 		fprintf(stderr,"ERROR - Invalid index table in %s\n",fileheader);
        fclose(in);
        return 3;
    }

    /* Create bitmap */

    bitmap = FreeImage_AllocateEx(width, height, bits_out,palette,FI_COLOR_IS_RGB_COLOR,palette,0,0,0);
    bitmappal = FreeImage_GetPalette(bitmap);

/* a revoir
    FreeImage_SetDotsPerMeterX(bitmap,rx);
    FreeImage_SetDotsPerMeterY(bitmap,ry);
*/

    /* uncompress and write kap image into bitmap */
    for (cpt=0;cpt<height;cpt++)
    {
        fseek(in,index[cpt],SEEK_SET);
        line = FreeImage_GetScanLine(bitmap,height-cpt-1);
        bsb_uncompress_row (typein,in,line,bits_in,bits_out,width);
    }

    free(index);
    fclose(in);

    cpt = 0;
    if (optionpal)
    {
        cpt = -2;
        if (!strcasecmp(optionpal,"ALL")) cpt = -1;
        if (!strcasecmp(optionpal,"RGB")) cpt = 0;
        if (!strcasecmp(optionpal,"DAY")) cpt = 1;
        if (!strcasecmp(optionpal,"DSK")) cpt = 2;
        if (!strcasecmp(optionpal,"NGT")) cpt = 3;
        if (!strcasecmp(optionpal,"NGR")) cpt = 4;
        if (!strcasecmp(optionpal,"GRY")) cpt = 5;
        if (!strcasecmp(optionpal,"PRC")) cpt = 6;
        if (!strcasecmp(optionpal,"PRG")) cpt = 7;
        if (cpt == -2)
        {
            fprintf(stderr,"ERROR - Palette %s not exist in %s\n",optionpal,filein);
            FreeImage_Unload(bitmap);
            return 2;
        }
    }
    if (cpt >= 0)
    {
        if (cpt > 0)
        {
            RGBQUAD *pal = palette + 256*cpt;
            if (!pal->rgbReserved)
            {
                fprintf(stderr,"ERROR - Palette %s not exist in %s\n",optionpal,filein);
                FreeImage_Unload(bitmap);
                return 2;
            }
            for (result=0;result<256;result++) pal[result].rgbReserved = 0;
            memcpy(bitmappal,pal,sizeof(RGBQUAD)*256);
        }

        result = FreeImage_Save((FREE_IMAGE_FORMAT)typeout,bitmap,fileout,0);
    }
    else
    {
        FIMULTIBITMAP *multi;
        RGBQUAD *pal;

        multi = FreeImage_OpenMultiBitmap((FREE_IMAGE_FORMAT)typeout,fileout,TRUE,FALSE,TRUE,0);
        if (multi == NULL)
        {
            fprintf(stderr,"ERROR - Alloc multi bitmap\n");
            FreeImage_Unload(bitmap);
            return 2;
        }
        for (cpt=0;cpt<8;cpt++)
        {
            pal = palette + 256*cpt;
            if (pal->rgbReserved)
            {
                for (result=0;result<256;result++) pal[result].rgbReserved = 0;
                memcpy(bitmappal,pal,sizeof(RGBQUAD)*256);
                FreeImage_AppendPage(multi,bitmap);
            }
        }
        FreeImage_CloseMultiBitmap(multi,0);
        result = 1;
    }

    FreeImage_Unload(bitmap);

    return !result;
}


int imgheadertokap(int typein,char *filein,int typeheader, int optkap, int color, char *title, char *fileheader,char *fileout)
{
    int         widthin,heightin,widthout,heightout;
    int         bits_in,bits_out;
    int         result;
    RGBQUAD     palette[256*8];
    FIBITMAP    *bitmap = 0;
	FILE		*out;
    FILE        *header;
    char        datej[20];


    memset(palette,0,sizeof(palette));
    widthin = heightin = 0;

    /* Read image file */
    if (typein != FIF_KAP)
    {
        bitmap = FreeImage_Load((FREE_IMAGE_FORMAT)typein,filein,BMP_DEFAULT);
        if (bitmap == NULL)
        {
            fprintf(stderr, "ERROR - Could not open or error in image file\"%s\"\n", filein);
            return 2;
        }
        widthin = FreeImage_GetWidth(bitmap);
        heightin = FreeImage_GetHeight(bitmap);
        if (!widthin || !heightin)
        {
            fprintf(stderr, "ERROR - Invalid image size (width=%d,height=%d)\n", widthin, heightin);
            FreeImage_Unload(bitmap);
            return 2;
        }
    }

	out = fopen(fileout, "wb");
	if (out == NULL)
	{
		fprintf(stderr,"ERROR - Can't open KAP file %s\n",fileout);
		if (bitmap) FreeImage_Unload(bitmap);
		return 2;
	}

    /* Read date */
    {
        time_t      t;
        struct tm   *date;

        time(&t) ;
        date = localtime(&t);
        strftime(datej, sizeof(datej), "%d/%m/%Y",date);
    }

	header = fopen(fileheader, "rb");
	if (header == NULL)
	{
		fprintf(stderr,"ERROR - Can't open Header file %s\n",fileheader);
		if (bitmap) FreeImage_Unload(bitmap);
		fclose(out);
		return 2;
	}

    result = 1;
    if ((typeheader == FIF_TXT) || (typeheader == FIF_KAP))
    {
        /* Header comment file outut */
        char *s;

        for (s=filein+strlen(filein)-1;s>filein;s--)
            if ((*s == '\\') || (*s =='/'))
            {
                s++;
                break;
            }

        fprintf(out,"! 2011 imgkap %s - at %s from %.35s\r\n", VERS,datej,s);

        result = readkapheader(typeheader,header,FIF_KAP,out,datej,title,color, &widthout, &heightout,NULL,NULL,&bits_in,palette);
    }

    if (result)
    {
		fprintf(stderr,"ERROR - Invalid Header type %s\n",fileheader);
		if (bitmap) FreeImage_Unload(bitmap);
        fclose(header);
        fclose(out);
        return 2;
    }

    if (typein == FIF_KAP)
    {
        int cpt;
        uint8_t *line = NULL;
        uint32_t *index;

        widthin = widthout;
        heightin = heightout;
        bits_out = bits_in;
        if (bits_in > 1)
        {
            if (bits_in > 4)  bits_out = 8;
            else bits_out = 4;
        }

        index = bsb_read_index(typein,header,heightin);
        if (index == NULL)
        {
            fprintf(stderr,"ERROR - Invalid index table in %s\n",fileheader);
            fclose(header);
            fclose(out);
            return 3;
        }

        /* Create bitmap */
        bitmap = FreeImage_AllocateEx(widthin, heightin, bits_out,palette,FI_COLOR_IS_RGB_COLOR,palette,0,0,0);

        /* uncompress and write kap image into bitmap */
        for (cpt=0;cpt<heightin;cpt++)
        {
            fseek(header,index[cpt],SEEK_SET);
            line = FreeImage_GetScanLine(bitmap,heightin-cpt-1);
            bsb_uncompress_row (typein,header,line,bits_in,bits_out,widthin);
        }

        free(index);
    }
    fclose(header);

	if ((widthin > widthout) || (heightin > heightout))
	{
		fprintf(stderr, "ERROR - Image input is greater than outpout width=%d -> %d,height=%d -> %d \n", widthin,widthout, heightin,heightout);
		FreeImage_Unload(bitmap);
        fclose(out);
		return 2;
	}

	if (((widthout*10/widthin) > 11) || ((heightout*10/heightin) > 11))
	{
		fprintf(stderr, "ERROR - Image input is too smaller than outpout width=%d -> %d,height=%d -> %d \n", widthin,widthout, heightin,heightout);
		FreeImage_Unload(bitmap);
        fclose(out);
		return 2;
	}

    result = writeimgkap(out,&bitmap,optkap,color,(Color32 *)palette,widthin,heightin,widthout,heightout);

    FreeImage_Unload(bitmap);
    fclose(out);

    return result;
}

int imgtokap(int typein,char *filein, double lat0, double lon0, double lat1, double lon1,int optkap, int color, char *title,int units, char *sd,char *fileout)
{
    uint16_t    dpi,widthout,heightout;
 	uint32_t    widthin,heightin;
	double      scale;
	double      lx,ly,dx,dy ;
    char        datej[20];
    int         result;
    const char  *sunits;
    FIBITMAP    *bitmap;
    FREE_IMAGE_TYPE type;
    RGBQUAD     palette[256*8];

	FILE		*out;

    sunits = "METERS";
    if (units != METTERS) sunits = "FATHOMS";

    /* get latitude and longitude  */

    if (abs((int)lat0) > 85) return 1;
    if (abs((int)lon0) > 180) return 1;
    if (abs((int)lat1) > 85) return 1;
    if (abs((int)lon1) > 180) return 1;


    memset(palette,0,sizeof(palette));

    /* Read image file */
    bitmap = FreeImage_Load((FREE_IMAGE_FORMAT)typein,filein,BMP_DEFAULT);
	if (bitmap == NULL)
	{
		fprintf(stderr, "ERROR - Could not open or error in image file\"%s\"\n", filein);
		return 2;
	}

	type = FreeImage_GetImageType(bitmap);
	if (type != FIT_BITMAP)
	{
		fprintf(stderr, "ERROR - Image is not a bitmap file\"%s\"\n", filein);
		FreeImage_Unload(bitmap);
		return 2;
	}

	widthin = FreeImage_GetWidth(bitmap);
	heightin = FreeImage_GetHeight(bitmap);
	if (!widthin || !heightin)
	{
		fprintf(stderr, "ERROR - Invalid image size (width=%d,height=%d)\n", widthin, heightin);
		FreeImage_Unload(bitmap);
		return 2;
	}

	out = fopen(fileout, "wb");
	if (! out)
	{
		fprintf(stderr,"ERROR - Can't open KAP file %s\n",fileout);
		FreeImage_Unload(bitmap);
		return 2;
	};

    /* Header comment file outut */

    /* Read date */
    {
        time_t      t;
        struct tm   *date;

        time(&t) ;
        date = localtime(&t);
        strftime(datej, sizeof(datej), "%d/%m/%Y",date);
    }

    /* Header comment file outut */
    fprintf(out,"! 2011 imgkap %s file generator by M'dJ\r\n", VERS);
    fprintf(out,"! Map generated not for navigation created at %s\r\n",datej);

    /* calculate size */
    dpi = 254;
    lx = lontox(lon1)-lontox(lon0);
    if (lx < 0) lx = -lx;
    ly = lattoy_WS84(lat0)-lattoy_WS84(lat1);
    if (ly < 0) ly = -ly;

    /* calculate extend widthout heightout */
    dx = heightin * lx / ly - widthin;
    dy = widthin * ly / lx - heightin;

    widthout = widthin ;
    if (dy < 0) widthout = (int)round(widthin + dx) ;
    heightout = (int)round(widthout * ly / lx) ;

    fprintf(out,"! Extend widthin %d heightin %d to widthout %d heightout %d\r\n",
            widthin,heightin,widthout,heightout);

    scale = (1-(widthin/lx) / (heightin/ly)) *100;
    if ((scale > 5) || (scale < -5))
    {
   		fprintf(stderr,"ERROR - size of image is not correct\n");
        fprintf(stderr,"\tExtend widthin %d heightin %d to widthout %d heightout %d\n",
            widthin,heightin,widthout,heightout);
        FreeImage_Unload(bitmap);
        fclose(out);
		return 2;
    }

    /* calculate resolution en size in meters */

    dx = postod((lat0+lat1)/2,lon0,(lat0+lat1)/2,lon1);
    dy = postod(lat0,lon0,lat1,lon0);
    fprintf(out,"! Size in milles %.2f x %.2f\r\n",dx,dy) ;

    scale = round(dy*18520000.0*dpi/(heightout*254));

    if (units == METTERS)
    {
        dx = dx*1852.0/(double)widthout;
        dy = dy*1852.0/(double)heightout;
    }
    else
    {
        dx = dx*1157500./((double)widthout*1143.);
        dy = dy*1157500./((double)heightout*1143.);
    }

    fprintf(out,"! Resolution units %s - %.2fx%.2f -> %.0f at %d dpi\r\n", sunits, dx,dy,scale,dpi) ;


    /* Write KAP header */
    if (color == COLOR_NONE)
        fputs("VER/2.0\r\n", out);
    else
        fputs("VER/3.0\r\n", out);

    fprintf(out,"CED/SE=1,RE=1,ED=%s\r\n",datej);

    /* write filename and date*/
    {
        char *s;

        if (title == NULL)
        {
            s = fileout + strlen(fileout) -1;
            while ((s > fileout) && (*s != '.')) s--;
            if (s > fileout) *s = 0;
            while ((s > fileout) && (*s != '\\') && (*s != '/')) s--;
            if (s > fileout) s++;

            title = s;
        }

        fprintf(out,"BSB/NA=%.70s\r\n",title);
    }

    fprintf(out,"    NU=UNKNOWN,RA=%d,%d,DU=%d\r\n",widthout,heightout,dpi);
    fprintf(out,"KNP/SC=%0.f,GD=WGS 84,PR=MERCATOR,PP=%.2f\r\n", scale,0.0);
    fputs("    PI=UNKNOWN,SP=UNKNOWN,SK=0.0,TA=90\r\n", out);
    fprintf(out,"    UN=%s,SD=%s,DX=%.2f,DY=%.2f\r\n", sunits, sd,dx,dy);

    fprintf(out,"REF/1,%u,%u,%f,%f\r\n",0,0,lat0,lon0);
    fprintf(out,"REF/2,%u,%u,%f,%f\r\n",widthout-1,0,lat0,lon1);
    fprintf(out,"REF/3,%u,%u,%f,%f\r\n",widthout-1,heightout-1,lat1,lon1);
    fprintf(out,"REF/4,%u,%u,%f,%f\r\n",0,heightout-1,lat1,lon0);
    fprintf(out,"PLY/1,%f,%f\r\n",lat0,lon0);
    fprintf(out,"PLY/2,%f,%f\r\n",lat0,lon1);
    fprintf(out,"PLY/3,%f,%f\r\n",lat1,lon1);
    fprintf(out,"PLY/4,%f,%f\r\n",lat1,lon0);

    fprintf(out,"DTM/%.6f,%.6f\r\n", 0.0, 0.0);

    result = writeimgkap(out,&bitmap,optkap,color,(Color32 *)palette,widthin,heightin,widthout,heightout);
    FreeImage_Unload(bitmap);
    fclose(out);

    return result;
}

typedef struct sxml
{
    struct sxml *child;
    struct sxml *next;
    char tag[1];
} mxml;

static int mxmlreadtag(FILE *in, char *buftag)
{
    int c;
    int end = 0;
    int endtag = 0;
    int i = 0;

    while ((c = getc(in)) != EOF)
        if (strchr(" \t\r\n",c) == NULL) break;

    if (c == EOF) return -1;

    if (c == '<')
    {
        endtag = 1;
        c = getc(in);
        if (strchr("?!/",c) != NULL)
        {
            end = 1;
            c = getc(in);
        }
    }

    if (c == EOF) return -1;

    do
    {
        if (endtag && (c == '>')) break;
        else if (c == '<')
        {
            ungetc(c,in);
            break;
        }
        buftag[i++] = c;

        if (i > 1024)
            return -1;

    } while ((c = getc(in)) != EOF) ;

    while ((i > 0) && (strchr(" \t\r\n",buftag[i-1]) != NULL)) i--;

    buftag[i] = 0;

    if (end) return 1;
    if (endtag) return 2;
    return 0;
}

static int mistag(char *tag, const char *s)
{
    while (*s)
    {
        if (!*tag || (*s != *tag)) return 0;
        s++;
        tag++;
    }
    if (!*tag || (strchr(" \t\r\n",*tag) != NULL)) return 1;
    return 0;
}

static mxml *mxmlread(FILE *in, mxml *parent, char *buftag)
{
    int r;
    mxml *x , *cur , *first;

    x = cur = first = 0 ;

    while ((r = mxmlreadtag(in,buftag)) >= 0)
    {
        if (parent && mistag(parent->tag,buftag)) return first;

        x = (mxml *)myalloc(sizeof(mxml)+strlen(buftag)+1);
        if (x == NULL)
        {
            fprintf(stderr,"ERROR - Intern malloc\n");
            return first;
        }
        if (!first) first = x;
        if (cur) cur->next = x;
        cur = x;

        x->child = 0;
        x->next = 0;
        strcpy(x->tag,buftag);

        if (!r) break;
        if (r > 1) x->child = mxmlread(in,x,buftag);
    }
    return first;
}


static mxml *mxmlfindtag(mxml *first,const char *tag)
{
    while (first)
    {
        if (mistag(first->tag,tag)) break;
        first = first->next;
    }
    return first;
}

#define mxmlfree(x) myfree()

static int readkml(char *filein,double *lat0, double *lon0, double *lat1, double *lon1, char *title)
{
    int         result;
    mxml        *kml,*ground,*cur;
    FILE		*in;
    char        *s;
    char        buftag[1024];

	in = fopen(filein, "rb");
	if (in == NULL)
	{
		fprintf(stderr,"ERROR - Can't open KML file %s\n",filein);
		return 2;
	}

    if (*filein)
	{
        s = filein + strlen(filein) - 1;
	    while ((s >= filein) && (strchr("/\\",*s) == NULL)) s--;
 	    s[1] = 0;
	}

    kml = mxmlread(in,0,buftag);
    fclose(in);

    if (kml == NULL)
    {
		fprintf(stderr,"ERROR - Not XML KML file %s\n",filein);
		return 2;
    }

    ground = mxmlfindtag(kml,"kml");

    result = 2;
    while (ground)
    {
        ground = mxmlfindtag(ground->child,"GroundOverlay");
        if (!ground || ground->next)
        {
            fprintf(stderr,"ERROR - KML no GroundOverlay or more one\n");
            break;
        }
        cur = mxmlfindtag(ground->child,"name");
        if (!cur || !cur->child)
        {
            fprintf(stderr,"ERROR - KML no Name\n");
            break;
        }
        if (!*title)
            strcpy(title,cur->child->tag);

        cur = mxmlfindtag(ground->child,"Icon");
        if (!cur || !cur->child)
        {
            fprintf(stderr,"ERROR - KML no Icon\n");
            break;
        }
        cur = mxmlfindtag(cur->child,"href");
        if (!cur || !cur->child)
        {
            fprintf(stderr,"ERROR - KML no href\n");
            break;
        }
        strcat(filein,cur->child->tag);

#if (defined(_WIN32) || defined(__WIN32__))
        s = filein + strlen(filein);
        while (*s)
        {
            if (*s == '/') *s = '\\';
            s++;
        }
#endif

        cur = mxmlfindtag(ground->child,"LatLonBox");
        if (!cur || !cur->child)
        {
            fprintf(stderr,"ERROR - KML no LatLonBox\n");
            break;
        }
        result = 3;
        ground = cur->child;

        cur = mxmlfindtag(ground,"rotation");
        if (cur && cur->child)
        {
            *lat0 = strtod(cur->child->tag,&s);
            if (*s || (*lat0 > 0.5))
            {
                result = 4;
                fprintf(stderr,"ERROR - KML rotation is not accepted\n");
                break;
            }
        }
        cur = mxmlfindtag(ground,"north");
        if (!cur || !cur->child) break;
        *lat0 = strtod(cur->child->tag,&s);
        if (*s) break;

        cur = mxmlfindtag(ground,"south");
        if (!cur || !cur->child) break;
        *lat1 = strtod(cur->child->tag,&s);
        if (*s) break;

        cur = mxmlfindtag(ground,"west");
        if (!cur || !cur->child) break;
        *lon0 = strtod(cur->child->tag,&s);
        if (*s) break;

        cur = mxmlfindtag(ground,"east");
        if (!cur || !cur->child) break;
        *lon1 = strtod(cur->child->tag,&s);
        if (*s) break;

        result = 0;
        break;
    }

    mxmlfree(kml);

    if (result == 3) fprintf(stderr,"ERROR - KML no Lat Lon\n");
    return result;
}

#ifndef LIBIMGKAP

inline double mystrtod(char *s, char **end)
{
    double d = 0, r = 1;

    while ((*s >= '0') && (*s <= '9')) {d = d*10 + (*s-'0'); s++;}
    if ((*s == '.') || (*s == ','))
    {
        s++;
        while ((*s >= '0') && (*s <= '9')) { r /= 10; d += r * (*s-'0'); s++;}
    }
    *end = s;
    return d;
}


double strtopos(char *s, char **end)
{
    int     sign = 1;
    double  degree = 0;
    double  minute = 0;
    double  second = 0;

    char c;

    *end = s;

   /* eliminate space */
    while (*s == ' ') s++;

    /* begin read sign */
    c = toupper(*s);
    if ((c == '-') || (c == 'O') || (c == 'W') || (c == 'S')) {s++;sign = -1;}
    if ((c == '+') || (c == 'E') || (c == 'N')) s++;

    /* eliminate space */
    while (*s == ' ') s++;

    /* error */
    if (!*s) return 0;

    /* read degree */
    degree = mystrtod(s,&s);

    /* eliminate space and degree */
    while ((*s == ' ') || (*s == 'd') || (*s < 0)) s++;

    /* read minute */
    minute = mystrtod(s,&s);

    /* eliminate space and minute separator */
    while ((*s == ' ') || (*s == 'm') || (*s == 'n') || (*s == '\'')) s++;

    /* read second */
    second = mystrtod(s,&s);

    /* eliminate space and second separator*/
    while ((*s == ' ') || (*s == '\'') || (*s == '\"') || (*s == 's')) s++;

    /* end read sign */
    c = toupper(*s);
    if ((c == '-') || (c == 'O') || (c == 'W') || (c == 'S')) {s++;sign = -1;}
    if ((c == '+') || (c == 'E') || (c == 'N')) s++;

   /* eliminate space */
    while (*s == ' ') s++;

    *end = s;
    return sign * (degree + ((minute + (second/60.))/60.));
}

static void makefileout(char *fileout, const char *filein)

{
    char *s;

    strcpy(fileout,filein);

    s = fileout + strlen(fileout)-1;
    while ((s > fileout) && (*s != '.')) s--;

    if (s > fileout) strcpy(s,".kap");
}


/* Main programme */

int main (int argc, char *argv[])
{
    int     result = 0;
    char    filein[1024];
    char    *fileheader = NULL;
 	char    fileout[1024];
 	int     typein, typeheader,typeout;
    char    *optionsd ;
    int     optionunits = METTERS;
    int     optionkap = NORMAL;
    int     optcolor;
    char    *optionpal ;
    char    optiontitle[256];
	double  lat0,lon0,lat1,lon1;
    double  l;

    optionsd = (char *)"UNKNOWN" ;
    optionpal = NULL;

    typein = typeheader = typeout = FIF_UNKNOWN;
    lat0 = lat1 = lon0 = lon1 = HUGE_VAL;
    *filein = *fileout = *optiontitle = 0;

    while (--argc)
    {
        argv++;
        if (*argv == NULL) break;
        if (((*argv)[0] == '-') && ((*argv)[2] == 0))
        {
            /* options */
            char c = toupper((*argv)[1]);
            if (c == 'N')
            {
                optionkap = OLDKAP;
                continue;
            }
            if (c == 'F')
            {
                optionunits = FATHOMS;
                continue;
            }
            if (c == 'S')
            {
                if (argc > 1) optionsd = argv[1];
                argc--;
                argv++;
                continue;
            }
            if (c == 'T')
            {
                if (argc > 1) strcpy(optiontitle,argv[1]);
                argc--;
                argv++;
                continue;
            }
            if (c == 'P')
            {
                if (argc > 1) optionpal = argv[1];

                argc--;
                argv++;
                continue;
            }
            if ((c < '0') || (c > '9'))
            {
                result = 1;
                break;
            }
        }
        if (!*filein)
        {
            strcpy(filein,*argv);
            continue;
        }
        if (fileheader == NULL)
        {
            char *s;
            /* if numeric */
            l = strtopos(*argv,&s);
            if (!*s)
            {
                if (lat0 == HUGE_VAL)
                {
                    lat0 = l;
                    continue;
                }
                if (lon0 == HUGE_VAL)
                {
                    lon0 = l;
                    continue;
                }
                if (lat1 == HUGE_VAL)
                {
                    lat1 = l;
                    continue;
                }
                if (lon1 == HUGE_VAL)
                {
                    lon1 = l;
                    continue;
                }
                result = 1;
                break;
            }
            fileheader = *argv;
            continue;
        }
        if (!*fileout)
        {
            strcpy(fileout,*argv);
            continue;
        }
        result = 1;
        break;
    }
    if (!*filein) result = 1;

    if (!result)
    {
        FreeImage_Initialise(0);

        typein = findfiletype(filein);
        if (typein == FIF_UNKNOWN) typein = (int)FreeImage_GetFileType(filein,0);

        switch (typein)
        {
            case FIF_KML :
                if (fileheader != NULL) strcpy(fileout,fileheader);

                result = readkml(filein,&lat0,&lon0,&lat1,&lon1,optiontitle);
                if (result) break;

                if (!*fileout) makefileout(fileout,filein);

                typein = (int)FreeImage_GetFileType(filein,0);
                optcolor = COLOR_NONE;
                if (optionpal) optcolor = findoptlist(listoptcolor,optionpal);
                result = imgtokap(typein,filein,lat0,lon0,lat1,lon1,optionkap,optcolor,optiontitle,optionunits,optionsd,fileout);
                break;

            case FIF_KAP :
            case FIF_NO1 :
                if (fileheader == NULL)
                {
                    result = 1;
                    break;
                }
                typeheader = findfiletype(fileheader);
                if (typeheader == FIF_UNKNOWN)
                {
                    if (*fileout)
                    {
                        result = 1;
                        break;
                    }
                    typeout = typeheader;
                    typeheader = FIF_UNKNOWN;
                    strcpy(fileout,fileheader);
                    fileheader = NULL;
                }
                if (*fileout)
                {
                    typeout = FreeImage_GetFIFFromFilename(fileout);
                    if (typeout == FIF_UNKNOWN)
                    {
                        result = 1;
                        break;
                    }
                }
                if (!*fileout && (typeheader == FIF_KAP))
                {
                    optcolor = COLOR_KAP;
                    if (optionpal) optcolor = findoptlist(listoptcolor,optionpal);

                    result = imgheadertokap(typein,filein,typein,optionkap,optcolor,optiontitle,filein,fileheader);
                    break;
                }
                result = kaptoimg(typein,filein,typeheader,fileheader,typeout,fileout,optionpal);
                break;

            case (int)FIF_UNKNOWN:
                fprintf(stderr, "ERROR - Could not open or error in image file\"%s\"\n", filein);
                result = 2;
                break;
            default:
                    if ((lon1 != HUGE_VAL) && (fileheader != NULL))
                    {
                        strcpy(fileout,fileheader);
                        fileheader = NULL;
                    }

                    optcolor = COLOR_NONE;
                    if (optionpal) optcolor = findoptlist(listoptcolor,optionpal);

                    if (!*fileout) makefileout(fileout,filein);
                    if (fileheader != NULL)
                    {
                        typeheader = findfiletype(fileheader);
                        result = imgheadertokap(typein,filein,typeheader,optionkap,optcolor,optiontitle,fileheader,fileout);
                        break;
                    }
                    if (lon1 == HUGE_VAL)
                    {
                        result = 1;
                        break;
                    }
                    result = imgtokap(typein,filein,lat0,lon0,lat1,lon1,optionkap,optcolor,optiontitle,optionunits,optionsd,fileout);
                break;
        }
        FreeImage_DeInitialise();
    }

    // si kap et fileheader avec ou sans  file out lire kap - > image ou header et image
    // sinon lire image et header ou image et position -> kap

    if (result == 1)
	{
		fprintf(stderr, "ERROR - Usage:\\imgkap [option] [inputfile] [lat0 lon0 lat1 lon1 | headerfile] [outputfile]\n");
		fprintf(stderr, "\nimgkap Version %s by M'dJ\n",VERS);
		fprintf(stderr, "\nConvert kap to img :\n");
        fprintf(stderr,  "\timgkap mykap.kap myimg.png : convert mykap into myimg.png\n" );
        fprintf(stderr,  "\timgkap mykap.kap mheader.kap myimg.png : convert mykap into header myheader (only text header kap file) and myimg.png\n" );
		fprintf(stderr, "\nConvert img to kap : \n");
        fprintf(stderr,  "\timgkap myimg.png myheaderkap.kap : convert myimg.png into myimg.kap using myheader.kap for kap informations\n" );
        fprintf(stderr,  "\timgkap myimg.png myheaderkap.kap myresult.kap : convert myimg.png into myresult.kap using myheader.kap for kap informations\n" );
        fprintf(stderr,  "\timgkap mykap.png lat0 lon0 lat1 lon2 myresult.kap : convert myimg.png into myresult.kap using WGS84 positioning\n" );
        fprintf(stderr,  "\timgkap -s 'LOWEST LOW WATER' myimg.png lat0 lon0 lat1 lon2 -f : convert myimg.png into myimg.kap using WGS84 positioning and options\n" );
		fprintf(stderr, "\nConvert kml to kap : \n");
        fprintf(stderr,  "\timgkap mykml.kml : convert GroundOverlay mykml file into kap file using name and directory of image\n" );
        fprintf(stderr,  "\timgkap mykml.kml mykap.kap: convert GroundOverlay mykml into mykap file\n" );
		fprintf(stderr, "\nWGS84 positioning :\n");
		fprintf(stderr, "\tlat0 lon0 is a left,top point\n");
		fprintf(stderr, "\tlat1 lon1 is a right,bottom point\n");
		fprintf(stderr, "\tlat to be beetwen -85 and +85 degree\n");
		fprintf(stderr, "\tlon to be beetwen -180 and +180 degree\n");
		fprintf(stderr, "\t    different format are accepted : -1.22  1°10'20.123N  -1d22.123 ...\n");
		fprintf(stderr, "Options :\n");
        fprintf(stderr,  "\t-n  : Force compatibilty all KAP software, max 127 colors\n" );
        fprintf(stderr,  "\t-f  : fix units to FATHOMS\n" );
        fprintf(stderr,  "\t-s name : fix souding datum\n" );
        fprintf(stderr,  "\t-t title : change name of map\n" );
        fprintf(stderr,  "\t-p color : color of map\n" );
        fprintf(stderr,  "\t    - Kap to image color : ALL|RGB|DAY|DSK|NGT|NGR|GRY|PRC|PRG\n" );
        fprintf(stderr,  "\t\t   ALL generate multipage image, use only with GIF or TIF" );
        fprintf(stderr,  "\t    - image or Kap to Kap color :  NONE|KAP|MAP|IMG\n" );
        fprintf(stderr,  "\t\t   NONE use colors in image file, default\n" );
        fprintf(stderr,  "\t\t   KAP only width KAP or header file, use RGB tag in KAP file\n" );
        fprintf(stderr,  "\t\t   MAP generate DSK and NGB colors for map scan (< 64 colors) Black -> Gray, White -> Black\n" );
        fprintf(stderr,  "\t\t   IMG generate DSK and NGB colors for image (photo, satellite...)\n" );
        return 1;
	}
    if (result) fprintf(stderr,  "ERROR - imgkap return %d\n",result );
    return result;
}

#endif
