/*
 * pixmap.c
 *
 * PIXMAP image type, Tcl/Tk package.
 *
 * A new image type for the PIXMAP image format.
 *
 * For a list of available format options see the documentation img-pixmap.
 *
 * Copyright (c) 1995-2024 Jan Nijtmans    <nijtmans@users.sourceforge.net>
 * Copyright (c) 2002-2024 Andreas Kupries <andreas_kupries@users.sourceforge.net>
 * Copyright (c) 2003-2024 Paul Obermeier  <obermeier@users.sourceforge.net>
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "pixmapInt.h"
#include "tkimg.h"

#ifndef PACKAGE_TCLNAME
#define PACKAGE_TCLNAME "img::pixmap"
#endif

#if defined(_WIN32) && !defined (__GNUC__)
#define strncasecmp _strnicmp
#else
#include <strings.h>
#endif

#ifndef MAC_TCL
#include <sys/types.h>
#include <sys/stat.h>
#endif

#define UCHAR(c) ((unsigned char) (c))

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

static int TkimgXpmCreate(Tcl_Interp *interp,
        const char *name, Tcl_Size objc, Tcl_Obj *const objv[],
        const Tk_ImageType *typePtr, Tk_ImageMaster master,
        void **clientDataPtr);
static void *TkimgXpmGet(Tk_Window tkwin,
        void *clientData);
static void TkimgXpmDisplay(void *clientData,
        Display *display, Drawable drawable,
        int imageX, int imageY, int width, int height,
        int drawableX, int drawableY);
static void TkimgXpmFree(void *clientData,
        Display *display);
static void TkimgXpmDelete(void *clientData);
static Tcl_ObjCmdProc TkimgXpmObjCmd;
static void TkimgXpmCmdDeletedProc(
        void *clientData);
static void TkimgXpmConfigureInstance(
        PixmapInstance *instancePtr);
static int TkimgXpmConfigureMaster(
        PixmapMaster *masterPtr, int objc, Tcl_Obj *const *objv,
        int flags);
static int TkimgXpmGetData(Tcl_Interp *interp,
        PixmapMaster *masterPtr);
static const char **TkimgXpmGetDataFromFile(Tcl_Interp *interp,
        char *string, int *numLines_return);
static const char **TkimgXpmGetDataFromString(Tcl_Interp *interp,
        char *string, int *numLines_return);
static void TkimgXpmGetPixmapFromData(
        Tcl_Interp *interp,
        PixmapMaster *masterPtr,
        PixmapInstance *instancePtr);
static char *GetType(char *colorDefn,
        int *type_ret);
static char *GetColor(char *colorDefn,
        char *colorName, int *type_ret);

/*
 * Information used for parsing configuration specs:
 */

static Tk_ConfigSpec configSpecs[] = {
    {TK_CONFIG_STRING, "-data", (char *) NULL, (char *) NULL,
        (char *) NULL, offsetof(PixmapMaster, dataString), TK_CONFIG_NULL_OK},
    {TK_CONFIG_STRING, "-file", (char *) NULL, (char *) NULL,
        (char *) NULL, offsetof(PixmapMaster, fileString), TK_CONFIG_NULL_OK},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
        (char *) NULL, 0, 0}
};

Tk_ImageType imgPixmapImageType = {
    "pixmap",                           /* name */
    TkimgXpmCreate,                     /* createProc */
    TkimgXpmGet,                        /* getProc */
    TkimgXpmDisplay,                    /* displayProc */
    TkimgXpmFree,                       /* freeProc */
    TkimgXpmDelete,                     /* deleteProc */
#ifdef TK_CONFIG_OBJS
    (Tk_ImagePostscriptProc *) NULL,    /* postscriptProc */
#endif
    (Tk_ImageType *) NULL               /* nextPtr */
};

/*
 *----------------------------------------------------------------------
 *
 * TkimgXpmCreate --
 *
 *      This procedure is called by the Tk image code to create "pixmap"
 *      images.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      The data structure for a new image is allocated.
 *
 *----------------------------------------------------------------------
 */

