/* +-------------------------------------------------------------------+ */
/* | This file is derived from                                         | */
/* | Xpaint's read/write routines, copyrighted                         | */
/* | by David Koblas (koblas@netcom.com),                              | */
/* | Torsten Martinsen (                                               | */
/* | Greg Roelofs (newt@pobox.com)		        	       | */
/* |								       | */
/* | Permission to use, copy, modify, and to distribute this software  | */
/* | and its documentation for any purpose is hereby granted without   | */
/* | fee, provided that the above copyright notice appear in all       | */
/* | copies and that both that copyright notice and this permission    | */
/* | notice appear in supporting documentation.	 There is no	       | */
/* | representations about the suitability of this software for	       | */
/* | any purpose.  this software is provided "as is" without express   | */
/* | or implied warranty.					       | */
/* |								       | */
/* +-------------------------------------------------------------------+ */

#include <stdio.h>
#include <stdlib.h>
#include <png.h>
#include <jpeglib.h>

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/xpm.h>

#include "xrmap.h"
#include "numdefs.h"

extern Display * dpy;
extern Visual *	visual;
extern Colormap cmap;
extern GC gc;

extern int scr;
extern int bigendian;
extern int color_depth;
extern int color_pad;
extern int runlevel;
extern int num_flag_types;
extern int brief_menu;
extern char *color_table;

extern int cmdwin_on;
extern Window mainwin, datawin, cmdwin;

extern char *error_msg;
extern char **msg;
extern char *flag_formats[];
extern char *flag_ordering;
extern char **flag_type;
extern char **flag_dir;
extern int flag_size;
extern int num_flag_dirs;
extern int num_flag_types;

int bytes_per_pixel;

/* Make a temporary file */

char * mk_tmp(char *suff)
{
   static char template[256];
   int fd;
   sprintf(template, "%s/xrmapXXXXXX", P_tmpdir);
   fd = mkstemp(template);
   if (fd == -1) return NULL;
   close(fd);
   unlink(template);
   strcat(template, suff);
   return template;
}

XImage *
ximage_rescale(XImage * image, int height)
{
     XImage * ximage;
     int i, ip, j, jp, k, b, bp, width;

     width = (int)
       (((double)image->width * (double) height / (double) image->height)+0.5);
     ximage = XCreateImage(dpy, visual, 
         DefaultDepth(dpy, scr), ZPixmap,0, NULL, 
         width, height, color_pad,0);
     XFlush(dpy);
     if (!ximage) return NULL;
     ximage->data = (char *) malloc(ximage->bytes_per_line 
                                       * ximage->height);
     if (!ximage->data) return NULL;
     for (j=0; j<height; j++) {
     jp = (j * image->height)/height;
     for (i=0; i<width; i++) {
        ip = (i * image->width)/width;
	b = i*bytes_per_pixel + j * ximage->bytes_per_line;
	bp = ip*bytes_per_pixel + jp*image->bytes_per_line;
        for (k=0; k<bytes_per_pixel; k++)
            ximage->data[b+k] = image->data[bp+k];
	}
     }
     XDestroyImage(image);
     return ximage;
}

/* PNG routines */

typedef struct _jmpbuf_wrapper {
  jmp_buf jmpbuf;
} jmpbuf_wrapper;

/* this can be shared between reading/writing code since they never overlap: */
static jmpbuf_wrapper jmpbuf_struct;

static void
png_error_handler (png_structp png_ptr, png_const_charp msg)
{
    jmpbuf_wrapper  *jmpbuf_ptr;

    /* this function, aside from the extra step of retrieving the "error
     * pointer" (below) and the fact that it exists within the application
     * rather than within libpng, is essentially identical to libpng's
     * default error handler.  The second point is critical:  since both
     * setjmp() and longjmp() are called from the same code, they are
     * guaranteed to have compatible notions of how big a jmp_buf is,
     * regardless of whether _BSD_SOURCE or anything else has (or has not)
     * been defined. */

    fprintf(stderr, "Fatal libpng error !!\n");
    fflush(stderr);

    jmpbuf_ptr = png_get_error_ptr(png_ptr);
    if (jmpbuf_ptr == NULL) {         /* we are completely hosed now */
        exit(99);
    }

    longjmp(jmpbuf_ptr->jmpbuf, 1);
}

