/* nxpm.c - load "normalized" XPM image
 * 
 *  Raster graphics library
 * 
 *  Copyright (c) 1997-2002 Alfredo K. Kojima
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *  
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *  
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <config.h>

/* AIX requires this to be the first thing in the file.  */
#ifdef __GNUC__
# define alloca __builtin_alloca
#else
# if HAVE_ALLOCA_H
#  include <alloca.h>
# else
#  ifdef _AIX
#   pragma alloca
#  else
#   ifndef alloca /* predefined by HP cc +Olibcalls */
char *alloca ();
#   endif
#  endif
# endif
#endif


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

#include "wraster.h"


/*
 * Restricted support for XPM images.
 * 
 * The images must be in the following "normalized" format:
 *
 *
 * line   	content
 * 1	        signature comment
 * 2		ignored ( normally "static char *xpm[] = {" )
 * 3		"width height color_count chars" where chars is 1 or 2
 * 4	        color definitions. Only c values with #rrggbb or #rrrrggggbbb
 *			format OR None
 * n		data
 *
 * - no comments or blank lines are allowed, except for the signature
 * - all lines must have at most 256 characters
 * - no white spaces allowed at left of each line
 */

#define LINEWIDTH 64

#ifndef USE_XPM


RImage*
RGetImageFromXPMData(RContext *context, char **data)
{
    RImage *image = NULL;
    unsigned char *color_table[4];
    unsigned short *symbol_table;
    unsigned char *r, *g, *b, *a;
    int i, j, k, line = 0;
    int transp;
    unsigned short color;
    int bsize;
    int w, h, ccount, csize;

    if (sscanf(data[line++], "%i %i %i %i", &w, &h, &ccount, &csize)!=4
	|| w <= 0 || h <= 0 || ccount <= 0 || csize <= 0)
	goto bad_format;
    
    if (csize!=1 && csize!=2) 
	goto bad_format;

    color_table[0] = alloca(ccount);
    color_table[1] = alloca(ccount);
    color_table[2] = alloca(ccount);
    color_table[3] = alloca(ccount);
    symbol_table = alloca(ccount * sizeof(unsigned short));
    
    bsize = csize * w + 16;

    if (!color_table[0] || !color_table[1] || !color_table[2] ||
	!color_table[3] || !symbol_table || !bsize) {
	RErrorCode = RERR_NOMEMORY;
	alloca(0);
	return NULL;
    }

    transp = 0;
    /* get color table */
    for (i=0; i<ccount; i++) {
	symbol_table[i] = data[line][0];
	if (csize==2)
	    symbol_table[i] |= data[line][1]<<8;

	j = csize;
	while (data[line][j]!='#' && data[line][j]!=0
	       && data[line][j]!='N') j++;

	if (data[line][j]=='#') {
	    unsigned int red, green, blue;
	    
	    k = 0;
	    j++;
	    while (data[line][j+k]!=0) k++;
	    if (k==6) {
		if (sscanf(&(data[line][j]), "%2x%2x%2x", &red, &green, &blue)!=3)
		    goto bad_format;
	    } else if (k==12) {		
		if (sscanf(&(data[line][j]), "%4x%4x%4x", &red, &green, &blue)!=3)
		    goto bad_format;
		red >>= 8;
		green >>= 8;
		blue >>= 8;
	    } else
		goto bad_format;
	    
	    color_table[0][i] = red;
	    color_table[1][i] = green;
	    color_table[2][i] = blue;
	    color_table[3][i] = 255;
	} else if (strncmp(&(data[line][j]), "None", 4)==0
		   || strncmp(&(data[line][j]), "none", 4)==0) {
	    color_table[3][i] = 0;
	    transp = 1;
	} else {
	    goto bad_format;
	}
	line++;
    }

    image = RCreateImage(w, h, transp);
    if (!image) {
	alloca(0);
	return NULL;
    }

    r = image->data;
    g = image->data+1;
    b = image->data+2;
    if (image->format == RRGBAFormat)
	a = image->data+3;
    else
	a = NULL;

    for (i=0; i<h; i++) {
	if (csize==1) {
	    for (j=0; j<w; j++) {
		color = data[line][j];
		
		for (k=0; k<ccount; k++) {
		    if (symbol_table[k] == color)
			break;
		}
		if (k==ccount)
		    k = 0;
		
		*r = color_table[0][k];
		*g = color_table[1][k];
		*b = color_table[2][k];
		if (a) {
		    *a = color_table[3][k];
		    r += 4; g += 4; b += 4; a += 4;
		} else {
		    r += 3; g += 3; b += 3;
		}
	    }
	} else {
	    for (j=0; j<w*2; j++) {
		color = data[line][j++];
		color |= data[line][j];
		
		for (k=0; k<ccount; k++) {
		    if (symbol_table[k] == color)
			break;
		}	
		if (k==ccount)
		    k = 0;
	
		*r = color_table[0][k];
		*g = color_table[1][k];
		*b = color_table[2][k];
		if (a) {
		    *a = color_table[3][k];
		    r += 4; g += 4; b += 4; a += 4;
		} else {
		    r += 3; g += 3; b += 3;
		}
	    }
	}
	line++;
    }

#ifdef C_ALLOCA
    alloca(0);
#endif
    return image;

  bad_format:
    RErrorCode = RERR_BADIMAGEFILE;
#ifdef C_ALLOCA
    alloca(0);
#endif
    if (image)
	RReleaseImage(image);
    return NULL;
}



