/*
 * tkImgXPM.c --
 *
 * A photo image file handler for XPM files. 
 *
 * This needs libXpm 4.7 (ideally 4.9)
 *
 * Copyright (c) 1997 by Oliver Graf <ograf@fga.de>
 * 
 *   adapted to tcl8.0 Tcl_Channels
 *
 * Copyright (c) 1997 by Oliver Graf <ograf@fga.de>
 *
 *   Fixed read from data bug (use FromBuffer instead of FromData)
 *
 *   Modified for transparency
 *   -- adapted from tkImaging.c fromthe python Imaging library
 *
 * Original hack copyright (c) 1996 by Alfredo Kengi Kojima 
 *
 *   Adapted from tkImgBmap.c
 * 
 * Copyright (c) 1994 The Regents of the University of California.
 * Copyright (c) 1994-1996 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 * 
 * and tkImgGIF.c
 * 
 * Reed Wade (wade@cs.utk.edu), University of Tennessee
 *
 * Copyright (c) 1995-1996 Sun Microsystems, Inc.
 *
 */

#ifdef ENABLE_XPM

#include "tkInt.h"
#include "tkPort.h"
#include <X11/xpm.h>

/*
 * The type record for XPM images:
 */
static int      ImgXPMStringMatch _ANSI_ARGS_((char *string,
			char *formatString, int *widthPtr, int *heightPtr));

static int 	ImgXPMStringRead _ANSI_ARGS_((Tcl_Interp *interp,
			char *string, char *formatString, 
			Tk_PhotoHandle imageHandle,
			int destX, int destY, int width, int height, 
			int srcX, int srcY));

static int      ImgXPMFileMatch _ANSI_ARGS_((Tcl_Channel chan, char *fileName,
			char *formatString, int *widthPtr, int *heightPtr));

static int      ImgXPMFileRead  _ANSI_ARGS_((Tcl_Interp *interp,
		    	Tcl_Channel chan, char *fileName, char *formatString,
		    	Tk_PhotoHandle imageHandle, int destX, int destY,
		    	int width, int height, int srcX, int srcY));


Tk_PhotoImageFormat tkImgFmtXPM = {
	"XPM",			/* name */
	ImgXPMFileMatch,  	/* fileMatchProc */
	ImgXPMStringMatch,     	/* stringMatchProc */
	ImgXPMFileRead,    	/* fileReadProc */
	ImgXPMStringRead,     	/* stringReadProc */
	NULL,           	/* fileWriteProc */
	NULL,	           	/* stringWriteProc */
};

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

/*
 * Prototypes for procedures used only locally in this file:
 */

static unsigned char *	MakeColortable _ANSI_ARGS_((Tcl_Interp *interp, 
			    XpmImage image, 
			    int *transparent));
static int		ReadImage _ANSI_ARGS_((Tcl_Interp *interp,
			    char *imagePtr, XpmImage image, int len, int height,
			    unsigned char *cmap,
			    int transparent));

/*
 *----------------------------------------------------------------------
 *
 * ImgXPMFileMatch --
 *
 *  This procedure is invoked by the photo image type to see if
 *  a file contains image data in XPM format.
 *
 * Results:
 *  The return value is 1 if the first characters in channel look
 *  like XPM data, and 0 otherwise. 
 *  The XPM file must contain the keyword XPM in the first line
 *
 * Side effects:
 *  None
 *
 *----------------------------------------------------------------------
 */