void
fill_line(char *scan, char* c, int w, int wp)
{
unsigned char r, g, b;
int i, j, k;
  k = 0;
  if (color_depth>16) {
    if (bigendian)
       k = bytes_per_pixel - 3;
    for (i = 0; i < wp; i++) {
       if (i==0) j = 0 ; else j = 3 * ((i* (w-1))/(wp-1));
       if (bigendian) {
          c[k] = scan[j];
          c[k+1] = scan[j+1];
          c[k+2] = scan[j+2];
       } else {
          c[k] = scan[j+2];
          c[k+1] = scan[j+1];
          c[k+2] = scan[j];
       }
       k +=  bytes_per_pixel;
    }
  } else
  if (color_depth==16)
     for (i = 0; i < wp; i++) {
       if (i==0) j = 0 ; else j = 3 * ((i* (w-1))/(wp-1));
       r = scan[j];
       g = scan[j+1];
       b = scan[j+2];
       /* blue  c[k] = 31;  c[k+1] = 0;
          green c[k] = 224  (low weight) c[k+1] = 7 (high weight)
          red   c[k] = 0;   c[k+1] = 248; */
       if (bigendian) {
          c[k+1] = (((b&248)>>3) | ((g&28)<<3));
          c[k] = (((g&224)>>5) | (r&248));
       } else {
          c[k] = (((b&248)>>3) | ((g&28)<<3));
          c[k+1] = (((g&224)>>5) | (r&248));
       }
       k += 2;
     }
  else
  if (color_depth==15)
     for (i = 0; i < wp; i++) {
       if (i==0) j = 0 ; else j = 3 * ((i* (w-1))/(wp-1));
       r = scan[j];
       g = scan[j+1];
       b = scan[j+2];
       /* blue  c[k] = 31;  c[k+1] = 0;
          green c[k] = 224  (low weight) c[k+1] = 7 (high weight)
          red   c[k] = 0;   c[k+1] = 248; */
       if (bigendian) {
          c[k+1] = (b&248)>>3 | (g&56)<<2;
          c[k] = (g&192)>>6 | (r&248)>>1;
       } else {
          c[k] = (b&248)>>3 | (g&56)<<2;
          c[k+1] = (g&192)>>6 | (r&248)>>1;
       }
       k += 2;
     }
  else {
     for (i = 0; i < wp; i++) {
        if (i==0) j = 0 ; else j = 3 * ((i* (w-1))/(wp-1));
        r = scan[j];
        g = scan[j+1];
        b = scan[j+2];
        c[k] = color_table[(((6*g)>>8)*36)+(((6*r)>>8)*6)+((6*b)>>8)];
        k++;
     }
  }
}

XImage *
readPNG(char *file, int height, Colordata *color)
{
    XImage        *ximage;
    FILE          *fp;
    png_structp   png_ptr;
    png_infop     info_ptr;
    png_uint_32   width0, height0;
    png_colorp    palette;
    png_bytep	  *row_pointers, png_data, png;
    int           bit_depth, color_type, interlace_type, num_palette, rowbytes;
    int           i, i1, i2, x, y, yp, size, width = 0;
    int *         ys;
    unsigned char * scan, *scanp, *out;
    unsigned char c;
    unsigned char bg[4];
   
    if ((fp = fopen(file, "rb")) == (FILE *)NULL) {
        return NULL;
    }

    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
      &jmpbuf_struct, png_error_handler, NULL);
    if (!png_ptr) {
        fclose(fp);
        return NULL;
    }

    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
        png_destroy_read_struct(&png_ptr, NULL, NULL);
        fclose(fp);
        return NULL;
    }

    if (setjmp(jmpbuf_struct.jmpbuf)) {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        fclose(fp);
        return NULL;
    }

    png_init_io(png_ptr, fp);
    png_read_info(png_ptr, info_ptr);  /* read all PNG info up to image data */

    png_get_IHDR(png_ptr, info_ptr, &width0, &height0, &bit_depth, &color_type,
      &interlace_type, NULL, NULL);

    switch (color_type) {

        case PNG_COLOR_TYPE_PALETTE:
            if (!png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette)) {
                png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
                fclose(fp);
                return NULL;
            }
            break;

        case PNG_COLOR_TYPE_RGB:
            if (bit_depth == 16) {
                png_set_strip_16(png_ptr);
            }
            break;

        case PNG_COLOR_TYPE_GRAY:   /* treat grayscale as special colormap */
            if (bit_depth == 16) {
                png_set_strip_16(png_ptr);
                bit_depth = 8;
            }
            break;

        case PNG_COLOR_TYPE_RGB_ALPHA:
            if (bit_depth == 16) {
                png_set_strip_16(png_ptr);
            }
            break;

        case PNG_COLOR_TYPE_GRAY_ALPHA:
            if (bit_depth == 16) {
                png_set_strip_16(png_ptr);
            }
            break;

        default:
            png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
            fclose(fp);
            return NULL;
    }
  
    if (height == 0) height = height0;
    if (height == height0) 
       width = width0;
    else
       width = (int)
       (((double)width0 * (double) height / (double) height0)+0.5);
    
    ximage = XCreateImage(dpy, visual, 
                     DefaultDepth(dpy, scr), ZPixmap, 0, NULL, 
                     width, height, color_pad, 0);
    XFlush(dpy);
    if (!ximage) return NULL;

    bytes_per_pixel = ximage->bits_per_pixel/8;
    size = ximage->bytes_per_line * height;
    ximage->data = (char *) malloc(size);
    if (!ximage->data) {
        XDestroyImage(ximage);
        return NULL;
    }

    if (bit_depth < 8)
        png_set_packing(png_ptr);

    if (interlace_type)
        /* npasses = */ png_set_interlace_handling(png_ptr);

    png_read_update_info(png_ptr, info_ptr);

    rowbytes = png_get_rowbytes(png_ptr, info_ptr);

    png_data = (png_bytep) malloc(height0*rowbytes);
    row_pointers = (png_bytep *)malloc(height0 * sizeof(png_bytep));
    scan = (char *)malloc(3*width0);
    ys = (int *)malloc((height0+1)*sizeof(int));
   
    if (!png_data || !row_pointers || !scan || !ys) {
       if (png_data) free(png_data);
       png_data = NULL;
       if (row_pointers) free(row_pointers);
       row_pointers = NULL;
       if (scan) free(scan);
       if (ys) free(ys);
       XDestroyImage(ximage);
       png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
       fclose(fp);
       return NULL;
    }

    for (y=0; y<height0; ++y)
        row_pointers[y] = (png_bytep)png_data + y*rowbytes;
    png_read_image(png_ptr, row_pointers);

    bg[1] = color->b>>8;   
    bg[2] = color->g>>8;   
    bg[3] = color->r>>8;

    for (y=0; y<height0; ++y) {
        if (y==0) yp=0; else yp = (y*(height-1))/(height0-1);
        ys[y] = yp;
        out = ximage->data + yp * ximage->bytes_per_line;
	png = png_data + y*rowbytes;
        scanp = scan;
	if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
           for (x=0; x<width0; x++) {
	      *scanp++ = *png++;
	      *scanp++ = *png++;
	      *scanp++ = *png++;
	      if ((c=*png++)<255) /* alpha channel */
		 for (i=1; i<=3; i++)
		 *(scanp-i) = (*(scanp-i) * c + bg[i] * ( 255-c))>>8;
	   }
	} else
	if (color_type == PNG_COLOR_TYPE_RGB) {
           for (x=0; x<width0; x++) {
	      *scanp++ = *png++;
	      *scanp++ = *png++;
	      *scanp++ = *png++;
	   }
	} else	 
	if (color_type == PNG_COLOR_TYPE_PALETTE) {
	   for(x=0; x<width0; x++) {
	          c = *png++;
	          *scanp++ = palette[c].red;
	          *scanp++ = palette[c].green;
	          *scanp++ = palette[c].blue;
	   }
	} else
	if (color_type == PNG_COLOR_TYPE_GRAY) {
	   for(x=0; x<width0; x++) {
	      *scanp++ = c = *png++;
	      *scanp++ = c;
	      *scanp++ = c;
	   }
	} else
	if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
	   for(x=0; x<width0; x++) {
	      *scanp++ = c = *png++;
	      *scanp++ = c;
	      *scanp++ = c;
	      if ((c=*png++)<255) /* alpha channel */
		 for (i=1; i<=3; i++)
		 *(scanp-i) = (*(scanp-i) * c + bg[i] * ( 255-c))>>8;
	   }
	}
        fill_line(scan, out, width0, width);
    }

    /* fill intermediate lines in output, if necessary*/
    if (height>height0) {
       ys[height0] = height;
       for(y=0; y<height0; ++y) {
	  if (ys[y]>=0 || ys[y+1]<height) {
	     if (ys[y]<0) {
                out = ximage->data + 
                        ys[y+1] * ximage->bytes_per_line;
                i1 = 0;
                i2 = ys[y+1];
	     } else {
                out = ximage->data + 
                        ys[y]*ximage->bytes_per_line;
                i1 = ys[y]+1;
                i2 = ys[y+1];
                if (i2>height) i2=height;
	     }
             for (i=i1; i<i2; i++) 
                memcpy(ximage->data+i*ximage->bytes_per_line, out,
                       ximage->bytes_per_line);
	  }
       }
    }

    /* Close everything */
    png_read_end(png_ptr, info_ptr);
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    free(png_data);
    free(row_pointers);
    free(scan);
    free(ys);
    fclose(fp);
    return ximage;
} /* end function ReadPNG() */