RImage*
RLoadXPM(RContext *context, char *file, int index)
{
    RImage *image = NULL;
    char line[LINEWIDTH+1];
    char *buffer;
    unsigned char *color_table[4];
    unsigned short *symbol_table;
    unsigned char *r, *g, *b, *a;
    int i, j, k;
    int transp;
    unsigned short color;
    int bsize;
    int w, h, ccount, csize;
    FILE *f;
    
    f = fopen(file, "r");
    if (!f) {
	RErrorCode = RERR_OPEN;
	return NULL;
    }
    /* sig */
    if (!fgets(line, LINEWIDTH, f))
	goto bad_file;
    /* declaration */
    if (!fgets(line, LINEWIDTH, f))
	goto bad_file;

    /* data */
    if (!fgets(line, LINEWIDTH, f))
	goto bad_file;
    
    if (line[0]=='/')
	if (!fgets(line, LINEWIDTH, f))
	    goto bad_file;

    if (sscanf(line, "\"%i %i %i %i\"", &w, &h, &ccount, &csize)!=4
	|| w <= 0 || h <= 0 || ccount <= 0 || csize <= 0)
	goto bad_file;
    
    if (csize!=1 && csize!=2) 
	goto bad_format;

    color_table[0] = alloca(ccount);
    color_table[1] = alloca(ccount);
    color_table[2] = alloca(ccount);
    color_table[3] = alloca(ccount);
    symbol_table = alloca(ccount * sizeof(unsigned short));
    
    bsize = csize * w + 16;
    buffer = alloca(bsize);

    if (!color_table[0] || !color_table[1] || !color_table[2] ||
	!color_table[3] || !symbol_table || !bsize) {
	RErrorCode = RERR_NOMEMORY;
	fclose(f);
	alloca(0);
	return NULL;
    }

    transp = 0;
    /* get color table */
    for (i=0; i<ccount; i++) {
	if (!fgets(line, LINEWIDTH, f)) 
	    goto bad_file;
	if (line[0]=='/')
	    if (!fgets(line, LINEWIDTH, f))
		goto bad_file;

	symbol_table[i] = line[1];
	if (csize==2)
	    symbol_table[i] |= line[2]<<8;

	j = csize+1;
	while (line[j]!='#' && line[j]!='"' && line[j]!=0 && line[j]!='N') j++;

	if (line[j]=='#') {
	    unsigned int red, green, blue;
	    
	    k = 0;
	    j++;
	    while (line[j+k]!='"' && line[j+k]!=0) k++;
	    if (k==6) {
		if (sscanf(&(line[j]), "%2x%2x%2x", &red, &green, &blue)!=3)
		    goto bad_format;
	    } else if (k==12) {		
		if (sscanf(&(line[j]), "%4x%4x%4x", &red, &green, &blue)!=3)
		    goto bad_format;
		red >>= 8;
		green >>= 8;
		blue >>= 8;
	    } else
		goto bad_format;
	    
	    color_table[0][i] = red;
	    color_table[1][i] = green;
	    color_table[2][i] = blue;
	    color_table[3][i] = 255;
	} else if (strncmp(&(line[j]), "None", 4)==0
		   || strncmp(&(line[j]), "none", 4)==0) {
	    color_table[3][i] = 0;
	    transp = 1;
	} else {
	    goto bad_format;
	}
    }

    image = RCreateImage(w, h, transp);
    if (!image) {
	fclose(f);
	alloca(0);
	return NULL;
    }

    r = image->data;
    g = image->data+1;
    b = image->data+2;
    if (image->format == RRGBAFormat)
	a = image->data+3;
    else
	a = NULL;

    for (i=0; i<h; i++) {
	if (!fgets(buffer, bsize, f))
	    goto bad_file;
	if (buffer[0]=='/')
	    if (!fgets(buffer, bsize, f))
		goto bad_file;
	
	if (csize==1) {
	    for (j=1; j<=w; j++) {
		color = buffer[j];
		
		for (k=0; k<ccount; k++) {
		    if (symbol_table[k] == color)
			break;
		}
		if (k==ccount)
		    k = 0;
		
		*r = color_table[0][k];
		*g = color_table[1][k];
		*b = color_table[2][k];
                if (a) {
                    *a = color_table[3][k];
                    r += 4; g += 4; b += 4; a += 4;
                } else {
                    r += 3; g += 3; b += 3;
                }
	    }
	} else {
	    for (j=1; j<=w*2; j++) {
		color = buffer[j++];
		color |= buffer[j] << 8;
		
		for (k=0; k<ccount; k++) {
		    if (symbol_table[k] == color)
			break;
		}
		if (k==ccount) {
		    k = 0;
		}
	
		*r = color_table[0][k];
		*g = color_table[1][k];
		*b = color_table[2][k];
                if (a) {
                    *a = color_table[3][k];
                    r += 4; g += 4; b += 4; a += 4;
                } else {
                    r += 3; g += 3; b += 3;
                }
	    }
	}
    }

    fclose(f);
#ifdef C_ALLOCA
    alloca(0);
#endif
    return image;

  bad_format:
    RErrorCode = RERR_BADIMAGEFILE;
    fclose(f);
#ifdef C_ALLOCA
    alloca(0);
#endif
    if (image)
	RReleaseImage(image);
    return NULL;
    
  bad_file:
    RErrorCode = RERR_BADIMAGEFILE;
    fclose(f);
#ifdef C_ALLOCA
    alloca(0);
#endif
    if (image)
	RReleaseImage(image);
    return NULL;
}