static int
TkimgXpmCreate(
    Tcl_Interp *interp,         /* Interpreter for application containing
                                 * image. */
    const char *name,           /* Name to use for image. */
    Tcl_Size objc,              /* Number of arguments. */
    Tcl_Obj *const objv[],      /* Argument strings for options (doesn't
                                 * include image name or type). */
    const Tk_ImageType *typePtr,/* Pointer to our type record (not used). */
    Tk_ImageMaster master,       /* Token for image, to be used by us in
                                 * later callbacks. */
    void **clientDataPtr        /* Store manager's token for image here;
                                 * it will be returned in later callbacks. */
) {
    PixmapMaster *masterPtr;

    /*
     * Convert the objc/objv arguments into string equivalent.
     */

    masterPtr = (PixmapMaster *) attemptckalloc(sizeof(PixmapMaster));
    if (masterPtr == NULL) {
        Tcl_AppendResult (interp, "Unable to allocate memory for PixmapMaster.", (char *) NULL);
        return TCL_ERROR;
    }
    masterPtr->tkMaster = master;
    masterPtr->interp = interp;
    masterPtr->imageCmd = Tcl_CreateObjCommand(interp, name, TkimgXpmObjCmd,
            (ClientData) masterPtr, TkimgXpmCmdDeletedProc);

    masterPtr->fileString = NULL;
    masterPtr->dataString = NULL;
    masterPtr->data = NULL;
    masterPtr->isDataAlloced = 0;
    masterPtr->instancePtr = NULL;

    if (TkimgXpmConfigureMaster(masterPtr, objc, objv, 0) != TCL_OK) {
        TkimgXpmDelete((ClientData) masterPtr);
        return TCL_ERROR;
    }
    *clientDataPtr = (ClientData) masterPtr;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TkimgXpmConfigureMaster --
 *
 *      This procedure is called when a pixmap image is created or
 *      reconfigured.  It process configuration options and resets
 *      any instances of the image.
 *
 * Results:
 *      A standard Tcl return value.  If TCL_ERROR is returned then
 *      an error message is left in masterPtr->interp->result.
 *
 * Side effects:
 *      Existing instances of the image will be redisplayed to match
 *      the new configuration options.
 *
 *      If any error occurs, the state of *masterPtr is restored to
 *      previous state.
 *
 *----------------------------------------------------------------------
 */

static int
TkimgXpmConfigureMaster(
    PixmapMaster *masterPtr,    /* Pointer to data structure describing
                                 * overall pixmap image to (reconfigure). */
    int objc,                   /* Number of entries in argv. */
    Tcl_Obj *const *objv,       /* Pairs of configuration options for image. */
    int flags                   /* Flags to pass to Tk_ConfigureWidget,
                                 * such as TK_CONFIG_ARGV_ONLY. */
) {
    PixmapInstance *instancePtr;
    char * oldData, * oldFile;

    oldData = masterPtr->dataString;
    oldFile = masterPtr->fileString;

    if (Tk_ConfigureWidget(masterPtr->interp, Tk_MainWindow(masterPtr->interp),
            configSpecs, objc, (void *)objv, (char *) masterPtr, flags|TK_CONFIG_OBJS)
            != TCL_OK) {
        return TCL_ERROR;
    }

    if (masterPtr->dataString != NULL ||
        masterPtr->fileString != NULL) {
        if (TkimgXpmGetData(masterPtr->interp, masterPtr) != TCL_OK) {
            goto error;
        }
    } else {
        Tcl_AppendResult(masterPtr->interp,
            "must specify one of -data or -file", (char *)NULL);
        goto error;
    }

    /*
     * Cycle through all of the instances of this image, regenerating
     * the information for each instance.  Then force the image to be
     * redisplayed everywhere that it is used.
     */
    for (instancePtr = masterPtr->instancePtr; instancePtr != NULL;
        instancePtr = instancePtr->nextPtr) {
        TkimgXpmConfigureInstance(instancePtr);
    }

    if (masterPtr->data) {
        Tk_ImageChanged(masterPtr->tkMaster, 0, 0,
            masterPtr->size[0], masterPtr->size[1],
            masterPtr->size[0], masterPtr->size[1]);
    } else {
        Tk_ImageChanged(masterPtr->tkMaster, 0, 0, 0, 0, 0, 0);
    }

    return TCL_OK;

  error:
    /* Restore it to the original (possible valid) mode */
    if (masterPtr->dataString && masterPtr->dataString != oldData) {
        ckfree(masterPtr->dataString);
    }
    if (masterPtr->fileString && masterPtr->fileString != oldFile) {
        ckfree(masterPtr->fileString);
    }
    masterPtr->dataString = oldData;
    masterPtr->fileString = oldFile;
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * TkimgXpmGetData --
 *
 *      Given a file name or ASCII string, this procedure parses the
 *      file or string contents to produce binary data for a pixmap.
 *
 * Results:
 *      If the pixmap description was parsed successfully then the data
 *      is read into an array of strings. This array will later be used
 *      to create X Pixmaps for each instance.
 *
 * Side effects:
 *      The masterPtr->data array is allocated when successful. Contents of
 *      *masterPtr is changed only when successful.
 *----------------------------------------------------------------------
 */

static int
TkimgXpmGetData(
    Tcl_Interp *interp,                 /* For reporting errors. */
    PixmapMaster *masterPtr
) {
    const char ** data = NULL;
    int  isAllocated = 0;               /* do we need to free "data"? */
    Tcl_Size listArgc;
    const char ** listArgv = NULL;
    int numLines;
    int size[2];
    int cpp;
    int ncolors;
    int code = TCL_OK;

    if (masterPtr->fileString != NULL) {
        if (Tcl_IsSafe(interp)) {
            Tcl_AppendResult(interp, "can't get image from a file in a",
                    " safe interpreter", (char *) NULL);
            return TCL_ERROR;
        }
        data = TkimgXpmGetDataFromFile(interp, masterPtr->fileString, &numLines);
        isAllocated = 1;
    }
    else if (masterPtr->dataString != NULL) {
        data = TkimgXpmGetDataFromString(interp,masterPtr->dataString,&numLines);
        isAllocated = 1;
    }
    else {
        /* Should have been enforced by TkimgXpmConfigureMaster() */
        Tcl_Panic("TkimgXpmGetData(): -data and -file are all NULL");
    }

    if (data == NULL) {
        /* nothing has been allocated yet. Don't need to goto done */
        return TCL_ERROR;
    }

    /* Parse the first line of the data and get info about this pixmap */
    if (Tcl_SplitList(interp, data[0], &listArgc, &listArgv) != TCL_OK) {
        code = TCL_ERROR; goto done;
    }

    if (listArgc < 4) { /* file format error */
        code = TCL_ERROR; goto done;
    }

    if (Tcl_GetInt(interp, listArgv[0], &size[0]) != TCL_OK) {
        code = TCL_ERROR; goto done;
    }
    if (Tcl_GetInt(interp, listArgv[1], &size[1]) != TCL_OK) {
        code = TCL_ERROR; goto done;
    }
    if (Tcl_GetInt(interp, listArgv[2], &ncolors) != TCL_OK) {
        code = TCL_ERROR; goto done;
    }
    if (Tcl_GetInt(interp, listArgv[3], &cpp) != TCL_OK) {
        code = TCL_ERROR; goto done;
    }

    if (isAllocated) {
        if (numLines != size[1] + ncolors + 1) {
            /* the number of lines read from the file/data
             * is not the same as specified in the data
             */
            code = TCL_ERROR; goto done;
        }
    }

  done:
    if (code == TCL_OK) {
        if (masterPtr->isDataAlloced && masterPtr->data) {
            ckfree((char*)masterPtr->data);
        }
        masterPtr->isDataAlloced = isAllocated;
        masterPtr->data = (char **) data;
        masterPtr->size[0] = size[0];
        masterPtr->size[1] = size[1];
        masterPtr->cpp = cpp;
        masterPtr->ncolors = ncolors;
    } else {
        if (isAllocated && data) {
            ckfree((char*)data);
        }

        Tcl_ResetResult(interp);
        Tcl_AppendResult(interp, "File format error", (char *)NULL);
    }

    if (listArgv) {
        ckfree((char*)listArgv);
    }

    return code;
}

static const char **
TkimgXpmGetDataFromString(
    Tcl_Interp * interp,
    char * string,
    int * numLines_return
) {
    int quoted;
    char * p, * list;
    Tcl_Size numLines;
    const char ** data;

    /* skip the leading blanks (leading blanks are not defined in the
     * the XPM definition, but skipping them shouldn't hurt. Also, the ability
     * to skip the leading blanks is good for using in-line XPM data in TCL
     * scripts
     */
    while (isspace(UCHAR(*string))) {
        ++ string;
    }

    /* parse the header */
    if (strncmp("/* XPM", string, 6) != 0) {
        goto error;
    }

    /* strip the comments */
    for (quoted = 0, p=string; *p;) {
        if (!quoted) {
            if (*p == '"') {
                quoted = 1;
                ++ p;
                continue;
            }

            if (*p == '/' && *(p+1) == '*') {
                *p++ = ' ';
                *p++ = ' ';
                while (1) {
                    if (*p == 0) {
                        break;
                    }
                    if (*p == '*' && *(p+1) == '/') {
                        *p++ = ' ';
                        *p++ = ' ';
                        break;
                    }
                    *p++ = ' ';
                }
                continue;
            }
            ++ p;
        } else {
            if (*p == '"') {
                quoted = 0;
            }
            ++ p;
        }
    }

    /* Search for the opening brace */
    for (p=string; *p;) {
        if (*p != '{') {
            ++ p;
        } else {
            ++p;
            break;
        }
    }

    /* Change the buffer in to a proper TCL list */
    quoted = 0;
    list = p;

    while (*p) {
        if (!quoted) {
            if (*p == '"') {
                quoted = 1;
                ++ p;
                continue;
            }

            if (isspace(UCHAR(*p))) {
                *p = ' ';
            }
            else if (*p == ',') {
                *p = ' ';
            }
            else if (*p == '}') {
                *p = 0;
                break;
            }
            ++p;
        }
        else {
            if (*p == '"') {
                quoted = 0;
            }
            ++ p;
        }
    }

    /* The following code depends on the fact that Tcl_SplitList
     * strips away double quoates inside a list: ie:
     * if string == "\"1\" \"2\"" then
     *          list[0] = "1"
     *          list[1] = "2"
     * and NOT
     *
     *          list[0] = "\"1\""
     *          list[1] = "\"2\""
     */
    if (Tcl_SplitList(interp, list, &numLines, &data) != TCL_OK) {
        goto error;
    } else {
        if (numLines == 0) {
            /* error: empty data? */
            if (data != NULL) {
                ckfree((char*)data);
                goto error;
            }
        }
        * numLines_return = numLines;
        return data;
    }

  error:
    Tcl_AppendResult(interp, "File format error", (char *)NULL);
    return (const char**) NULL;
}

static const char **
TkimgXpmGetDataFromFile(
    Tcl_Interp * interp,
    char * fileName,
    int * numLines_return
) {
    Tcl_Channel chan;
    int size;
    const char ** data = (const char **) NULL;
    char *cmdBuffer = NULL;

    chan = tkimg_OpenFileChannel(interp, fileName, "r");
    if (!chan) {
        return (const char **) NULL;
    }

    size = Tcl_Seek(chan, 0, SEEK_END);
    if (size > 0) {
        Tcl_Seek(chan, 0, SEEK_SET);
        cmdBuffer = (char *) attemptckalloc(size+1);
        if (cmdBuffer == NULL) {
            Tcl_AppendResult (interp, "Unable to allocate memory for command buffer.", (char *) NULL);
            goto error;
        }
        size = Tcl_Read(chan, cmdBuffer, size);
    }
    if (Tcl_Close(interp, chan) != TCL_OK) {
        goto error;
    }
    if (size < 0) {
        Tcl_AppendResult(interp, fileName, ": ",
                Tcl_PosixError(interp), (char *)NULL);
        goto error;
    }
    cmdBuffer[size] = 0;

    data = TkimgXpmGetDataFromString(interp, cmdBuffer, numLines_return);
    error:
    if (cmdBuffer) {
        ckfree(cmdBuffer);
    }
    return data;
}


static char *
GetType(
    char * colorDefn,
    int  * type_ret
) {
    char * p = colorDefn;

    /* skip white spaces */
    while (*p && isspace(UCHAR(*p))) {
        p ++;
    }

    /* parse the type */
    if (p[0] != '\0' && p[0] == 'm' &&
        p[1] != '\0' && isspace(UCHAR(p[1]))) {
        *type_ret = XPM_MONO;
        p += 2;
    }
    else if (p[0] != '\0' && p[0] == 'g' &&
             p[1] != '\0' && p[1] == '4' &&
             p[2] != '\0' && isspace(UCHAR(p[2]))) {
        *type_ret = XPM_GRAY_4;
        p += 3;
    }
    else if (p[0] != '\0' && p[0] == 'g' &&
             p[1] != '\0' && isspace(UCHAR(p[1]))) {
        *type_ret = XPM_GRAY;
        p += 2;
    }
    else if (p[0] != '\0' && p[0] == 'c' &&
             p[1] != '\0' && isspace(UCHAR(p[1]))) {
        *type_ret = XPM_COLOR;
        p += 2;
    }
    else if (p[0] != '\0' && p[0] == 's' &&
             p[1] != '\0' && isspace(UCHAR(p[1]))) {
        *type_ret = XPM_SYMBOLIC;
        p += 2;
    }
    else {
        *type_ret = XPM_UNKNOWN;
        return NULL;
    }

    return p;
}

/*
 * colorName is guaranteed to be big enough
 */

static char *
GetColor(
    char * colorDefn,
    char * colorName,           /* if found, name is copied to this array */
    int  * type_ret
) {
    int type;
    char * p;

    if (!colorDefn) {
        return NULL;
    }

    if ((colorDefn = GetType(colorDefn, &type)) == NULL) {
        /* unknown type */
        return NULL;
    }
    else {
        *type_ret = type;
    }

    /* skip white spaces */
    while (*colorDefn && isspace(UCHAR(*colorDefn))) {
        colorDefn ++;
    }

    p = colorName;

    while (1) {
        int dummy;

        while (*colorDefn && !isspace(UCHAR(*colorDefn))) {
            *p++ = *colorDefn++;
        }

        if (!*colorDefn) {
            break;
        }

        if (GetType(colorDefn, &dummy) == NULL) {
            /* the next string should also be considered as a part of a color
             * name */

            while (*colorDefn && isspace(UCHAR(*colorDefn))) {
                *p++ = *colorDefn++;
            }
        } else {
            break;
        }
        if (!*colorDefn) {
            break;
        }
    }

    /* Mark the end of the colorName */
    *p = '\0';

    return colorDefn;
}

/*----------------------------------------------------------------------
 * TkimgXpmGetPixmapFromData --
 *
 *      Creates a pixmap for an image instance.
 *----------------------------------------------------------------------
 */

static void
TkimgXpmGetPixmapFromData(
    Tcl_Interp * interp,
    PixmapMaster *masterPtr,
    PixmapInstance *instancePtr
) {
    XImage * image = NULL, * mask = NULL;
    int depth, i, j, k, lOffset, isTransp = 0, isMono;
    ColorStruct * colors;

    depth = Tk_Depth(instancePtr->tkwin);

    switch ((Tk_Visual(instancePtr->tkwin))->class) {
      case StaticGray:
      case GrayScale:
        isMono = 1;
        break;
      default:
        isMono = 0;
    }

    TkimgXpmAllocTmpBuffer(masterPtr, instancePtr, &image, &mask);

    /*
     * Parse the colors
     */
    lOffset = 1;
    colors = (ColorStruct*)attemptckalloc(sizeof(ColorStruct)*masterPtr->ncolors);
    if (colors == NULL) {
        Tcl_AppendResult (interp, "Unable to allocate memory for ColorStruct.", (char *) NULL);
        return;
    }

    /*
     * Initialize the color structures
     */
    for (i=0; i<masterPtr->ncolors; i++) {
        colors[i].colorPtr = NULL;
        if (masterPtr->cpp == 1) {
            colors[i].c = 0;
        } else {
            colors[i].cstring = (char*)attemptckalloc(masterPtr->cpp);
            if (colors[i].cstring == NULL) {
                Tcl_AppendResult (interp, "Unable to allocate memory for color string.", (char *) NULL);
                return;
            }
            colors[i].cstring[0] = 0;
        }
    }

    for (i=0; i<masterPtr->ncolors; i++) {
        char * colorDefn;               /* the color definition line */
        char * colorName;               /* temp place to hold the color name
                                         * defined for one type of visual */
        char * useName;                 /* the color name used for this
                                         * color. If there are many names
                                         * defined, choose the name that is
                                         * "best" for the target visual
                                         */
        int found;

        colorDefn = masterPtr->data[i+lOffset]+masterPtr->cpp;
        colorName = (char*)attemptckalloc(strlen(colorDefn));
        useName   = (char*)attemptckalloc(strlen(colorDefn));
        if (colorName == NULL) {
            Tcl_AppendResult (interp, "Unable to allocate memory for color name.", (char *) NULL);
            return;
        }
        if (useName == NULL) {
            Tcl_AppendResult (interp, "Unable to allocate memory for color name.", (char *) NULL);
            return;
        }
        found = 0;

        while (colorDefn && *colorDefn) {
            int type;

            if ((colorDefn=GetColor(colorDefn, colorName, &type)) == NULL) {
                break;
            }
            if (colorName[0] == '\0') {
                continue;
            }

            switch (type) {
              case XPM_MONO:
                if (isMono && depth == 1) {
                    strcpy(useName, colorName);
                    found = 1; goto gotcolor;
                }
                break;
              case XPM_GRAY_4:
                if (isMono && depth == 4) {
                    strcpy(useName, colorName);
                    found = 1; goto gotcolor;
                }
                break;
              case XPM_GRAY:
                if (isMono && depth > 4) {
                    strcpy(useName, colorName);
                    found = 1; goto gotcolor;
                }
                break;
              case XPM_COLOR:
                if (!isMono) {
                    strcpy(useName, colorName);
                    found = 1; goto gotcolor;
                }
                break;
            }
            if (type != XPM_SYMBOLIC && type != XPM_UNKNOWN) {
                if (!found) {                   /* use this color as default */
                    strcpy(useName, colorName);
                    found = 1;
                }
            }
        }

      gotcolor:
        if (masterPtr->cpp == 1) {
            colors[i].c = masterPtr->data[i+lOffset][0];
        } else {
            strncpy(colors[i].cstring, masterPtr->data[i+lOffset],
                (size_t)masterPtr->cpp);
        }

        if (found) {
            if (strncasecmp(useName, "none", 5) != 0) {
                colors[i].colorPtr = Tk_GetColor(interp,
                    instancePtr->tkwin, Tk_GetUid(useName));
                if (colors[i].colorPtr == NULL) {
                    colors[i].colorPtr = Tk_GetColor(interp,
                        instancePtr->tkwin, Tk_GetUid("black"));
                }
            }
        } else {
            colors[i].colorPtr = Tk_GetColor(interp,
                instancePtr->tkwin, Tk_GetUid("black"));
        }

        ckfree(colorName);
        ckfree(useName);
    }

    lOffset += masterPtr->ncolors;

    /*
     * Parse the main body of the image
     */
    for (i=0; i<masterPtr->size[1]; i++) {
        char * p = masterPtr->data[i+lOffset];

        for (j=0; j<masterPtr->size[0]; j++) {
            if (masterPtr->cpp == 1) {
                for (k=0; k<masterPtr->ncolors; k++) {
                    if (*p == colors[k].c) {
                        TkimgXpmSetPixel(instancePtr, image, mask, j, i,
                                colors[k].colorPtr, &isTransp);
                        break;
                    }
                }
                if (*p) {
                    p++;
                }
            } else {
                for (k=0; k<masterPtr->ncolors; k++) {
                    if (strncmp(p, colors[k].cstring,
                            (size_t)masterPtr->cpp) == 0) {
                        TkimgXpmSetPixel(instancePtr, image, mask, j, i,
                                colors[k].colorPtr, &isTransp);
                        break;
                    }
                }
                for (k=0; *p && k<masterPtr->cpp; k++) {
                    p++;
                }
            }
        }
    }

    instancePtr->colors = colors;

    TkimgXpmRealizePixmap(masterPtr, instancePtr, image, mask, isTransp);
    TkimgXpmFreeTmpBuffer(masterPtr, instancePtr, image, mask);
}

/*
 *----------------------------------------------------------------------
 *
 * TkimgXpmConfigureInstance --
 *
 *      This procedure is called to create displaying information for
 *      a pixmap image instance based on the configuration information
 *      in the master.  It is invoked both when new instances are
 *      created and when the master is reconfigured.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Generates errors via Tk_BackgroundError if there are problems
 *      in setting up the instance.
 *
 *----------------------------------------------------------------------
 */

static void
TkimgXpmConfigureInstance(
    PixmapInstance *instancePtr         /* Instance to reconfigure. */
) {
    PixmapMaster *masterPtr = instancePtr->masterPtr;

    if (instancePtr->pixmap != None) {
        Tk_FreePixmap(Tk_Display(instancePtr->tkwin), instancePtr->pixmap);
    }
    TkimgXpmFreeInstanceData(instancePtr, 0);

    if (instancePtr->colors != NULL) {
        int i;
        for (i=0; i<masterPtr->ncolors; i++) {
            if (instancePtr->colors[i].colorPtr != NULL) {
                Tk_FreeColor(instancePtr->colors[i].colorPtr);
            }
            if (masterPtr->cpp != 1) {
                ckfree(instancePtr->colors[i].cstring);
            }
        }
        ckfree((char*)instancePtr->colors);
    }

    if (Tk_WindowId(instancePtr->tkwin) == None) {
        Tk_MakeWindowExist(instancePtr->tkwin);
    }

    /*
     * Assumption: masterPtr->data is always non NULL (enforced by
     * TkimgXpmConfigureMaster()). Also, the data must be in a valid
     * format (partially enforced by TkimgXpmConfigureMaster(), see comments
     * inside that function).
     */
    TkimgXpmGetPixmapFromData(masterPtr->interp, masterPtr, instancePtr);
}

/*
 *--------------------------------------------------------------
 *
 * TkimgXpmObjCmd --
 *
 *      This procedure is invoked to process the Tcl command
 *      that corresponds to an image managed by this module.
 *      See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *--------------------------------------------------------------
 */

static int
TkimgXpmObjCmd(
    void *clientData,   /* Information about button widget. */
    Tcl_Interp *interp,         /* Current interpreter. */
    int objc,                   /* Number of arguments. */
    Tcl_Obj *const *objv                /* Arguments. */
) {
    PixmapMaster *masterPtr = (PixmapMaster *) clientData;
    int c, code;
    size_t length;

    if (objc < 2) {
        Tcl_AppendResult(interp, "wrong # args: should be \"",
                Tcl_GetString(objv[0]), " option ?arg arg ...?\"",
                (char *) NULL);
        return TCL_ERROR;
    }
    c = Tcl_GetString(objv[1])[0];
    length = strlen(Tcl_GetString(objv[1]));

    if ((c == 'c') && (strncmp(Tcl_GetString(objv[1]), "cget", length) == 0)
            && (length >= 2)) {
        if (objc != 3) {
            Tcl_AppendResult(interp, "wrong # args: should be \"",
                    Tcl_GetString(objv[0]), " cget option\"",
                    (char *) NULL);
            return TCL_ERROR;
        }
        return Tk_ConfigureValue(interp, Tk_MainWindow(interp), configSpecs,
                (char *) masterPtr, Tcl_GetString(objv[2]), 0);
    } else if ((c == 'c') && (strncmp(Tcl_GetString(objv[1]), "configure", length) == 0)
            && (length >= 2)) {
        if (objc == 2) {
            code = Tk_ConfigureInfo(interp, Tk_MainWindow(interp),
                    configSpecs, (char *) masterPtr, (char *) NULL, 0);
        } else if (objc == 3) {
            code = Tk_ConfigureInfo(interp, Tk_MainWindow(interp),
                    configSpecs, (char *) masterPtr, Tcl_GetString(objv[2]), 0);
        } else {
            code = TkimgXpmConfigureMaster(masterPtr, objc-2, objv+2,
                    TK_CONFIG_ARGV_ONLY);
        }
        return code;
    } else if ((c == 'r') && (strncmp(Tcl_GetString(objv[1]), "refcount", length) == 0)) {
        /*
         * The "refcount" command is for debugging only
         */
        PixmapInstance *instancePtr;
        int count = 0;
        char buff[30];

        if (objc != 1) {
            Tcl_AppendResult(interp, "wrong # args: should be \"",
                    Tcl_GetString(objv[0]), "\"", (char *) NULL);
            return TCL_ERROR;
        }
        for (instancePtr=masterPtr->instancePtr; instancePtr;
             instancePtr = instancePtr->nextPtr) {
            count += instancePtr->refCount;
        }
        tkimg_snprintf(buff, 30, "%d", count);
        Tcl_AppendResult(interp, buff, (char *) NULL);
        return TCL_OK;
    } else {
        Tcl_AppendResult(interp, "bad option \"", Tcl_GetString(objv[1]),
            "\": must be cget, configure or refcount", (char *) NULL);
        return TCL_ERROR;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkimgXpmGet --
 *
 *      This procedure is called for each use of a pixmap image in a
 *      widget.
 *
 * Results:
 *      The return value is a token for the instance, which is passed
 *      back to us in calls to TkimgXpmDisplay and TkimgXpmFre.
 *
 * Side effects:
 *      A data structure is set up for the instance (or, an existing
 *      instance is re-used for the new one).
 *
 *----------------------------------------------------------------------
 */

static ClientData
TkimgXpmGet(
    Tk_Window tkwin,            /* Window in which the instance will be
                                 * used. */
    void *masterData    /* Pointer to our master structure for the
                                 * image. */
) {
    PixmapMaster *masterPtr = (PixmapMaster *) masterData;
    PixmapInstance *instancePtr;

    /*
     * See if there is already an instance for this window.  If so
     * then just re-use it.
     */

    for (instancePtr = masterPtr->instancePtr; instancePtr != NULL;
            instancePtr = instancePtr->nextPtr) {
        if (instancePtr->tkwin == tkwin) {
            instancePtr->refCount++;
            return (ClientData) instancePtr;
        }
    }

    /*
     * The image isn't already in use in this window.  Make a new
     * instance of the image.
     */
    instancePtr = (PixmapInstance *) attemptckalloc(sizeof(PixmapInstance));
    if (instancePtr == NULL) {
        Tcl_AppendResult (masterPtr->interp, "Unable to allocate memory for PixmapInstance.", (char *) NULL);
        return NULL;
    }
    instancePtr->refCount = 1;
    instancePtr->masterPtr = masterPtr;
    instancePtr->tkwin = tkwin;
    instancePtr->pixmap = None;
    instancePtr->nextPtr = masterPtr->instancePtr;
    instancePtr->colors = NULL;
    masterPtr->instancePtr = instancePtr;

    TkimgInitPixmapInstance(masterPtr, instancePtr);
    TkimgXpmConfigureInstance(instancePtr);

    /*
     * If this is the first instance, must set the size of the image.
     */
    if (instancePtr->nextPtr == NULL) {
        if (masterPtr->data) {
            Tk_ImageChanged(masterPtr->tkMaster, 0, 0,
                masterPtr->size[0], masterPtr->size[1],
                masterPtr->size[0], masterPtr->size[1]);
        } else {
            Tk_ImageChanged(masterPtr->tkMaster, 0, 0, 0, 0, 0, 0);
        }
    }

    return (ClientData) instancePtr;
}

/*
 *----------------------------------------------------------------------
 *
 * TkimgXpmDisplay --
 *
 *      This procedure is invoked to draw a pixmap image.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      A portion of the image gets rendered in a pixmap or window.
 *
 *----------------------------------------------------------------------
 */

static void
TkimgXpmDisplay(
    void *clientData,   /* Pointer to PixmapInstance structure for
                                 * for instance to be displayed. */
    Display *display,           /* Display on which to draw image. */
    Drawable drawable,          /* Pixmap or window in which to draw image. */
    int imageX, int imageY,     /* Upper-left corner of region within image
                                 * to draw. */
    int width, int height,      /* Dimensions of region within image to draw.*/
    int drawableX, int drawableY/* Coordinates within drawable that
                                 * correspond to imageX and imageY. */
) {
    TkimgpXpmDisplay(clientData, display, drawable, imageX, imageY, width,
        height, drawableX, drawableY);
}

/*
 *----------------------------------------------------------------------
 *
 * TkimgXpmFree --
 *
 *      This procedure is called when a widget ceases to use a
 *      particular instance of an image.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Internal data structures get cleaned up.
 *
 *----------------------------------------------------------------------
 */

static void
TkimgXpmFree(
    void *clientData,   /* Pointer to PixmapInstance structure for
                                 * for instance to be displayed. */
    Display *display            /* Display containing window that used image.*/
) {
    PixmapInstance *instancePtr = (PixmapInstance *) clientData;
    PixmapInstance *prevPtr;

    instancePtr->refCount--;
    if (instancePtr->refCount > 0) {
        return;
    }

    /*
     * There are no more uses of the image within this widget.  Free
     * the instance structure.
     */
    if (instancePtr->pixmap != None) {
        Tk_FreePixmap(display, instancePtr->pixmap);
    }
    TkimgXpmFreeInstanceData(instancePtr, 1);

    if (instancePtr->colors != NULL) {
        int i;
        for (i=0; i<instancePtr->masterPtr->ncolors; i++) {
            if (instancePtr->colors[i].colorPtr != NULL) {
                Tk_FreeColor(instancePtr->colors[i].colorPtr);
            }
            if (instancePtr->masterPtr->cpp != 1) {
                ckfree(instancePtr->colors[i].cstring);
            }
        }
        ckfree((char*)instancePtr->colors);
    }

    if (instancePtr->masterPtr->instancePtr == instancePtr) {
        instancePtr->masterPtr->instancePtr = instancePtr->nextPtr;
    } else {
        for (prevPtr = instancePtr->masterPtr->instancePtr;
                prevPtr->nextPtr != instancePtr; prevPtr = prevPtr->nextPtr) {
            /* Empty loop body */
        }
        prevPtr->nextPtr = instancePtr->nextPtr;
    }
    ckfree((char *) instancePtr);
}

/*
 *----------------------------------------------------------------------
 *
 * TkimgXpmDelete --
 *
 *      This procedure is called by the image code to delete the
 *      master structure for an image.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Resources associated with the image get freed.
 *
 *----------------------------------------------------------------------
 */

static void
TkimgXpmDelete(
    void *masterData    /* Pointer to PixmapMaster structure for
                                 * image.  Must not have any more instances. */
) {
    PixmapMaster *masterPtr = (PixmapMaster *) masterData;

    if (masterPtr->instancePtr != NULL) {
        Tcl_Panic("tried to delete pixmap image when instances still exist");
    }
    masterPtr->tkMaster = NULL;
    if (masterPtr->imageCmd != NULL) {
        Tcl_DeleteCommand(masterPtr->interp,
                Tcl_GetCommandName(masterPtr->interp, masterPtr->imageCmd));
    }
    if (masterPtr->isDataAlloced && masterPtr->data != NULL) {
        ckfree((char*)masterPtr->data);
        masterPtr->data = NULL;
    }

    Tk_FreeOptions(configSpecs, (char *) masterPtr, (Display *) NULL, 0);
    ckfree((char *) masterPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * TkimgXpmCmdDeletedProc --
 *
 *      This procedure is invoked when the image command for an image
 *      is deleted.  It deletes the image.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The image is deleted.
 *
 *----------------------------------------------------------------------
 */

static void
TkimgXpmCmdDeletedProc(
    void *clientData    /* Pointer to PixmapMaster structure for
                                 * image. */
) {
    PixmapMaster *masterPtr = (PixmapMaster *) clientData;

    masterPtr->imageCmd = NULL;
    if (masterPtr->tkMaster != NULL) {
        Tk_DeleteImage(masterPtr->interp, Tk_NameOfImage(masterPtr->tkMaster));
    }
}

/*
 * Package management. Initialization of stub information.
 */

/*
 *----------------------------------------------------------------------------
 *
 * Tkimgpixmap_Init --
 *
 *  Initialisation routine for loadable module
 *
 * Results:
 *  None.
 *
 * Side effects:
 *  Creates commands in the interpreter, loads package.
 *
 *----------------------------------------------------------------------------
 */

int
Tkimgpixmap_Init(
      Tcl_Interp *interp        /* Interpreter to initialise. */
) {
    static int initialized = 0;

    if (Tcl_InitStubs(interp, "8.6-", 0) == NULL) {
        return TCL_ERROR;
    }
    if (Tk_InitStubs(interp, "8.6-", 0) == NULL) {
        return TCL_ERROR;
    }
    if (Tkimg_InitStubs(interp, TKIMG_VERSION, 0) == NULL) {
        return TCL_ERROR;
    }

#ifndef TCL_MAC
    if (!initialized) {
        Tk_CreateImageType(&imgPixmapImageType);
        initialized = 1;
    }
#endif

    /*
     * At last provide the package ...
     */

    if (Tcl_PkgProvide(interp, PACKAGE_TCLNAME, PACKAGE_VERSION) != TCL_OK) {
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------------
 *
 * Tkimgpixmap_SafeInit --
 *
 *  Initialisation routine for loadable module in a safe interpreter.
 *
 * Results:
 *  None.
 *
 * Side effects:
 *  Creates commands in the interpreter,
 *  loads xml package.
 *
 *----------------------------------------------------------------------------
 */

int
Tkimgpixmap_SafeInit(
      Tcl_Interp *interp        /* Interpreter to initialise. */
) {
    return Tkimgpixmap_Init (interp);
}