/* GIF routines */

#define	MAXCOLORMAPSIZE		256

#define	TRUE	1
#define	FALSE	0

#define CM_RED		0
#define CM_GREEN	1
#define CM_BLUE		2

#define	MAX_LWZ_BITS		12

#define INTERLACE		0x40
#define LOCALCOLORMAP	0x80
#define BitSet(byte, bit)	(((byte) & (bit)) == (bit))

#define	ReadOK(file,buffer,len)	(fread(buffer, len, 1, file) != 0)

#define LM_to_uint(a,b)			(((b)<<8)|(a))

struct {
    unsigned int Width;
    unsigned int Height;
    unsigned char ColorMap[3][MAXCOLORMAPSIZE];
    unsigned int BitPixel;
    unsigned int ColorResolution;
    unsigned int Background;
    unsigned int AspectRatio;
    int Grayscale;
} GifScreen;

static struct {
    int transparent;
    int delayTime;
    int inputFlag;
    int disposal;
} Gif89 = {
    -1, -1, -1, 0
};

static int ReadColorMap(FILE * fd, int number,
			unsigned char buffer[3][MAXCOLORMAPSIZE], int *flag);
static int DoExtension(FILE * fd, int label);
static int GetDataBlock(FILE * fd, unsigned char *buf);
static int GetCode(FILE * fd, int code_size, int flag);
static int LWZReadByte(FILE * fd, int flag, int input_code_size);
static int ReadImage(FILE * fd, XImage *ximage,
		     int width, int height, int bitPixel,
		     unsigned char cmap[3][MAXCOLORMAPSIZE],
		     int gray, int interlace);

