diff options
author | William Joye <wjoye@cfa.harvard.edu> | 2016-10-18 17:31:55 (GMT) |
---|---|---|
committer | William Joye <wjoye@cfa.harvard.edu> | 2016-10-18 17:31:55 (GMT) |
commit | 39e34335fb6eb6eaf2b7ee51ccf172006dd46fbb (patch) | |
tree | 8e5374666c7f0b3017176ec9d6e6b6eae0dcabac /tk8.6/generic/tkImgGIF.c | |
parent | 066971b1e6e77991d9161bb0216a63ba94ea04f9 (diff) | |
parent | 6b095f3c8521ca7215e6ff5dcbada52b197ef7d0 (diff) | |
download | blt-39e34335fb6eb6eaf2b7ee51ccf172006dd46fbb.zip blt-39e34335fb6eb6eaf2b7ee51ccf172006dd46fbb.tar.gz blt-39e34335fb6eb6eaf2b7ee51ccf172006dd46fbb.tar.bz2 |
Merge commit '6b095f3c8521ca7215e6ff5dcbada52b197ef7d0' as 'tk8.6'
Diffstat (limited to 'tk8.6/generic/tkImgGIF.c')
-rw-r--r-- | tk8.6/generic/tkImgGIF.c | 2239 |
1 files changed, 2239 insertions, 0 deletions
diff --git a/tk8.6/generic/tkImgGIF.c b/tk8.6/generic/tkImgGIF.c new file mode 100644 index 0000000..1c28b54 --- /dev/null +++ b/tk8.6/generic/tkImgGIF.c @@ -0,0 +1,2239 @@ +/* + * tkImgGIF.c -- + * + * A photo image file handler for GIF files. Reads 87a and 89a GIF files. + * At present, there only is a file write function. GIF images may be + * read using the -data option of the photo image. The data may be given + * as a binary string in a Tcl_Obj or by representing the data as BASE64 + * encoded ascii. Derived from the giftoppm code found in the pbmplus + * package and tkImgFmtPPM.c in the tk4.0b2 distribution. + * + * Copyright (c) Reed Wade (wade@cs.utk.edu), University of Tennessee + * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * Copyright (c) 1997 Australian National University + * Copyright (c) 2005-2010 Donal K. Fellows + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * This file also contains code from the giftoppm program, which is + * copyrighted as follows: + * + * +--------------------------------------------------------------------+ + * | Copyright 1990, David Koblas. | + * | Permission to use, copy, modify, and distribute this software | + * | and its documentation for any purpose and without fee is hereby | + * | granted, provided that the above copyright notice appear in all | + * | copies and that both that copyright notice and this permission | + * | notice appear in supporting documentation. This software is | + * | provided "as is" without express or implied warranty. | + * +--------------------------------------------------------------------+ + */ + +#include "tkInt.h" + +/* + * GIF's are represented as data in either binary or base64 format. base64 + * strings consist of 4 6-bit characters -> 3 8 bit bytes. A-Z, a-z, 0-9, + + * and / represent the 64 values (in order). '=' is a trailing padding char + * when the un-encoded data is not a multiple of 3 bytes. We'll ignore white + * space when encountered. Any other invalid character is treated as an EOF + */ + +#define GIF_SPECIAL (256) +#define GIF_PAD (GIF_SPECIAL+1) +#define GIF_SPACE (GIF_SPECIAL+2) +#define GIF_BAD (GIF_SPECIAL+3) +#define GIF_DONE (GIF_SPECIAL+4) + +/* + * structure to "mimic" FILE for Mread, so we can look like fread. The decoder + * state keeps track of which byte we are about to read, or EOF. + */ + +typedef struct mFile { + unsigned char *data; /* mmencoded source string */ + int c; /* bits left over from previous character */ + int state; /* decoder state (0-4 or GIF_DONE) */ + int length; /* Total amount of bytes in data */ +} MFile; + +/* + * Non-ASCII encoding support: + * Most data in a GIF image is binary and is treated as such. However, a few + * key bits are stashed in ASCII. If we try to compare those pieces to the + * char they represent, it will fail on any non-ASCII (eg, EBCDIC) system. To + * accomodate these systems, we test against the numeric value of the ASCII + * characters instead of the characters themselves. This is encoding + * independant. + */ + +static const char GIF87a[] = { /* ASCII GIF87a */ + 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x00 +}; +static const char GIF89a[] = { /* ASCII GIF89a */ + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x00 +}; +#define GIF_TERMINATOR 0x3b /* ASCII ; */ +#define GIF_EXTENSION 0x21 /* ASCII ! */ +#define GIF_START 0x2c /* ASCII , */ + +/* + * Flags used to notify that we've got inline data instead of a file to read + * from. Note that we need to figure out which type of inline data we've got + * before handing off to the GIF reading code; this is done in StringReadGIF. + */ + +#define INLINE_DATA_BINARY ((const char *) 0x01) +#define INLINE_DATA_BASE64 ((const char *) 0x02) + +/* + * HACK ALERT!! HACK ALERT!! HACK ALERT!! + * This code is hard-wired for reading from files. In order to read from a + * data stream, we'll trick fread so we can reuse the same code. 0==from file; + * 1==from base64 encoded data; 2==from binary data + */ + +typedef struct { + const char *fromData; + unsigned char workingBuffer[280]; + struct { + int bytes; + int done; + unsigned int window; + int bitsInWindow; + unsigned char *c; + } reader; +} GIFImageConfig; + +/* + * Type of a function used to do the writing to a file or buffer when + * serializing in the GIF format. + */ + +typedef int (WriteBytesFunc) (ClientData clientData, const char *bytes, + int byteCount); + +/* + * The format record for the GIF file format: + */ + +static int FileMatchGIF(Tcl_Channel chan, const char *fileName, + Tcl_Obj *format, int *widthPtr, int *heightPtr, + Tcl_Interp *interp); +static int FileReadGIF(Tcl_Interp *interp, Tcl_Channel chan, + const char *fileName, Tcl_Obj *format, + Tk_PhotoHandle imageHandle, int destX, int destY, + int width, int height, int srcX, int srcY); +static int StringMatchGIF(Tcl_Obj *dataObj, Tcl_Obj *format, + int *widthPtr, int *heightPtr, Tcl_Interp *interp); +static int StringReadGIF(Tcl_Interp *interp, Tcl_Obj *dataObj, + Tcl_Obj *format, Tk_PhotoHandle imageHandle, + int destX, int destY, int width, int height, + int srcX, int srcY); +static int FileWriteGIF(Tcl_Interp *interp, const char *filename, + Tcl_Obj *format, Tk_PhotoImageBlock *blockPtr); +static int StringWriteGIF(Tcl_Interp *interp, Tcl_Obj *format, + Tk_PhotoImageBlock *blockPtr); +static int CommonWriteGIF(Tcl_Interp *interp, ClientData clientData, + WriteBytesFunc *writeProc, Tcl_Obj *format, + Tk_PhotoImageBlock *blockPtr); + +Tk_PhotoImageFormat tkImgFmtGIF = { + "gif", /* name */ + FileMatchGIF, /* fileMatchProc */ + StringMatchGIF, /* stringMatchProc */ + FileReadGIF, /* fileReadProc */ + StringReadGIF, /* stringReadProc */ + FileWriteGIF, /* fileWriteProc */ + StringWriteGIF, /* stringWriteProc */ + NULL +}; + +#define INTERLACE 0x40 +#define LOCALCOLORMAP 0x80 +#define BitSet(byte, bit) (((byte) & (bit)) == (bit)) +#define MAXCOLORMAPSIZE 256 +#define CM_RED 0 +#define CM_GREEN 1 +#define CM_BLUE 2 +#define CM_ALPHA 3 +#define MAX_LWZ_BITS 12 +#define LM_to_uint(a,b) (((b)<<8)|(a)) + +/* + * Prototypes for local functions defined in this file: + */ + +static int DoExtension(GIFImageConfig *gifConfPtr, + Tcl_Channel chan, int label, unsigned char *buffer, + int *transparent); +static int GetCode(Tcl_Channel chan, int code_size, int flag, + GIFImageConfig *gifConfPtr); +static int GetDataBlock(GIFImageConfig *gifConfPtr, + Tcl_Channel chan, unsigned char *buf); +static int ReadColorMap(GIFImageConfig *gifConfPtr, + Tcl_Channel chan, int number, + unsigned char buffer[MAXCOLORMAPSIZE][4]); +static int ReadGIFHeader(GIFImageConfig *gifConfPtr, + Tcl_Channel chan, int *widthPtr, int *heightPtr); +static int ReadImage(GIFImageConfig *gifConfPtr, + Tcl_Interp *interp, unsigned char *imagePtr, + Tcl_Channel chan, int len, int rows, + unsigned char cmap[MAXCOLORMAPSIZE][4], int srcX, + int srcY, int interlace, int transparent); + +/* + * these are for the BASE64 image reader code only + */ + +static int Fread(GIFImageConfig *gifConfPtr, unsigned char *dst, + size_t size, size_t count, Tcl_Channel chan); +static int Mread(unsigned char *dst, size_t size, size_t count, + MFile *handle); +static int Mgetc(MFile *handle); +static int char64(int c); +static void mInit(unsigned char *string, MFile *handle, + int length); + +/* + * Types, defines and variables needed to write and compress a GIF. + */ + +#define LSB(a) ((unsigned char) (((short)(a)) & 0x00FF)) +#define MSB(a) ((unsigned char) (((short)(a)) >> 8)) + +#define GIFBITS 12 +#define HSIZE 5003 /* 80% occupancy */ + +#define DEFAULT_BACKGROUND_VALUE 0xD9 + +typedef struct { + int ssize; + int csize; + int rsize; + unsigned char *pixelOffset; + int pixelSize; + int pixelPitch; + int greenOffset; + int blueOffset; + int alphaOffset; + int num; + unsigned char mapa[MAXCOLORMAPSIZE][3]; +} GifWriterState; + +typedef int (* ifunptr) (GifWriterState *statePtr); + +/* + * Support for compression of GIFs. + */ + +#define MAXCODE(numBits) (((long) 1 << (numBits)) - 1) + +#ifdef SIGNED_COMPARE_SLOW +#define U(x) ((unsigned) (x)) +#else +#define U(x) (x) +#endif + +typedef struct { + int numBits; /* Number of bits/code. */ + long maxCode; /* Maximum code, given numBits. */ + int hashTable[HSIZE]; + unsigned int codeTable[HSIZE]; + long hSize; /* For dynamic table sizing. */ + + /* + * To save much memory, we overlay the table used by compress() with those + * used by decompress(). The tab_prefix table is the same size and type as + * the codeTable. The tab_suffix table needs 2**GIFBITS characters. We get + * this from the beginning of hashTable. The output stack uses the rest of + * hashTable, and contains characters. There is plenty of room for any + * possible stack (stack used to be 8000 characters). + */ + + int freeEntry; /* First unused entry. */ + + /* + * Block compression parameters. After all codes are used up, and + * compression rate changes, start over. + */ + + int clearFlag; + + int offset; + unsigned int inCount; /* Length of input */ + unsigned int outCount; /* # of codes output (for debugging) */ + + /* + * Algorithm: use open addressing double hashing (no chaining) on the + * prefix code / next character combination. We do a variant of Knuth's + * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + * secondary probe. Here, the modular division first probe is gives way to + * a faster exclusive-or manipulation. Also do block compression with an + * adaptive reset, whereby the code table is cleared when the compression + * ratio decreases, but after the table fills. The variable-length output + * codes are re-sized at this point, and a special CLEAR code is generated + * for the decompressor. Late addition: construct the table according to + * file size for noticeable speed improvement on small files. Please + * direct questions about this implementation to ames!jaw. + */ + + int initialBits; + ClientData destination; + WriteBytesFunc *writeProc; + + int clearCode; + int eofCode; + + unsigned long currentAccumulated; + int currentBits; + + /* + * Number of characters so far in this 'packet' + */ + + int accumulatedByteCount; + + /* + * Define the storage for the packet accumulator + */ + + unsigned char packetAccumulator[256]; +} GIFState_t; + +/* + * Definition of new functions to write GIFs + */ + +static int ColorNumber(GifWriterState *statePtr, + int red, int green, int blue); +static void Compress(int initBits, ClientData handle, + WriteBytesFunc *writeProc, ifunptr readValue, + GifWriterState *statePtr); +static int IsNewColor(GifWriterState *statePtr, + int red, int green, int blue); +static void SaveMap(GifWriterState *statePtr, + Tk_PhotoImageBlock *blockPtr); +static int ReadValue(GifWriterState *statePtr); +static WriteBytesFunc WriteToChannel; +static WriteBytesFunc WriteToByteArray; +static void Output(GIFState_t *statePtr, long code); +static void ClearForBlock(GIFState_t *statePtr); +static void ClearHashTable(GIFState_t *statePtr, int hSize); +static void CharInit(GIFState_t *statePtr); +static void CharOut(GIFState_t *statePtr, int c); +static void FlushChar(GIFState_t *statePtr); + +/* + *---------------------------------------------------------------------- + * + * FileMatchGIF -- + * + * This function is invoked by the photo image type to see if a file + * contains image data in GIF format. + * + * Results: + * The return value is 1 if the first characters in file f look like GIF + * data, and 0 otherwise. + * + * Side effects: + * The access position in f may change. + * + *---------------------------------------------------------------------- + */ + +static int +FileMatchGIF( + Tcl_Channel chan, /* The image file, open for reading. */ + const char *fileName, /* The name of the image file. */ + Tcl_Obj *format, /* User-specified format object, or NULL. */ + int *widthPtr, int *heightPtr, + /* The dimensions of the image are returned + * here if the file is a valid raw GIF file. */ + Tcl_Interp *interp) /* not used */ +{ + GIFImageConfig gifConf; + + memset(&gifConf, 0, sizeof(GIFImageConfig)); + return ReadGIFHeader(&gifConf, chan, widthPtr, heightPtr); +} + +/* + *---------------------------------------------------------------------- + * + * FileReadGIF -- + * + * This function is called by the photo image type to read GIF format + * data from a file 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 the interp's result. + * + * Side effects: + * The access position in file f is changed, and new data is added to the + * image given by imageHandle. + * + *---------------------------------------------------------------------- + */ + +static int +FileReadGIF( + Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ + Tcl_Channel chan, /* The image file, open for reading. */ + const char *fileName, /* The name of the image file. */ + Tcl_Obj *format, /* User-specified format object, or NULL. */ + Tk_PhotoHandle imageHandle, /* The photo image to write into. */ + int destX, int destY, /* Coordinates of top-left pixel in photo + * image to be written to. */ + int width, int height, /* Dimensions of block of photo image to be + * written to. */ + int srcX, int srcY) /* Coordinates of top-left pixel to be used in + * image being read. */ +{ + int fileWidth, fileHeight, imageWidth, imageHeight; + unsigned int nBytes; + int index = 0, argc = 0, i, result = TCL_ERROR; + Tcl_Obj **objv; + unsigned char buf[100]; + unsigned char *trashBuffer = NULL; + int bitPixel; + unsigned char colorMap[MAXCOLORMAPSIZE][4]; + int transparent = -1; + static const char *const optionStrings[] = { + "-index", NULL + }; + GIFImageConfig gifConf, *gifConfPtr = &gifConf; + + /* + * Decode the magic used to convey when we're sourcing data from a string + * source and not a file. + */ + + memset(colorMap, 0, MAXCOLORMAPSIZE*4); + memset(gifConfPtr, 0, sizeof(GIFImageConfig)); + if (fileName == INLINE_DATA_BINARY || fileName == INLINE_DATA_BASE64) { + gifConfPtr->fromData = fileName; + fileName = "inline data"; + } + + /* + * Parse the format string to get options. + */ + + if (format && Tcl_ListObjGetElements(interp, format, + &argc, &objv) != TCL_OK) { + return TCL_ERROR; + } + for (i = 1; i < argc; i++) { + int optionIdx; + if (Tcl_GetIndexFromObjStruct(interp, objv[i], optionStrings, + sizeof(char *), "option name", 0, &optionIdx) != TCL_OK) { + return TCL_ERROR; + } + if (i == (argc-1)) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "no value given for \"%s\" option", + Tcl_GetString(objv[i]))); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "OPT_VALUE", NULL); + return TCL_ERROR; + } + if (Tcl_GetIntFromObj(interp, objv[++i], &index) != TCL_OK) { + return TCL_ERROR; + } + } + + /* + * Read the GIF file header and check for some sanity. + */ + + if (!ReadGIFHeader(gifConfPtr, chan, &fileWidth, &fileHeight)) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't read GIF header from file \"%s\"", fileName)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "HEADER", NULL); + return TCL_ERROR; + } + if ((fileWidth <= 0) || (fileHeight <= 0)) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "GIF image file \"%s\" has dimension(s) <= 0", fileName)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "BOGUS_SIZE", NULL); + return TCL_ERROR; + } + + /* + * Get the general colormap information. + */ + + if (Fread(gifConfPtr, buf, 1, 3, chan) != 3) { + return TCL_OK; + } + bitPixel = 2 << (buf[0] & 0x07); + + if (BitSet(buf[0], LOCALCOLORMAP)) { /* Global Colormap */ + if (!ReadColorMap(gifConfPtr, chan, bitPixel, colorMap)) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "error reading color map", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "COLOR_MAP", NULL); + 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)) { + return TCL_OK; + } + + /* + * Make sure we have enough space in the photo image to hold the data from + * the GIF. + */ + + if (Tk_PhotoExpand(interp, imageHandle, + destX + width, destY + height) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Search for the frame from the GIF to display. + */ + + while (1) { + if (Fread(gifConfPtr, buf, 1, 1, chan) != 1) { + /* + * Premature end of image. + */ + + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "premature end of image data for this index", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "PREMATURE_END", + NULL); + goto error; + } + + switch (buf[0]) { + case GIF_TERMINATOR: + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "no image data for this index", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "NO_DATA", NULL); + goto error; + + case GIF_EXTENSION: + /* + * This is a GIF extension. + */ + + if (Fread(gifConfPtr, buf, 1, 1, chan) != 1) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "error reading extension function code in GIF image", + -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "BAD_EXT", + NULL); + goto error; + } + if (DoExtension(gifConfPtr, chan, buf[0], + gifConfPtr->workingBuffer, &transparent) < 0) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "error reading extension in GIF image", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "BAD_EXT", + NULL); + goto error; + } + continue; + case GIF_START: + if (Fread(gifConfPtr, buf, 1, 9, chan) != 9) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "couldn't read left/top/width/height in GIF image", + -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "DIMENSIONS", + NULL); + goto error; + } + break; + default: + /* + * Not a valid start character; ignore it. + */ + + continue; + } + + /* + * We've read the header for a GIF frame. Work out what we are going + * to do about it. + */ + + imageWidth = LM_to_uint(buf[4], buf[5]); + imageHeight = LM_to_uint(buf[6], buf[7]); + bitPixel = 1 << ((buf[8] & 0x07) + 1); + + if (index--) { + /* + * This is not the GIF frame we want to read: skip it. + */ + + if (BitSet(buf[8], LOCALCOLORMAP)) { + if (!ReadColorMap(gifConfPtr, chan, bitPixel, colorMap)) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "error reading color map", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", + "COLOR_MAP", NULL); + goto error; + } + } + + /* + * If we've not yet allocated a trash buffer, do so now. + */ + + if (trashBuffer == NULL) { + if (fileWidth > (int)((UINT_MAX/3)/fileHeight)) { + goto error; + } + nBytes = fileWidth * fileHeight * 3; + trashBuffer = ckalloc(nBytes); + if (trashBuffer) { + memset(trashBuffer, 0, nBytes); + } + } + + /* + * Slurp! Process the data for this image and stuff it in a trash + * buffer. + * + * Yes, it might be more efficient here to *not* store the data + * (we're just going to throw it away later). However, I elected + * to implement it this way for good reasons. First, I wanted to + * avoid duplicating the (fairly complex) LWZ decoder in + * ReadImage. Fine, you say, why didn't you just modify it to + * allow the use of a NULL specifier for the output buffer? I + * tried that, but it negatively impacted the performance of what + * I think will be the common case: reading the first image in the + * file. Rather than marginally improve the speed of the less + * frequent case, I chose to maintain high performance for the + * common case. + */ + + if (ReadImage(gifConfPtr, interp, trashBuffer, chan, imageWidth, + imageHeight, colorMap, 0, 0, 0, -1) != TCL_OK) { + goto error; + } + continue; + } + break; + } + + /* + * Found the frame we want to read. Next, check for a local color map for + * this frame. + */ + + if (BitSet(buf[8], LOCALCOLORMAP)) { + if (!ReadColorMap(gifConfPtr, chan, bitPixel, colorMap)) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "error reading color map", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "COLOR_MAP", NULL); + goto error; + } + } + + /* + * Extract the location within the overall visible image to put the data + * in this frame, together with the size of this frame. + */ + + index = LM_to_uint(buf[0], buf[1]); + srcX -= index; + if (srcX<0) { + destX -= srcX; width += srcX; + srcX = 0; + } + + if (width > imageWidth) { + width = imageWidth; + } + + index = LM_to_uint(buf[2], buf[3]); + srcY -= index; + if (index > srcY) { + destY -= srcY; height += srcY; + srcY = 0; + } + if (height > imageHeight) { + height = imageHeight; + } + + if ((width > 0) && (height > 0)) { + Tk_PhotoImageBlock block; + + /* + * Read the data and put it into the photo buffer for display by the + * general image machinery. + */ + + block.width = width; + block.height = height; + block.pixelSize = (transparent>=0) ? 4 : 3; + block.offset[0] = 0; + block.offset[1] = 1; + block.offset[2] = 2; + block.offset[3] = (transparent>=0) ? 3 : 0; + if (imageWidth > INT_MAX/block.pixelSize) { + goto error; + } + block.pitch = block.pixelSize * imageWidth; + if (imageHeight > (int)(UINT_MAX/block.pitch)) { + goto error; + } + nBytes = block.pitch * imageHeight; + block.pixelPtr = ckalloc(nBytes); + if (block.pixelPtr) { + memset(block.pixelPtr, 0, nBytes); + } + + if (ReadImage(gifConfPtr, interp, block.pixelPtr, chan, imageWidth, + imageHeight, colorMap, srcX, srcY, BitSet(buf[8], INTERLACE), + transparent) != TCL_OK) { + ckfree(block.pixelPtr); + goto error; + } + if (Tk_PhotoPutBlock(interp, imageHandle, &block, destX, destY, + width, height, TK_PHOTO_COMPOSITE_SET) != TCL_OK) { + ckfree(block.pixelPtr); + goto error; + } + ckfree(block.pixelPtr); + } + + /* + * We've successfully read the GIF frame (or there was nothing to read, + * which suits as well). We're done. + */ + + Tcl_SetObjResult(interp, Tcl_NewStringObj(tkImgFmtGIF.name, -1)); + result = TCL_OK; + + error: + /* + * If a trash buffer has been allocated, free it now. + */ + + if (trashBuffer != NULL) { + ckfree(trashBuffer); + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * StringMatchGIF -- + * + * This function is invoked by the photo image type to see if an object + * contains image data in GIF format. + * + * Results: + * The return value is 1 if the first characters in the data are like GIF + * data, and 0 otherwise. + * + * Side effects: + * The size of the image is placed in widthPtr and heightPtr. + * + *---------------------------------------------------------------------- + */ + +static int +StringMatchGIF( + Tcl_Obj *dataObj, /* the object containing the image data */ + Tcl_Obj *format, /* the image format object, or NULL */ + int *widthPtr, /* where to put the string width */ + int *heightPtr, /* where to put the string height */ + Tcl_Interp *interp) /* not used */ +{ + unsigned char *data, header[10]; + int got, length; + MFile handle; + + data = Tcl_GetByteArrayFromObj(dataObj, &length); + + /* + * Header is a minimum of 10 bytes. + */ + + if (length < 10) { + return 0; + } + + /* + * Check whether the data is Base64 encoded. + */ + + if ((strncmp(GIF87a, (char *) data, 6) != 0) && + (strncmp(GIF89a, (char *) data, 6) != 0)) { + /* + * Try interpreting the data as Base64 encoded + */ + + mInit((unsigned char *) data, &handle, length); + got = Mread(header, 10, 1, &handle); + if (got != 10 || + ((strncmp(GIF87a, (char *) header, 6) != 0) + && (strncmp(GIF89a, (char *) header, 6) != 0))) { + return 0; + } + } else { + memcpy(header, data, 10); + } + *widthPtr = LM_to_uint(header[6], header[7]); + *heightPtr = LM_to_uint(header[8], header[9]); + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * StringReadGIF -- + * + * This function is called by the photo image type to read GIF format + * data from an object, optionally base64 encoded, and give it to the + * photo image. + * + * Results: + * A standard TCL completion code. If TCL_ERROR is returned then an error + * message is left in the interp's result. + * + * Side effects: + * New data is added to the image given by imageHandle. This function + * calls FileReadGIF by redefining the operation of fprintf temporarily. + * + *---------------------------------------------------------------------- + */ + +static int +StringReadGIF( + Tcl_Interp *interp, /* interpreter for reporting errors in */ + Tcl_Obj *dataObj, /* object containing the image */ + Tcl_Obj *format, /* format object, or NULL */ + Tk_PhotoHandle imageHandle, /* the image to write this data into */ + int destX, int destY, /* The rectangular region of the */ + int width, int height, /* image to copy */ + int srcX, int srcY) +{ + MFile handle, *hdlPtr = &handle; + int length; + const char *xferFormat; + unsigned char *data = Tcl_GetByteArrayFromObj(dataObj, &length); + + mInit(data, hdlPtr, length); + + /* + * Check whether the data is Base64 encoded by doing a character-by- + * charcter comparison with the binary-format headers; BASE64-encoded + * never matches (matching the other way is harder because of potential + * padding of the BASE64 data). + */ + + if (strncmp(GIF87a, (char *) data, 6) + && strncmp(GIF89a, (char *) data, 6)) { + xferFormat = INLINE_DATA_BASE64; + } else { + xferFormat = INLINE_DATA_BINARY; + } + + /* + * Fall through to the file reader now that we have a correctly-configured + * pseudo-channel to pull the data from. + */ + + return FileReadGIF(interp, (Tcl_Channel) hdlPtr, xferFormat, format, + imageHandle, destX, destY, width, height, srcX, srcY); +} + +/* + *---------------------------------------------------------------------- + * + * ReadGIFHeader -- + * + * This function reads the GIF header from the beginning of a GIF file + * and returns the dimensions of the image. + * + * Results: + * The return value is 1 if file "f" appears to start with a valid GIF + * header, 0 otherwise. If the header is valid, then *widthPtr and + * *heightPtr are modified to hold the dimensions of the image. + * + * Side effects: + * The access position in f advances. + * + *---------------------------------------------------------------------- + */ + +static int +ReadGIFHeader( + GIFImageConfig *gifConfPtr, + Tcl_Channel chan, /* Image file to read the header from */ + int *widthPtr, int *heightPtr) + /* The dimensions of the image are returned + * here. */ +{ + unsigned char buf[7]; + + if ((Fread(gifConfPtr, buf, 1, 6, chan) != 6) + || ((strncmp(GIF87a, (char *) buf, 6) != 0) + && (strncmp(GIF89a, (char *) buf, 6) != 0))) { + return 0; + } + + if (Fread(gifConfPtr, buf, 1, 4, chan) != 4) { + return 0; + } + + *widthPtr = LM_to_uint(buf[0], buf[1]); + *heightPtr = LM_to_uint(buf[2], buf[3]); + return 1; +} + +/* + *----------------------------------------------------------------- + * The code below is copied from the giftoppm program and modified just + * slightly. + *----------------------------------------------------------------- + */ + +static int +ReadColorMap( + GIFImageConfig *gifConfPtr, + Tcl_Channel chan, + int number, + unsigned char buffer[MAXCOLORMAPSIZE][4]) +{ + int i; + unsigned char rgb[3]; + + for (i = 0; i < number; ++i) { + if (Fread(gifConfPtr, rgb, sizeof(rgb), 1, chan) <= 0) { + return 0; + } + + if (buffer) { + buffer[i][CM_RED] = rgb[0]; + buffer[i][CM_GREEN] = rgb[1]; + buffer[i][CM_BLUE] = rgb[2]; + buffer[i][CM_ALPHA] = 255; + } + } + return 1; +} + +static int +DoExtension( + GIFImageConfig *gifConfPtr, + Tcl_Channel chan, + int label, + unsigned char *buf, + int *transparent) +{ + int count; + + switch (label) { + case 0x01: /* Plain Text Extension */ + break; + + case 0xff: /* Application Extension */ + break; + + case 0xfe: /* Comment Extension */ + do { + count = GetDataBlock(gifConfPtr, chan, buf); + } while (count > 0); + return count; + + case 0xf9: /* Graphic Control Extension */ + count = GetDataBlock(gifConfPtr, chan, buf); + if (count < 0) { + return 1; + } + if ((buf[0] & 0x1) != 0) { + *transparent = buf[3]; + } + + do { + count = GetDataBlock(gifConfPtr, chan, buf); + } while (count > 0); + return count; + } + + do { + count = GetDataBlock(gifConfPtr, chan, buf); + } while (count > 0); + return count; +} + +static int +GetDataBlock( + GIFImageConfig *gifConfPtr, + Tcl_Channel chan, + unsigned char *buf) +{ + unsigned char count; + + if (Fread(gifConfPtr, &count, 1, 1, chan) <= 0) { + return -1; + } + + if ((count != 0) && (Fread(gifConfPtr, buf, count, 1, chan) <= 0)) { + return -1; + } + + return count; +} + +/* + *---------------------------------------------------------------------- + * + * ReadImage -- + * + * Process a GIF image from a given source, with a given height, width, + * transparency, etc. + * + * This code is based on the code found in the ImageMagick GIF decoder, + * which is (c) 2000 ImageMagick Studio. + * + * Some thoughts on our implementation: + * It sure would be nice if ReadImage didn't take 11 parameters! I think + * that if we were smarter, we could avoid doing that. + * + * Possible further optimizations: we could pull the GetCode function + * directly into ReadImage, which would improve our speed. + * + * Results: + * Processes a GIF image and loads the pixel data into a memory array. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +ReadImage( + GIFImageConfig *gifConfPtr, + Tcl_Interp *interp, + unsigned char *imagePtr, + Tcl_Channel chan, + int len, int rows, + unsigned char cmap[MAXCOLORMAPSIZE][4], + int srcX, int srcY, + int interlace, + int transparent) +{ + unsigned char initialCodeSize; + int xpos = 0, ypos = 0, pass = 0, i; + register unsigned char *pixelPtr; + static const int interlaceStep[] = { 8, 8, 4, 2 }; + static const int interlaceStart[] = { 0, 4, 2, 1 }; + unsigned short prefix[(1 << MAX_LWZ_BITS)]; + unsigned char append[(1 << MAX_LWZ_BITS)]; + unsigned char stack[(1 << MAX_LWZ_BITS)*2]; + register unsigned char *top; + int codeSize, clearCode, inCode, endCode, oldCode, maxCode; + int code, firstCode, v; + + /* + * Initialize the decoder + */ + + if (Fread(gifConfPtr, &initialCodeSize, 1, 1, chan) <= 0) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "error reading GIF image: %s", Tcl_PosixError(interp))); + return TCL_ERROR; + } + + if (initialCodeSize > MAX_LWZ_BITS) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("malformed image", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "MALFORMED", NULL); + return TCL_ERROR; + } + + if (transparent != -1) { + cmap[transparent][CM_RED] = 0; + cmap[transparent][CM_GREEN] = 0; + cmap[transparent][CM_BLUE] = 0; + cmap[transparent][CM_ALPHA] = 0; + } + + pixelPtr = imagePtr; + + /* + * Initialize the decoder. + * + * Set values for "special" numbers: + * clear code reset the decoder + * end code stop decoding + * code size size of the next code to retrieve + * max code next available table position + */ + + clearCode = 1 << (int) initialCodeSize; + endCode = clearCode + 1; + codeSize = (int) initialCodeSize + 1; + maxCode = clearCode + 2; + oldCode = -1; + firstCode = -1; + + memset(prefix, 0, (1 << MAX_LWZ_BITS) * sizeof(short)); + memset(append, 0, (1 << MAX_LWZ_BITS) * sizeof(char)); + for (i = 0; i < clearCode; i++) { + append[i] = i; + } + top = stack; + + GetCode(chan, 0, 1, gifConfPtr); + + /* + * Read until we finish the image + */ + + for (i = 0, ypos = 0; i < rows; i++) { + for (xpos = 0; xpos < len; ) { + if (top == stack) { + /* + * Bummer - our stack is empty. Now we have to work! + */ + + code = GetCode(chan, codeSize, 0, gifConfPtr); + if (code < 0) { + return TCL_OK; + } + + if (code > maxCode || code == endCode) { + /* + * If we're doing things right, we should never receive a + * code that is greater than our current maximum code. If + * we do, bail, because our decoder does not yet have that + * code set up. + * + * If the code is the magic endCode value, quit. + */ + + return TCL_OK; + } + + if (code == clearCode) { + /* + * Reset the decoder. + */ + + codeSize = initialCodeSize + 1; + maxCode = clearCode + 2; + oldCode = -1; + continue; + } + + if (oldCode == -1) { + /* + * Last pass reset the decoder, so the first code we see + * must be a singleton. Seed the stack with it, and set up + * the old/first code pointers for insertion into the + * string table. We can't just roll this into the + * clearCode test above, because at that point we have not + * yet read the next code. + */ + + *top++ = append[code]; + oldCode = code; + firstCode = code; + continue; + } + + inCode = code; + + if (code == maxCode) { + /* + * maxCode is always one bigger than our highest assigned + * code. If the code we see is equal to maxCode, then we + * are about to add a new string to the table. ??? + */ + + *top++ = firstCode; + code = oldCode; + } + + while (code > clearCode) { + /* + * Populate the stack by tracing the string in the string + * table from its tail to its head + */ + + *top++ = append[code]; + code = prefix[code]; + } + firstCode = append[code]; + + /* + * If there's no more room in our string table, quit. + * Otherwise, add a new string to the table + */ + + if (maxCode >= (1 << MAX_LWZ_BITS)) { + return TCL_OK; + } + + /* + * Push the head of the string onto the stack. + */ + + *top++ = firstCode; + + /* + * Add a new string to the string table + */ + + prefix[maxCode] = oldCode; + append[maxCode] = firstCode; + maxCode++; + + /* + * maxCode tells us the maximum code value we can accept. If + * we see that we need more bits to represent it than we are + * requesting from the unpacker, we need to increase the + * number we ask for. + */ + + if ((maxCode >= (1 << codeSize)) + && (maxCode < (1<<MAX_LWZ_BITS))) { + codeSize++; + } + oldCode = inCode; + } + + /* + * Pop the next color index off the stack. + */ + + v = *(--top); + if (v < 0) { + return TCL_OK; + } + + /* + * If pixelPtr is null, we're skipping this image (presumably + * there are more in the file and we will be called to read one of + * them later) + */ + + *pixelPtr++ = cmap[v][CM_RED]; + *pixelPtr++ = cmap[v][CM_GREEN]; + *pixelPtr++ = cmap[v][CM_BLUE]; + if (transparent >= 0) { + *pixelPtr++ = cmap[v][CM_ALPHA]; + } + xpos++; + + } + + /* + * If interlacing, the next ypos is not just +1. + */ + + if (interlace) { + ypos += interlaceStep[pass]; + while (ypos >= rows) { + pass++; + if (pass > 3) { + return TCL_OK; + } + ypos = interlaceStart[pass]; + } + } else { + ypos++; + } + pixelPtr = imagePtr + (ypos) * len * ((transparent>=0)?4:3); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * GetCode -- + * + * Extract the next compression code from the file. In GIF's, the + * compression codes are between 3 and 12 bits long and are then packed + * into 8 bit bytes, left to right, for example: + * bbbaaaaa + * dcccccbb + * eeeedddd + * ... + * We use a byte buffer read from the file and a sliding window to unpack + * the bytes. Thanks to ImageMagick for the sliding window idea. + * args: chan the channel to read from + * code_size size of the code to extract + * flag boolean indicating whether the extractor should be + * reset or not + * + * Results: + * code the next compression code + * + * Side effects: + * May consume more input from chan. + * + *---------------------------------------------------------------------- + */ + +static int +GetCode( + Tcl_Channel chan, + int code_size, + int flag, + GIFImageConfig *gifConfPtr) +{ + int ret; + + if (flag) { + /* + * Initialize the decoder. + */ + + gifConfPtr->reader.bitsInWindow = 0; + gifConfPtr->reader.bytes = 0; + gifConfPtr->reader.window = 0; + gifConfPtr->reader.done = 0; + gifConfPtr->reader.c = NULL; + return 0; + } + + while (gifConfPtr->reader.bitsInWindow < code_size) { + /* + * Not enough bits in our window to cover the request. + */ + + if (gifConfPtr->reader.done) { + return -1; + } + if (gifConfPtr->reader.bytes == 0) { + /* + * Not enough bytes in our buffer to add to the window. + */ + + gifConfPtr->reader.bytes = + GetDataBlock(gifConfPtr, chan, gifConfPtr->workingBuffer); + gifConfPtr->reader.c = gifConfPtr->workingBuffer; + if (gifConfPtr->reader.bytes <= 0) { + gifConfPtr->reader.done = 1; + break; + } + } + + /* + * Tack another byte onto the window, see if that's enough. + */ + + gifConfPtr->reader.window += + (*gifConfPtr->reader.c) << gifConfPtr->reader.bitsInWindow; + gifConfPtr->reader.c++; + gifConfPtr->reader.bitsInWindow += 8; + gifConfPtr->reader.bytes--; + } + + /* + * The next code will always be the last code_size bits of the window. + */ + + ret = gifConfPtr->reader.window & ((1 << code_size) - 1); + + /* + * Shift data in the window to put the next code at the end. + */ + + gifConfPtr->reader.window >>= code_size; + gifConfPtr->reader.bitsInWindow -= code_size; + return ret; +} + +/* + *---------------------------------------------------------------------- + * + * Minit -- -- + * + * This function initializes a base64 decoder handle + * + * Results: + * None + * + * Side effects: + * The base64 handle is initialized + * + *---------------------------------------------------------------------- + */ + +static void +mInit( + unsigned char *string, /* string containing initial mmencoded data */ + MFile *handle, /* mmdecode "file" handle */ + int length) /* Number of bytes in string */ +{ + handle->data = string; + handle->state = 0; + handle->c = 0; + handle->length = length; +} + +/* + *---------------------------------------------------------------------- + * + * Mread -- + * + * This function is invoked by the GIF file reader as a temporary + * replacement for "fread", to get GIF data out of a string (using + * Mgetc). + * + * Results: + * The return value is the number of characters "read" + * + * Side effects: + * The base64 handle will change state. + * + *---------------------------------------------------------------------- + */ + +static int +Mread( + unsigned char *dst, /* where to put the result */ + size_t chunkSize, /* size of each transfer */ + size_t numChunks, /* number of chunks */ + MFile *handle) /* mmdecode "file" handle */ +{ + register int i, c; + int count = chunkSize * numChunks; + + for (i=0; i<count && (c=Mgetc(handle)) != GIF_DONE; i++) { + *dst++ = c; + } + return i; +} + +/* + *---------------------------------------------------------------------- + * + * Mgetc -- + * + * This function gets the next decoded character from an mmencode handle. + * This causes at least 1 character to be "read" from the encoded string. + * + * Results: + * The next byte (or GIF_DONE) is returned. + * + * Side effects: + * The base64 handle will change state. + * + *---------------------------------------------------------------------- + */ + +static int +Mgetc( + MFile *handle) /* Handle containing decoder data and state */ +{ + int c; + int result = 0; /* Initialization needed only to prevent gcc + * compiler warning. */ + + if (handle->state == GIF_DONE) { + return GIF_DONE; + } + + do { + if (handle->length-- <= 0) { + return GIF_DONE; + } + c = char64(*handle->data); + handle->data++; + } while (c == GIF_SPACE); + + if (c > GIF_SPECIAL) { + handle->state = GIF_DONE; + return handle->c; + } + + switch (handle->state++) { + case 0: + handle->c = c<<2; + result = Mgetc(handle); + break; + case 1: + result = handle->c | (c>>4); + handle->c = (c&0xF)<<4; + break; + case 2: + result = handle->c | (c>>2); + handle->c = (c&0x3) << 6; + break; + case 3: + result = handle->c | c; + handle->state = 0; + break; + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * char64 -- + * + * This function converts a base64 ascii character into its binary + * equivalent. This code is a slightly modified version of the char64 + * function in N. Borenstein's metamail decoder. + * + * Results: + * The binary value, or an error code. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +char64( + int c) +{ + switch(c) { + case 'A': return 0; case 'B': return 1; case 'C': return 2; + case 'D': return 3; case 'E': return 4; case 'F': return 5; + case 'G': return 6; case 'H': return 7; case 'I': return 8; + case 'J': return 9; case 'K': return 10; case 'L': return 11; + case 'M': return 12; case 'N': return 13; case 'O': return 14; + case 'P': return 15; case 'Q': return 16; case 'R': return 17; + case 'S': return 18; case 'T': return 19; case 'U': return 20; + case 'V': return 21; case 'W': return 22; case 'X': return 23; + case 'Y': return 24; case 'Z': return 25; case 'a': return 26; + case 'b': return 27; case 'c': return 28; case 'd': return 29; + case 'e': return 30; case 'f': return 31; case 'g': return 32; + case 'h': return 33; case 'i': return 34; case 'j': return 35; + case 'k': return 36; case 'l': return 37; case 'm': return 38; + case 'n': return 39; case 'o': return 40; case 'p': return 41; + case 'q': return 42; case 'r': return 43; case 's': return 44; + case 't': return 45; case 'u': return 46; case 'v': return 47; + case 'w': return 48; case 'x': return 49; case 'y': return 50; + case 'z': return 51; case '0': return 52; case '1': return 53; + case '2': return 54; case '3': return 55; case '4': return 56; + case '5': return 57; case '6': return 58; case '7': return 59; + case '8': return 60; case '9': return 61; case '+': return 62; + case '/': return 63; + + case ' ': case '\t': case '\n': case '\r': case '\f': + return GIF_SPACE; + case '=': + return GIF_PAD; + case '\0': + return GIF_DONE; + default: + return GIF_BAD; + } +} + +/* + *---------------------------------------------------------------------- + * + * Fread -- + * + * This function calls either fread or Mread to read data from a file or + * a base64 encoded string. + * + * Results: - same as POSIX fread() or Tcl Tcl_Read() + * + *---------------------------------------------------------------------- + */ + +static int +Fread( + GIFImageConfig *gifConfPtr, + unsigned char *dst, /* where to put the result */ + size_t hunk, size_t count, /* how many */ + Tcl_Channel chan) +{ + if (gifConfPtr->fromData == INLINE_DATA_BASE64) { + return Mread(dst, hunk, count, (MFile *) chan); + } + + if (gifConfPtr->fromData == INLINE_DATA_BINARY) { + MFile *handle = (MFile *) chan; + + if (handle->length <= 0 || (size_t) handle->length < hunk*count) { + return -1; + } + memcpy(dst, handle->data, (size_t) (hunk * count)); + handle->data += hunk * count; + return (int)(hunk * count); + } + + /* + * Otherwise we've got a real file to read. + */ + + return Tcl_Read(chan, (char *) dst, (int) (hunk * count)); +} + +/* + * ChanWriteGIF - writes a image in GIF format. + *------------------------------------------------------------------------- + * Author: Lolo + * Engeneering Projects Area + * Department of Mining + * University of Oviedo + * e-mail zz11425958@zeus.etsimo.uniovi.es + * lolo@pcsig22.etsimo.uniovi.es + * Date: Fri September 20 1996 + * + * Modified for transparency handling (gif89a) + * by Jan Nijtmans <nijtmans@users.sourceforge.net> + * + *---------------------------------------------------------------------- + * FileWriteGIF- + * + * This function is called by the photo image type to write GIF format + * data from a photo image into a given file + * + * Results: + * A standard TCL completion code. If TCL_ERROR is returned then an + * error message is left in the interp's result. + * + *---------------------------------------------------------------------- + */ + + +static int +FileWriteGIF( + Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ + const char *filename, + Tcl_Obj *format, + Tk_PhotoImageBlock *blockPtr) +{ + Tcl_Channel chan = NULL; + int result; + + chan = Tcl_OpenFileChannel(interp, (char *) filename, "w", 0644); + if (!chan) { + return TCL_ERROR; + } + if (Tcl_SetChannelOption(interp, chan, "-translation", + "binary") != TCL_OK) { + Tcl_Close(NULL, chan); + return TCL_ERROR; + } + + result = CommonWriteGIF(interp, chan, WriteToChannel, format, blockPtr); + + if (Tcl_Close(interp, chan) == TCL_ERROR) { + return TCL_ERROR; + } + return result; +} + +static int +StringWriteGIF( + Tcl_Interp *interp, /* Interpreter to use for reporting errors and + * returning the GIF data. */ + Tcl_Obj *format, + Tk_PhotoImageBlock *blockPtr) +{ + int result; + Tcl_Obj *objPtr = Tcl_NewObj(); + + Tcl_IncrRefCount(objPtr); + result = CommonWriteGIF(interp, objPtr, WriteToByteArray, format, + blockPtr); + if (result == TCL_OK) { + Tcl_SetObjResult(interp, objPtr); + } + Tcl_DecrRefCount(objPtr); + return result; +} + +static int +WriteToChannel( + ClientData clientData, + const char *bytes, + int byteCount) +{ + Tcl_Channel handle = clientData; + + return Tcl_Write(handle, bytes, byteCount); +} + +static int +WriteToByteArray( + ClientData clientData, + const char *bytes, + int byteCount) +{ + Tcl_Obj *objPtr = clientData; + Tcl_Obj *tmpObj = Tcl_NewByteArrayObj((unsigned char *) bytes, byteCount); + + Tcl_IncrRefCount(tmpObj); + Tcl_AppendObjToObj(objPtr, tmpObj); + Tcl_DecrRefCount(tmpObj); + return byteCount; +} + +static int +CommonWriteGIF( + Tcl_Interp *interp, + ClientData handle, + WriteBytesFunc *writeProc, + Tcl_Obj *format, + Tk_PhotoImageBlock *blockPtr) +{ + GifWriterState state; + int resolution; + long width, height, x; + unsigned char c; + unsigned int top, left; + + top = 0; + left = 0; + + memset(&state, 0, sizeof(state)); + + state.pixelSize = blockPtr->pixelSize; + state.greenOffset = blockPtr->offset[1]-blockPtr->offset[0]; + state.blueOffset = blockPtr->offset[2]-blockPtr->offset[0]; + state.alphaOffset = blockPtr->offset[0]; + if (state.alphaOffset < blockPtr->offset[2]) { + state.alphaOffset = blockPtr->offset[2]; + } + if (++state.alphaOffset < state.pixelSize) { + state.alphaOffset -= blockPtr->offset[0]; + } else { + state.alphaOffset = 0; + } + + writeProc(handle, (char *) (state.alphaOffset ? GIF89a : GIF87a), 6); + + for (x = 0; x < MAXCOLORMAPSIZE ;x++) { + state.mapa[x][CM_RED] = 255; + state.mapa[x][CM_GREEN] = 255; + state.mapa[x][CM_BLUE] = 255; + } + + width = blockPtr->width; + height = blockPtr->height; + state.pixelOffset = blockPtr->pixelPtr + blockPtr->offset[0]; + state.pixelPitch = blockPtr->pitch; + SaveMap(&state, blockPtr); + if (state.num >= MAXCOLORMAPSIZE) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("too many colors", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "COLORFUL", NULL); + return TCL_ERROR; + } + if (state.num<2) { + state.num = 2; + } + c = LSB(width); + writeProc(handle, (char *) &c, 1); + c = MSB(width); + writeProc(handle, (char *) &c, 1); + c = LSB(height); + writeProc(handle, (char *) &c, 1); + c = MSB(height); + writeProc(handle, (char *) &c, 1); + + resolution = 0; + while (state.num >> resolution) { + resolution++; + } + c = 111 + resolution * 17; + writeProc(handle, (char *) &c, 1); + + state.num = 1 << resolution; + + /* + * Background color + */ + + c = 0; + writeProc(handle, (char *) &c, 1); + + /* + * Zero for future expansion. + */ + + writeProc(handle, (char *) &c, 1); + + for (x = 0; x < state.num; x++) { + c = state.mapa[x][CM_RED]; + writeProc(handle, (char *) &c, 1); + c = state.mapa[x][CM_GREEN]; + writeProc(handle, (char *) &c, 1); + c = state.mapa[x][CM_BLUE]; + writeProc(handle, (char *) &c, 1); + } + + /* + * Write out extension for transparent colour index, if necessary. + */ + + if (state.alphaOffset) { + c = GIF_EXTENSION; + writeProc(handle, (char *) &c, 1); + writeProc(handle, "\371\4\1\0\0\0", 7); + } + + c = GIF_START; + writeProc(handle, (char *) &c, 1); + c = LSB(top); + writeProc(handle, (char *) &c, 1); + c = MSB(top); + writeProc(handle, (char *) &c, 1); + c = LSB(left); + writeProc(handle, (char *) &c, 1); + c = MSB(left); + writeProc(handle, (char *) &c, 1); + + c = LSB(width); + writeProc(handle, (char *) &c, 1); + c = MSB(width); + writeProc(handle, (char *) &c, 1); + + c = LSB(height); + writeProc(handle, (char *) &c, 1); + c = MSB(height); + writeProc(handle, (char *) &c, 1); + + c = 0; + writeProc(handle, (char *) &c, 1); + c = resolution; + writeProc(handle, (char *) &c, 1); + + state.ssize = state.rsize = blockPtr->width; + state.csize = blockPtr->height; + Compress(resolution+1, handle, writeProc, ReadValue, &state); + + c = 0; + writeProc(handle, (char *) &c, 1); + c = GIF_TERMINATOR; + writeProc(handle, (char *) &c, 1); + + return TCL_OK; +} + +static int +ColorNumber( + GifWriterState *statePtr, + int red, int green, int blue) +{ + int x = (statePtr->alphaOffset != 0); + + for (; x <= MAXCOLORMAPSIZE; x++) { + if ((statePtr->mapa[x][CM_RED] == red) && + (statePtr->mapa[x][CM_GREEN] == green) && + (statePtr->mapa[x][CM_BLUE] == blue)) { + return x; + } + } + return -1; +} + +static int +IsNewColor( + GifWriterState *statePtr, + int red, int green, int blue) +{ + int x = (statePtr->alphaOffset != 0); + + for (; x<=statePtr->num ; x++) { + if ((statePtr->mapa[x][CM_RED] == red) && + (statePtr->mapa[x][CM_GREEN] == green) && + (statePtr->mapa[x][CM_BLUE] == blue)) { + return 0; + } + } + return 1; +} + +static void +SaveMap( + GifWriterState *statePtr, + Tk_PhotoImageBlock *blockPtr) +{ + unsigned char *colores; + int x, y; + unsigned char red, green, blue; + + if (statePtr->alphaOffset) { + statePtr->num = 0; + statePtr->mapa[0][CM_RED] = DEFAULT_BACKGROUND_VALUE; + statePtr->mapa[0][CM_GREEN] = DEFAULT_BACKGROUND_VALUE; + statePtr->mapa[0][CM_BLUE] = DEFAULT_BACKGROUND_VALUE; + } else { + statePtr->num = -1; + } + + for (y=0 ; y<blockPtr->height ; y++) { + colores = blockPtr->pixelPtr + blockPtr->offset[0] + y*blockPtr->pitch; + for (x=0 ; x<blockPtr->width ; x++) { + if (!statePtr->alphaOffset || colores[statePtr->alphaOffset]!=0) { + red = colores[0]; + green = colores[statePtr->greenOffset]; + blue = colores[statePtr->blueOffset]; + if (IsNewColor(statePtr, red, green, blue)) { + statePtr->num++; + if (statePtr->num >= MAXCOLORMAPSIZE) { + return; + } + statePtr->mapa[statePtr->num][CM_RED] = red; + statePtr->mapa[statePtr->num][CM_GREEN] = green; + statePtr->mapa[statePtr->num][CM_BLUE] = blue; + } + } + colores += statePtr->pixelSize; + } + } +} + +static int +ReadValue( + GifWriterState *statePtr) +{ + unsigned int col; + + if (statePtr->csize == 0) { + return EOF; + } + if (statePtr->alphaOffset + && (statePtr->pixelOffset[statePtr->alphaOffset]==0)) { + col = 0; + } else { + col = ColorNumber(statePtr, statePtr->pixelOffset[0], + statePtr->pixelOffset[statePtr->greenOffset], + statePtr->pixelOffset[statePtr->blueOffset]); + } + statePtr->pixelOffset += statePtr->pixelSize; + if (--statePtr->ssize <= 0) { + statePtr->ssize = statePtr->rsize; + statePtr->csize--; + statePtr->pixelOffset += statePtr->pixelPitch + - (statePtr->rsize * statePtr->pixelSize); + } + + return col; +} + +/* + * GIF Image compression - modified 'Compress' + * + * Based on: compress.c - File compression ala IEEE Computer, June 1984. + * + * By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) + * Jim McKie (decvax!mcvax!jim) + * Steve Davies (decvax!vax135!petsd!peora!srd) + * Ken Turkowski (decvax!decwrl!turtlevax!ken) + * James A. Woods (decvax!ihnp4!ames!jaw) + * Joe Orost (decvax!vax135!petsd!joe) + */ + +static void +Compress( + int initialBits, + ClientData handle, + WriteBytesFunc *writeProc, + ifunptr readValue, + GifWriterState *statePtr) +{ + long fcode, ent, disp, hSize, i = 0; + int c, hshift; + GIFState_t state; + + memset(&state, 0, sizeof(state)); + + /* + * Set up the globals: initialBits - initial number of bits + * outChannel - pointer to output file + */ + + state.initialBits = initialBits; + state.destination = handle; + state.writeProc = writeProc; + + /* + * Set up the necessary values. + */ + + state.offset = 0; + state.hSize = HSIZE; + state.outCount = 0; + state.clearFlag = 0; + state.inCount = 1; + state.maxCode = MAXCODE(state.numBits = state.initialBits); + state.clearCode = 1 << (initialBits - 1); + state.eofCode = state.clearCode + 1; + state.freeEntry = state.clearCode + 2; + CharInit(&state); + + ent = readValue(statePtr); + + hshift = 0; + for (fcode = (long) state.hSize; fcode < 65536L; fcode *= 2L) { + hshift++; + } + hshift = 8 - hshift; /* Set hash code range bound */ + + hSize = state.hSize; + ClearHashTable(&state, (int) hSize); /* Clear hash table */ + + Output(&state, (long) state.clearCode); + + while (U(c = readValue(statePtr)) != U(EOF)) { + state.inCount++; + + fcode = (long) (((long) c << GIFBITS) + ent); + i = ((long)c << hshift) ^ ent; /* XOR hashing */ + + if (state.hashTable[i] == fcode) { + ent = state.codeTable[i]; + continue; + } else if ((long) state.hashTable[i] < 0) { /* Empty slot */ + goto nomatch; + } + + disp = hSize - i; /* Secondary hash (after G. Knott) */ + if (i == 0) { + disp = 1; + } + + probe: + if ((i -= disp) < 0) { + i += hSize; + } + + if (state.hashTable[i] == fcode) { + ent = state.codeTable[i]; + continue; + } + if ((long) state.hashTable[i] > 0) { + goto probe; + } + + nomatch: + Output(&state, (long) ent); + state.outCount++; + ent = c; + if (U(state.freeEntry) < U((long)1 << GIFBITS)) { + state.codeTable[i] = state.freeEntry++; /* code -> hashtable */ + state.hashTable[i] = fcode; + } else { + ClearForBlock(&state); + } + } + + /* + * Put out the final code. + */ + + Output(&state, (long) ent); + state.outCount++; + Output(&state, (long) state.eofCode); +} + +/***************************************************************** + * Output -- + * Output the given code. + * + * Inputs: + * code: A numBits-bit integer. If == -1, then EOF. This assumes that + * numBits =< (long) wordsize - 1. + * Outputs: + * Outputs code to the file. + * Assumptions: + * Chars are 8 bits long. + * Algorithm: + * Maintain a GIFBITS character long buffer (so that 8 codes will fit in + * it exactly). Use the VAX insv instruction to insert each code in turn. + * When the buffer fills up empty it and start over. + */ + +static void +Output( + GIFState_t *statePtr, + long code) +{ + static const unsigned long masks[] = { + 0x0000, + 0x0001, 0x0003, 0x0007, 0x000F, + 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, + 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF + }; + + statePtr->currentAccumulated &= masks[statePtr->currentBits]; + if (statePtr->currentBits > 0) { + statePtr->currentAccumulated |= ((long) code << statePtr->currentBits); + } else { + statePtr->currentAccumulated = code; + } + statePtr->currentBits += statePtr->numBits; + + while (statePtr->currentBits >= 8) { + CharOut(statePtr, (unsigned) (statePtr->currentAccumulated & 0xff)); + statePtr->currentAccumulated >>= 8; + statePtr->currentBits -= 8; + } + + /* + * If the next entry is going to be too big for the code size, then + * increase it, if possible. + */ + + if ((statePtr->freeEntry > statePtr->maxCode) || statePtr->clearFlag) { + if (statePtr->clearFlag) { + statePtr->maxCode = MAXCODE( + statePtr->numBits = statePtr->initialBits); + statePtr->clearFlag = 0; + } else { + statePtr->numBits++; + if (statePtr->numBits == GIFBITS) { + statePtr->maxCode = (long)1 << GIFBITS; + } else { + statePtr->maxCode = MAXCODE(statePtr->numBits); + } + } + } + + if (code == statePtr->eofCode) { + /* + * At EOF, write the rest of the buffer. + */ + + while (statePtr->currentBits > 0) { + CharOut(statePtr, + (unsigned) (statePtr->currentAccumulated & 0xff)); + statePtr->currentAccumulated >>= 8; + statePtr->currentBits -= 8; + } + FlushChar(statePtr); + } +} + +/* + * Clear out the hash table + */ + +static void +ClearForBlock( /* Table clear for block compress. */ + GIFState_t *statePtr) +{ + ClearHashTable(statePtr, (int) statePtr->hSize); + statePtr->freeEntry = statePtr->clearCode + 2; + statePtr->clearFlag = 1; + + Output(statePtr, (long) statePtr->clearCode); +} + +static void +ClearHashTable( /* Reset code table. */ + GIFState_t *statePtr, + int hSize) +{ + register int *hashTablePtr = statePtr->hashTable + hSize; + register long i; + register long m1 = -1; + + i = hSize - 16; + do { /* might use Sys V memset(3) here */ + *(hashTablePtr-16) = m1; + *(hashTablePtr-15) = m1; + *(hashTablePtr-14) = m1; + *(hashTablePtr-13) = m1; + *(hashTablePtr-12) = m1; + *(hashTablePtr-11) = m1; + *(hashTablePtr-10) = m1; + *(hashTablePtr-9) = m1; + *(hashTablePtr-8) = m1; + *(hashTablePtr-7) = m1; + *(hashTablePtr-6) = m1; + *(hashTablePtr-5) = m1; + *(hashTablePtr-4) = m1; + *(hashTablePtr-3) = m1; + *(hashTablePtr-2) = m1; + *(hashTablePtr-1) = m1; + hashTablePtr -= 16; + } while ((i -= 16) >= 0); + + for (i += 16; i > 0; i--) { + *--hashTablePtr = m1; + } +} + +/* + ***************************************************************************** + * + * GIF Specific routines + * + ***************************************************************************** + */ + +/* + * Set up the 'byte output' routine + */ + +static void +CharInit( + GIFState_t *statePtr) +{ + statePtr->accumulatedByteCount = 0; + statePtr->currentAccumulated = 0; + statePtr->currentBits = 0; +} + +/* + * Add a character to the end of the current packet, and if it is 254 + * characters, flush the packet to disk. + */ + +static void +CharOut( + GIFState_t *statePtr, + int c) +{ + statePtr->packetAccumulator[statePtr->accumulatedByteCount++] = c; + if (statePtr->accumulatedByteCount >= 254) { + FlushChar(statePtr); + } +} + +/* + * Flush the packet to disk, and reset the accumulator + */ + +static void +FlushChar( + GIFState_t *statePtr) +{ + unsigned char c; + + if (statePtr->accumulatedByteCount > 0) { + c = statePtr->accumulatedByteCount; + statePtr->writeProc(statePtr->destination, (const char *) &c, 1); + statePtr->writeProc(statePtr->destination, + (const char *) statePtr->packetAccumulator, + statePtr->accumulatedByteCount); + statePtr->accumulatedByteCount = 0; + } +} + +/* The End */ + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |