/* * 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 © Reed Wade (wade@cs.utk.edu), University of Tennessee * Copyright © 1995-1997 Sun Microsystems, Inc. * Copyright © 1997 Australian National University * Copyright © 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 hold the data of a Graphic Control Extension block. */ typedef struct { int blockPresent; /* if 1, the block was read and is in scope */ int transparent; /* Transparency index */ int delayTime; /* update delay time in 10ms */ int disposalMethod; /* disposal method 0-3 */ int userInteraction; /* user interaction 0/1 */ } GIFGraphicControlExtensionBlock; /* * 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) */ Tcl_Size 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 * accommodate these systems, we test against the numeric value of the ASCII * characters instead of the characters themselves. This is encoding * independent. */ 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 Tcl_Size (WriteBytesFunc) (void *clientData, const char *bytes, Tcl_Size byteCount); /* * The format record for the GIF file format: */ static int FileMatchGIF(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataInObj, int *widthPtr, int *heightPtr, Tcl_Obj *metadataOutObj); static int FileReadGIF(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataInObj, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, int srcX, int srcY, Tcl_Obj *metadataOutObj); static int StringMatchGIF(Tcl_Interp *interp, Tcl_Obj *dataObj, Tcl_Obj *format, Tcl_Obj *metadataInObj, int *widthPtr, int *heightPtr, Tcl_Obj *metadataOutObj); static int StringReadGIF(Tcl_Interp *interp, Tcl_Obj *dataObj, Tcl_Obj *format, Tcl_Obj *metadataInObj, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, int srcX, int srcY, Tcl_Obj *metadataOutObj); static int FileWriteGIF(Tcl_Interp *interp, const char *filename, Tcl_Obj *format, Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr); static int StringWriteGIF(Tcl_Interp *interp, Tcl_Obj *format, Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr); static int CommonWriteGIF(Tcl_Interp *interp, void *clientData, WriteBytesFunc *writeProc, Tcl_Obj *format, Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr); Tk_PhotoImageFormatVersion3 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 ReadOneByte(Tcl_Interp *interp, GIFImageConfig *gifConfPtr, Tcl_Channel chan); static int DoExtension(GIFImageConfig *gifConfPtr, Tcl_Channel chan, int label, unsigned char *buffer, GIFGraphicControlExtensionBlock *gifGraphicControlExtensionBlock, Tcl_Obj *metadataOutObj); 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 Tcl_Size Fread(GIFImageConfig *gifConfPtr, unsigned char *dst, Tcl_Size size, Tcl_Size count, Tcl_Channel chan); static Tcl_Size Mread(unsigned char *dst, Tcl_Size size, Tcl_Size count, MFile *handle); static int Mgetc(MFile *handle); static int char64(int c); static void mInit(unsigned char *string, MFile *handle, Tcl_Size 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; void *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, void *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_UNUSED(Tcl_Interp *), /* not used */ Tcl_Channel chan, /* The image file, open for reading. */ TCL_UNUSED(const char *), /* The name of the image file. */ TCL_UNUSED(Tcl_Obj *), /* User-specified format object, or NULL. */ TCL_UNUSED(Tcl_Obj *), /* metadata input, may be NULL */ int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here if the file is a valid raw GIF file. */ TCL_UNUSED(Tcl_Obj *)) /* metadata return dict, may be NULL */ { 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. */ TCL_UNUSED(Tcl_Obj *), /* metadata input, may be 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. */ Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ { int fileWidth, fileHeight, imageWidth, imageHeight; unsigned int nBytes; int index = 0, result = TCL_ERROR; Tcl_Size argc = 0, i; Tcl_Obj **objv; unsigned char buf[100]; unsigned char *trashBuffer = NULL; int bitPixel; int gifLabel; unsigned char colorMap[MAXCOLORMAPSIZE][4]; GIFGraphicControlExtensionBlock gifGraphicControlExtensionBlock; static const char *const optionStrings[] = { "-index", NULL }; GIFImageConfig gifConf, *gifConfPtr = &gifConf; gifGraphicControlExtensionBlock.blockPresent = 0; /* * 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", TCL_INDEX_NONE)); 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; } /* * ------------------------------------------------------------------------- * From here on, go to error to not leave memory leaks * ------------------------------------------------------------------------- */ /* * Search for the frame from the GIF to display. */ while (1) { if (-1 == (gifLabel = ReadOneByte( interp, gifConfPtr, chan ) ) ) { goto error; } switch (gifLabel) { case GIF_TERMINATOR: Tcl_SetObjResult(interp, Tcl_NewStringObj( "no image data for this index", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "NO_DATA", NULL); goto error; case GIF_EXTENSION: /* * This is a GIF extension. */ if (-1 == (gifLabel = ReadOneByte( interp, gifConfPtr, chan ) ) ) { goto error; } if (DoExtension(gifConfPtr, chan, gifLabel, gifConfPtr->workingBuffer, &gifGraphicControlExtensionBlock, metadataOutObj) < 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "error reading extension in GIF image", TCL_INDEX_NONE)); 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", TCL_INDEX_NONE)); 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 = (unsigned char *)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; } /* * This extension starts a new scope, so Graphic control Extension * data should be cleared */ gifGraphicControlExtensionBlock.blockPresent = 0; 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", TCL_INDEX_NONE)); 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; int transparent = -1; if (gifGraphicControlExtensionBlock.blockPresent) { transparent = gifGraphicControlExtensionBlock.transparent; } /* * 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 = (unsigned char *)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); } /* * Update the metadata dictionary with current image data */ if (NULL != metadataOutObj) { /* * Save the update box, if not the whole image */ if ( width != fileWidth || height != fileHeight) { Tcl_Obj *itemList[4]; itemList[0] = Tcl_NewIntObj(destX); itemList[1] = Tcl_NewIntObj(destY); itemList[2] = Tcl_NewIntObj(width); itemList[3] = Tcl_NewIntObj(height); if ( TCL_OK != Tcl_DictObjPut(interp, metadataOutObj, Tcl_NewStringObj("update region",-1), Tcl_NewListObj(4, itemList) )) { result = TCL_ERROR; goto error; } } /* * Copy the Graphic Control Extension Block data to the metadata * dictionary */ if (gifGraphicControlExtensionBlock.blockPresent) { if ( gifGraphicControlExtensionBlock.delayTime != 0) { if ( TCL_OK != Tcl_DictObjPut(interp, metadataOutObj, Tcl_NewStringObj("delay time",-1), Tcl_NewIntObj(gifGraphicControlExtensionBlock.delayTime) )) { result = TCL_ERROR; goto error; } } switch ( gifGraphicControlExtensionBlock.disposalMethod ) { case 1: /* Do not dispose */ if ( TCL_OK != Tcl_DictObjPut(interp, metadataOutObj, Tcl_NewStringObj("disposal method",-1), Tcl_NewStringObj("do not dispose",-1))) { result = TCL_ERROR; goto error; } break; case 2: /* Restore to background color */ if ( TCL_OK != Tcl_DictObjPut(interp, metadataOutObj, Tcl_NewStringObj("disposal method",-1), Tcl_NewStringObj("restore to background color",-1))) { result = TCL_ERROR; goto error; } break; case 3: /* Restore to previous */ if ( TCL_OK != Tcl_DictObjPut(interp, metadataOutObj, Tcl_NewStringObj("disposal method",-1), Tcl_NewStringObj("restore to previous",-1))) { result = TCL_ERROR; goto error; } break; } if ( gifGraphicControlExtensionBlock.userInteraction != 0) { if ( TCL_OK != Tcl_DictObjPut(interp, metadataOutObj, Tcl_NewStringObj("user interaction",-1), Tcl_NewBooleanObj(1))) { result = TCL_ERROR; goto error; } } } } /* * We've successfully read the GIF frame (or there was nothing to read, * which suits as well). We're done. */ while (1) { if (-1 == (gifLabel = ReadOneByte( interp, gifConfPtr, chan ) ) ) { goto error; } switch (gifLabel) { case GIF_TERMINATOR: break; case GIF_EXTENSION: /* * This is a GIF extension. */ if (-1 == (gifLabel = ReadOneByte( interp, gifConfPtr, chan ) ) ) { goto error; } if (DoExtension(gifConfPtr, chan, gifLabel, gifConfPtr->workingBuffer, &gifGraphicControlExtensionBlock, metadataOutObj) < 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "error reading extension in GIF image", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "BAD_EXT", NULL); goto error; } continue; case GIF_START: /* * There should not be a second image block - bail out without error */ break; default: /* * Not a valid start character; ignore it. */ continue; } break; } Tcl_SetObjResult(interp, Tcl_NewStringObj(tkImgFmtGIF.name, TCL_INDEX_NONE)); result = TCL_OK; error: /* * If a trash buffer has been allocated, free it now. */ if (trashBuffer != NULL) { ckfree(trashBuffer); } return result; } /* *---------------------------------------------------------------------- * * Read one Byte -- * * Read one byte (label byte) from the image stream. * * Results: * The return value is 1 if the first characters in the data are like GIF * data, and 0 otherwise. * * Side effects: * The access position in the source is incremented. * *---------------------------------------------------------------------- */ static int ReadOneByte( Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ GIFImageConfig *gifConfPtr, Tcl_Channel chan /* The image file, open for reading. */ ) { unsigned char buf[2]; if (Fread(gifConfPtr, buf, 1, 1, chan) != 1) { /* * Premature end of image. */ Tcl_SetObjResult(interp, Tcl_NewStringObj( "premature end of image data", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "PREMATURE_END", NULL); return -1; } return buf[0]; } /* *---------------------------------------------------------------------- * * 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_UNUSED(Tcl_Interp *), /* not used */ Tcl_Obj *dataObj, /* the object containing the image data */ TCL_UNUSED(Tcl_Obj *), /* the image format object, or NULL */ TCL_UNUSED(Tcl_Obj *), /* metadata input, may be NULL */ int *widthPtr, /* where to put the string width */ int *heightPtr, /* where to put the string height */ TCL_UNUSED(Tcl_Obj *)) /* metadata return dict, may be NULL */ { unsigned char *data, header[10]; Tcl_Size 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 */ Tcl_Obj *metadataInObj, /* metadata input, may be 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, Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ { MFile handle, *hdlPtr = &handle; Tcl_Size 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, metadataInObj, imageHandle, destX, destY, width, height, srcX, srcY, metadataOutObj); } /* *---------------------------------------------------------------------- * * 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; } /* *---------------------------------------------------------------------- * * DoExtension -- * * Process a GIF extension block * * Results: * -1 to trigger an extension read error * >= 0 ok * * Side effects: * The gifGraphicControlExtensionBlock is set if present in current * extensions * The data of the following extensions are saved to the metadata dict: * - Application extension * - Comment extension in key "comment" * Plain text extensions are currently ignored. * *---------------------------------------------------------------------- */ static int DoExtension( GIFImageConfig *gifConfPtr, Tcl_Channel chan, int label, unsigned char *buf, /* defined as 280 byte working buffer */ GIFGraphicControlExtensionBlock *gifGraphicControlExtensionBlock, Tcl_Obj *metadataOutObj) { int count; /* Prepare extension name * Maximum string size: "comment" + Code(3) + trailing zero */ char extensionStreamName[8]; extensionStreamName[0] = '\0'; switch (label) { case 0x01: /* Plain Text Extension */ /* * This extension starts a new scope, so Graphic control Extension * data should be cleared */ gifGraphicControlExtensionBlock->blockPresent = 0; /* this extension is ignored, skip below */ break; case 0xf9: /* Graphic Control Extension */ count = GetDataBlock(gifConfPtr, chan, buf); if (count < 0) { return -1; } gifGraphicControlExtensionBlock->blockPresent=1; /* save disposal method */ gifGraphicControlExtensionBlock->disposalMethod = ((buf[0] & 0x1C) >> 2); /* save disposal method */ gifGraphicControlExtensionBlock->userInteraction = ((buf[0] & 2) >> 1); /* save delay time */ gifGraphicControlExtensionBlock->delayTime = LM_to_uint(buf[1], buf[2]); /* save transparent index if given */ if ((buf[0] & 0x1) == 0) { gifGraphicControlExtensionBlock->transparent = -1; } else { gifGraphicControlExtensionBlock->transparent = buf[3]; } break; case 0xfe: /* Comment Extension */ strcpy(extensionStreamName,"comment"); /* copy the extension data below */ break; } /* Add extension to dict */ if (NULL != metadataOutObj && extensionStreamName[0] != '\0' ) { Tcl_Obj *ValueObj = NULL; int length = 0; for (;;) { count = GetDataBlock(gifConfPtr, chan, buf); switch (count) { case -1: /* error */ return -1; case 0: /* end of data */ if (length > 0) { if ( TCL_OK != Tcl_DictObjPut(NULL, metadataOutObj, Tcl_NewByteArrayObj( (unsigned char *)extensionStreamName, strlen(extensionStreamName)), ValueObj)) { return -1; } } /* return success */ return 0; default: /* block received */ if (length == 0) { /* first block */ ValueObj = Tcl_NewByteArrayObj(buf, count); length = count; } else { /* consecutive block */ unsigned char *bytePtr; bytePtr = Tcl_SetByteArrayLength(ValueObj, length+count); memcpy(bytePtr+length,buf,count); length += count; } break; } } /* for */ } /* skip eventual remaining data block bytes */ do { count = GetDataBlock(gifConfPtr, chan, buf); } while (count > 0); return count; /* this may be -1 for error or 0 */ } 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 © 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], TCL_UNUSED(int), TCL_UNUSED(int), int interlace, int transparent) { unsigned char initialCodeSize; int xpos = 0, ypos = 0, pass = 0, i, count; 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]; 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", TCL_INDEX_NONE)); 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 * codes 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 < (1 << MAX_LWZ_BITS))) { /* * 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 entry to the codes table. */ *top++ = firstCode; code = oldCode; } while (code > clearCode) { /* * Populate the stack by tracing the code in the codes * table from its tail to its head */ *top++ = append[code]; code = prefix[code]; } firstCode = append[code]; /* * Push the head of the code onto the stack. */ *top++ = firstCode; if (maxCode < (1 << MAX_LWZ_BITS)) { /* * If there's still room in our codes table, add a new entry. * Otherwise don't, and keep using the current table. * See DEFERRED CLEAR CODE IN LZW COMPRESSION in the GIF89a * specification. */ 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 >= rows) { pass++; if (pass > 3) { return TCL_OK; } ypos = interlaceStart[pass]; } } else { ypos++; } pixelPtr = imagePtr + (ypos) * len * ((transparent>=0)?4:3); } /* * Now read until the final zero byte. * It was observed that there might be 1 length blocks * (test imgPhoto-14.1) which are not read. * * The field "stack" is abused for temporary buffer. it has 4096 bytes * and we need 256. * * Loop until we hit a 0 length block which is the end sign. */ while ( 0 < (count = GetDataBlock(gifConfPtr, chan, stack))) { if (-1 == count ) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error reading GIF image: %s", Tcl_PosixError(interp))); return TCL_ERROR; } } 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 */ Tcl_Size 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 Tcl_Size Mread( unsigned char *dst, /* where to put the result */ Tcl_Size chunkSize, /* size of each transfer */ Tcl_Size numChunks, /* number of chunks */ MFile *handle) /* mmdecode "file" handle */ { int c; Tcl_Size i, count = chunkSize * numChunks; for (i=0; istate == 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 Tcl_Size Fread( GIFImageConfig *gifConfPtr, unsigned char *dst, /* where to put the result */ Tcl_Size hunk, Tcl_Size count, /* how many */ Tcl_Channel chan) { if (hunk < 0 || count < 0) { return -1; } 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) || (handle->length < hunk*count)) { return -1; } memcpy(dst, handle->data, hunk * count); handle->data += hunk * count; handle->length -= hunk * count; return hunk * count; } /* * Otherwise we've got a real file to read. */ return Tcl_Read(chan, (char *) dst, 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 * *---------------------------------------------------------------------- * 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, Tcl_Obj *metadataInObj, 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, metadataInObj, 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, Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr) { int result; Tcl_Obj *objPtr = Tcl_NewObj(); Tcl_IncrRefCount(objPtr); result = CommonWriteGIF(interp, objPtr, WriteToByteArray, format, metadataInObj, blockPtr); if (result == TCL_OK) { Tcl_SetObjResult(interp, objPtr); } Tcl_DecrRefCount(objPtr); return result; } static Tcl_Size WriteToChannel( void *clientData, const char *bytes, Tcl_Size byteCount) { Tcl_Channel handle = (Tcl_Channel)clientData; return Tcl_Write(handle, bytes, byteCount); } static Tcl_Size WriteToByteArray( void *clientData, const char *bytes, Tcl_Size byteCount) { Tcl_Obj *objPtr = (Tcl_Obj *)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, void *handle, WriteBytesFunc *writeProc, TCL_UNUSED(Tcl_Obj *), Tcl_Obj *metadataInObj, 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", TCL_INDEX_NONE)); 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); /* * Check for metadata keys to add to file */ if (NULL != metadataInObj) { Tcl_Obj *itemData; /* * Check and code comment block */ if (TCL_ERROR == Tcl_DictObjGet(interp, metadataInObj, Tcl_NewStringObj("comment",-1), &itemData)) { return TCL_ERROR; } if (itemData != NULL) { Tcl_Size length; unsigned char *comment; comment = Tcl_GetByteArrayFromObj(itemData, &length); if (length > 0) { /* write comment header */ writeProc(handle, (char *) "\x21\xfe", 2); /* write comment blocks */ for (;length > 0;) { int blockLength; unsigned char blockLengthChar; if (length > 255) { length -=255; blockLength = 255; } else { blockLength = length; length = 0; } blockLengthChar = (unsigned char) blockLength; writeProc(handle, (char *) &blockLengthChar, 1); writeProc(handle, (char *) comment, blockLength); comment += blockLength; } /* Block terminator */ 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 ; yheight ; y++) { colores = blockPtr->pixelPtr + blockPtr->offset[0] + y*blockPtr->pitch; for (x=0 ; xwidth ; 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, void *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) { int *hashTablePtr = statePtr->hashTable + hSize; long i; 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: */