XImage *
readGIF(char *file, int height, Colordata *color)
{
    XImage *image;
    unsigned char buf[16];
    unsigned char c;
    unsigned char localColorMap[3][MAXCOLORMAPSIZE];
    int output = 0, size;
    int grayScale;
    int useGlobalColormap;
    int bitPixel;
    char version[4];
    FILE *fd;
   
    fd = fopen(file, "r");
   
    if (fd == NULL)
	return NULL;

    if (!ReadOK(fd, buf, 6)) {
	fclose(fd);
	return NULL;
    }
    if (strncmp((char *) buf, "GIF", 3) != 0) {
	fclose(fd);
	return NULL;
    }
    strncpy(version, (char *) buf + 3, 3);
    version[3] = '\0';

    if ((strcmp(version, "87a") != 0) && (strcmp(version, "89a") != 0)) {
	fclose(fd);
	return NULL;
    }
    if (!ReadOK(fd, buf, 7)) {
	fclose(fd);
	return NULL;
    }
    GifScreen.Width = LM_to_uint(buf[0], buf[1]);
    GifScreen.Height = LM_to_uint(buf[2], buf[3]);
    GifScreen.BitPixel = 2 << (buf[4] & 0x07);
    GifScreen.ColorResolution = (((buf[4] & 0x70) >> 3) + 1);
    GifScreen.Background = buf[5];
    GifScreen.AspectRatio = buf[6];

    if (BitSet(buf[4], LOCALCOLORMAP)) {	/* Global Colormap */
	if (ReadColorMap(fd, GifScreen.BitPixel, GifScreen.ColorMap,
			 &GifScreen.Grayscale)) {
	    fclose(fd);
	    return NULL;
	}
    }
 repeat:
    if (!ReadOK(fd, &c, 1)) {
        fclose(fd);
        return NULL;
    }
    if (c == '!') {        /* Extension */
        if (!ReadOK(fd, &c, 1)) {
           fclose(fd);
           return NULL;
	}
        DoExtension(fd, c);
        goto repeat;
    }
    if (c != ',') {        /* Not a valid start character */
        goto repeat;
    }

    if (!ReadOK(fd, buf, 9)) {
        fclose(fd);
        return NULL;
    }
    useGlobalColormap = !BitSet(buf[8], LOCALCOLORMAP);

    bitPixel = 1 << ((buf[8] & 0x07) + 1);

    image = XCreateImage(dpy, visual, 
              DefaultDepth(dpy, scr), ZPixmap, 0, NULL, 
              GifScreen.Width, GifScreen.Height, color_pad, 0);
    XFlush(dpy);
    if (!image) return NULL;
    bytes_per_pixel = (image->bits_per_pixel/8);
    size = image->bytes_per_line * GifScreen.Height;
    image->data = (char *) malloc(size);
    if (!image->data) {
       XDestroyImage(image);
       return NULL;
    }

    if (!useGlobalColormap) {
       if (ReadColorMap(fd, bitPixel, localColorMap, &grayScale)) {
          XDestroyImage(image);
          return NULL;
       }
       output = ReadImage(fd, image,
	          LM_to_uint(buf[4], buf[5]),
                  LM_to_uint(buf[6], buf[7]),
                  bitPixel, localColorMap, grayScale,
                  BitSet(buf[8], INTERLACE));
    } else {
       output = ReadImage(fd, image,
		  LM_to_uint(buf[4], buf[5]),
                  LM_to_uint(buf[6], buf[7]),
                  GifScreen.BitPixel, GifScreen.ColorMap,
                  GifScreen.Grayscale, BitSet(buf[8], INTERLACE));
    }

    if (output) {
       XDestroyImage(image);
       return NULL;
    }

    if (height == 0 || height == image->height) {
       return image;
    }

    return ximage_rescale(image, height);
}

static int
ReadColorMap(FILE *fd, int number, unsigned char buffer[3][MAXCOLORMAPSIZE],
	     int *gray)
{
    int i;
    unsigned char rgb[3];
    int flag;

    flag = TRUE;

    for (i = 0; i < number; ++i) {
	if (!ReadOK(fd, rgb, sizeof(rgb))) {
	    return 1;
	}
	buffer[CM_RED][i] = rgb[0];
	buffer[CM_GREEN][i] = rgb[1];
	buffer[CM_BLUE][i] = rgb[2];
	flag &= (rgb[0] == rgb[1] && rgb[1] == rgb[2]);
    }

#if 0
    if (flag)
	*gray = (number == 2) ? PBM_TYPE : PGM_TYPE;
    else
	*gray = PPM_TYPE;
#else
    *gray = 0;
#endif

    return FALSE;
}

static int
DoExtension(FILE *fd, int label)
{
    static char buf[256];
    char *str;

    switch (label) {
    case 0x01:			/* Plain Text Extension */
	str = "Plain Text Extension";
	break;
    case 0xff:			/* Application Extension */
	str = "Application Extension";
	break;
    case 0xfe:			/* Comment Extension */
	str = "Comment Extension";
	while (GetDataBlock(fd, (unsigned char *) buf) != 0);
	return FALSE;
    case 0xf9:			/* Graphic Control Extension */
	str = "Graphic Control Extension";
	(void) GetDataBlock(fd, (unsigned char *) buf);
	Gif89.disposal = (buf[0] >> 2) & 0x7;
	Gif89.inputFlag = (buf[0] >> 1) & 0x1;
	Gif89.delayTime = LM_to_uint(buf[1], buf[2]);
	if ((buf[0] & 0x1) != 0)
	    Gif89.transparent = buf[3];

	while (GetDataBlock(fd, (unsigned char *) buf) != 0);
	return FALSE;
    default:
	str = buf;
	sprintf(buf, "UNKNOWN (0x%02x)", label);
	break;
    }

    while (GetDataBlock(fd, (unsigned char *) buf) != 0);

    return FALSE;
}