#endif


typedef struct XPMColor {
    unsigned char red;
    unsigned char green;
    unsigned char blue;
    int index;
    struct XPMColor *next;
} XPMColor;



#define I2CHAR(i)	((i)<12 ? (i)+'0' : ((i)<38 ? (i)+'A'-12 : (i)+'a'-38))
#define CINDEX(xpmc)	(((unsigned)(xpmc)->red)<<16|((unsigned)(xpmc)->green)<<8|((unsigned)(xpmc)->blue))



static XPMColor*
lookfor(XPMColor *list, int index)
{
    if (!list)
	return NULL;

    for (; list!=NULL; list=list->next) {
	if (CINDEX(list) == index)
	    return list;
    }
    return NULL;
}

/*
 * Looks for the color in the colormap and inserts if it is not found.
 * 
 * list is a binary search list. The unbalancing problem is just ignored.
 * 
 * Returns False on error
 */
static Bool
addcolor(XPMColor **list, unsigned r, unsigned g, unsigned b, int *colors)
{
    XPMColor *tmpc;
    XPMColor *newc;
    int index;

    index = r<<16|g<<8|b;
    tmpc = *list;

    tmpc = lookfor(*list, index);

    if (tmpc)
	return True;

    newc = malloc(sizeof(XPMColor));

    if (!newc) {

	RErrorCode = RERR_NOMEMORY;

	return False;
    }

    newc->red = r;
    newc->green = g;
    newc->blue = b;
    newc->next = *list;
    *list = newc;

    (*colors)++;

    return True;
}