static int
ImgXPMFileMatch(chan, fileName, formatString, widthPtr, heightPtr)
    Tcl_Channel chan;		/* The image channel, open for reading. */
    char *fileName;		/* The name of the image file. */
    char *formatString;		/* User-specified format string, or NULL. */
    int *widthPtr, *heightPtr;	/* The dimensions of the image are
				 * returned here if the file is a valid
				 * XPM file. */
{
    XpmImage image;
    
    if (XpmReadFileToXpmImage(fileName, &image, (XpmInfo*)NULL)!=XpmSuccess) {
	return 0;
    }
    *widthPtr = image.width;
    *heightPtr = image.height;
    XpmFreeXpmImage(&image);
    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * ImgXPMFileRead --
 *
 *	This procedure is called by the photo image type to read
 *	XPM format data from a channel and write it into a given
 *	photo image.
 *
 * Results:
 *	A standard TCL completion code.  If TCL_ERROR is returned
 *	then an error message is left in interp->result.
 *
 * Side effects:
 *	New data is added to the image given by imageHandle.
 *
 *----------------------------------------------------------------------
 */

static int
ImgXPMFileRead(interp, chan, fileName, formatString, imageHandle, destX, destY,
	width, height, srcX, srcY)
    Tcl_Interp *interp;		/* Interpreter to use for reporting errors. */
    Tcl_Channel chan;		/* The image channel, open for reading. */
    char *fileName;		/* The name of the image file. */
    char *formatString;		/* User-specified format string, or NULL. */
    Tk_PhotoHandle imageHandle;	/* The photo image to write into. */
    int destX, destY;		/* Coordinates of top-left pixel in
				 * photo image to be written to. */
    int width, height;		/* Dimensions of block of photo image to
				 * be written to. */
    int srcX, srcY;		/* Coordinates of top-left pixel to be used
				 * in image being read. */
{
    int fileWidth, fileHeight;
    int nBytes;
    Tk_PhotoImageBlock block;
    unsigned char *colorMap;
    int transparent = -1;
    XpmImage image;
    
    if (XpmReadFileToXpmImage(fileName, &image, (XpmInfo *)NULL)!=XpmSuccess) {
	Tcl_AppendResult(interp, "error reading XPM file \"", fileName,
		"\"", (char *) NULL);
	return TCL_ERROR;
    }
    fileWidth = image.width;
    fileHeight = image.height;
    if ((fileWidth <= 0) || (fileHeight <= 0)) {
	Tcl_AppendResult(interp, "XPM image file \"", fileName,
		"\" has dimension(s) <= 0", (char *) NULL);
	XpmFreeXpmImage(&image);
	return TCL_ERROR;
    }

    if ((srcX + width) > fileWidth) {
	width = fileWidth - srcX;
    }
    if ((srcY + height) > fileHeight) {
	height = fileHeight - srcY;
    }
    if ((width <= 0) || (height <= 0)
	    || (srcX >= fileWidth) || (srcY >= fileHeight)) {
	XpmFreeXpmImage(&image);
	return TCL_OK;
    }

    colorMap=MakeColortable(interp, image, &transparent);
    if (colorMap==NULL)
	goto error;
    Tk_PhotoExpand(imageHandle, destX + width, destY + height);

    block.width = fileWidth;
    block.height = fileHeight;
    block.pixelSize = 4;
    block.pitch = 4 * fileWidth;
    block.offset[0] = 0;
    block.offset[1] = 1;
    block.offset[2] = 2;
    nBytes = fileHeight * block.pitch;
    block.pixelPtr = (unsigned char *) ckalloc((unsigned) nBytes);

    if (ReadImage(interp, (char *) block.pixelPtr, image, fileWidth,
		  fileHeight, colorMap, transparent) != TCL_OK) {
	goto error;
    }

    if (transparent!=-1)
      {
        int x, y;
        Tk_PhotoImageBlock run;

        /* Clear current contents */
        Tk_PhotoBlank(imageHandle);

        /* Setup run descriptor */
        run.height = 1;
        run.pitch = block.pitch;
        run.pixelSize = block.pixelSize;
        run.offset[0] = 0;
        run.offset[1] = 1;
        run.offset[2] = 2;

        /* Copy opaque runs to photo image */
        for (y = 0; y < block.height; y++) {
            char* p = block.pixelPtr + y*block.pitch;
            char* s = p;
            int   w = 0;
            for (x = 0; x < block.width; x++) {
                if (p[3]) {
                    /* opaque: add pixel to current run */
                    if (w == 0)
                        s = p;
                    w = w + 1;
                } else if (s) {
                    /* copy run to photo image */
                    if (w > 0) {
                        run.width = w;
                        run.pixelPtr = s;
                        Tk_PhotoPutBlock(imageHandle, &run, x-w, y, run.width, 1);
                    }
                    w = 0;
                }
                p += block.pixelSize;
            }
            if (w > 0) {
                /* copy final run, if any */
                run.width = w;
                run.pixelPtr = s;
                Tk_PhotoPutBlock(imageHandle, &run, x-w, y, run.width, 1);
          }
        }
      }
    else
      Tk_PhotoPutBlock(imageHandle, &block, destX, destY, fileWidth, fileHeight);

    ckfree((char *) block.pixelPtr);
    XpmFreeXpmImage(&image);
    if (colorMap) ckfree(colorMap);
    return TCL_OK;

    error:
    ckfree((char *) block.pixelPtr);
    XpmFreeXpmImage(&image);
    if (colorMap) ckfree(colorMap);
    return TCL_ERROR;

}


/*
 *----------------------------------------------------------------------
 *
 * ImgXPMStringMatch --
 *
 *  This procedure is invoked by the photo image type to see if
 *  a string contains image data in XPM format.
 *
 * Results:
 *  The return value is 1 if the first characters in the string look
 *  like XPM data, and 0 otherwise. 
 *
 * Side effects:
 *  None
 *
 *----------------------------------------------------------------------
 */
static int      
ImgXPMStringMatch (string, formatString, widthPtr, heightPtr)
char *string;
char *formatString;
int *widthPtr;
int *heightPtr;
{
    XpmImage image;
    
    if (XpmCreateXpmImageFromBuffer(string, &image, (XpmInfo *)NULL)
	!=XpmSuccess) {
	return 0;
    }
    *widthPtr = image.width;
    *heightPtr = image.height;
    XpmFreeXpmImage(&image);
    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * ImgXPMStringRead --
 *
 *	This procedure is called by the photo image type to read
 *	XPM format data from a string and write it into a given
 *	photo image.
 *
 * Results:
 *	A standard TCL completion code.  If TCL_ERROR is returned
 *	then an error message is left in interp->result.
 *
 * Side effects:
 *	New data is added to the image given by imageHandle.
 *
 *----------------------------------------------------------------------
 */
static int 	
ImgXPMStringRead (interp, string, formatString, imageHandle, destX, destY, 
		  width, height, srcX, srcY)
Tcl_Interp *interp;
char *string;
char *formatString;
Tk_PhotoHandle imageHandle;
int destX, destY, width, height, srcX, srcY;
{
    int nBytes;
    Tk_PhotoImageBlock block;
    unsigned char *colorMap;
    int transparent = -1;
    XpmImage image;

    
    if (XpmCreateXpmImageFromBuffer(string, &image, (XpmInfo *)NULL)
	!=XpmSuccess) {
	return 0;
    }

    if ((image.width <= 0) || (image.height <= 0)) {
	Tcl_AppendResult(interp, "XPM image data has dimension(s) <= 0", 
			 (char *) NULL);
	XpmFreeXpmImage(&image);
	return TCL_ERROR;
    }

    if ((srcX + width) > image.width) {
	width = image.width - srcX;
    }
    if ((srcY + height) > image.height) {
	height = image.height - srcY;
    }
    if ((width <= 0) || (height <= 0)
	    || (srcX >= image.width) || (srcY >= image.height)) {
	XpmFreeXpmImage(&image);
	return TCL_OK;
    }

    colorMap=MakeColortable(interp, image, &transparent);
    if (colorMap==NULL)
	goto error;
    Tk_PhotoExpand(imageHandle, destX + width, destY + height);

    block.width = image.width;
    block.height = image.height;
    block.pixelSize = 4;
    block.pitch = 4 * image.width;
    block.offset[0] = 0;
    block.offset[1] = 1;
    block.offset[2] = 2;
    nBytes = image.height * block.pitch;
    block.pixelPtr = (unsigned char *) ckalloc((unsigned) nBytes);

    if (ReadImage(interp, (char *) block.pixelPtr, image, image.width,
		  image.height, colorMap, transparent) != TCL_OK) {
	goto error;
    }

    if (transparent!=-1)
      {
        int x, y;
        Tk_PhotoImageBlock run;

        /* Clear current contents */
        Tk_PhotoBlank(imageHandle);

        /* Setup run descriptor */
        run.height = 1;
        run.pitch = block.pitch;
        run.pixelSize = block.pixelSize;
        run.offset[0] = 0;
        run.offset[1] = 1;
        run.offset[2] = 2;

        /* Copy opaque runs to photo image */
        for (y = 0; y < block.height; y++) {
            char* p = block.pixelPtr + y*block.pitch;
            char* s = p;
            int   w = 0;
            for (x = 0; x < block.width; x++) {
                if (p[3]) {
                    /* opaque: add pixel to current run */
                    if (w == 0)
                        s = p;
                    w = w + 1;
                } else if (s) {
                    /* copy run to photo image */
                    if (w > 0) {
                        run.width = w;
                        run.pixelPtr = s;
                        Tk_PhotoPutBlock(imageHandle, &run, x-w, y, run.width, 1);
                    }
                    w = 0;
                }
                p += block.pixelSize;
            }
            if (w > 0) {
                /* copy final run, if any */
                run.width = w;
                run.pixelPtr = s;
                Tk_PhotoPutBlock(imageHandle, &run, x-w, y, run.width, 1);
          }
        }
      }
    else
      Tk_PhotoPutBlock(imageHandle, &block, destX, destY, image.width, 
		       image.height);
    ckfree((char *) block.pixelPtr);
    XpmFreeXpmImage(&image);
    if (colorMap) ckfree(colorMap);
    return TCL_OK;

    error:
    ckfree((char *) block.pixelPtr);
    XpmFreeXpmImage(&image);    
    if (colorMap) ckfree(colorMap);
    return TCL_ERROR;
}


/*
 * Private 
 */

/*
 *------------------------------------------------------------------ 
 * 
 * MakeColortable --
 * 	Converts a XpmColor colortable to a indexed RGB colortable
 * 
 *------------------------------------------------------------------ 
 */
static unsigned char *
MakeColortable(interp, image, transparent)
Tcl_Interp *interp;
XpmImage image;
int *transparent;
{
    int i;
    unsigned char *cmap;
    
    if (image.colorTable==NULL) 
      return NULL;
    
    cmap=(unsigned char *)ckalloc(sizeof(unsigned char)*CM_COLSIZE*image.ncolors);
    if (cmap==NULL)
        return NULL;

    for (i=0; i<image.ncolors; i++) {
	XColor *colorPtr;
	
	if (strncmp(image.colorTable[i].c_color,"None",4)==0) {
	    *transparent = i;
	    cmap[i*CM_COLSIZE+CM_RED]   = 0xbf;
	    cmap[i*CM_COLSIZE+CM_GREEN] = 0xbf;
	    cmap[i*CM_COLSIZE+CM_BLUE]  = 0xbf;
	    continue;
	}
	colorPtr = Tk_GetColor(interp, Tk_MainWindow(interp), 
			       Tk_GetUid(image.colorTable[i].c_color));
	if (colorPtr) {
	    cmap[i*CM_COLSIZE+CM_RED]   = colorPtr->red >> 8;
	    cmap[i*CM_COLSIZE+CM_GREEN] = colorPtr->green >> 8;
	    cmap[i*CM_COLSIZE+CM_BLUE]  = colorPtr->blue >> 8;
	    Tk_FreeColor(colorPtr);
	} else {
	    cmap[i*CM_COLSIZE+CM_RED]   = 0xbf;
	    cmap[i*CM_COLSIZE+CM_GREEN] = 0xbf;
	    cmap[i*CM_COLSIZE+CM_BLUE]  = 0xbf;
	}
    }
    return cmap;
}

/*
 *------------------------------------------------------------------ 
 * 
 * ReadImage --
 * 	Converts a XpmImage image to a RGB image
 * 
 *------------------------------------------------------------------ 
 */
static int
ReadImage(interp, imagePtr, image, len, height, cmap, transparent)
Tcl_Interp *interp;
char 	*imagePtr;
XpmImage image;
int len, height;
unsigned char   *cmap;
int transparent;
{
    int		v;
    int		xpos = 0, ypos = 0;
    char	*colStr;
    unsigned int offset;

    offset = 0;
    for(ypos=0; ypos<height; ypos++) {
	for(xpos=0; xpos<len; xpos++) {
	    v = image.data[offset];
	    imagePtr[(xpos*4)+(ypos*len*4)  ] = cmap[v*CM_COLSIZE+CM_RED];
	    imagePtr[(xpos*4)+(ypos*len*4)+1] = cmap[v*CM_COLSIZE+CM_GREEN];
	    imagePtr[(xpos*4)+(ypos*len*4)+2] = cmap[v*CM_COLSIZE+CM_BLUE];
	    imagePtr[(xpos*4)+(ypos*len*4)+3] = (v==transparent)?0:1;
	    offset++;
	}
    }
    return TCL_OK;    
}
#endif