static int ZeroDataBlock = FALSE;

static int
GetDataBlock(FILE *fd, unsigned char *buf)
{
    unsigned char count;

    if (!ReadOK(fd, &count, 1)) {
	/* pm_message("error in getting DataBlock size" ); */
	return -1;
    }
    ZeroDataBlock = count == 0;

    if ((count != 0) && (!ReadOK(fd, buf, count))) {
	/* pm_message("error in reading DataBlock" ); */
	return -1;
    }
    return count;
}

static int
GetCode(FILE *fd, int code_size, int flag)
{
    static unsigned char buf[280];
    static int curbit, lastbit, done, last_byte;
    int i, j, ret;
    unsigned char count;

    if (flag) {
	curbit = 0;
	lastbit = 0;
	done = FALSE;
	return 0;
    }
    if ((curbit + code_size) >= lastbit) {
	if (done) {
	    if (curbit >= lastbit)
		fprintf(stderr, "ran off the end of my bits...\n");
	    return -1;
	}
	buf[0] = buf[last_byte - 2];
	buf[1] = buf[last_byte - 1];

	if ((count = GetDataBlock(fd, &buf[2])) == 0)
	    done = TRUE;

	last_byte = 2 + count;
	curbit = (curbit - lastbit) + 16;
	lastbit = (2 + count) * 8;
    }
    ret = 0;
    for (i = curbit, j = 0; j < code_size; ++i, ++j)
	ret |= ((buf[i / 8] & (1 << (i % 8))) != 0) << j;

    curbit += code_size;

    return ret;
}

static int
LWZReadByte(FILE *fd, int flag, int input_code_size)
{
    static int fresh = FALSE;
    int code, incode;
    static int code_size, set_code_size;
    static int max_code, max_code_size;
    static int firstcode, oldcode;
    static int clear_code, end_code;
    static int table[2][(1 << MAX_LWZ_BITS)];
    static int stack[(1 << (MAX_LWZ_BITS)) * 2], *sp;
    register int i;

    if (flag) {
	set_code_size = input_code_size;
	code_size = set_code_size + 1;
	clear_code = 1 << set_code_size;
	end_code = clear_code + 1;
	max_code_size = 2 * clear_code;
	max_code = clear_code + 2;

	GetCode(fd, 0, TRUE);

	fresh = TRUE;

	for (i = 0; i < clear_code; ++i) {
	    table[0][i] = 0;
	    table[1][i] = i;
	}
	for (; i < (1 << MAX_LWZ_BITS); ++i)
	    table[0][i] = table[1][0] = 0;

	sp = stack;

	return 0;
    } else if (fresh) {
	fresh = FALSE;
	do {
	    firstcode = oldcode = GetCode(fd, code_size, FALSE);
	} while (firstcode == clear_code);
	return firstcode;
    }
    if (sp > stack)
	return *--sp;

    while ((code = GetCode(fd, code_size, FALSE)) >= 0) {
	if (code == clear_code) {
	    for (i = 0; i < clear_code; ++i) {
		table[0][i] = 0;
		table[1][i] = i;
	    }
	    for (; i < (1 << MAX_LWZ_BITS); ++i)
		table[0][i] = table[1][i] = 0;
	    code_size = set_code_size + 1;
	    max_code_size = 2 * clear_code;
	    max_code = clear_code + 2;
	    sp = stack;
	    firstcode = oldcode = GetCode(fd, code_size, FALSE);
	    return firstcode;
	} else if (code == end_code) {
	    int count;
	    unsigned char buf[260];

	    if (ZeroDataBlock)
		return -2;

	    while ((count = GetDataBlock(fd, buf)) > 0);

	    if (count != 0) {
		/*
		 * pm_message("missing EOD in data stream (common occurence)");
		 */
	    }
	    return -2;
	}
	incode = code;

	if (code >= max_code) {
	    *sp++ = firstcode;
	    code = oldcode;
	}
	while (code >= clear_code) {
	    *sp++ = table[1][code];
	    if (code == table[0][code])
		fprintf(stderr, "circular table entry BIG ERROR...\n");
	    code = table[0][code];
	}

	*sp++ = firstcode = table[1][code];

	if ((code = max_code) < (1 << MAX_LWZ_BITS)) {
	    table[0][code] = oldcode;
	    table[1][code] = firstcode;
	    ++max_code;
	    if ((max_code >= max_code_size) &&
		(max_code_size < (1 << MAX_LWZ_BITS))) {
		max_code_size *= 2;
		++code_size;
	    }
	}
	oldcode = incode;

	if (sp > stack)
	    return *--sp;
    }
    return code;
}