static char*
index2str(char *buffer, int index, int charsPerPixel)
{
    int i;

    for (i=0; i<charsPerPixel; i++) {
	buffer[i] = I2CHAR(index&63);
	index >>= 6;
    }
    buffer[i] = 0;
    
    return buffer;
}


static void
outputcolormap(FILE *file, XPMColor *colormap, int charsPerPixel)
{
    int index;
    char buf[128];

    if (!colormap)
	return;

    for (index=0; colormap!=NULL; colormap=colormap->next,index++) {
	colormap->index = index;
	fprintf(file, "\"%s c #%02x%02x%02x\",\n", 
		index2str(buf, index, charsPerPixel), colormap->red,
		colormap->green, colormap->blue);
    }
}


static void
freecolormap(XPMColor *colormap)
{
    XPMColor *tmp;
    
    while (colormap) {
	tmp = colormap->next;
	free(colormap);
	colormap = tmp;
    }
}



/* save routine is common to internal support and library support */
Bool 
RSaveXPM(RImage *image, char *filename)
{
    FILE *file;
    int x, y;
    int colorCount=0;
    int charsPerPixel;
    XPMColor *colormap = NULL;
    XPMColor *tmpc;
    int i;
    int ok = 0;
    unsigned char *r, *g, *b, *a;
    char transp[16];
    char buf[128];

    file = fopen(filename, "w+");
    if (!file) {
	RErrorCode = RERR_OPEN;
	return False;
    }

    fprintf(file, "/* XPM */\n");

    fprintf(file, "static char *image[] = {\n");

    r = image->data;
    g = image->data+1;
    b = image->data+2;
    if (image->format == RRGBAFormat)
	a = image->data+3;
    else
	a = NULL;

    /* first pass: make colormap for the image */
    if (a)
	colorCount = 1;
    for (y = 0; y < image->height; y++) {
	for (x = 0; x < image->width; x++) {
            if (!a || *a>127) {
		if (!addcolor(&colormap, *r, *g, *b, &colorCount)) {
		    goto uhoh;
                }
            }
            if (a) {
                r += 4; g += 4; b += 4; a += 4;
            } else {
                r += 3; g += 3; b += 3;
            }
	}
    }

    charsPerPixel = 1;
    while ((1 << charsPerPixel*6) < colorCount)
        charsPerPixel++;

    /* write header info */
    fprintf(file, "\"%i %i %i %i\",\n", image->width, image->height, 
	    colorCount, charsPerPixel);

    /* write colormap data */
    if (a) {
	for (i=0; i<charsPerPixel; i++)
	    transp[i] = ' ';
	transp[i] = 0;

	fprintf(file, "\"%s c None\",\n", transp);
    }
    
    i = 0;
    outputcolormap(file, colormap, charsPerPixel);


    r = image->data;
    g = image->data+1;
    b = image->data+2;
    if (image->format == RRGBAFormat)
	a = image->data+3;
    else
	a = NULL;

    /* write data */
    for (y = 0; y < image->height; y++) {

	fprintf(file, "\"");

	for (x = 0; x < image->width; x++) {

	    if (!a || *a>127) {
		tmpc = lookfor(colormap, (unsigned)*r<<16|(unsigned)*g<<8|(unsigned)*b);

		fprintf(file, index2str(buf, tmpc->index, charsPerPixel));
	    } else {
		fprintf(file, transp);
	    }

            if (a) {
                r += 4; g += 4; b += 4; a += 4;
            } else {
                r += 3; g += 3; b += 3;
            }
	}

	if (y < image->height-1)
	    fprintf(file, "\",\n");
	else
	    fprintf(file, "\"};\n");
    }

    ok = 1;
uhoh:
    errno = 0;
    fclose(file);
    if (ok && errno==ENOSPC) {
	RErrorCode = RERR_WRITE;
    }

    freecolormap(colormap);

    return ok ? True : False;
}


