Path: utzoo!attcan!uunet!lll-winken!lll-lcc!ames!mailrus!eecae!netnews.upenn.edu!xmos.cis.upenn.edu!bradley From: bradley@xmos.cis.upenn.edu (John Bradley) Newsgroups: comp.windows.x Subject: Re: Wanted: GIF to X11 converter Message-ID: <7287@netnews.upenn.edu> Date: 24 Jan 89 20:52:12 GMT References: <4050@ncrcae.Columbia.NCR.COM> <11880008@hpsmtc1.HP.COM> <4079@ncrcae.Columbia.NCR.COM> Sender: news@netnews.upenn.edu Reply-To: bradley@cis.upenn.edu (John Bradley) Organization: University of Pennsylvania Lines: 1012 >>Robert Heath asks: >> >>>Does anyone have a translator for converting picture files in CompuServe's >>>Graphic Interchange Format (GIF) to X11 ? I've written a program that displays color and/or monochrome GIF pictures on 8-bit X11 displays. It's fairly small, so I'm just going to tack it on at the bottom of this message. Sorry if this is considered annoying behavior. It's also available for anonymous ftp on dsl.cis.upenn.edu (128.91.2.12). Also, to any who are interested, there's a slew (30 or so (okay, a *small* slew)) of GIF pictures also available for anonymous ftp, same place. I'd appreciate any pointers to more GIF images, as I'm trying to put together a reasonable collection of these things. John Bradley - bradley@cis.upenn.edu -----------------------------------(cut here)----------------------------- #!/bin/sh # to extract, remove the header and type "sh filename" if `test ! -s ./xgif.man` then echo "writing ./xgif.man" cat > ./xgif.man << '\Rogue\Monster\' .TH xgif 1X .SH NAME xgif \- displays GIF (*) pictures on X11 displays .SH SYNTAX \fBxgif\fP [\fIdisplay\fP] [ [-g] \fIgeometry\fP] [-e \fIexpansion\fP] [-s \fIstrip\fP] [-ns] [\fIfilename\fP] .SH DESCRIPTION \fBxgif\fP is an X11 program that displays GIF pictures on an 8-plane display. .SH OPTIONS The '-e' option allows you to expand the picture by an integer amount. For example, viewing a 320x200 picture with an expansion factor of '2' will result in a 640x400 picture, each pixel of which is a 2x2 block. .PP You may also specify an expansion of the picture by specifying the size of the window in the \fIgeometry\fP option. This also allows you specify non-integer expansion factors, and different aspect ratios. Example: if you view a 320x200 picture, but specify a window size of 640x300, the picture will be expanded by a factor of two along the X-axis, but only by a factor of 1.5 along the Y-axis. .PP If you specify both the '-e' option and a window size (via \fIgeometry\fP), the '-e' will be ignored. .PP The '-s' option allows you specify the number of bits to strip off of the colors. The theory runs like this: if you have 256 unique colors in your GIF file, you will almost certainly be unable to allocate all of them on an 8-plane display, as a couple colors will already be allocated for the window manager, and such. Setting this option allows you to strip off the low \fIstrip\fP bits of the R,G,B entries in the GIF colormap. This will have the desired effect of making some of the (previously different) colors the SAME, and you will be able to allocate all the colors you need now. .PP You shouldn't ever HAVE to set this option, because if the program is unable to allocate the required colors, it will try again after incrementing \fIstrip\fP. You can, however save some time, or alternately get neat visual effects by setting this option. .PP The '-ns' option turns off the 'auto-strip' feature described above. You use this to FORCE the program to use as many colors as possible. The theory works like this: if you have 256 unique colors in your GIF file, you will probably be able to allocate all but a few of them. Rather than stripping off bits, decreasing the color resolution for the whole picture uniformly, the 'nostrip' option makes the program set the few unallocatable colors equal to the 'closest' colors that were allocated. This may cause nasty 'blotches' on the picture. Then again, it might not. Only way to tell is to try both with and without the 'auto-strip' 'feature'. .PP One DEFINITE drawback of the '-ns' option is that it's only really useful on the FIRST picture you try to display. If you try to display two pictures simultaneously, the first picture will (presumably) use up most (if not ALL) of the colortable, leaving NO colors for the second picture. Therefore, the second (and succeeding pictures) will probably not look very good. When you use the 'auto-strip' feature, you will be able to get considerably more pictures on the screen simultaneously. Probably. Varies wildly, based on the actual pictures being displayed. .PP Also, it should be noted that if the optional \fIfilename\fP is not supplied, the program will read the picture from stdin. .SH LIMITATIONS You'll require (at least) an 8-plane X11 display. This program ignores 'local colormaps' in GIF files (see the GIF spec for details). It also only displays the first image in GIF files that have multiple images in them. .PP The number of pictures you can display simultaneously varies wildly. It depends on how many colors are in the GIF files, and how many of them are shared by other GIF files. Suffice it to say that you can ALWAYS display a picture, though the colors may or may not be right. If the program was unable to get all the desired colors, it will mention that fact. .PP Note: This program points out a bug in the X11R2 server for the IBM RT Megapel display. This bug will occasionally cause the colors in a picture to be wrong, but no 'unable to allocate' message will be printed by the program. (Essentially, the problem is a discrepancy between what the server THINKS the colormap is, and what the colormap in the hardware ACTUALLY is.) .PP .SH AUTHOR John Bradley - bradley@cis.upenn.edu .PP Based (heavily) on gif2ras.c, by Patrick J. Naughton (naughton@wind.sun.com), a program that converts GIF pictures to Sun Rasterfiles. .PP (*) GIF is a no doubt a trademark of CompuServe, so watch it! \Rogue\Monster\ else echo "will not over write ./xgif.man" fi if `test ! -s ./xgif.h` then echo "writing ./xgif.h" cat > ./xgif.h << '\Rogue\Monster\' /* * xgif.h - header file for xgif, but you probably already knew as much */ #define REVDATE "Rev: 1/20/89" #define MAXEXPAND 16 /* include files */ #include #include #include #include #include #include #include #ifndef MAIN #define WHERE extern #else #define WHERE #endif typedef unsigned char byte; #define CENTERX(f,x,str) ((x)-XTextWidth(f,str,strlen(str))/2) #define CENTERY(f,y) ((y)-((f->ascent+f->descent)/2)+f->ascent) /* X stuff */ WHERE Display *theDisp; WHERE int theScreen, dispcells; WHERE Colormap theCmap; WHERE Window rootW, mainW; WHERE GC theGC; WHERE unsigned long fcol,bcol; WHERE Font mfont; WHERE XFontStruct *mfinfo; WHERE Visual *theVisual; WHERE XImage *theImage, *expImage; /* global vars */ WHERE int iWIDE,iHIGH,eWIDE,eHIGH,expand,numcols,strip,nostrip; WHERE unsigned long cols[256]; WHERE XColor defs[256]; WHERE char *cmd; \Rogue\Monster\ else echo "will not over write ./xgif.h" fi if `test ! -s ./xgif.c` then echo "writing ./xgif.c" cat > ./xgif.c << '\Rogue\Monster\' /* * xgif.c - displays GIF pictures on an X11 display * * Author: John Bradley, University of Pennsylvania * (bradley@cis.upenn.edu) */ #define MAIN #include "xgif.h" /*******************************************/ main(argc, argv) int argc; char *argv[]; /*******************************************/ { int i; char *display, *geom, *fname; XEvent event; cmd = argv[0]; display = geom = fname = NULL; expImage = NULL; expand = 1; strip = 0; nostrip = 0; /*********************Options*********************/ for (i = 1; i < argc; i++) { char *strind; if (argv[i][0] == '=') { geom = argv[i]; continue; } if (!strncmp(argv[i],"-g",2)) { /* geometry */ i++; geom = argv[i]; continue; } strind = index(argv[i], ':'); if(strind != NULL) { display = argv[i]; continue; } if (!strcmp(argv[i],"-e")) { i++; expand=atoi(argv[i]); continue; } if (!strcmp(argv[i],"-s")) { i++; strip=atoi(argv[i]); continue; } if (!strcmp(argv[i],"-ns")) { nostrip++; continue; } if (argv[i][0] != '-') { /* the file name */ fname = argv[i]; continue; } Syntax(cmd); } if (fname==NULL) fname="-"; if (expand<1 || expand>MAXEXPAND) Syntax(cmd); if (strip<0 || strip>7) Syntax(cmd); /*****************************************************/ /* Open up the display. */ if ( (theDisp=XOpenDisplay(display)) == NULL) { fprintf(stderr, "%s: Can't open display\n",argv[0]); exit(1); } theScreen = DefaultScreen(theDisp); theCmap = DefaultColormap(theDisp, theScreen); rootW = RootWindow(theDisp,theScreen); theGC = DefaultGC(theDisp,theScreen); fcol = WhitePixel(theDisp,theScreen); bcol = BlackPixel(theDisp,theScreen); theVisual = DefaultVisual(theDisp,theScreen); dispcells = DisplayCells(theDisp, theScreen); if (dispcells<255) FatalError("This program requires an 8-plane display, at least."); /****************** Open/Read the File *****************/ LoadGIF(fname); iWIDE = theImage->width; iHIGH = theImage->height; eWIDE = iWIDE * expand; eHIGH = iHIGH * expand; if (eWIDE > DisplayWidth(theDisp,theScreen)) eWIDE = DisplayWidth(theDisp,theScreen); if (eHIGH > DisplayHeight(theDisp,theScreen)) eHIGH = DisplayHeight(theDisp,theScreen); /**************** Create/Open X Resources ***************/ if ((mfinfo = XLoadQueryFont(theDisp,"variable"))==NULL) FatalError("couldn't open 'variable' font\n"); mfont=mfinfo->fid; XSetFont(theDisp,theGC,mfont); XSetForeground(theDisp,theGC,fcol); XSetBackground(theDisp,theGC,bcol); CreateMainWindow(cmd,geom,argc,argv); Resize(eWIDE,eHIGH); XSelectInput(theDisp, mainW, ExposureMask | KeyPressMask | StructureNotifyMask); XMapWindow(theDisp,mainW); /**************** Main loop *****************/ while (1) { XNextEvent(theDisp, &event); HandleEvent(&event); } } /****************/ HandleEvent(event) XEvent *event; /****************/ { switch (event->type) { case Expose: { XExposeEvent *exp_event = (XExposeEvent *) event; if (exp_event->window==mainW) DrawWindow(exp_event->x,exp_event->y, exp_event->width, exp_event->height); } break; case KeyPress: { XKeyEvent *key_event = (XKeyEvent *) event; char buf[128]; KeySym ks; XComposeStatus status; XLookupString(key_event,buf,128,&ks,&status); if (buf[0]=='q' || buf[0]=='Q') Quit(); } break; case ConfigureNotify: { XConfigureEvent *conf_event = (XConfigureEvent *) event; if (conf_event->window == mainW && (conf_event->width != eWIDE || conf_event->height != eHIGH)) Resize(conf_event->width, conf_event->height); } break; case CirculateNotify: case MapNotify: case DestroyNotify: case GravityNotify: case ReparentNotify: case UnmapNotify: break; default: printf("event type=%ld\n",event->type); FatalError("Unexpected X_Event"); } /* end of switch */ } /***********************************/ Syntax() { printf("Usage: %s filename [=geometry | -geometry geom] [display]\n",cmd); printf(" [-e 1..%d] [-s 0-7] [-ns]\n",MAXEXPAND); exit(1); } /***********************************/ FatalError (identifier) char *identifier; { fprintf(stderr, "%s: %s\n",cmd, identifier); exit(-1); } /***********************************/ Quit() { exit(0); } /***********************************/ DrawWindow(x,y,w,h) { XPutImage(theDisp,mainW,theGC,expImage,x,y,x,y,w,h); } /***********************************/ Resize(w,h) int w,h; { int ix,iy,ex,ey; byte *ximag,*ilptr,*ipptr,*elptr,*epptr; static char *rstr = "Resizing Image. Please wait..."; /* warning: this code'll only run machines where int=32-bits */ if (w==iWIDE && h==iHIGH) { /* very special case */ if (expImage != theImage) { if (expImage) XDestroyImage(expImage); expImage = theImage; eWIDE = iWIDE; eHIGH = iHIGH; } } else { /* have to do some work */ /* if it's a big image, this'll take a while. mention it */ if (w*h>(500*500)) { XDrawImageString(theDisp,mainW,theGC,CENTERX(mfinfo,w/2,rstr), CENTERY(mfinfo,h/2),rstr, strlen(rstr)); XFlush(theDisp); } /* first, kill the old expImage, if one exists */ if (expImage && expImage != theImage) XDestroyImage(expImage); /* create expImage of the appropriate size */ eWIDE = w; eHIGH = h; ximag = (byte *) malloc(w*h); expImage = XCreateImage(theDisp,theVisual,8,ZPixmap,0,ximag, eWIDE,eHIGH,8,eWIDE); if (!ximag || !expImage) { fprintf(stderr,"ERROR: unable to create a %dx%d image\n",w,h); exit(0); } elptr = epptr = (byte *) expImage->data; for (ey=0; eydata + (iy * iWIDE); for (ex=0; ex ./xgifload.c << '\Rogue\Monster\' /* * xgifload.c - based strongly on... * * gif2ras.c - Converts from a Compuserve GIF (tm) image to a Sun Raster image. * * Copyright (c) 1988, 1989 by Patrick J. Naughton * * Author: Patrick J. Naughton * naughton@wind.sun.com * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose and without fee is hereby granted, * provided that the above copyright notice appear in all copies and that * both that copyright notice and this permission notice appear in * supporting documentation. * * This file is provided AS IS with no warranties of any kind. The author * shall have no liability with respect to the infringement of copyrights, * trade secrets or any patents by this file or any part thereof. In no * event will the author be liable for any lost revenue or profits or * other special, indirect and consequential damages. * */ #include "xgif.h" typedef int boolean; #define NEXTBYTE (*ptr++) #define IMAGESEP 0x2c #define INTERLACEMASK 0x40 #define COLORMAPMASK 0x80 FILE *fp; int BitOffset = 0, /* Bit Offset of next code */ XC = 0, YC = 0, /* Output X and Y coords of current pixel */ Pass = 0, /* Used by output routine if interlaced pic */ OutCount = 0, /* Decompressor output 'stack count' */ RWidth, RHeight, /* screen dimensions */ Width, Height, /* image dimensions */ LeftOfs, TopOfs, /* image offset */ BitsPerPixel, /* Bits per pixel, read from GIF header */ BytesPerScanline, /* bytes per scanline in output raster */ ColorMapSize, /* number of colors */ Background, /* background color */ CodeSize, /* Code size, read from GIF header */ InitCodeSize, /* Starting code size, used during Clear */ Code, /* Value returned by ReadCode */ MaxCode, /* limiting value for current code size */ ClearCode, /* GIF clear code */ EOFCode, /* GIF end-of-information code */ CurCode, OldCode, InCode, /* Decompressor variables */ FirstFree, /* First free code, generated per GIF spec */ FreeCode, /* Decompressor, next free slot in hash table */ FinChar, /* Decompressor variable */ BitMask, /* AND mask for data size */ ReadMask; /* Code AND mask for current code size */ boolean Interlace, HasColormap; boolean Verbose = False; byte *Image; /* The result array */ byte *RawGIF; /* The heap array to hold it, raw */ byte *Raster; /* The raster data stream, unblocked */ /* The hash table used by the decompressor */ int Prefix[4096]; int Suffix[4096]; /* An output array used by the decompressor */ int OutCode[1025]; /* The color map, read from the GIF header */ byte Red[256], Green[256], Blue[256]; char *id = "GIF87a"; /*****************************/ LoadGIF(fname) char *fname; /*****************************/ { int filesize; register byte ch, ch1; register byte *ptr, *ptr1; register int i,j; static byte lmasks[8] = {0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80}; byte lmask; if (strcmp(fname,"-")==0) { fp = stdin; fname = ""; } else fp = fopen(fname,"r"); if (!fp) FatalError("file not found"); /* find the size of the file */ fseek(fp, 0L, 2); filesize = ftell(fp); fseek(fp, 0L, 0); if (!(ptr = RawGIF = (byte *) malloc(filesize))) FatalError("not enough memory to read gif file"); if (!(Raster = (byte *) malloc(filesize))) FatalError("not enough memory to read gif file"); if (fread(ptr, filesize, 1, fp) != 1) FatalError("GIF data read failed"); if (strncmp(ptr, id, 6)) FatalError("not a GIF file"); ptr += 6; /* Get variables from the GIF screen descriptor */ ch = NEXTBYTE; RWidth = ch + 0x100 * NEXTBYTE; /* screen dimensions... not used. */ ch = NEXTBYTE; RHeight = ch + 0x100 * NEXTBYTE; if (Verbose) fprintf(stderr, "screen dims: %dx%d.\n", RWidth, RHeight); ch = NEXTBYTE; HasColormap = ((ch & COLORMAPMASK) ? True : False); BitsPerPixel = (ch & 7) + 1; numcols = ColorMapSize = 1 << BitsPerPixel; BitMask = ColorMapSize - 1; Background = NEXTBYTE; /* background color... not used. */ if (NEXTBYTE) /* supposed to be NULL */ FatalError("corrupt GIF file (bad screen descriptor)"); /* Read in global colormap. */ if (HasColormap) { if (Verbose) fprintf(stderr, "%s is %dx%d, %d bits per pixel, (%d colors).\n", fname, Width,Height,BitsPerPixel, ColorMapSize); for (i = 0; i < ColorMapSize; i++) { Red[i] = NEXTBYTE; Green[i] = NEXTBYTE; Blue[i] = NEXTBYTE; } /* Allocate the X colors for this picture */ if (nostrip) { /* nostrip was set. try REAL hard to do it */ j = 0; lmask = lmasks[strip]; for (i=0; i>8)) + abs(g - (ctab[j].green>>8)) + abs(b - (ctab[j].blue>>8)); if (d filesize) FatalError("corrupt GIF file (unblock)"); } while(ch1); free(RawGIF); /* We're done with the raw data now... */ if (Verbose) { fprintf(stderr, "done.\n"); fprintf(stderr, "Decompressing..."); } /* Allocate the X Image */ Image = (byte *) malloc(Width*Height); if (!Image) FatalError("not enough memory for XImage"); theImage = XCreateImage(theDisp,theVisual,8,ZPixmap,0,Image, Width,Height,8,Width); if (!theImage) FatalError("unable to create XImage"); BytesPerScanline = Width; /* Decompress the file, continuing until you see the GIF EOF code. * One obvious enhancement is to add checking for corrupt files here. */ Code = ReadCode(); while (Code != EOFCode) { /* Clear code sets everything back to its initial value, then reads the * immediately subsequent code as uncompressed data. */ if (Code == ClearCode) { CodeSize = InitCodeSize; MaxCode = (1 << CodeSize); ReadMask = MaxCode - 1; FreeCode = FirstFree; CurCode = OldCode = Code = ReadCode(); FinChar = CurCode & BitMask; AddToPixel(FinChar); } else { /* If not a clear code, then must be data: save same as CurCode and InCode */ CurCode = InCode = Code; /* If greater or equal to FreeCode, not in the hash table yet; * repeat the last character decoded */ if (CurCode >= FreeCode) { CurCode = OldCode; OutCode[OutCount++] = FinChar; } /* Unless this code is raw data, pursue the chain pointed to by CurCode * through the hash table to its end; each code in the chain puts its * associated output code on the output queue. */ while (CurCode > BitMask) { if (OutCount > 1024) FatalError("corrupt GIF file (OutCount)"); OutCode[OutCount++] = Suffix[CurCode]; CurCode = Prefix[CurCode]; } /* The last code in the chain is treated as raw data. */ FinChar = CurCode & BitMask; OutCode[OutCount++] = FinChar; /* Now we put the data out to the Output routine. * It's been stacked LIFO, so deal with it that way... */ for (i = OutCount - 1; i >= 0; i--) AddToPixel(OutCode[i]); OutCount = 0; /* Build the hash table on-the-fly. No table is stored in the file. */ Prefix[FreeCode] = OldCode; Suffix[FreeCode] = FinChar; OldCode = InCode; /* Point to the next slot in the table. If we exceed the current * MaxCode value, increment the code size unless it's already 12. If it * is, do nothing: the next code decompressed better be CLEAR */ FreeCode++; if (FreeCode >= MaxCode) { if (CodeSize < 12) { CodeSize++; MaxCode *= 2; ReadMask = (1 << CodeSize) - 1; } } } Code = ReadCode(); } free(Raster); if (Verbose) fprintf(stderr, "done.\n"); if (fp != stdin) fclose(fp); } /* Fetch the next code from the raster data stream. The codes can be * any length from 3 to 12 bits, packed into 8-bit bytes, so we have to * maintain our location in the Raster array as a BIT Offset. We compute * the byte Offset into the raster array by dividing this by 8, pick up * three bytes, compute the bit Offset into our 24-bit chunk, shift to * bring the desired code to the bottom, then mask it off and return it. */ ReadCode() { int RawCode, ByteOffset; ByteOffset = BitOffset / 8; RawCode = Raster[ByteOffset] + (0x100 * Raster[ByteOffset + 1]); if (CodeSize >= 8) RawCode += (0x10000 * Raster[ByteOffset + 2]); RawCode >>= (BitOffset % 8); BitOffset += CodeSize; return(RawCode & ReadMask); } AddToPixel(Index) byte Index; { *(Image + YC * BytesPerScanline + XC) = cols[Index&(numcols-1)]; /* Update the X-coordinate, and if it overflows, update the Y-coordinate */ if (++XC == Width) { /* If a non-interlaced picture, just increment YC to the next scan line. * If it's interlaced, deal with the interlace as described in the GIF * spec. Put the decoded scan line out to the screen if we haven't gone * past the bottom of it */ XC = 0; if (!Interlace) YC++; else { switch (Pass) { case 0: YC += 8; if (YC >= Height) { Pass++; YC = 4; } break; case 1: YC += 8; if (YC >= Height) { Pass++; YC = 2; } break; case 2: YC += 4; if (YC >= Height) { Pass++; YC = 1; } break; case 3: YC += 2; break; default: break; } } } } \Rogue\Monster\ else echo "will not over write ./xgifload.c" fi if `test ! -s ./Makefile` then echo "writing ./Makefile" cat > ./Makefile << '\Rogue\Monster\' XLIB = -lX11 CFLAGS = -O OBJS = xgif.o xgifload.o all: xgif xgif: $(OBJS) cc $(CFLAGS) -o xgif $(OBJS) $(XLIB) $(CLIBS) clean: rm -f $(OBJS) \Rogue\Monster\ else echo "will not over write ./Makefile" fi echo "Finished archive 1 of 1" exit