static int
ReadImage(FILE * fd, XImage *ximage, int width, int height, int cmapSize,
	  unsigned char cmap[3][MAXCOLORMAPSIZE],
	  int gray, int interlace)
{
    unsigned char c;
    char *out;
    int v, ind;
    int xpos = 0, ypos = 0, pass = 0;
    unsigned char *scan;

    /*
    **	Initialize the compression routines
     */
   
    scan = (unsigned char *)malloc(3*width);
    if (!scan) return 4;
   
     
    if (!ReadOK(fd, &c, 1)) {
	return 2;
    }
    if (LWZReadByte(fd, TRUE, c) < 0) {
	return 2;
    }

    while ((v = LWZReadByte(fd, FALSE, c)) >= 0) {
        ind = 3*xpos;
        scan[ind] = cmap[CM_RED][v];
        scan[ind+1] = cmap[CM_GREEN][v];
        scan[ind+2] = cmap[CM_BLUE][v];
        out = ximage->data + ypos * ximage->bytes_per_line;

	++xpos;
	if (xpos == width) {
	    xpos = 0;
	    if (interlace) {
		switch (pass) {
		case 0:
		case 1:
		    ypos += 8;
		    break;
		case 2:
		    ypos += 4;
		    break;
		case 3:
		    ypos += 2;
		    break;
		}

		if (ypos >= height) {
		    ++pass;
		    switch (pass) {
		    case 1:
			ypos = 4;
			break;
		    case 2:
			ypos = 2;
			break;
		    case 3:
			ypos = 1;
			break;
		    default:
			goto fini;
		    }
		}
	    } else {
		++ypos;
	    }
	    fill_line(scan, out, width, width);
	}
	if (ypos >= height)
	    break;
    }

  fini:
    free(scan);
    return 0;
}

/* JPEG routines */

struct error_mgr {
  struct jpeg_error_mgr pub;    /* "public" fields */

  jmp_buf setjmp_buffer;        /* for return to caller */
};

typedef struct error_mgr * error_ptr;

void
error_exit (j_common_ptr cinfo)
{
  /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
  error_ptr err = (error_ptr) cinfo->err;

  /* Always display the message. */
  /* We could postpone this until after returning, if we chose. */
  (*cinfo->err->output_message) (cinfo);

  /* Return control to the setjmp point */
  longjmp(err->setjmp_buffer, 1);
}

XImage *
readJPG(char * file, int height, Colordata *color)
{
    FILE *input_file;
    XImage *ximage;
    struct jpeg_decompress_struct cinfo;
    struct error_mgr jerr;
    double ratio;
    int  l, prev, next=0, size, width;
    JSAMPROW scanline[1];
    char *scan, *c;

    if ((input_file = fopen(file, "r")) == NULL) return NULL;

    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = error_exit;

    if (setjmp(jerr.setjmp_buffer)) {
	/* If we get here, the JPEG code has signaled an error.
	 * We need to clean up the JPEG object, close the input file,
	 * and return.
	 */
        jpeg_destroy_decompress(&cinfo);
	fclose(input_file);
	return NULL;
    }

    jpeg_create_decompress(&cinfo);
    jpeg_stdio_src(&cinfo, input_file);
    jpeg_read_header(&cinfo, TRUE);

    if (cinfo.jpeg_color_space == JCS_GRAYSCALE) return NULL;
   
    if (height == 0) {
       width = cinfo.image_width;
       height = cinfo.image_height;
       ratio = 1.0;
       cinfo.scale_denom = 1;
    } else {
       width = (int)((double) cinfo.image_width * (double) height /
	             (double) cinfo.image_height + 0.5);
       ratio = ((double) cinfo.image_width/(double) width + 
                (double) cinfo.image_height/(double) height )/1.8;
       if (ratio>=8.0) 
          cinfo.scale_denom = 8;
       else
       if (ratio>=4.0) 
          cinfo.scale_denom = 4;
       else
       if (ratio>=2.0) 
          cinfo.scale_denom = 2;
       else
          cinfo.scale_denom = 1;
    }
   
    jpeg_start_decompress(&cinfo);

    ximage = XCreateImage(dpy, visual, 
              DefaultDepth(dpy, scr), ZPixmap, 0, NULL, 
              width, height, color_pad, 0);
    XFlush(dpy);
    if (!ximage) return NULL;

    bytes_per_pixel = (ximage->bits_per_pixel/8);
    size = ximage->bytes_per_line * height;
    ximage->data = (char *) malloc(size);
    scan = (char *) malloc(3 * cinfo.output_width * sizeof(char));

#if 0 /* verbose */
       fprintf(stderr, "Loading %s\n"
	    "Rescaling JPEG data by 1/%d,  "
            "%d %d  -->  %d %d,  %d bytes per pixel\n",
	    file, cinfo.scale_denom,
            cinfo.image_width, cinfo.image_height, 
            width, height, 
            bytes_per_pixel);
#endif

    prev = -1;
    scanline[0] = (JSAMPROW) scan;

    while (cinfo.output_scanline < cinfo.output_height) {
      (void) jpeg_read_scanlines(&cinfo, scanline, (JDIMENSION) 1);
      next = ((2*cinfo.output_scanline - 1) * height)/
                  (2*(int)cinfo.output_height);
      if (next>=0) {
	if (next>=height) {
	   next = height - 1;
	   /* get loop to stop at next iteration ! */
	   cinfo.output_scanline = cinfo.output_height;
	}
	for (l = prev+1; l<= next; l++) {
	  c = ximage->data + l * ximage->bytes_per_line;
          fill_line(scan, c, cinfo.output_width, width);
	}
        prev = next;
      }
    }

    free(scan);
    jpeg_destroy_decompress(&cinfo);

    fclose(input_file);

    if (jerr.pub.num_warnings > 0) {	
	longjmp(jerr.setjmp_buffer, 1);
    }
    return ximage;
}


/* XPM routines */

