OSDN Git Service

Initial revision
[pf3gnuchains/pf3gnuchains4x.git] / tk / generic / tkImgGIF.c
1 /*
2  * tkImgGIF.c --
3  *
4  *      A photo image file handler for GIF files. Reads 87a and 89a GIF
5  *      files. At present there is no write function.  GIF images may be
6  *      read using the -data option of the photo image.  The data may be
7  *      given as a binary string in a Tcl_Obj or by representing
8  *      the data as BASE64 encoded ascii.  Derived from the giftoppm code
9  *      found in the pbmplus package and tkImgFmtPPM.c in the tk4.0b2
10  *      distribution.
11  *
12  * Copyright (c) Reed Wade (wade@cs.utk.edu), University of Tennessee
13  * Copyright (c) 1995-1997 Sun Microsystems, Inc.
14  * Copyright (c) 1997 Australian National University
15  *
16  * See the file "license.terms" for information on usage and redistribution
17  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
18  *
19  * This file also contains code from the giftoppm program, which is
20  * copyrighted as follows:
21  *
22  * +-------------------------------------------------------------------+
23  * | Copyright 1990, David Koblas.                                     |
24  * |   Permission to use, copy, modify, and distribute this software   |
25  * |   and its documentation for any purpose and without fee is hereby |
26  * |   granted, provided that the above copyright notice appear in all |
27  * |   copies and that both that copyright notice and this permission  |
28  * |   notice appear in supporting documentation.  This software is    |
29  * |   provided "as is" without express or implied warranty.           |
30  * +-------------------------------------------------------------------+
31  *
32  * RCS: @(#) $Id$
33  */
34
35 /*
36  * GIF's are represented as data in base64 format.
37  * base64 strings consist of 4 6-bit characters -> 3 8 bit bytes.
38  * A-Z, a-z, 0-9, + and / represent the 64 values (in order).
39  * '=' is a trailing padding char when the un-encoded data is not a
40  * multiple of 3 bytes.  We'll ignore white space when encountered.
41  * Any other invalid character is treated as an EOF
42  */
43
44 #define GIF_SPECIAL      (256)
45 #define GIF_PAD         (GIF_SPECIAL+1)
46 #define GIF_SPACE       (GIF_SPECIAL+2)
47 #define GIF_BAD         (GIF_SPECIAL+3)
48 #define GIF_DONE        (GIF_SPECIAL+4)
49
50 /*
51  * structure to "mimic" FILE for Mread, so we can look like fread.
52  * The decoder state keeps track of which byte we are about to read,
53  * or EOF.
54  */
55
56 typedef struct mFile {
57     unsigned char *data;        /* mmencoded source string */
58     int c;                      /* bits left over from previous character */
59     int state;                  /* decoder state (0-4 or GIF_DONE) */
60 } MFile;
61
62 #include "tkInt.h"
63 #include "tkPort.h"
64
65 /*
66  * The format record for the GIF file format:
67  */
68
69 static int      FileMatchGIF _ANSI_ARGS_((Tcl_Channel chan, char *fileName,
70                     char *formatString, int *widthPtr, int *heightPtr));
71 static int      FileReadGIF  _ANSI_ARGS_((Tcl_Interp *interp,
72                     Tcl_Channel chan, char *fileName, char *formatString,
73                     Tk_PhotoHandle imageHandle, int destX, int destY,
74                     int width, int height, int srcX, int srcY));
75 static int      StringMatchGIF _ANSI_ARGS_(( Tcl_Obj *dataObj,
76                     char *formatString, int *widthPtr, int *heightPtr));
77 static int      StringReadGIF _ANSI_ARGS_((Tcl_Interp *interp, Tcl_Obj *dataObj,
78                     char *formatString, Tk_PhotoHandle imageHandle,
79                     int destX, int destY, int width, int height,
80                     int srcX, int srcY));
81
82 Tk_PhotoImageFormat tkImgFmtGIF = {
83         "GIF",                  /* name */
84         FileMatchGIF,   /* fileMatchProc */
85         StringMatchGIF, /* stringMatchProc */
86         FileReadGIF,    /* fileReadProc */
87         StringReadGIF,  /* stringReadProc */
88         NULL,           /* fileWriteProc */
89         NULL,           /* stringWriteProc */
90 };
91
92 #define INTERLACE               0x40
93 #define LOCALCOLORMAP           0x80
94 #define BitSet(byte, bit)       (((byte) & (bit)) == (bit))
95 #define MAXCOLORMAPSIZE         256
96 #define CM_RED                  0
97 #define CM_GREEN                1
98 #define CM_BLUE                 2
99 #define CM_ALPHA                3
100 #define MAX_LWZ_BITS            12
101 #define LM_to_uint(a,b)         (((b)<<8)|(a))
102 #define ReadOK(file,buffer,len) (Fread(buffer, len, 1, file) != 0)
103
104 /*
105  *                       HACK ALERT!!  HACK ALERT!!  HACK ALERT!!
106  * This code is hard-wired for reading from files.  In order to read
107  * from a data stream, we'll trick fread so we can reuse the same code.
108  * 0==from file; 1==from base64 encoded data; 2==from binary data
109  */
110  
111 static int fromData=0;
112
113 /*
114  * Prototypes for local procedures defined in this file:
115  */
116
117 static int              DoExtension _ANSI_ARGS_((Tcl_Channel chan, int label,
118                             int *transparent));
119 static int              GetCode _ANSI_ARGS_((Tcl_Channel chan, int code_size,
120                             int flag));
121 static int              GetDataBlock _ANSI_ARGS_((Tcl_Channel chan,
122                             unsigned char *buf));
123 static int              LWZReadByte _ANSI_ARGS_((Tcl_Channel chan, int flag,
124                             int input_code_size));
125 static int              ReadColorMap _ANSI_ARGS_((Tcl_Channel chan, int number,
126                             unsigned char buffer[MAXCOLORMAPSIZE][4]));
127 static int              ReadGIFHeader _ANSI_ARGS_((Tcl_Channel chan,
128                             int *widthPtr, int *heightPtr));
129 static int              ReadImage _ANSI_ARGS_((Tcl_Interp *interp,
130                             char *imagePtr, Tcl_Channel chan,
131                             int len, int rows,
132                             unsigned char cmap[MAXCOLORMAPSIZE][4],
133                             int width, int height, int srcX, int srcY,
134                             int interlace, int transparent));
135
136 /*
137  * these are for the BASE64 image reader code only
138  */
139
140 static int              Fread _ANSI_ARGS_((unsigned char *dst, size_t size,
141                             size_t count, Tcl_Channel chan));
142 static int              Mread _ANSI_ARGS_((unsigned char *dst, size_t size,
143                             size_t count, MFile *handle));
144 static int              Mgetc _ANSI_ARGS_((MFile *handle));
145 static int              char64 _ANSI_ARGS_((int c));
146 static void             mInit _ANSI_ARGS_((unsigned char *string,
147                             MFile *handle));
148 \f
149 /*
150  *----------------------------------------------------------------------
151  *
152  * FileMatchGIF --
153  *
154  *      This procedure is invoked by the photo image type to see if
155  *      a file contains image data in GIF format.
156  *
157  * Results:
158  *      The return value is 1 if the first characters in file f look
159  *      like GIF data, and 0 otherwise.
160  *
161  * Side effects:
162  *      The access position in f may change.
163  *
164  *----------------------------------------------------------------------
165  */
166
167 static int
168 FileMatchGIF(chan, fileName, formatString, widthPtr, heightPtr)
169     Tcl_Channel chan;           /* The image file, open for reading. */
170     char *fileName;             /* The name of the image file. */
171     char *formatString;         /* User-specified format string, or NULL. */
172     int *widthPtr, *heightPtr;  /* The dimensions of the image are
173                                  * returned here if the file is a valid
174                                  * raw GIF file. */
175 {
176         return ReadGIFHeader(chan, widthPtr, heightPtr);
177 }
178 \f
179 /*
180  *----------------------------------------------------------------------
181  *
182  * FileReadGIF --
183  *
184  *      This procedure is called by the photo image type to read
185  *      GIF format data from a file and write it into a given
186  *      photo image.
187  *
188  * Results:
189  *      A standard TCL completion code.  If TCL_ERROR is returned
190  *      then an error message is left in interp->result.
191  *
192  * Side effects:
193  *      The access position in file f is changed, and new data is
194  *      added to the image given by imageHandle.
195  *
196  *----------------------------------------------------------------------
197  */
198
199 static int
200 FileReadGIF(interp, chan, fileName, formatString, imageHandle, destX, destY,
201         width, height, srcX, srcY)
202     Tcl_Interp *interp;         /* Interpreter to use for reporting errors. */
203     Tcl_Channel chan;           /* The image file, open for reading. */
204     char *fileName;             /* The name of the image file. */
205     char *formatString;         /* User-specified format string, or NULL. */
206     Tk_PhotoHandle imageHandle; /* The photo image to write into. */
207     int destX, destY;           /* Coordinates of top-left pixel in
208                                  * photo image to be written to. */
209     int width, height;          /* Dimensions of block of photo image to
210                                  * be written to. */
211     int srcX, srcY;             /* Coordinates of top-left pixel to be used
212                                  * in image being read. */
213 {
214     int fileWidth, fileHeight;
215     int nBytes;
216     Tk_PhotoImageBlock block;
217     unsigned char buf[100];
218     int bitPixel;
219     unsigned char colorMap[MAXCOLORMAPSIZE][4];
220     int transparent = -1;
221
222     if (!ReadGIFHeader(chan, &fileWidth, &fileHeight)) {
223         Tcl_AppendResult(interp, "couldn't read GIF header from file \"",
224                 fileName, "\"", NULL);
225         return TCL_ERROR;
226     }
227     if ((fileWidth <= 0) || (fileHeight <= 0)) {
228         Tcl_AppendResult(interp, "GIF image file \"", fileName,
229                 "\" has dimension(s) <= 0", (char *) NULL);
230         return TCL_ERROR;
231     }
232
233     if (Fread(buf, 1, 3, chan) != 3) {
234         return TCL_OK;
235     }
236     bitPixel = 2<<(buf[0]&0x07);
237
238     if (BitSet(buf[0], LOCALCOLORMAP)) {    /* Global Colormap */
239         if (!ReadColorMap(chan, bitPixel, colorMap)) {
240             Tcl_AppendResult(interp, "error reading color map",
241                     (char *) NULL);
242             return TCL_ERROR;
243         }
244     }
245
246     if ((srcX + width) > fileWidth) {
247         width = fileWidth - srcX;
248     }
249     if ((srcY + height) > fileHeight) {
250         height = fileHeight - srcY;
251     }
252     if ((width <= 0) || (height <= 0)
253             || (srcX >= fileWidth) || (srcY >= fileHeight)) {
254         return TCL_OK;
255     }
256
257     Tk_PhotoExpand(imageHandle, destX + width, destY + height);
258
259     block.width = width;
260     block.height = height;
261     block.pixelSize = 4;
262     block.pitch = block.pixelSize * block.width;
263     block.offset[0] = 0;
264     block.offset[1] = 1;
265     block.offset[2] = 2;
266     nBytes = height * block.pitch;
267     block.pixelPtr = (unsigned char *) ckalloc((unsigned) nBytes);
268
269     while (1) {
270         if (Fread(buf, 1, 1, chan) != 1) {
271             /*
272              * Premature end of image.  We should really notify
273              * the user, but for now just show garbage.
274              */
275
276             break;
277         }
278
279         if (buf[0] == ';') {
280             /*
281              * GIF terminator.
282              */
283
284             break;
285         }
286
287         if (buf[0] == '!') {
288             /*
289              * This is a GIF extension.
290              */
291
292             if (Fread(buf, 1, 1, chan) != 1) {
293                 interp->result =
294                         "error reading extension function code in GIF image";
295                 goto error;
296             }
297             if (DoExtension(chan, buf[0], &transparent) < 0) {
298                 interp->result = "error reading extension in GIF image";
299                 goto error;
300             }
301             continue;
302         }
303
304         if (buf[0] != ',') {
305             /*
306              * Not a valid start character; ignore it.
307              */
308             continue;
309         }
310
311         if (Fread(buf, 1, 9, chan) != 9) {
312             interp->result = "couldn't read left/top/width/height in GIF image";
313             goto error;
314         }
315
316         bitPixel = 1<<((buf[8]&0x07)+1);
317
318         if (BitSet(buf[8], LOCALCOLORMAP)) {
319             if (!ReadColorMap(chan, bitPixel, colorMap)) {
320                     Tcl_AppendResult(interp, "error reading color map", 
321                             (char *) NULL);
322                     goto error;
323             }
324         }
325         if (ReadImage(interp, (char *) block.pixelPtr, chan, width,
326                 height, colorMap, fileWidth, fileHeight, srcX, srcY,
327                 BitSet(buf[8], INTERLACE), transparent) != TCL_OK) {
328             goto error;
329         }
330         break;
331    }
332
333     if (transparent == -1) {
334         Tk_PhotoPutBlock(imageHandle, &block, destX, destY, width, height);
335     } else {
336         int x, y, end;
337         unsigned char *imagePtr, *rowPtr, *pixelPtr;
338
339         imagePtr = rowPtr = block.pixelPtr;
340         for (y = 0; y < height; y++) {
341             x = 0;
342             pixelPtr = rowPtr;
343             while(x < width) {
344                 /* search for first non-transparent pixel */
345                 while ((x < width) && !(pixelPtr[CM_ALPHA])) {
346                     x++; pixelPtr += 4;
347                 }
348                 end = x;
349                 /* search for first transparent pixel */
350                 while ((end < width) && pixelPtr[CM_ALPHA]) {
351                     end++; pixelPtr += 4;
352                 }
353                 if (end > x) {
354                     block.pixelPtr = rowPtr + 4 * x;
355                     Tk_PhotoPutBlock(imageHandle, &block, destX+x,
356                             destY+y, end-x, 1);
357                 }
358                 x = end;
359             }
360             rowPtr += block.pitch;
361         }
362         block.pixelPtr = imagePtr;
363     }
364     ckfree((char *) block.pixelPtr);
365     return TCL_OK;
366
367     error:
368     ckfree((char *) block.pixelPtr);
369     return TCL_ERROR;
370
371 }
372 \f
373 /*
374  *----------------------------------------------------------------------
375  *
376  * StringMatchGIF --
377  *
378  *  This procedure is invoked by the photo image type to see if
379  *  an object contains image data in GIF format.
380  *
381  * Results:
382  *  The return value is 1 if the first characters in the data are
383  *  like GIF data, and 0 otherwise.
384  *
385  * Side effects:
386  *  the size of the image is placed in widthPre and heightPtr.
387  *
388  *----------------------------------------------------------------------
389  */
390
391 static int
392 StringMatchGIF(dataObj, formatString, widthPtr, heightPtr)
393     Tcl_Obj *dataObj;           /* the object containing the image data */
394     char *formatString;         /* the image format string */
395     int *widthPtr;              /* where to put the string width */
396     int *heightPtr;             /* where to put the string height */
397 {
398     unsigned char *data, header[10];
399     int got, length;
400     MFile handle;
401
402     data = Tcl_GetStringFromObj(dataObj, &length);
403
404     /* Header is a minimum of 10 bytes */
405     if (length < 10) {
406       return 0;
407     }
408
409     /* Check whether the data is Base64 encoded */
410
411     if ((strncmp("GIF87a", data, 6) != 0) && 
412         (strncmp("GIF89a", data, 6) != 0)) {
413       /* Try interpreting the data as Base64 encoded */
414       mInit((unsigned char *) data, &handle);
415       got = Mread(header, 10, 1, &handle);
416       if (got != 10
417               || ((strncmp("GIF87a", (char *) header, 6) != 0)
418               && (strncmp("GIF89a", (char *) header, 6) != 0))) {
419           return 0;
420       }
421     } else {
422       memcpy((VOID *) header, (VOID *) data, 10);
423     }
424     *widthPtr = LM_to_uint(header[6],header[7]);
425     *heightPtr = LM_to_uint(header[8],header[9]);
426     return 1;
427 }
428 \f
429 /*
430  *----------------------------------------------------------------------
431  *
432  * StringReadGif -- --
433  *
434  *      This procedure is called by the photo image type to read
435  *      GIF format data from an object, optionally base64 encoded, 
436  *      and give it to the photo image.
437  *
438  * Results:
439  *      A standard TCL completion code.  If TCL_ERROR is returned
440  *      then an error message is left in interp->result.
441  *
442  * Side effects:
443  *      new data is added to the image given by imageHandle.  This
444  *      procedure calls FileReadGif by redefining the operation of
445  *      fprintf temporarily.
446  *
447  *----------------------------------------------------------------------
448  */
449
450 static int
451 StringReadGIF(interp,dataObj,formatString,imageHandle,
452         destX, destY, width, height, srcX, srcY)
453     Tcl_Interp *interp;         /* interpreter for reporting errors in */
454     Tcl_Obj *dataObj;           /* object containing the image */
455     char *formatString;         /* format string if any */
456     Tk_PhotoHandle imageHandle; /* the image to write this data into */
457     int destX, destY;           /* The rectangular region of the  */
458     int  width, height;         /*   image to copy */
459     int srcX, srcY;
460 {
461         int result;
462         MFile handle;
463         Tcl_Channel dataSrc;
464         char *data;
465         /* Check whether the data is Base64 encoded */
466         data = Tcl_GetStringFromObj(dataObj, NULL);
467         if ((strncmp("GIF87a", data, 6) != 0) && 
468             (strncmp("GIF89a", data, 6) != 0)) {
469           mInit((unsigned char *)data,&handle);
470           fromData = 1;
471           dataSrc = (Tcl_Channel) &handle;
472         } else {
473           fromData = 2;
474           mInit((unsigned char *)data,&handle);
475           dataSrc = (Tcl_Channel) &handle;
476         }
477         result = FileReadGIF(interp, dataSrc, "inline data",
478                 formatString, imageHandle, destX, destY, width, height,
479                 srcX, srcY);
480         fromData = 0;
481         return(result);
482 }
483
484 /*
485  *----------------------------------------------------------------------
486  *
487  * ReadGIFHeader --
488  *
489  *      This procedure reads the GIF header from the beginning of a
490  *      GIF file and returns the dimensions of the image.
491  *
492  * Results:
493  *      The return value is 1 if file "f" appears to start with
494  *      a valid GIF header, 0 otherwise.  If the header is valid,
495  *      then *widthPtr and *heightPtr are modified to hold the
496  *      dimensions of the image.
497  *
498  * Side effects:
499  *      The access position in f advances.
500  *
501  *----------------------------------------------------------------------
502  */
503
504 static int
505 ReadGIFHeader(chan, widthPtr, heightPtr)
506     Tcl_Channel chan;           /* Image file to read the header from */
507     int *widthPtr, *heightPtr;  /* The dimensions of the image are
508                                  * returned here. */
509 {
510     unsigned char buf[7];
511
512     if ((Fread(buf, 1, 6, chan) != 6)
513             || ((strncmp("GIF87a", (char *) buf, 6) != 0)
514             && (strncmp("GIF89a", (char *) buf, 6) != 0))) {
515         return 0;
516     }
517
518     if (Fread(buf, 1, 4, chan) != 4) {
519         return 0;
520     }
521
522     *widthPtr = LM_to_uint(buf[0],buf[1]);
523     *heightPtr = LM_to_uint(buf[2],buf[3]);
524     return 1;
525 }
526
527 /*
528  *-----------------------------------------------------------------
529  * The code below is copied from the giftoppm program and modified
530  * just slightly.
531  *-----------------------------------------------------------------
532  */
533
534 static int
535 ReadColorMap(chan, number, buffer)
536      Tcl_Channel chan;
537      int number;
538      unsigned char buffer[MAXCOLORMAPSIZE][4];
539 {
540         int i;
541         unsigned char rgb[3];
542
543         for (i = 0; i < number; ++i) {
544             if (! ReadOK(chan, rgb, sizeof(rgb))) {
545                 return 0;
546             }
547             
548             buffer[i][CM_RED] = rgb[0] ;
549             buffer[i][CM_GREEN] = rgb[1] ;
550             buffer[i][CM_BLUE] = rgb[2] ;
551             buffer[i][CM_ALPHA] = 255 ;
552         }
553         return 1;
554 }
555
556
557
558 static int
559 DoExtension(chan, label, transparent)
560      Tcl_Channel chan;
561      int label;
562      int *transparent;
563 {
564     static unsigned char buf[256];
565     int count;
566
567     switch (label) {
568         case 0x01:      /* Plain Text Extension */
569             break;
570             
571         case 0xff:      /* Application Extension */
572             break;
573
574         case 0xfe:      /* Comment Extension */
575             do {
576                 count = GetDataBlock(chan, (unsigned char*) buf);
577             } while (count > 0);
578             return count;
579
580         case 0xf9:      /* Graphic Control Extension */
581             count = GetDataBlock(chan, (unsigned char*) buf);
582             if (count < 0) {
583                 return 1;
584             }
585             if ((buf[0] & 0x1) != 0) {
586                 *transparent = buf[3];
587             }
588
589             do {
590                 count = GetDataBlock(chan, (unsigned char*) buf);
591             } while (count > 0);
592             return count;
593     }
594
595     do {
596         count = GetDataBlock(chan, (unsigned char*) buf);
597     } while (count > 0);
598     return count;
599 }
600
601 static int ZeroDataBlock = 0;
602
603 static int
604 GetDataBlock(chan, buf)
605      Tcl_Channel chan;
606      unsigned char *buf;
607 {
608     unsigned char count;
609
610     if (! ReadOK(chan, &count,1)) {
611         return -1;
612     }
613
614     ZeroDataBlock = count == 0;
615
616     if ((count != 0) && (! ReadOK(chan, buf, count))) {
617         return -1;
618     }
619
620     return count;
621 }
622
623
624 static int
625 ReadImage(interp, imagePtr, chan, len, rows, cmap,
626         width, height, srcX, srcY, interlace, transparent)
627      Tcl_Interp *interp;
628      char *imagePtr;
629      Tcl_Channel chan;
630      int len, rows;
631      unsigned char cmap[MAXCOLORMAPSIZE][4];
632      int width, height;
633      int srcX, srcY;
634      int interlace;
635      int transparent;
636 {
637     unsigned char c;
638     int v;
639     int xpos = 0, ypos = 0, pass = 0;
640     char *pixelPtr;
641
642
643     /*
644      *  Initialize the Compression routines
645      */
646     if (! ReadOK(chan, &c, 1))  {
647         Tcl_AppendResult(interp, "error reading GIF image: ",
648                 Tcl_PosixError(interp), (char *) NULL);
649         return TCL_ERROR;
650     }
651
652     if (LWZReadByte(chan, 1, c) < 0) {
653         interp->result = "format error in GIF image";
654         return TCL_ERROR;
655     }
656
657     if (transparent!=-1) {
658         cmap[transparent][CM_RED] = 0;
659         cmap[transparent][CM_GREEN] = 0;
660         cmap[transparent][CM_BLUE] = 0;
661         cmap[transparent][CM_ALPHA] = 0;
662     }
663
664     pixelPtr = imagePtr;
665     while ((v = LWZReadByte(chan, 0, c)) >= 0 ) {
666
667         if ((xpos>=srcX) && (xpos<srcX+len) &&
668                 (ypos>=srcY) && (ypos<srcY+rows)) {
669             *pixelPtr++ = cmap[v][CM_RED];
670             *pixelPtr++ = cmap[v][CM_GREEN];
671             *pixelPtr++ = cmap[v][CM_BLUE];
672             *pixelPtr++ = cmap[v][CM_ALPHA];
673         }
674
675         ++xpos;
676         if (xpos == width) {
677             xpos = 0;
678             if (interlace) {
679                 switch (pass) {
680                     case 0:
681                     case 1:
682                         ypos += 8; break;
683                     case 2:
684                         ypos += 4; break;
685                     case 3:
686                         ypos += 2; break;
687                 }
688                 
689                 while (ypos >= height) {
690                     ++pass;
691                     switch (pass) {
692                         case 1:
693                             ypos = 4; break;
694                         case 2:
695                             ypos = 2; break;
696                         case 3:
697                             ypos = 1; break;
698                         default:
699                             return TCL_OK;
700                     }
701                 }
702             } else {
703                 ++ypos;
704             }
705             pixelPtr = imagePtr + (ypos-srcY) * len * 4;
706         }
707         if (ypos >= height)
708             break;
709     }
710     return TCL_OK;
711 }
712
713 static int
714 LWZReadByte(chan, flag, input_code_size)
715      Tcl_Channel chan;
716      int flag;
717      int input_code_size;
718 {
719     static int  fresh = 0;
720     int code, incode;
721     static int code_size, set_code_size;
722     static int max_code, max_code_size;
723     static int firstcode, oldcode;
724     static int clear_code, end_code;
725     static int table[2][(1<< MAX_LWZ_BITS)];
726     static int stack[(1<<(MAX_LWZ_BITS))*2], *sp;
727     register int    i;
728
729     if (flag) {
730         set_code_size = input_code_size;
731         code_size = set_code_size+1;
732         clear_code = 1 << set_code_size ;
733         end_code = clear_code + 1;
734         max_code_size = 2*clear_code;
735         max_code = clear_code+2;
736
737         GetCode(chan, 0, 1);
738
739         fresh = 1;
740
741         for (i = 0; i < clear_code; ++i) {
742             table[0][i] = 0;
743             table[1][i] = i;
744         }
745         for (; i < (1<<MAX_LWZ_BITS); ++i) {
746             table[0][i] = table[1][0] = 0;
747         }
748
749         sp = stack;
750
751         return 0;
752     } else if (fresh) {
753         fresh = 0;
754         do {
755             firstcode = oldcode = GetCode(chan, code_size, 0);
756         } while (firstcode == clear_code);
757         return firstcode;
758     }
759
760     if (sp > stack) {
761         return *--sp;
762     }
763
764     while ((code = GetCode(chan, code_size, 0)) >= 0) {
765         if (code == clear_code) {
766             for (i = 0; i < clear_code; ++i) {
767                 table[0][i] = 0;
768                 table[1][i] = i;
769             }
770             
771             for (; i < (1<<MAX_LWZ_BITS); ++i) {
772                 table[0][i] = table[1][i] = 0;
773             }
774
775             code_size = set_code_size+1;
776             max_code_size = 2*clear_code;
777             max_code = clear_code+2;
778             sp = stack;
779             firstcode = oldcode = GetCode(chan, code_size, 0);
780             return firstcode;
781
782         } else if (code == end_code) {
783             int count;
784             unsigned char buf[260];
785
786             if (ZeroDataBlock) {
787                 return -2;
788             }
789             
790             while ((count = GetDataBlock(chan, buf)) > 0)
791                 /* Empty body */;
792
793             if (count != 0) {
794                 return -2;
795             }
796         }
797
798         incode = code;
799
800         if (code >= max_code) {
801             *sp++ = firstcode;
802             code = oldcode;
803         }
804
805         while (code >= clear_code) {
806             *sp++ = table[1][code];
807             if (code == table[0][code]) {
808                 return -2;
809
810                 /*
811                  * Used to be this instead, Steve Ball suggested
812                  * the change to just return.
813                  printf("circular table entry BIG ERROR\n");
814                  */
815             }
816             code = table[0][code];
817         }
818
819         *sp++ = firstcode = table[1][code];
820
821         if ((code = max_code) <(1<<MAX_LWZ_BITS)) {
822             table[0][code] = oldcode;
823             table[1][code] = firstcode;
824             ++max_code;
825             if ((max_code>=max_code_size) && (max_code_size < (1<<MAX_LWZ_BITS))) {
826                 max_code_size *= 2;
827                 ++code_size;
828             }
829         }
830
831         oldcode = incode;
832
833         if (sp > stack)
834             return *--sp;
835         }
836         return code;
837 }
838
839
840 static int
841 GetCode(chan, code_size, flag)
842      Tcl_Channel chan;
843      int code_size;
844      int flag;
845 {
846     static unsigned char buf[280];
847     static int curbit, lastbit, done, last_byte;
848     int i, j, ret;
849     unsigned char count;
850
851     if (flag) {
852         curbit = 0;
853         lastbit = 0;
854         done = 0;
855         return 0;
856     }
857
858
859     if ( (curbit+code_size) >= lastbit) {
860         if (done) {
861             /* ran off the end of my bits */
862             return -1;
863         }
864         if (last_byte >= 2) {
865             buf[0] = buf[last_byte-2];
866         }
867         if (last_byte >= 1) {
868             buf[1] = buf[last_byte-1];
869         }
870
871         if ((count = GetDataBlock(chan, &buf[2])) == 0) {
872             done = 1;
873         }
874
875         last_byte = 2 + count;
876         curbit = (curbit - lastbit) + 16;
877         lastbit = (2+count)*8 ;
878     }
879
880     ret = 0;
881     for (i = curbit, j = 0; j < code_size; ++i, ++j) {
882         ret |= ((buf[ i / 8 ] & (1 << (i % 8))) != 0) << j;
883     }
884
885     curbit += code_size;
886
887     return ret;
888 }
889 \f
890 /*
891  *----------------------------------------------------------------------
892  *
893  * Minit -- --
894  *
895  *  This procedure initializes a base64 decoder handle
896  *
897  * Results:
898  *  none
899  *
900  * Side effects:
901  *  the base64 handle is initialized
902  *
903  *----------------------------------------------------------------------
904  */
905
906 static void
907 mInit(string, handle)
908    unsigned char *string;       /* string containing initial mmencoded data */
909    MFile *handle;               /* mmdecode "file" handle */
910 {
911    handle->data = string;
912    handle->state = 0;
913    handle->c = 0;
914 }
915 \f
916 /*
917  *----------------------------------------------------------------------
918  *
919  * Mread --
920  *
921  *      This procedure is invoked by the GIF file reader as a 
922  *      temporary replacement for "fread", to get GIF data out
923  *      of a string (using Mgetc).
924  *
925  * Results:
926  *      The return value is the number of characters "read"
927  *
928  * Side effects:
929  *      The base64 handle will change state.
930  *
931  *----------------------------------------------------------------------
932  */
933
934 static int
935 Mread(dst, chunkSize, numChunks, handle)  
936    unsigned char *dst;  /* where to put the result */
937    size_t chunkSize;    /* size of each transfer */
938    size_t numChunks;    /* number of chunks */
939    MFile *handle;       /* mmdecode "file" handle */
940 {
941    register int i, c;
942    int count = chunkSize * numChunks;
943
944    for(i=0; i<count && (c=Mgetc(handle)) != GIF_DONE; i++) {
945         *dst++ = c;
946    }
947    return i;
948 }
949
950 /*
951  * get the next decoded character from an mmencode handle
952  * This causes at least 1 character to be "read" from the encoded string
953  */
954 \f
955 /*
956  *----------------------------------------------------------------------
957  *
958  * Mgetc --
959  *
960  *  This procedure decodes and returns the next byte from a base64
961  *  encoded string.
962  *
963  * Results:
964  *  The next byte (or GIF_DONE) is returned.
965  *
966  * Side effects:
967  *  The base64 handle will change state.
968  *
969  *----------------------------------------------------------------------
970  */
971
972 static int
973 Mgetc(handle)
974    MFile *handle;               /* Handle containing decoder data and state. */
975 {
976     int c;
977     int result = 0;             /* Initialization needed only to prevent
978                                  * gcc compiler warning. */
979      
980     if (handle->state == GIF_DONE) {
981         return(GIF_DONE);
982     }
983
984     do {
985         c = char64(*handle->data);
986         handle->data++;
987     } while (c==GIF_SPACE);
988
989     if (c>GIF_SPECIAL) {
990         handle->state = GIF_DONE;
991         return(handle->state ? handle->c : GIF_DONE);
992     }
993
994     switch (handle->state++) {
995         case 0:
996            handle->c = c<<2;
997            result = Mgetc(handle);
998            break;
999         case 1:
1000            result = handle->c | (c>>4);
1001            handle->c = (c&0xF)<<4;
1002            break;
1003         case 2:
1004            result = handle->c | (c>>2);
1005            handle->c = (c&0x3) << 6;
1006            break;
1007         case 3:
1008            result = handle->c | c;
1009            handle->state = 0;
1010            break;
1011     }
1012     return(result);
1013 }
1014 \f
1015 /*
1016  *----------------------------------------------------------------------
1017  *
1018  * char64 --
1019  *
1020  *      This procedure converts a base64 ascii character into its binary
1021  *      equivalent.  This code is a slightly modified version of the
1022  *      char64 proc in N. Borenstein's metamail decoder.
1023  *
1024  * Results:
1025  *      The binary value, or an error code.
1026  *
1027  * Side effects:
1028  *      None.
1029  *----------------------------------------------------------------------
1030  */
1031
1032 static int
1033 char64(c)
1034 int c;
1035 {
1036     switch(c) {
1037         case 'A': return(0);  case 'B': return(1);  case 'C': return(2);
1038         case 'D': return(3);  case 'E': return(4);  case 'F': return(5);
1039         case 'G': return(6);  case 'H': return(7);  case 'I': return(8);
1040         case 'J': return(9);  case 'K': return(10); case 'L': return(11);
1041         case 'M': return(12); case 'N': return(13); case 'O': return(14);
1042         case 'P': return(15); case 'Q': return(16); case 'R': return(17);
1043         case 'S': return(18); case 'T': return(19); case 'U': return(20);
1044         case 'V': return(21); case 'W': return(22); case 'X': return(23);
1045         case 'Y': return(24); case 'Z': return(25); case 'a': return(26);
1046         case 'b': return(27); case 'c': return(28); case 'd': return(29);
1047         case 'e': return(30); case 'f': return(31); case 'g': return(32);
1048         case 'h': return(33); case 'i': return(34); case 'j': return(35);
1049         case 'k': return(36); case 'l': return(37); case 'm': return(38);
1050         case 'n': return(39); case 'o': return(40); case 'p': return(41);
1051         case 'q': return(42); case 'r': return(43); case 's': return(44);
1052         case 't': return(45); case 'u': return(46); case 'v': return(47);
1053         case 'w': return(48); case 'x': return(49); case 'y': return(50);
1054         case 'z': return(51); case '0': return(52); case '1': return(53);
1055         case '2': return(54); case '3': return(55); case '4': return(56);
1056         case '5': return(57); case '6': return(58); case '7': return(59);
1057         case '8': return(60); case '9': return(61); case '+': return(62);
1058         case '/': return(63);
1059
1060         case ' ': case '\t': case '\n': case '\r': case '\f': return(GIF_SPACE);
1061         case '=':  return(GIF_PAD);
1062         case '\0': return(GIF_DONE);
1063         default: return(GIF_BAD);
1064     }
1065 }
1066 \f
1067 /*
1068  *----------------------------------------------------------------------
1069  *
1070  * Fread --
1071  *
1072  *  This procedure calls either fread or Mread to read data
1073  *  from a file or a base64 encoded string.
1074  *
1075  * Results: - same as fread
1076  *
1077  *----------------------------------------------------------------------
1078  */
1079
1080 static int
1081 Fread(dst, hunk, count, chan)
1082     unsigned char *dst;         /* where to put the result */
1083     size_t hunk,count;          /* how many */
1084     Tcl_Channel chan;
1085 {
1086   MFile *handle;
1087     switch (fromData) {
1088       case 0:
1089         return Tcl_Read(chan, (char *) dst, (int) (hunk * count));
1090       case 1:
1091         return(Mread(dst, hunk, count, (MFile *) chan));
1092       case 2:
1093         handle = (MFile *) chan;
1094         memcpy((VOID *)dst, (VOID *) handle->data, (int) (hunk * count));
1095         handle->data += hunk * count;
1096         return((int) (hunk * count));
1097     }
1098 }