/* * imgGIF.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 * * 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. | * +-------------------------------------------------------------------+ * * RCS: @(#) $Id: tkImgGIF.c,v 1.14 2000/03/30 19:44:41 ericm Exp $ */ #include "tk.h" #include "tkVMacro.h" #include "imgInt.h" #include #include /* * 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 , */ /* * The format record for the GIF file format: */ static int ChanMatchGIF _ANSI_ARGS_((Tcl_Channel chan, struct Tcl_Obj *fileName, struct Tcl_Obj *format, int *widthPtr, int *heightPtr, Tcl_Interp *interp)); static int ObjMatchGIF _ANSI_ARGS_((struct Tcl_Obj *data, struct Tcl_Obj *format, int *widthPtr, int *heightPtr, Tcl_Interp *interp)); static int ChanReadGIF _ANSI_ARGS_((Tcl_Interp *interp, Tcl_Channel chan, struct Tcl_Obj *fileName, struct Tcl_Obj *format, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, int srcX, int srcY)); static int ObjReadGIF _ANSI_ARGS_((Tcl_Interp *interp, Tcl_Obj *data, Tcl_Obj *format, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, int srcX, int srcY)); static int ChanWriteGIF _ANSI_ARGS_(( Tcl_Interp *interp, char *filename, Tcl_Obj *format, Tk_PhotoImageBlock *blockPtr)); static int StringWriteGIF _ANSI_ARGS_((Tcl_Interp *interp, Tcl_Obj *format, Tk_PhotoImageBlock *blockPtr)); static int CommonReadGIF _ANSI_ARGS_((Tcl_Interp *interp, MFile *handle, CONST char *fileName, Tcl_Obj *format, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, int srcX, int srcY)); static int CommonWriteGIF _ANSI_ARGS_((Tcl_Interp *interp, MFile *handle, Tcl_Obj *format, Tk_PhotoImageBlock *blockPtr)); Tk_PhotoImageFormat imgFmtGIF = { "gif", /* name */ ChanMatchGIF, /* fileMatchProc */ ObjMatchGIF, /* stringMatchProc */ ChanReadGIF, /* fileReadProc */ ObjReadGIF, /* stringReadProc */ ChanWriteGIF, /* fileWriteProc */ StringWriteGIF, /* stringWriteProc */ }; #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,buffer,len) (ImgRead(handle, buffer, len) == len) /* * Prototypes for local procedures defined in this file: */ static int DoExtension _ANSI_ARGS_((MFile *handle, int label, int *transparent)); static int GetCode _ANSI_ARGS_((MFile *handle, int code_size, int flag)); static int GetDataBlock _ANSI_ARGS_((MFile *handle, unsigned char *buf)); static int ReadColorMap _ANSI_ARGS_((MFile *handle, int number, unsigned char buffer[MAXCOLORMAPSIZE][4])); static int ReadGIFHeader _ANSI_ARGS_((MFile *handle, int *widthPtr, int *heightPtr)); static int ReadImage _ANSI_ARGS_((Tcl_Interp *interp, char *imagePtr, MFile *handle, int len, int rows, unsigned char cmap[MAXCOLORMAPSIZE][4], int width, int height, int srcX, int srcY, int interlace, int transparent)); /* *---------------------------------------------------------------------- * * ChanMatchGIF -- * * 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 ChanMatchGIF(chan, fileName, format, widthPtr, heightPtr, interp) Tcl_Channel chan; /* The image channel, open for reading. */ struct Tcl_Obj *fileName; /* The name of the image file. */ struct Tcl_Obj *format; /* User-specified format object, or NULL. */ int *widthPtr, *heightPtr; /* The dimensions of the image are * returned here if the file is a valid * raw GIF file. */ Tcl_Interp *interp; { MFile handle; ImgFixChanMatchProc(&interp, &chan, &fileName, &format, &widthPtr, &heightPtr); handle.data = (char *) chan; handle.state = IMG_CHAN; return ReadGIFHeader(&handle, widthPtr, heightPtr); } /* *---------------------------------------------------------------------- * * ChanReadGIF -- * * 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 ChanReadGIF(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. */ struct Tcl_Obj *fileName; /* The name of the image file. */ struct 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. */ { MFile handle; int nBytes; handle.data = (char *) chan; handle.state = IMG_CHAN; return CommonReadGIF(interp, &handle, Tcl_GetStringFromObj(fileName,&nBytes), format, imageHandle, destX, destY, width, height, srcX, srcY); } /* *---------------------------------------------------------------------- * * CommonReadGIF -- * * 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. * *---------------------------------------------------------------------- */ typedef struct myblock { Tk_PhotoImageBlock ck; int dummy; /* extra space for offset[3], if not included already in Tk_PhotoImageBlock */ } myblock; #define block bl.ck static int CommonReadGIF(interp, handle, fileName, format, imageHandle, destX, destY, width, height, srcX, srcY) Tcl_Interp *interp; /* Interpreter to use for reporting errors. */ MFile *handle; /* 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; int nBytes, index = 0, objc = 0; Tcl_Obj **objv = NULL; myblock bl; unsigned char buf[100]; unsigned char *trashBuffer = NULL; int bitPixel; unsigned int colorResolution; unsigned int background; unsigned int aspectRatio; unsigned char colorMap[MAXCOLORMAPSIZE][4]; int transparent = -1; if (ImgListObjGetElements(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: \"", ImgGetStringFromObj(format, NULL), "\"", (char *) NULL); return TCL_ERROR; } } if (objc > 1) { if (Tcl_GetIntFromObj(interp, objv[objc-1], &index) != TCL_OK) { return TCL_ERROR; } } if (!ReadGIFHeader(handle, &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 (ImgRead(handle, buf, 3) != 3) { return TCL_OK; } bitPixel = 2<<(buf[0]&0x07); colorResolution = ((((unsigned int) buf[0]&0x70)>>3)+1); background = buf[1]; aspectRatio = buf[2]; if (BitSet(buf[0], LOCALCOLORMAP)) { /* Global Colormap */ if (!ReadColorMap(handle, 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; } Tk_PhotoExpand(imageHandle, destX + width, destY + height); 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 (ImgRead(handle, 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 (ImgRead(handle, buf, 1) != 1) { Tcl_AppendResult(interp, "error reading extension function code in GIF image", (char *) NULL); goto error; } if (DoExtension(handle, 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 (ImgRead(handle, buf, 9) != 9) { Tcl_AppendResult(interp, "couldn't read left/top/width/height in GIF image", (char *) NULL); goto error; } fileWidth = LM_to_uint(buf[4],buf[5]); fileHeight = 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(handle, 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 = (unsigned 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, handle, fileWidth, fileHeight, 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(handle, 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 > fileWidth) { width = fileWidth; } index = LM_to_uint(buf[2],buf[3]); srcY -= index; if (index > srcY) { destY -= srcY; height += srcY; srcY = 0; } if (height > fileHeight) { height = fileHeight; } 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 * width; nBytes = block.pitch * height; block.pixelPtr = (unsigned char *) ckalloc((unsigned) nBytes); if (ReadImage(interp, (char *) block.pixelPtr, handle, width, height, colorMap, fileWidth, fileHeight, srcX, srcY, BitSet(buf[8], INTERLACE), transparent) != TCL_OK) { goto error; } break; } if (transparent == -1) { Tk_PhotoPutBlock(imageHandle, &block, destX, destY, width, height, TK_PHOTO_COMPOSITE_SET); } else { ImgPhotoPutBlock(imageHandle, &block, destX, destY, width, height); } noerror: if (block.pixelPtr) { ckfree((char *) block.pixelPtr); } return TCL_OK; error: if (block.pixelPtr) { ckfree((char *) block.pixelPtr); } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * ObjMatchGIF -- * * 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 ObjMatchGIF(data, format, widthPtr, heightPtr, interp) 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 */ { MFile handle; ImgFixObjMatchProc(&interp, &data, &format, &widthPtr, &heightPtr); if (!ImgReadInit(data, 'G', &handle)) { return 0; } return ReadGIFHeader(&handle, widthPtr, heightPtr); } /* *---------------------------------------------------------------------- * * ObjReadGIF -- * * 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 ObjReadGIF(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; { MFile handle; ImgReadInit(data, 'G', &handle); return CommonReadGIF(interp, &handle, "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(handle, widthPtr, heightPtr) MFile *handle; /* Image file to read the header from */ int *widthPtr, *heightPtr; /* The dimensions of the image are * returned here. */ { unsigned char buf[7]; if ((ImgRead(handle, buf, 6) != 6) || ((strncmp(GIF87a, (char *) buf, 6) != 0) && (strncmp(GIF89a, (char *) buf, 6) != 0))) { return 0; } if (ImgRead(handle, 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(handle, number, buffer) MFile *handle; int number; unsigned char buffer[MAXCOLORMAPSIZE][4]; { int i; unsigned char rgb[3]; for (i = 0; i < number; ++i) { if (! ReadOK(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(handle, label, transparent) MFile *handle; int label; int *transparent; { static unsigned char buf[256]; int count; switch (label) { case 0x01: /* Plain Text Extension */ break; case 0xff: /* Application Extension */ break; case 0xfe: /* Comment Extension */ do { count = GetDataBlock(handle, (unsigned char*) buf); } while (count > 0); return count; case 0xf9: /* Graphic Control Extension */ count = GetDataBlock(handle, (unsigned char*) buf); if (count < 0) { return 1; } if ((buf[0] & 0x1) != 0) { *transparent = buf[3]; } do { count = GetDataBlock(handle, (unsigned char*) buf); } while (count > 0); return count; } do { count = GetDataBlock(handle, (unsigned char*) buf); } while (count > 0); return count; } static int ZeroDataBlock = 0; static int GetDataBlock(handle, buf) MFile *handle; unsigned char *buf; { unsigned char count; if (! ReadOK(handle,&count,1)) { return -1; } ZeroDataBlock = count == 0; if ((count != 0) && (! ReadOK(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, handle, len, rows, cmap, width, height, srcX, srcY, interlace, transparent) Tcl_Interp *interp; char *imagePtr; MFile *handle; 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(handle,&initialCodeSize,1) || inititalCodeSize > MAX_LWZ_BITS) { Tcl_AppendResult(interp, "error reading GIF image: ", Tcl_PosixError(interp), (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(handle, 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(handle, 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<= 0) { *pixelPtr++ = cmap[v][CM_ALPHA]; } xpos++; } /* If interlacing, the next ypos is not just +1 */ if (interlace) { ypos += interlaceStep[pass]; while (ypos >= height) { 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(handle, code_size, flag) MFile *handle; int code_size; int flag; { static unsigned char buf[280]; static int bytes = 0, done; static unsigned char *c; static unsigned int window; static int bitsInWindow = 0; int ret; if (flag) { /* Initialize the decoder */ bitsInWindow = 0; bytes = 0; window = 0; done = 0; c = NULL; return 0; } while (bitsInWindow < code_size) { /* Not enough bits in our window to cover the request */ if (done) { return -1; } if (bytes == 0) { /* Not enough bytes in our buffer to add to the window */ bytes = GetDataBlock(handle, buf); c = buf; if (bytes <= 0) { done = 1; break; } } /* Tack another byte onto the window, see if that's enough */ window += (*c) << bitsInWindow; c++; bitsInWindow += 8; bytes--; } /* The next code will always be the last code_size bits of the window */ ret = window & ((1 << code_size) - 1); /* Shift data in the window to put the next code at the end */ window >>= code_size; 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. */ /* * 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) and miGIF compression * by Jan Nijtmans * *---------------------------------------------------------------------- * 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. */ typedef int (* ifunptr) _ANSI_ARGS_((void)); #define LSB(a) ((unsigned char) (((short)(a)) & 0x00FF)) #define MSB(a) ((unsigned char) (((short)(a)) >> 8)) #define GIFBITS 12 #define HSIZE 5003 /* 80% occupancy */ static int ssize; static int csize; static int rsize; static unsigned char *pixelo; static int pixelSize; static int pixelPitch; static int greenOffset; static int blueOffset; static int alphaOffset; static int num; static unsigned char mapa[MAXCOLORMAPSIZE][3]; /* * Definition of new functions to write GIFs */ static int color _ANSI_ARGS_((int red,int green, int blue)); static void compress _ANSI_ARGS_((int init_bits, MFile *handle, ifunptr readValue)); static int nuevo _ANSI_ARGS_((int red, int green ,int blue, unsigned char mapa[MAXCOLORMAPSIZE][3])); static int savemap _ANSI_ARGS_((Tk_PhotoImageBlock *blockPtr, unsigned char mapa[MAXCOLORMAPSIZE][3])); static int ReadValue _ANSI_ARGS_((void)); static int no_bits _ANSI_ARGS_((int colors)); static int ChanWriteGIF (interp, filename, format, blockPtr) Tcl_Interp *interp; /* Interpreter to use for reporting errors. */ char *filename; Tcl_Obj *format; Tk_PhotoImageBlock *blockPtr; { Tcl_Channel chan = NULL; MFile handle; int result; chan = ImgOpenFileChannel(interp, filename, 0644); if (!chan) { return TCL_ERROR; } handle.data = (char *) chan; handle.state = IMG_CHAN; result = CommonWriteGIF(interp, &handle, format, blockPtr); if (Tcl_Close(interp, chan) == TCL_ERROR) { return TCL_ERROR; } return result; } static int StringWriteGIF(interp, format, blockPtr) Tcl_Interp *interp; Tcl_Obj *format; Tk_PhotoImageBlock *blockPtr; { int result; MFile handle; Tcl_DString data; Tcl_DString *dataPtr; ImgFixStringWriteProc(&data, &interp, &dataPtr, &format, &blockPtr); Tcl_DStringSetLength(dataPtr, 1024); ImgWriteInit(dataPtr, &handle); result = CommonWriteGIF(interp, &handle, format, blockPtr); ImgPutc(IMG_DONE, &handle); if ((result == TCL_OK) && (dataPtr == &data)) { Tcl_DStringResult(interp, dataPtr); } return result; } static int CommonWriteGIF(interp, handle, format, blockPtr) Tcl_Interp *interp; MFile *handle; Tcl_Obj *format; Tk_PhotoImageBlock *blockPtr; { int resolution; long numcolormap; long width,height,x; unsigned char c; unsigned int top,left; int num; top = 0; left = 0; pixelSize=blockPtr->pixelSize; greenOffset=blockPtr->offset[1]-blockPtr->offset[0]; blueOffset=blockPtr->offset[2]-blockPtr->offset[0]; alphaOffset = blockPtr->offset[0]; if (alphaOffset < blockPtr->offset[2]) { alphaOffset = blockPtr->offset[2]; } if (++alphaOffset < pixelSize) { alphaOffset -= blockPtr->offset[0]; } else { alphaOffset = 0; } ImgWrite(handle, (CONST char *) (alphaOffset ? GIF89a:GIF87a), 6); for (x=0;xwidth; height=blockPtr->height; pixelo=blockPtr->pixelPtr + blockPtr->offset[0]; pixelPitch=blockPtr->pitch; if ((num=savemap(blockPtr,mapa))<0) { Tcl_AppendResult(interp, "too many colors", (char *) NULL); return TCL_ERROR; } if (num<3) num=3; c=LSB(width); ImgPutc(c,handle); c=MSB(width); ImgPutc(c,handle); c=LSB(height); ImgPutc(c,handle); c=MSB(height); ImgPutc(c,handle); c= (1 << 7) | (no_bits(num) << 4) | (no_bits(num)); ImgPutc(c,handle); resolution = no_bits(num)+1; numcolormap=1 << resolution; /* background color */ ImgPutc(0,handle); /* zero for future expansion */ ImgPutc(0,handle); for (x=0; xwidth; csize = blockPtr->height; compress(resolution+1, handle, ReadValue); ImgPutc(0,handle); c = GIF_TERMINATOR; ImgPutc(c,handle); return TCL_OK; } static int color(red, green, blue) int red; int green; int blue; { int x; for (x=(alphaOffset != 0);x<=MAXCOLORMAPSIZE;x++) { if ((mapa[x][CM_RED]==red) && (mapa[x][CM_GREEN]==green) && (mapa[x][CM_BLUE]==blue)) { return x; } } return -1; } static int nuevo(red, green, blue, mapa) int red,green,blue; unsigned char mapa[MAXCOLORMAPSIZE][3]; { int x; for (x=(alphaOffset != 0);xheight;y++) { colores=blockPtr->pixelPtr + blockPtr->offset[0] + y * blockPtr->pitch; for(x=0;xwidth;x++) { if (!alphaOffset || (colores[alphaOffset] != 0)) { red = colores[0]; green = colores[greenOffset]; blue = colores[blueOffset]; if (nuevo(red,green,blue,mapa)) { if (num>255) return -1; mapa[num][CM_RED]=red; mapa[num][CM_GREEN]=green; mapa[num][CM_BLUE]=blue; num++; } } colores += pixelSize; } } return num; } static int ReadValue() { unsigned int col; if (csize == 0) { return EOF; } if (alphaOffset && (pixelo[alphaOffset]==0)) { col = 0; } else { col = color(pixelo[0],pixelo[greenOffset],pixelo[blueOffset]); } pixelo += pixelSize; if (--ssize <= 0) { ssize = rsize; csize--; pixelo += pixelPitch - (rsize * pixelSize); } return col; } /* * Return the number of bits ( -1 ) to represent a given * number of colors ( ex: 256 colors => 7 ). */ static int no_bits( colors ) int colors; { register int bits = 0; colors--; while ( colors >> bits ) { bits++; } return (bits-1); } /*----------------------------------------------------------------------- * * miGIF Compression - mouse and ivo's GIF-compatible compression * * -run length encoding compression routines- * * Copyright (C) 1998 Hutchison Avenue Software Corporation * http://www.hasc.com * info@hasc.com * * 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." The Hutchison Avenue * Software Corporation disclaims all warranties, either express or implied, * including but not limited to implied warranties of merchantability and * fitness for a particular purpose, with respect to this code and accompanying * documentation. * * The miGIF compression routines do not, strictly speaking, generate files * conforming to the GIF spec, since the image data is not LZW-compressed * (this is the point: in order to avoid transgression of the Unisys patent * on the LZW algorithm.) However, miGIF generates data streams that any * reasonably sane LZW decompresser will decompress to what we want. * * miGIF compression uses run length encoding. It compresses horizontal runs * of pixels of the same color. This type of compression gives good results * on images with many runs, for example images with lines, text and solid * shapes on a solid-colored background. It gives little or no compression * on images with few runs, for example digital or scanned photos. * * der Mouse * mouse@rodents.montreal.qc.ca * 7D C8 61 52 5D E7 2D 39 4E F1 31 3E E8 B3 27 4B * * ivo@hasc.com * * The Graphics Interchange Format(c) is the Copyright property of * CompuServe Incorporated. GIF(sm) is a Service Mark property of * CompuServe Incorporated. * */ static int rl_pixel; static int rl_basecode; static int rl_count; static int rl_table_pixel; static int rl_table_max; static int just_cleared; static int out_bits; static int out_bits_init; static int out_count; static int out_bump; static int out_bump_init; static int out_clear; static int out_clear_init; static int max_ocodes; static int code_clear; static int code_eof; static unsigned int obuf; static int obits; static MFile *ofile; static unsigned char oblock[MAXCOLORMAPSIZE]; static int oblen; /* Used only when debugging GIF compression code */ /* #define DEBUGGING_ENVARS */ #ifdef DEBUGGING_ENVARS static int verbose_set = 0; static int verbose; #define VERBOSE (verbose_set?verbose:set_verbose()) static int set_verbose(void) { verbose = !!getenv("GIF_VERBOSE"); verbose_set = 1; return(verbose); } #else #define VERBOSE 0 #endif static CONST char * binformat(v, nbits) unsigned int v; int nbits; { static char bufs[8][64]; static int bhand = 0; unsigned int bit; int bno; char *bp; bhand --; if (bhand < 0) bhand = (sizeof(bufs)/sizeof(bufs[0]))-1; bp = &bufs[bhand][0]; for (bno=nbits-1,bit=1<=0;bno--,bit>>=1) { *bp++ = (v & bit) ? '1' : '0'; if (((bno&3) == 0) && (bno != 0)) *bp++ = '.'; } *bp = '\0'; return(&bufs[bhand][0]); } static void write_block() { int i; unsigned char c; if (VERBOSE) { printf("write_block %d:",oblen); for (i=0;i= 255) write_block(); } static void block_flush() { if (VERBOSE) printf("block_flush\n"); if (oblen > 0) write_block(); } static void output(val) int val; { if (VERBOSE) printf("output %s [%s %d %d]\n",binformat(val,out_bits),binformat(obuf,obits),obits,out_bits); obuf |= val << obits; obits += out_bits; while (obits >= 8) { block_out(obuf&0xff); obuf >>= 8; obits -= 8; } if (VERBOSE) printf("output leaving [%s %d]\n",binformat(obuf,obits),obits); } static void output_flush() { if (VERBOSE) printf("output_flush\n"); if (obits > 0) block_out(obuf); block_flush(); } static void did_clear() { if (VERBOSE) printf("did_clear\n"); out_bits = out_bits_init; out_bump = out_bump_init; out_clear = out_clear_init; out_count = 0; rl_table_max = 0; just_cleared = 1; } static void output_plain(c) int c; { if (VERBOSE) printf("output_plain %s\n",binformat(c,out_bits)); just_cleared = 0; output(c); out_count ++; if (out_count >= out_bump) { out_bits ++; out_bump += 1 << (out_bits - 1); } if (out_count >= out_clear) { output(code_clear); did_clear(); } } static unsigned int isqrt(x) unsigned int x; { unsigned int r; unsigned int v; if (x < 2) return(x); for (v=x,r=1;v;v>>=2,r<<=1) ; while (1) { v = ((x / r) + r) / 2; if ((v == r) || (v == r+1)) return(r); r = v; } } static unsigned int compute_triangle_count(count, nrepcodes) unsigned int count; unsigned int nrepcodes; { unsigned int perrep; unsigned int cost; cost = 0; perrep = (nrepcodes * (nrepcodes+1)) / 2; while (count >= perrep) { cost += nrepcodes; count -= perrep; } if (count > 0) { unsigned int n; n = isqrt(count); while ((n*(n+1)) >= 2*count) n --; while ((n*(n+1)) < 2*count) n ++; cost += n; } return(cost); } static void max_out_clear() { out_clear = max_ocodes; } static void reset_out_clear() { out_clear = out_clear_init; if (out_count >= out_clear) { output(code_clear); did_clear(); } } static void rl_flush_fromclear(count) int count; { int n; if (VERBOSE) printf("rl_flush_fromclear %d\n",count); max_out_clear(); rl_table_pixel = rl_pixel; n = 1; while (count > 0) { if (n == 1) { rl_table_max = 1; output_plain(rl_pixel); count --; } else if (count >= n) { rl_table_max = n; output_plain(rl_basecode+n-2); count -= n; } else if (count == 1) { rl_table_max ++; output_plain(rl_pixel); count = 0; } else { rl_table_max ++; output_plain(rl_basecode+count-2); count = 0; } if (out_count == 0) n = 1; else n ++; } reset_out_clear(); if (VERBOSE) printf("rl_flush_fromclear leaving table_max=%d\n",rl_table_max); } static void rl_flush_clearorrep(count) int count; { int withclr; if (VERBOSE) printf("rl_flush_clearorrep %d\n",count); withclr = 1 + compute_triangle_count(count,max_ocodes); if (withclr < count) { output(code_clear); did_clear(); rl_flush_fromclear(count); } else { for (;count>0;count--) output_plain(rl_pixel); } } static void rl_flush_withtable(count) int count; { int repmax; int repleft; int leftover; if (VERBOSE) printf("rl_flush_withtable %d\n",count); repmax = count / rl_table_max; leftover = count % rl_table_max; repleft = (leftover ? 1 : 0); if (out_count+repmax+repleft > max_ocodes) { repmax = max_ocodes - out_count; leftover = count - (repmax * rl_table_max); repleft = 1 + compute_triangle_count(leftover,max_ocodes); } if (VERBOSE) printf("rl_flush_withtable repmax=%d leftover=%d repleft=%d\n",repmax,leftover,repleft); if (1+(int) compute_triangle_count(count,max_ocodes) < repmax+repleft) { output(code_clear); did_clear(); rl_flush_fromclear(count); return; } max_out_clear(); for (;repmax>0;repmax--) output_plain(rl_basecode+rl_table_max-2); if (leftover) { if (just_cleared) { rl_flush_fromclear(leftover); } else if (leftover == 1) { output_plain(rl_pixel); } else { output_plain(rl_basecode+leftover-2); } } reset_out_clear(); } static void rl_flush() { if (VERBOSE) printf("rl_flush [ %d %d\n",rl_count,rl_pixel); if (rl_count == 1) { output_plain(rl_pixel); rl_count = 0; if (VERBOSE) printf("rl_flush ]\n"); return; } if (just_cleared) { rl_flush_fromclear(rl_count); } else if ((rl_table_max < 2) || (rl_table_pixel != rl_pixel)) { rl_flush_clearorrep(rl_count); } else { rl_flush_withtable(rl_count); } if (VERBOSE) printf("rl_flush ]\n"); rl_count = 0; } static void compress( init_bits, handle, readValue ) int init_bits; MFile *handle; ifunptr readValue; { int c; ofile = handle; obuf = 0; obits = 0; oblen = 0; code_clear = 1 << (init_bits - 1); code_eof = code_clear + 1; rl_basecode = code_eof + 1; out_bump_init = (1 << (init_bits - 1)) - 1; /* for images with a lot of runs, making out_clear_init larger will give better compression. */ out_clear_init = (init_bits <= 3) ? 9 : (out_bump_init-1); #ifdef DEBUGGING_ENVARS { CONST char *ocienv; ocienv = getenv("GIF_OUT_CLEAR_INIT"); if (ocienv) { out_clear_init = atoi(ocienv); if (VERBOSE) printf("[overriding out_clear_init to %d]\n",out_clear_init); } } #endif out_bits_init = init_bits; max_ocodes = (1 << GIFBITS) - ((1 << (out_bits_init - 1)) + 3); did_clear(); output(code_clear); rl_count = 0; while (1) { c = readValue(); if ((rl_count > 0) && (c != rl_pixel)) rl_flush(); if (c == EOF) break; if (rl_pixel == c) { rl_count ++; } else { rl_pixel = c; rl_count = 1; } } output(code_eof); output_flush(); } /*----------------------------------------------------------------------- * * End of miGIF section - See copyright notice at start of section. * *-----------------------------------------------------------------------*/