XImage *
readXPM(char *file, int height, Colordata *color)
{
     XImage *image;
     XpmAttributes attrib;
     XpmColorSymbol flagbg;
     XColor xc;
#ifdef XPMBUG
     char *buffer;
     gzFile fd;
#endif
     int j, k;

     if (color_depth<=8) {
        attrib.valuemask = XpmColormap;
        attrib.valuemask = XpmColormap | XpmReturnPixels | XpmColorSymbols;
        attrib.colormap = XCreateColormap(dpy, mainwin, visual, AllocNone);
        attrib.numsymbols = 1;
        attrib.colorsymbols = &flagbg;
        flagbg.name = NULL;
        flagbg.value = "None";
        flagbg.pixel = 255;
     } else {
        attrib.valuemask = XpmColormap | XpmReturnPixels | XpmColorSymbols;
        attrib.colormap = cmap;
        attrib.numsymbols = 1;
        attrib.colorsymbols = &flagbg;
        flagbg.pixel = color->pix;
        flagbg.name = NULL;
        flagbg.value = "None";
     }

     if (!file || !*file) return NULL;
     image = NULL;

#ifdef XPMBUG
     fd = gzopen(file, "r");
     buffer = NULL;
     j = 0;
     k = -1;
     while (!gzeof(fd)) {
        ++k;
        if (k>=j) {
	   j += 4096;
           buffer = realloc(buffer, j);
        }
        buffer[k] = gzgetc(fd);
     }
     gzclose(fd);

     if (XpmCreateImageFromBuffer(dpy, buffer, &image, NULL, &attrib) 
         != XpmSuccess) {
        free(buffer);
        return NULL;
     }
     free(buffer);
#else

     if (XpmReadFileToImage(dpy, file, &image, NULL, &attrib) != XpmSuccess)
        return NULL;
#endif

     if (image)
         bytes_per_pixel = image->bits_per_pixel/8;
     else
         return NULL;

     if (color_depth<=8) {
        unsigned char * cvs_table;
        unsigned char r, g, b, *c;

        cvs_table = (unsigned char*) malloc(256);
	for (j=0; j<256; j++) {
 	   xc.pixel = j;
	   XQueryColor(dpy, attrib.colormap, &xc);
	   r = xc.red>>8;
	   g = xc.green>>8;
	   b = xc.blue>>8;
	   cvs_table[j]=color_table[(((6*g)>>8)*36)+(((6*r)>>8)*6)+((6*b)>>8)];
	}
	cvs_table[255] = color->pix;
        c = image->data;
	k = image->bytes_per_line * image->height;
        for (j=0; j<k; j++) {
           *c = cvs_table[*c];
	   c++;
	} 
	free(cvs_table);
	XFreeColormap(dpy, attrib.colormap);
     }

     if (height == 0 || height == image->height) {
        return image;
     }

     return ximage_rescale(image, height);
}

XImage *
readEPS(char *file, int height, Colordata *color)
{
    XImage *ximage;
    char scan[80], buf[512];
    double x1, x2, y1, y2, res;
    int l, width, zip;
    char *tmp;
#ifdef ZLIB
    gzFile fd;
#else
    FILE * fd;
#endif

    zip = 0;
    tmp = mk_tmp(".png");
    if (!tmp) return NULL;
#ifdef ZLIB
    strcpy(buf, file);
    l = strlen(file);
    if (l>=3 && !strcmp(file+l-3, ".gz"))
       zip = -1;
    fd = gzopen(buf, "r");
    if (!fd && zip==0) {
      strcat(buf, ".gz");
      fd = gzopen(buf, "r");
      if (!fd) return NULL;
      zip = 1;
    }

    width = 0;
    while (!gzeof(fd)) {
       gzgets(fd, buf, 256);
       if (!strncasecmp(buf, "%%HiResBoundingBox:", 19)) {
	   sscanf(buf, "%s %lf %lf %lf %lf", scan, &x1, &y1, &x2, &y2);
           width = -1;
           break;
       }
    }
    gzclose(fd);
#else
    fd = fopen(file, "r");

    width = 0;
    while (!feof(fd)) {
       fgets(buf, 256, fd);
       if (!strncasecmp(buf, "%%HiResBoundingBox:", 19)) {
	   sscanf(buf, "%s %lf %lf %lf %lf", scan, &x1, &y1, &x2, &y2);
           width = 1;
           break;
       }
    }
    fclose(fd);
#endif

    if (!width) {
       return NULL;
    }
    res = (72.0 * height) / y2;
    width = (int) (0.999 + height * x2 / y2);
    
    if (zip)
       sprintf(buf, "zcat %s%s | gs -r%f -sDEVICE=pngalpha -dNOPAUSE -dQUIET "
                    "-g%dx%d -sOutputFile=%s - -c quit", 
	         file, (zip==1)?".gz":"", res, width, height, tmp);
    else
       sprintf(buf, "gs -r%f -sDEVICE=pngalpha -dNOPAUSE -dQUIET -g%dx%d "
	         "-sOutputFile=%s %s -c quit", 
	         res, width, height, tmp, file);
    system(buf);
    ximage = readPNG(tmp, 0, color);
    unlink(tmp);
    return ximage;
}

