summaryrefslogtreecommitdiffstats
path: root/tkimg/gif/gif.c
diff options
context:
space:
mode:
authorWilliam Joye <wjoye@cfa.harvard.edu>2017-01-03 21:52:18 (GMT)
committerWilliam Joye <wjoye@cfa.harvard.edu>2017-01-03 21:52:18 (GMT)
commit4302a869f0212a3e4878e66a7260b434f6584476 (patch)
treee6976e66edf648406e32b092395121e045301692 /tkimg/gif/gif.c
parenta780057cc1b51dd3a557549c3cf2431f09136c0d (diff)
parent60d692811c12788ed4468d5ff680633304e8f641 (diff)
downloadblt-4302a869f0212a3e4878e66a7260b434f6584476.zip
blt-4302a869f0212a3e4878e66a7260b434f6584476.tar.gz
blt-4302a869f0212a3e4878e66a7260b434f6584476.tar.bz2
Merge commit '60d692811c12788ed4468d5ff680633304e8f641' as 'tkimg'
Diffstat (limited to 'tkimg/gif/gif.c')
-rwxr-xr-xtkimg/gif/gif.c1755
1 files changed, 1755 insertions, 0 deletions
diff --git a/tkimg/gif/gif.c b/tkimg/gif/gif.c
new file mode 100755
index 0000000..2f37686
--- /dev/null
+++ b/tkimg/gif/gif.c
@@ -0,0 +1,1755 @@
+/*
+ * gif.c --
+ *
+ * GIF photo image type, Tcl/Tk package
+ *
+ * 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) 2002 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+ * Copyright (c) 1997-2003 Jan Nijtmans <nijtmans@users.sourceforge.net>
+ *
+ * Copyright (c) Reed Wade (wade@cs.utk.edu), University of Tennessee
+ * Copyright (c) 1995-1997 Sun Microsystems, Inc.
+ * Copyright (c) 1997 Australian National University
+ *
+ * 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. |
+ * +-------------------------------------------------------------------+
+ *
+ * $Id: gif.c 371 2014-12-27 21:21:30Z obermeier $
+ *
+ */
+
+/*
+ * Generic initialization code, parameterized via CPACKAGE and PACKAGE.
+ */
+
+#include "init.c"
+
+/*
+ * 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)
+
+/*
+ * 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.
+ */
+
+# define GIF87a "\x47\x49\x46\x38\x37\x61" /* ASCII GIF87a */
+# define GIF89a "\x47\x49\x46\x38\x39\x61" /* ASCII GIF89a */
+# define GIF_TERMINATOR 0x3b /* ASCII ; */
+# define GIF_EXTENSION 0x21 /* ASCII ! */
+# define GIF_START 0x2c /* ASCII , */
+
+typedef struct {
+ unsigned char buf[280];
+ int bytes;
+ int done;
+ unsigned int window;
+ int bitsInWindow;
+ unsigned char *c;
+ tkimg_MFile handle;
+} GIFImageConfig;
+
+/*
+ * The format record for the GIF file format:
+ */
+
+static int CommonRead(Tcl_Interp *interp,
+ GIFImageConfig *gifConfPtr, const char *fileName, Tcl_Obj *format,
+ Tk_PhotoHandle imageHandle, int destX, int destY,
+ int width, int height, int srcX, int srcY);
+
+static int CommonWrite(Tcl_Interp *interp,
+ tkimg_MFile *handle, Tcl_Obj *format,
+ Tk_PhotoImageBlock *blockPtr);
+
+#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))
+
+#define ReadOK(handle,buf,len) (tkimg_Read(handle, (char *)(buf), len) == len)
+
+/*
+ * Prototypes for local procedures defined in this file:
+ */
+
+static int DoExtension(GIFImageConfig *gifConfPtr, int label, int *transparent);
+
+static int GetCode(GIFImageConfig *gifConfPtr, int code_size, int flag);
+
+static int GetDataBlock(GIFImageConfig *gifConfPtr, unsigned char *buf);
+
+static int ReadColorMap(GIFImageConfig *gifConfPtr, int number,
+ unsigned char buffer[MAXCOLORMAPSIZE][4]);
+
+static int ReadGIFHeader(GIFImageConfig *gifConfPtr, int *widthPtr, int *heightPtr);
+
+static int ReadImage(Tcl_Interp *interp,
+ char *imagePtr, GIFImageConfig *gifConfPtr, int len, int rows,
+ unsigned char cmap[MAXCOLORMAPSIZE][4],
+ int width, int height, int srcX, int srcY,
+ int interlace, int transparent);
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ChnMatch --
+ *
+ * This procedure is invoked by the photo image type to see if
+ * a channel contains image data in GIF format.
+ *
+ * Results:
+ * The return value is 1 if the first characters in channel chan
+ * look like GIF data, and 0 otherwise.
+ *
+ * Side effects:
+ * The access position in f may change.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+ChnMatch(
+ Tcl_Channel chan, /* The image channel, open for reading. */
+ const char *fileName, /* The name of the image file. */
+ Tcl_Obj *format, /* User-specified format object, or NULL. */
+ int *widthPtr, /* The dimensions of the image are */
+ int *heightPtr, /* returned here if the file is a valid raw GIF file. */
+ Tcl_Interp *interp /* interpreter */
+) {
+ GIFImageConfig gifConf;
+
+ memset(&gifConf, 0, sizeof(GIFImageConfig));
+
+ gifConf.handle.data = (char *) chan;
+ gifConf.handle.state = IMG_CHAN;
+
+ return ReadGIFHeader(&gifConf, widthPtr, heightPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ChnRead --
+ *
+ * This procedure is called by the photo image type to read
+ * GIF format data from a channel and write it into a given
+ * photo image.
+ *
+ * Results:
+ * A standard TCL completion code. If TCL_ERROR is returned
+ * then an error message is left in the interp's result.
+ *
+ * Side effects:
+ * The access position in channel chan is changed, and new data is
+ * added to the image given by imageHandle.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+ChnRead(interp, chan, fileName, format, imageHandle, destX, destY,
+ width, height, srcX, srcY)
+ Tcl_Interp *interp; /* Interpreter to use for reporting errors. */
+ Tcl_Channel chan; /* The image channel, open for reading. */
+ 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, destY; /* Coordinates of top-left pixel in
+ * photo image to be written to. */
+ int width, height; /* Dimensions of block of photo image to
+ * be written to. */
+ int srcX, srcY; /* Coordinates of top-left pixel to be used
+ * in image being read. */
+{
+ GIFImageConfig gifConf;
+
+ memset(&gifConf, 0, sizeof(GIFImageConfig));
+
+ gifConf.handle.data = (char *) chan;
+ gifConf.handle.state = IMG_CHAN;
+
+ return CommonRead(interp, &gifConf, fileName, format,
+ imageHandle, destX, destY, width, height, srcX, srcY);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * CommonRead --
+ *
+ * This procedure 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
+CommonRead(interp, gifConfPtr, fileName, format, imageHandle, destX, destY,
+ width, height, srcX, srcY)
+ Tcl_Interp *interp; /* Interpreter to use for reporting errors. */
+ GIFImageConfig *gifConfPtr; /* 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, destY; /* Coordinates of top-left pixel in
+ * photo image to be written to. */
+ int width, height; /* Dimensions of block of photo image to
+ * be written to. */
+ int srcX, srcY; /* Coordinates of top-left pixel to be used
+ * in image being read. */
+{
+ int fileWidth, fileHeight, imageWidth, imageHeight;
+ int nBytes, index = 0, objc = 0;
+ Tcl_Obj **objv = NULL;
+ Tk_PhotoImageBlock block;
+ unsigned char buf[100];
+ char *trashBuffer = NULL;
+ unsigned char *pixBuf = NULL;
+ int bitPixel;
+ unsigned char colorMap[MAXCOLORMAPSIZE][4];
+ int transparent = -1;
+
+ if (tkimg_ListObjGetElements(interp, format, &objc, &objv) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (objc > 1) {
+ char *c = Tcl_GetStringFromObj(objv[1], &nBytes);
+ if ((objc > 3) || ((objc == 3) && ((c[0] != '-') ||
+ (c[1] != 'i') || strncmp(c, "-index", strlen(c))))) {
+ Tcl_AppendResult(interp, "invalid format: \"",
+ tkimg_GetStringFromObj(format, NULL), "\"", (char *) NULL);
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIntFromObj(interp, objv[objc-1], &index) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+
+ if (!ReadGIFHeader(gifConfPtr, &fileWidth, &fileHeight)) {
+ Tcl_AppendResult(interp, "couldn't read GIF header from file \"", fileName, "\"", NULL);
+ return TCL_ERROR;
+ }
+ if ((fileWidth <= 0) || (fileHeight <= 0)) {
+ Tcl_AppendResult(interp, "GIF image file \"", fileName,
+ "\" has dimension(s) <= 0", (char *) NULL);
+ return TCL_ERROR;
+ }
+
+ if (tkimg_Read(&gifConfPtr->handle, (char *)buf, 3) != 3) {
+ return TCL_OK;
+ }
+
+ bitPixel = 2<<(buf[0]&0x07);
+
+ if (BitSet(buf[0], LOCALCOLORMAP)) { /* Global Colormap */
+ if (!ReadColorMap(gifConfPtr, bitPixel, colorMap)) {
+ Tcl_AppendResult(interp, "error reading color map", (char *) 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;
+ }
+
+ if (tkimg_PhotoExpand(interp, imageHandle, destX + width, destY + height) == TCL_ERROR) {
+ return TCL_ERROR;;
+ }
+
+ block.pixelSize = 4;
+ block.offset[0] = 0;
+ block.offset[1] = 1;
+ block.offset[2] = 2;
+ block.offset[3] = 3;
+ block.pixelPtr = NULL;
+
+ while (1) {
+ if (tkimg_Read(&gifConfPtr->handle, (char *)buf, 1) != 1) {
+ /*
+ * Premature end of image. We should really notify
+ * the user, but for now just show garbage.
+ */
+ break;
+ }
+
+ if (buf[0] == GIF_TERMINATOR) {
+ /*
+ * GIF terminator.
+ */
+ Tcl_AppendResult(interp,"no image data for this index", (char *) NULL);
+ goto error;
+ }
+
+ if (buf[0] == GIF_EXTENSION) {
+ /*
+ * This is a GIF extension.
+ */
+
+ if (tkimg_Read(&gifConfPtr->handle, (char *)buf, 1) != 1) {
+ Tcl_AppendResult(interp,
+ "error reading extension function code in GIF image",
+ (char *) NULL);
+ goto error;
+ }
+ if (DoExtension(gifConfPtr, buf[0], &transparent) < 0) {
+ Tcl_AppendResult(interp, "error reading extension in GIF image", (char *) NULL);
+ goto error;
+ }
+ continue;
+ }
+
+ if (buf[0] != GIF_START) {
+ /*
+ * Not a valid start character; ignore it.
+ */
+ continue;
+ }
+
+ if (tkimg_Read(&gifConfPtr->handle, (char *)buf, 9) != 9) {
+ Tcl_AppendResult(interp,
+ "couldn't read left/top/width/height in GIF image",
+ (char *) NULL);
+ goto error;
+ }
+
+ imageWidth = LM_to_uint(buf[4],buf[5]);
+ imageHeight = LM_to_uint(buf[6],buf[7]);
+
+ bitPixel = 2<<(buf[8]&0x07);
+
+ if (index--) {
+ /* this is not the image we want to read: skip it. */
+ if (BitSet(buf[8], LOCALCOLORMAP)) {
+ if (!ReadColorMap(gifConfPtr, bitPixel, colorMap)) {
+ Tcl_AppendResult(interp,
+ "error reading color map", (char *) NULL);
+ goto error;
+ }
+ }
+
+ /* If we've not yet allocated a trash buffer, do so now */
+ if (trashBuffer == NULL) {
+ nBytes = fileWidth * fileHeight * 3;
+ trashBuffer = (char *) ckalloc((unsigned int) 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(interp, trashBuffer, gifConfPtr, imageWidth,
+ imageHeight, colorMap, 0, 0, 0, 0, 0, -1) != TCL_OK) {
+ goto error;
+ }
+ continue;
+ }
+
+ /* If a trash buffer has been allocated, free it now */
+ if (trashBuffer != NULL) {
+ ckfree((char *)trashBuffer);
+ trashBuffer = NULL;
+ }
+ if (BitSet(buf[8], LOCALCOLORMAP)) {
+ if (!ReadColorMap(gifConfPtr, bitPixel, colorMap)) {
+ Tcl_AppendResult(interp, "error reading color map", (char *) NULL);
+ goto error;
+ }
+ }
+
+ 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)) {
+ block.pixelPtr = 0;
+ goto noerror;
+ }
+
+ block.width = width;
+ block.height = height;
+ block.pixelSize = (transparent>=0)? 4: 3;
+ block.pitch = block.pixelSize * imageWidth;
+ nBytes = block.pitch * imageHeight;
+ pixBuf = (unsigned char *) ckalloc((unsigned) nBytes);
+ block.pixelPtr = pixBuf;
+
+ if (ReadImage(interp, (char *) block.pixelPtr, gifConfPtr, imageWidth, imageHeight,
+ colorMap, fileWidth, fileHeight, srcX, srcY,
+ BitSet(buf[8], INTERLACE), transparent) != TCL_OK) {
+ goto error;
+ }
+ break;
+ }
+
+ block.pixelPtr = pixBuf + srcY * block.pitch + srcX * block.pixelSize;
+ if (tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, destY, width, height,
+ (transparent == -1)? TK_PHOTO_COMPOSITE_SET: TK_PHOTO_COMPOSITE_OVERLAY) == TCL_ERROR) {
+ goto error;
+ }
+
+noerror:
+ if (pixBuf) {
+ ckfree((char *) pixBuf);
+ }
+ return TCL_OK;
+
+error:
+ if (pixBuf) {
+ ckfree((char *) pixBuf);
+ }
+ return TCL_ERROR;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ObjMatch --
+ *
+ * This procedure 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 object are
+ * like GIF data, and 0 otherwise.
+ *
+ * Side effects:
+ * the size of the image is placed in widthPtr and heightPtr.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+ObjMatch(
+ Tcl_Obj *data, /* the object containing the image data */
+ Tcl_Obj *format, /* the image format object */
+ int *widthPtr, /* where to put the image width */
+ int *heightPtr, /* where to put the image height */
+ Tcl_Interp *interp /* interpreter */
+) {
+ GIFImageConfig gifConf;
+
+ memset(&gifConf, 0, sizeof(GIFImageConfig));
+
+ if (!tkimg_ReadInit(data, 'G', &gifConf.handle)) {
+ return 0;
+ }
+ return ReadGIFHeader(&gifConf, widthPtr, heightPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ObjRead --
+ *
+ * This procedure is called by the photo image type to read
+ * GIF format data from a base64 encoded string, 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
+ * procedure calls FileReadGif by redefining the operation of
+ * fprintf temporarily.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+ObjRead(interp, data, format, imageHandle,
+ destX, destY, width, height, srcX, srcY)
+ Tcl_Interp *interp; /* interpreter for reporting errors in */
+ Tcl_Obj *data; /* object containing the image */
+ Tcl_Obj *format; /* format object if any */
+ Tk_PhotoHandle imageHandle; /* the image to write this data into */
+ int destX, destY; /* The rectangular region of the */
+ int width, height; /* image to copy */
+ int srcX, srcY;
+{
+ GIFImageConfig gifConf;
+
+ memset(&gifConf, 0, sizeof(GIFImageConfig));
+
+ tkimg_ReadInit(data, 'G', &gifConf.handle);
+ return CommonRead(interp, &gifConf, "inline data", format,
+ imageHandle, destX, destY, width, height, srcX, srcY);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ReadGIFHeader --
+ *
+ * This procedure 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(gifConfPtr, widthPtr, heightPtr)
+ GIFImageConfig *gifConfPtr; /* Image file to read the header from */
+ int *widthPtr, *heightPtr; /* The dimensions of the image are
+ * returned here. */
+{
+ unsigned char buf[7];
+
+ if ((tkimg_Read(&gifConfPtr->handle, (char *)buf, 6) != 6)
+ || ((strncmp(GIF87a, (char *) buf, 6) != 0)
+ && (strncmp(GIF89a, (char *) buf, 6) != 0))) {
+ return 0;
+ }
+
+ if (tkimg_Read(&gifConfPtr->handle, (char *)buf, 4) != 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(gifConfPtr, number, buffer)
+ GIFImageConfig *gifConfPtr;
+ int number;
+ unsigned char buffer[MAXCOLORMAPSIZE][4];
+{
+ int i;
+ unsigned char rgb[3];
+
+ for (i=0; i<number; ++i) {
+ if (! ReadOK(&gifConfPtr->handle, rgb, sizeof(rgb))) {
+ 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(gifConfPtr, label, transparent)
+ GIFImageConfig *gifConfPtr;
+ int label;
+ 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, (unsigned char*) gifConfPtr->buf);
+ } while (count > 0);
+ return count;
+
+ case 0xf9: /* Graphic Control Extension */
+ count = GetDataBlock(gifConfPtr, (unsigned char*) gifConfPtr->buf);
+ if (count < 0) {
+ return 1;
+ }
+ if ((gifConfPtr->buf[0] & 0x1) != 0) {
+ *transparent = gifConfPtr->buf[3];
+ }
+
+ do {
+ count = GetDataBlock(gifConfPtr, (unsigned char*) gifConfPtr->buf);
+ } while (count > 0);
+ return count;
+ }
+
+ do {
+ count = GetDataBlock(gifConfPtr, (unsigned char*) gifConfPtr->buf);
+ } while (count > 0);
+ return count;
+}
+
+static int
+GetDataBlock(gifConfPtr, buf)
+ GIFImageConfig *gifConfPtr;
+ unsigned char *buf;
+{
+ unsigned char count;
+
+ if (! ReadOK(&gifConfPtr->handle,&count,1)) {
+ return -1;
+ }
+
+ if ((count != 0) && (! ReadOK(&gifConfPtr->handle, buf, count))) {
+ 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(interp, imagePtr, gifConfPtr, len, rows, cmap,
+ width, height, srcX, srcY, interlace, transparent)
+ Tcl_Interp *interp;
+ char *imagePtr;
+ GIFImageConfig *gifConfPtr;
+ int len, rows;
+ unsigned char cmap[MAXCOLORMAPSIZE][4];
+ int width, height;
+ int srcX, srcY;
+ int interlace;
+ int transparent;
+{
+ unsigned char initialCodeSize;
+ int v;
+ int xpos = 0, ypos = 0, pass = 0, i;
+ register char *pixelPtr;
+ const static int interlaceStep[] = { 8, 8, 4, 2 };
+ const static 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, code, firstCode;
+
+ /*
+ * Initialize the decoder
+ */
+ if (! ReadOK(&gifConfPtr->handle,&initialCodeSize,1)) {
+ Tcl_AppendResult(interp, "error reading GIF image: ",
+ Tcl_PosixError(interp), (char *) NULL);
+ return TCL_ERROR;
+ }
+ if (initialCodeSize > MAX_LWZ_BITS) {
+ Tcl_AppendResult(interp, "error reading GIF image: malformed image", (char *) 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((void *)prefix, 0, (1 << MAX_LWZ_BITS) * sizeof(short));
+ memset((void *)append, 0, (1 << MAX_LWZ_BITS) * sizeof(char));
+ for (i = 0; i < clearCode; i++) {
+ append[i] = i;
+ }
+ top = stack;
+
+ GetCode(gifConfPtr, 0, 1);
+
+ /* 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(gifConfPtr, codeSize, 0);
+ 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: handle the handle 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(gifConfPtr, code_size, flag)
+ GIFImageConfig *gifConfPtr;
+ int code_size;
+ int flag;
+{
+ int ret;
+
+ if (flag) {
+ /* Initialize the decoder */
+ gifConfPtr->bitsInWindow = 0;
+ gifConfPtr->bytes = 0;
+ gifConfPtr->window = 0;
+ gifConfPtr->done = 0;
+ gifConfPtr->c = NULL;
+ return 0;
+ }
+
+ while (gifConfPtr->bitsInWindow < code_size) {
+ /* Not enough bits in our window to cover the request */
+ if (gifConfPtr->done) {
+ return -1;
+ }
+ if (gifConfPtr->bytes == 0) {
+ /* Not enough bytes in our buffer to add to the window */
+ gifConfPtr->bytes = GetDataBlock(gifConfPtr, gifConfPtr->buf);
+ gifConfPtr->c = gifConfPtr->buf;
+ if (gifConfPtr->bytes <= 0) {
+ gifConfPtr->done = 1;
+ break;
+ }
+ }
+ /* Tack another byte onto the window, see if that's enough */
+ gifConfPtr->window += (*gifConfPtr->c) << gifConfPtr->bitsInWindow;
+ gifConfPtr->c++;
+ gifConfPtr->bitsInWindow += 8;
+ gifConfPtr->bytes--;
+ }
+
+ /* The next code will always be the last code_size bits of the window */
+ ret = gifConfPtr->window & ((1 << code_size) - 1);
+
+ /* Shift data in the window to put the next code at the end */
+ gifConfPtr->window >>= code_size;
+ gifConfPtr->bitsInWindow -= code_size;
+ return ret;
+}
+
+/*
+ * This software is copyrighted as noted below. It may be freely copied,
+ * modified, and redistributed, provided that the copyright notice is
+ * preserved on all copies.
+ *
+ * There is no warranty or other guarantee of fitness for this software,
+ * it is provided solely "as is". Bug reports or fixes may be sent
+ * to the author, who may or may not act on them as he desires.
+ *
+ * You may not include this software in a program or other software product
+ * without supplying the source, or without informing the end-user that the
+ * source is available for no extra charge.
+ *
+ * If you modify this software, you should include a notice giving the
+ * name of the person performing the modification, the date of modification,
+ * and the reason for such modification.
+ */
+
+
+/*
+ * ChnWrite - 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) and miGIF compression
+ * by Jan Nijtmans <nijtmans@users.sourceforge.net>
+ *
+ *----------------------------------------------------------------------
+ * FileWriteGIF-
+ *
+ * This procedure 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.
+ *
+ *----------------------------------------------------------------------
+ */
+
+ /*
+ * 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);
+
+/*
+ * Definition of new functions to write GIFs
+ */
+
+static int ColorNumber(GifWriterState *statePtr, int red, int green, int blue);
+
+static void Compress(GifWriterState *statePtr, int init_bits, tkimg_MFile *handle,
+ ifunptr readValue);
+
+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 int
+ChnWrite (interp, filename, format, blockPtr)
+ Tcl_Interp *interp; /* Interpreter to use for reporting errors. */
+ const char *filename;
+ Tcl_Obj *format;
+ Tk_PhotoImageBlock *blockPtr;
+{
+ Tcl_Channel chan = NULL;
+ tkimg_MFile handle;
+ int result;
+
+ chan = tkimg_OpenFileChannel(interp, filename, 0644);
+ if (!chan) {
+ return TCL_ERROR;
+ }
+
+ handle.data = (char *) chan;
+ handle.state = IMG_CHAN;
+
+ result = CommonWrite(interp, &handle, format, blockPtr);
+ if (Tcl_Close(interp, chan) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+ return result;
+}
+
+static int StringWrite(
+ Tcl_Interp *interp,
+ Tcl_Obj *format,
+ Tk_PhotoImageBlock *blockPtr
+) {
+ int result;
+ tkimg_MFile handle;
+ Tcl_DString data;
+
+ Tcl_DStringInit(&data);
+ Tcl_DStringSetLength(&data, 1024);
+ tkimg_WriteInit(&data, &handle);
+
+ result = CommonWrite(interp, &handle, format, blockPtr);
+ tkimg_Putc(IMG_DONE, &handle);
+
+ if (result == TCL_OK) {
+ Tcl_DStringResult(interp, &data);
+ } else {
+ Tcl_DStringFree(&data);
+ }
+ return result;
+}
+
+static int
+CommonWrite(interp, handle, format, blockPtr)
+ Tcl_Interp *interp;
+ tkimg_MFile *handle;
+ 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;
+ }
+
+ tkimg_Write(handle, (const 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_AppendResult(interp, "too many colors", (char *) NULL);
+ return TCL_ERROR;
+ }
+ if (state.num<2) {
+ state.num = 2;
+ }
+
+ c=LSB(width);
+ tkimg_Putc(c, handle);
+ c=MSB(width);
+ tkimg_Putc(c, handle);
+ c=LSB(height);
+ tkimg_Putc(c, handle);
+ c=MSB(height);
+ tkimg_Putc(c, handle);
+
+ resolution = 0;
+ while (state.num >> resolution) {
+ resolution++;
+ }
+ c = 111 + resolution * 17;
+ tkimg_Putc(c, handle);
+
+ state.num = 1 << resolution;
+
+ /* background color */
+ tkimg_Putc(0, handle);
+
+ /* zero for future expansion */
+ tkimg_Putc(0, handle);
+
+ for (x=0; x<state.num ;x++) {
+ tkimg_Putc(state.mapa[x][CM_RED], handle);
+ tkimg_Putc(state.mapa[x][CM_GREEN], handle);
+ tkimg_Putc(state.mapa[x][CM_BLUE], handle);
+ }
+
+ /*
+ * Write out extension for transparent color index, if necessary.
+ */
+
+ if (state.alphaOffset) {
+ c = GIF_EXTENSION;
+ tkimg_Putc(c, handle);
+ tkimg_Write(handle, "\371\4\1\0\0\0", 7);
+ }
+
+ c = GIF_START;
+ tkimg_Putc(c, handle);
+ c=LSB(top);
+ tkimg_Putc(c, handle);
+ c=MSB(top);
+ tkimg_Putc(c, handle);
+ c=LSB(left);
+ tkimg_Putc(c, handle);
+ c=MSB(left);
+ tkimg_Putc(c, handle);
+
+ c=LSB(width);
+ tkimg_Putc(c, handle);
+ c=MSB(width);
+ tkimg_Putc(c, handle);
+
+ c=LSB(height);
+ tkimg_Putc(c, handle);
+ c=MSB(height);
+ tkimg_Putc(c, handle);
+
+ c=0;
+ tkimg_Putc(c, handle);
+ c=resolution;
+ tkimg_Putc(c, handle);
+
+ state.ssize = state.rsize = blockPtr->width;
+ state.csize = blockPtr->height;
+ Compress(&state, resolution+1, handle, ReadValue);
+
+ tkimg_Putc(0, handle);
+ c = GIF_TERMINATOR;
+ tkimg_Putc(c, handle);
+
+ 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(statePtr, blockPtr)
+ GifWriterState *statePtr;
+ Tk_PhotoImageBlock *blockPtr;
+{
+ unsigned char *colores;
+ int x, y;
+ unsigned char red,green,blue;
+
+ if (statePtr->alphaOffset) {
+ statePtr->num = 1;
+ 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(statePtr)
+ 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)
+ *
+ */
+
+#define MAXCODE(n_bits) (((long) 1 << (n_bits)) - 1)
+
+typedef struct {
+ int n_bits; /* number of bits/code */
+ long maxcode; /* maximum code, given n_bits */
+ int htab[HSIZE];
+ unsigned int codetab[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 codetab. The tab_suffix table needs 2**GIFBITS characters. We
+ * get this from the beginning of htab. The output stack uses the rest
+ * of htab, and contains characters. There is plenty of room for any
+ * possible stack (stack used to be 8000 characters).
+ */
+ int free_ent; /* first unused entry */
+
+ /*
+ * block compression parameters -- after all codes are used up,
+ * and compression rate changes, start over.
+ */
+ int clear_flg;
+
+ int offset;
+ unsigned int in_count; /* length of input */
+ unsigned int out_count; /* # of codes output (for debugging) */
+
+ /*
+ * compress stdin to stdout
+ *
+ * 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 g_init_bits;
+ tkimg_MFile *g_outfile;
+
+ int ClearCode;
+ int EOFCode;
+
+ unsigned long cur_accum;
+ int cur_bits;
+
+ /*
+ * Number of characters so far in this 'packet'
+ */
+ int a_count;
+
+ /*
+ * Define the storage for the packet accumulator
+ */
+ unsigned char accum[256];
+} GIFState_t;
+
+static void output(GIFState_t *statePtr, long code);
+static void cl_block(GIFState_t *statePtr);
+static void cl_hash(GIFState_t *statePtr, int hsize);
+static void char_init(GIFState_t *statePtr);
+static void char_out(GIFState_t *statePtr, int c);
+static void flush_char(GIFState_t *statePtr);
+
+static void Compress(statePtr, init_bits, handle, readValue)
+ GifWriterState *statePtr;
+ int init_bits;
+ tkimg_MFile *handle;
+ ifunptr readValue;
+{
+ register long fcode;
+ register long i = 0;
+ register int c;
+ register long ent;
+ register long disp;
+ register long hsize_reg;
+ register int hshift;
+ GIFState_t state;
+
+ memset(&state, 0, sizeof(state));
+ /*
+ * Set up the globals: g_init_bits - initial number of bits
+ * g_outfile - pointer to output file
+ */
+ state.g_init_bits = init_bits;
+ state.g_outfile = handle;
+
+ /*
+ * Set up the necessary values
+ */
+ state.offset = 0;
+ state.hsize = HSIZE;
+ state.out_count = 0;
+ state.clear_flg = 0;
+ state.in_count = 1;
+ state.maxcode = MAXCODE(state.n_bits = state.g_init_bits);
+
+ state.ClearCode = (1 << (init_bits - 1));
+ state.EOFCode = state.ClearCode + 1;
+ state.free_ent = state.ClearCode + 2;
+
+ char_init(&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_reg = state.hsize;
+ cl_hash(&state, (int) hsize_reg); /* clear hash table */
+
+ output(&state, (long)state.ClearCode);
+
+#ifdef SIGNED_COMPARE_SLOW
+ while ( (c = readValue(statePtr) ) != (unsigned) EOF ) {
+#else
+ while ( (c = readValue(statePtr)) != EOF ) {
+#endif
+
+ state.in_count++;
+
+ fcode = (long) (((long) c << GIFBITS) + ent);
+ i = (((long)c << hshift) ^ ent); /* xor hashing */
+
+ if (state.htab[i] == fcode) {
+ ent = state.codetab[i];
+ continue;
+ } else if ( (long) state.htab[i] < 0 ) /* empty slot */
+ goto nomatch;
+ disp = hsize_reg - i; /* secondary hash (after G. Knott) */
+ if ( i == 0 ) {
+ disp = 1;
+ }
+probe:
+ if ( (i -= disp) < 0 ) {
+ i += hsize_reg;
+ }
+
+ if (state.htab[i] == fcode) {
+ ent = state.codetab[i];
+ continue;
+ }
+ if ( (long) state.htab[i] > 0 ) {
+ goto probe;
+ }
+nomatch:
+ output (&state, (long) ent);
+ state.out_count++;
+ ent = c;
+#ifdef SIGNED_COMPARE_SLOW
+ if ( (unsigned) free_ent < (unsigned) ((long)1 << GIFBITS)) {
+#else
+ if (state.free_ent < ((long)1 << GIFBITS)) {
+#endif
+ state.codetab[i] = state.free_ent++; /* code -> hashtable */
+ state.htab[i] = fcode;
+ } else {
+ cl_block(&state);
+ }
+ }
+ /*
+ * Put out the final code.
+ */
+ output(&state, (long)ent);
+ state.out_count++;
+ output(&state, (long) state.EOFCode);
+
+ return;
+}
+
+/*****************************************************************
+ * TAG( output )
+ *
+ * Output the given code.
+ * Inputs:
+ * code: A n_bits-bit integer. If == -1, then EOF. This assumes
+ * that n_bits =< (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 const
+unsigned long masks[] = {
+ 0x0000,
+ 0x0001, 0x0003, 0x0007, 0x000F,
+ 0x001F, 0x003F, 0x007F, 0x00FF,
+ 0x01FF, 0x03FF, 0x07FF, 0x0FFF,
+ 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF
+};
+
+static void
+output(statePtr, code)
+ GIFState_t *statePtr;
+ long code;
+{
+ statePtr->cur_accum &= masks[statePtr->cur_bits];
+
+ if (statePtr->cur_bits > 0) {
+ statePtr->cur_accum |= ((long) code << statePtr->cur_bits);
+ } else {
+ statePtr->cur_accum = code;
+ }
+
+ statePtr->cur_bits += statePtr->n_bits;
+
+ while (statePtr->cur_bits >= 8 ) {
+ char_out(statePtr, (unsigned int)(statePtr->cur_accum & 0xff));
+ statePtr->cur_accum >>= 8;
+ statePtr->cur_bits -= 8;
+ }
+
+ /*
+ * If the next entry is going to be too big for the code size,
+ * then increase it, if possible.
+ */
+
+ if ((statePtr->free_ent > statePtr->maxcode)|| statePtr->clear_flg ) {
+ if (statePtr->clear_flg) {
+ statePtr->maxcode = MAXCODE(statePtr->n_bits = statePtr->g_init_bits);
+ statePtr->clear_flg = 0;
+ } else {
+ statePtr->n_bits++;
+ if (statePtr->n_bits == GIFBITS) {
+ statePtr->maxcode = (long)1 << GIFBITS;
+ } else {
+ statePtr->maxcode = MAXCODE(statePtr->n_bits);
+ }
+ }
+ }
+
+ if (code == statePtr->EOFCode) {
+ /*
+ * At EOF, write the rest of the buffer.
+ */
+ while (statePtr->cur_bits > 0) {
+ char_out(statePtr, (unsigned int)(statePtr->cur_accum & 0xff));
+ statePtr->cur_accum >>= 8;
+ statePtr->cur_bits -= 8;
+ }
+ flush_char(statePtr);
+ }
+}
+
+/*
+ * Clear out the hash table
+ */
+static void
+cl_block(statePtr) /* table clear for block compress */
+ GIFState_t *statePtr;
+{
+
+ cl_hash (statePtr, (int) statePtr->hsize);
+ statePtr->free_ent = statePtr->ClearCode + 2;
+ statePtr->clear_flg = 1;
+
+ output(statePtr, (long) statePtr->ClearCode);
+}
+
+static void
+cl_hash(statePtr, hsize) /* reset code table */
+ GIFState_t *statePtr;
+ int hsize;
+{
+ register int *htab_p = statePtr->htab+hsize;
+ register long i;
+ register long m1 = -1;
+
+ i = hsize - 16;
+ do { /* might use Sys V memset(3) here */
+ *(htab_p-16) = m1;
+ *(htab_p-15) = m1;
+ *(htab_p-14) = m1;
+ *(htab_p-13) = m1;
+ *(htab_p-12) = m1;
+ *(htab_p-11) = m1;
+ *(htab_p-10) = m1;
+ *(htab_p-9) = m1;
+ *(htab_p-8) = m1;
+ *(htab_p-7) = m1;
+ *(htab_p-6) = m1;
+ *(htab_p-5) = m1;
+ *(htab_p-4) = m1;
+ *(htab_p-3) = m1;
+ *(htab_p-2) = m1;
+ *(htab_p-1) = m1;
+ htab_p -= 16;
+ } while ((i -= 16) >= 0);
+
+ for (i += 16; i > 0; i--) {
+ *--htab_p = m1;
+ }
+}
+
+/******************************************************************************
+ *
+ * GIF Specific routines
+ *
+ ******************************************************************************/
+
+/*
+ * Set up the 'byte output' routine
+ */
+static void
+char_init(statePtr)
+ GIFState_t *statePtr;
+{
+ statePtr->a_count = 0;
+ statePtr->cur_accum = 0;
+ statePtr->cur_bits = 0;
+}
+
+/*
+ * Add a character to the end of the current packet, and if it is 254
+ * characters, flush the packet to disk.
+ */
+static void
+char_out(statePtr, c)
+ GIFState_t *statePtr;
+ int c;
+{
+ statePtr->accum[statePtr->a_count++] = c;
+ if (statePtr->a_count >= 254) {
+ flush_char(statePtr);
+ }
+}
+
+/*
+ * Flush the packet to disk, and reset the accumulator
+ */
+static void
+flush_char(statePtr)
+ GIFState_t *statePtr;
+{
+ unsigned char c;
+
+ if (statePtr->a_count > 0) {
+ c = statePtr->a_count;
+ tkimg_Write(statePtr->g_outfile, (const char *) &c, 1);
+ tkimg_Write(statePtr->g_outfile, (const char *) statePtr->accum, statePtr->a_count);
+ statePtr->a_count = 0;
+ }
+}
+
+/* The End */