XImage *
readSVG(char *file, int height,  Colordata *color)
{
    XImage * ximage;
    char buf[512];
    char *tmp;
    int l, zip;
#ifdef ZLIB
    gzFile fd;
#else
    FILE * fd;
#endif
  
    zip = 0;
    tmp = mk_tmp(".png");
    if (!tmp) return NULL;

#ifdef ZLIB
    strcpy(buf, file);
    fd = gzopen(buf, "r");
    l = strlen(file);
    if (l>=3 && !strcmp(file+l-3, ".gz"))
       zip = -1;
    if (!fd && zip==0) {
      strcat(buf, ".gz");
      fd = gzopen(buf, "r");
      if (!fd) return NULL;
      zip = 1;
    }
    gzclose(fd);
#endif
    sprintf(buf, "inkscape %s%s --export-height=%d --export-png=%s",
	         file, (zip==1)?".gz":"", height, tmp); 
    system(buf);
    ximage = readPNG(tmp, 0, color);
    unlink(tmp);
    return ximage;
}

int image_type(char *name)
{
   int k, l, ret;
   char *ptr;

   ret = -1;
   if ((k=strlen(name))>=4 && name[k-4] == '.') {
      ptr = name + k-3;   
      for (l=0; l<num_flag_types; l++)
         if (!strcasecmp(ptr, flag_formats[l])) { ret = l; break; }
   }
   if ((k=strlen(name))>=7 && name[k-7] == '.' && !strcmp(name+k-3, ".gz")) {
      ptr = name + k-6;   
      for (l=0; l<num_flag_types; l++)
         if (!strncasecmp(ptr, flag_formats[l], 3)) { ret = l; break; }
   }
   return ret;
}

XImage *
readIMG(ImageLayout *scene, 
        char *default_dir, char *file, int height, Colordata *color)
{
   struct stat buf;
   int k, l, s;
   char *ptr;
   char name[512];
   char errstr[768];
   XImage *image;

   if (!file || !*file) {
      return NULL;
   }

   *name = '\0';

   if (*file == '/') /* absolute path */
      strcpy(name, file);
   else
   if (!strncmp(file, "./", 2)) /* CWD relative */
      strcpy(name, file+2);
   else
   if (!strncmp(file , "~/", 2)) { /* HOME relative */
      ptr = getenv("HOME");
      sprintf(name, "%s/%s", (ptr)? ptr:".", file+2);
   } else
   if (!strncmp(file , "$HOME/", 6)) { /* HOME relative */
      ptr = getenv("HOME");
      sprintf(name, "%s/%s", (ptr)? ptr:".", file+6);
   } else       
   if (!strncmp(file , "$SHAREDIR/", 10)) { /* SHAREDIR relative */
      sprintf(name, "%s/%s", SHAREDIR, file+10);
   } else { /* just a rough specification */
      if (image_type(file)>=0) { /* image extension specified */
	 if (default_dir && *default_dir)
            sprintf(name, "%s/%s", default_dir, file);
	 else
	    strcpy(name, file);
      } else { /* image extension not specified - try to find ... */
	 s = flag_size * (num_flag_dirs+1);
	 *name = '\0';
         for (l=0; l<num_flag_dirs; l++) {
            k = flag_ordering[l+s] - '1';
            if (k>9)
	       k = flag_ordering[l+s] - 'a' + 9;
            if (k<0 || k>=num_flag_dirs) continue;
            sprintf(name, "%s/%s/%s.%s", 
                          FLAGPATH, flag_dir[k], file, flag_type[k]);
            if (!stat(name, &buf) && S_ISREG(buf.st_mode)) break;
            strcat(name, ".gz");
            if (!stat(name, &buf) && S_ISREG(buf.st_mode)) break;
	 }
      }
   }

   if (!*name) {
      strcpy(name, file);
      image = NULL;
      goto error;
   }
   l = strlen(name);
   if (l<=2 || strcmp(name+l-3, ".gz")) {
      if (stat(name, &buf) || !S_ISREG(buf.st_mode)) strcat(name, ".gz");
   }
   if (stat(name, &buf) || !S_ISREG(buf.st_mode)) {
      image = NULL;
      goto error;
   }

   k = image_type(name);
   switch(k) {
   case 0: image = readXPM(name, height, color); break;
   case 1: image = readGIF(name, height, color); break;
   case 2: image = readPNG(name, height, color); break;
   case 3: image = readJPG(name, height, color); break;     
   case 4: image = readEPS(name, height, color); break;
   case 5: image = readSVG(name, height, color); break;
   default: image = NULL; break;
   }

   error:
   if (!image) {
      sprintf(errstr, msg[COULDNT_READ_IMAGE_FILE], name);
      if (runlevel) {
         if (error_msg) free(error_msg);
         error_msg = strdup(errstr);
         brief_menu = 4;
         show_brief_menu(scene);
      } else
         fprintf(stderr, "%s\n", errstr);
   }

   return image;
}

Pixmap xim2pix(XImage *ximage)
{
    GC gc;
    XGCValues values;
    Pixmap pixmap;

    if (!ximage) return None;
    pixmap = XCreatePixmap(dpy, datawin, ximage->width,
                                         ximage->height, ximage->depth);
    if (!pixmap) return pixmap;

    /* set fg and bg in case we have an XYBitmap */
    values.foreground = 1;
    values.background = 0;
    gc = XCreateGC(dpy, pixmap,
                   GCForeground | GCBackground, &values);
    if (!gc) {
       XFreePixmap(dpy, pixmap);
       return None;
    }
    XPutImage(dpy, pixmap, gc, ximage, 0, 0, 0, 0,
              ximage->width, ximage->height);

    XFreeGC(dpy, gc);
    return pixmap;
}
