/* gifpan -- preview GIFs on text displays, with mucho optimizations * for small memory/16 bit systems. Includes termios and curses support. * Tested on Minix-2.0.0/i86, NetBSD-1.2/i386, NewsOS-4.2R, ... * -- * v2.0 Copyright November 1997 Chris Baird * Released under the FSF's "Copyleft" V2 terms and conditions--see "COPYING". * Beta-testing by the trained gerbils at humbug.org.au * Code policing and curses support thanks to Andreas Burmester. * pixel->text mapping from Rob Harley and Jorn Barger. * Public domain GIF87 decoder from John Ferrell. * The term "GIF" belongs to Compuserve and has some sort of legal protection. * -- * Command line usage: gifpan [-hvi] [-t n] [-z f] [-] [image.gif] * - take Gif input from stdin (good for netpbm) * -v shift vertically by one pixel (more v's = more shift) * -h shift horizontally (only one -h makes sense) --fixes the "][" problem * -i invert * -t n set B/W threshold to n (0<=n<=255, default 128=50%) * -z f zoom out by (float)factor * * "hjkl" or "HJKL" to move the window around; ctrl-H returns to the home * position; "q" to quit. * -- * Notes: On small memory systems (Minix/i86), use the "-z" option to prescale * the image before it gets stored in memory; "-z 1.4" manages to squeeze most * ~600x768 images into the ~40kB of allocable memory (if compiled with * seperate I/D-- "cc -i ..." under Minix). * Doing the obvious thing with xterm and running this in a very large window * with a very small font has a good chance of crashing xterm, and a few * releases of curses are also known to be problematical. Using "-z" is the * intented method for fitting an entire image into the display... */ /* Choose from termios, curses, or neither */ #undef USE_TERMIOS #define USE_CURSES #include #include #include #include #if defined(USE_CURSES) #include #endif #if defined(USE_TERMIOS) #include extern tgetent(char*,char*); extern tgetnum(char*); extern char* tgetstr(char*,char**); extern char* tgoto(char*,int,int); struct termios origterm; #endif int peek(int,int); void showusage(void); void ERROR(char*); void init_tty(void); void reset_tty(void); int ReadCode(FILE*); void AddPixel(unsigned char); void parse_gif(FILE*); int DoImage(FILE*); FILE *GIFfh; unsigned char *Image, *ImageBase; unsigned char Backcol; int xoffset, yoffset, invert, fromstdin, thresh, zoom; int screenx, screeny, ImageWidth, ImageHeight, ImageWid8, iheight, iwidth; char clearscreen[16], homecursor[16]=""; /* see comment block at end */ char map[65] = " `'~-!/f_0!V=+Y*.|/7i[/Pv)/ZzDZA,0!Tct(5i0]YeN4M_L2XsLKKgGJ8mWW@"; int main(int argc, char* argv[]) { int x, y, xloc, yloc, screenwid, screenhgt; unsigned char c, *p, *ascmap; xoffset = yoffset = invert = fromstdin = 0; thresh = 128; zoom = 256; while (--argc > 0 && (*++argv)[0] == '-') if (p = (char*)argv[0]+1, *p == '\0') fromstdin = 1; else for (; *p != '\0'; p++) switch (*p) { case 'z': zoom = atof(*++argv) * 256; argc--; break; case 'h': xoffset++; break; case 'v': yoffset++; break; case 'i': invert = 1; break; case 't': thresh = atoi(*++argv); argc--; break; default: showusage(); } if (argc != 1 && fromstdin == 0) showusage(); /* look for the input file, then slurp gif file */ if (fromstdin) GIFfh = stdin; else if (!(GIFfh = fopen(*argv, "rb"))) ERROR("Open error\n"); parse_gif(GIFfh); /* bitmap -> text conversion */ p = ascmap = ImageBase; for (y = -yoffset; y < (ImageHeight-2-yoffset); y += 3) { for (x = -xoffset; x < (ImageWidth-1-xoffset); x += 2) { /* calc neighbourhood */ c = peek(x + 1, y + 2); c = (c << 1) + peek(x, y + 2); c = (c << 1) + peek(x + 1, y + 1); c = (c << 1) + peek(x, y + 1); c = (c << 1) + peek(x + 1, y); c = (c << 1) + peek(x, y); *p++ = map[c]; } } init_tty(); xloc = yloc = 0; if (screenx == 0) screeny = iheight, screenx = 79; /* ...or whatever */ screenwid = (screenx <= iwidth) ? screenx-1 : iwidth; screenhgt = (screeny <= iheight) ? screeny-1 : iheight; /* main loop - display current window, get character, loop */ while (1) { if (xloc > (iwidth - screenwid)) xloc = iwidth - screenwid; if (yloc > (iheight - screenhgt)) yloc = iheight - screenhgt; if (xloc < 0) xloc = 0; if (yloc < 0) yloc = 0; p = ascmap + xloc + (iwidth * yloc); #if defined(USE_CURSES) for (y = 0; y < screenhgt; p += iwidth, y++) { move(y, 0); for (x = 0; x <= screenwid; x++) addch(p[x]); /* XXX munges every character :( */ } move(0,0); refresh(); c = getch(); #else /* termios or dumbterm */ printf(homecursor); /* NULL if !termios */ for (y = 0; y < screenhgt; p += iwidth, y++) { fwrite (p, 1, screenwid, stdout); putchar('\n'); } c = getchar(); #endif if (c == 'q' || c == 'Q') break; if (c == 'H') xloc -= 8; if (c == 'h') xloc--; if (c == 'L') xloc += 8; if (c == 'l') xloc++; if (c == 'K') yloc -= 4; if (c == 'k') yloc--; if (c == 'J') yloc += 4; if (c == 'j') yloc++; if (c == 8) xloc = yloc = 0; /* ^H */ #if defined(USE_CURSES) if ((c == 19) || (c == 12)) refresh(); /* ^R & ^L */ #endif } /* broke loop-- tidy up and finish */ #if defined(USE_CURSES) endwin(); #elif defined(USE_TERMIOS) reset_tty(); #endif return 0; } int peek(x, y) int x, y; { char *o; if (x >= 0 && y >= 0 && x < ImageWidth && y < ImageHeight) { o = (char*) Image + (x >> 3) + (y * ImageWid8); return (*o & (1 << (x & 7))) ? 1 : 0; } else return Backcol == invert; /* magic fix for boundary problem */ } void showusage(void) { /* some preprocessors will choke on the string catenation...diddums */ ERROR("\nUsage: gifpan [-hvi] [-t n] [-] [image.gif]\n" " -h shift image one pixel horizontally (fixes \"][\" problem)\n" " -v shift image one pixel vertically (more v's = more shift)\n" " -i invert image\n" " -t n set B/W threshold to n (0<=n<=255)\n" " -z f zoom out by (float)factor\n" " - take image file from stdin\n" "Use \"hjkl\" and \"HJKL\" (vi keys) to scroll window.\n" "Written by Chris Baird, 1993-7. GIF87 decode engine originally by John Ferrell.\n" "Released under the FSF's \"Copyleft\" terms and conditions.\n\n"); } void ERROR (char *str) { fprintf(stderr, str); exit(1); } void init_tty(void) { #if defined(USE_TERMIOS) char *term, *tp; char termbuf[1024], t_cm[16], t_cl[16]; struct termios rawterm; if ((term = getenv("TERM")) == NULL || tgetent(termbuf, term) != 1) ERROR("Can't get terminal capabilities\n"); if (tgetstr("cl", (tp = t_cl, &tp)) == NULL || tgetstr("cm", (tp = t_cm, &tp)) == NULL) ERROR("This terminal is too dumb\n"); screeny = tgetnum("li"); screenx = tgetnum("co"); strcpy(clearscreen, t_cl); strcpy(homecursor, tgoto(t_cm, 0, 0)); /* save terminal flags */ if (tcgetattr(0, &origterm) < 0) ERROR("Can't save terminal flags\n"); /* set raw mode */ rawterm = origterm; /* copies entire structure */ rawterm.c_lflag &= ~(ICANON|ECHO); rawterm.c_cc[VMIN] = 1; rawterm.c_cc[VTIME] = 0; /* block indefinitely for a single char */ rawterm.c_iflag &= ~(ICRNL); if (tcsetattr(0, TCSADRAIN, &rawterm) < 0) ERROR("Can't set terminal RAW mode"); printf(clearscreen); #elif defined(USE_CURSES) if ((int)initscr() == ERR) ERROR("Curses initialisation failed.\n"); screenx = COLS; screeny = LINES; cbreak(); noecho(); leaveok(stdscr, TRUE); clear(); #endif } #if defined(USE_TERMIOS) void reset_tty(void) { if (tcsetattr(0, TCSADRAIN, &origterm) < 0) ERROR("Can't restore terminal flags"); } #endif /* GIF decoder engine * Most of it from PD ascgif by John Ferrell. * Hack history: * 23Aug93 Chris Baird,, * 27Aug94 Chris Baird,, * 16Sep95 Chris Baird,, * 24Feb96 Chris Baird,, * 26Oct96 Chris Baird,, Added kludgy GIF89 support * 12Oct97 Chris Baird,, Store image as a bitmap * 22Oct97 Chris Baird,, Overlay bitmap/text areas, * zooming feature, overhauled ReadCode(), fixed GIF89 extension parsing, * removed unnecessary colour code, profiled to 16bit architectures */ /* If you discover any [non-standard] GIF files that make this decoder complain * of "OutCount" errors, try setting OUTCOUNTMAX to 8192. I've only found files * made by "Photoshop Pro" to be a problem. */ #define OUTCOUNTMAX 4096 #define UBYTE unsigned char #define UWORD unsigned short #define ULONG unsigned long #define GIF_IMAGE 0x2C #define GIF_EXTENSION 0x21 #define GIF_TERMINATOR 0x3B #define GIF_COMMENT_EXT 0xFE #if !defined(TRUE) #define TRUE (1) #define FALSE (0) #endif struct GIFdescriptor { UWORD gd_Width; UWORD gd_Height; UBYTE gd_ColInfo; UBYTE gd_BackGround; UBYTE gd_PixelAspect; }; struct ImageDesc { UWORD id_Left; UWORD id_Top; UWORD id_Width; UWORD id_Height; UBYTE id_Info; }; struct GIFdescriptor gdesc; struct ImageDesc idesc; unsigned char Intensity[256]; unsigned int Xpos, Ypos; int interleave; int ReadError; UBYTE CodeSize; unsigned int EOFCode; UBYTE LeaveStep[5] = {1, 8, 8, 4, 2}; UBYTE LeaveFirst[5] = {0, 0, 4, 2, 1}; int CompDataPointer, CompDataCount; unsigned char CompData[256]; UWORD Prefix[4096]; UBYTE Suffix[4096]; UBYTE OutCode[OUTCOUNTMAX+1]; unsigned int mask[14] = {0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191}; int ReadCode (FILE* fh) { static unsigned long buffer=0; static unsigned int bitsinbuffer=0; int temp; while (CodeSize > bitsinbuffer) { if (CompDataPointer == CompDataCount) { if ((temp=fgetc(fh)) == EOF || fread(CompData,1,temp,fh)!=temp) { fprintf(stderr, "I/O error during decompression.\n"); ReadError=1; return EOFCode; } CompDataPointer=0; CompDataCount=temp; } buffer += ((long)CompData[CompDataPointer++] << bitsinbuffer); bitsinbuffer += 8; } temp = (int)buffer & mask[CodeSize]; buffer >>= CodeSize; bitsinbuffer -= CodeSize; return temp; } void AddPixel (UBYTE index) { char *o; unsigned int xpos, ypos; static int lx=(-1); xpos = ((long)Xpos << 8) / zoom; if ((xpos != lx) && (((invert == 0) && (Intensity[index] >= thresh)) || ((invert == 1) && (Intensity[index] < thresh)))) { lx = xpos; ypos = ((long)Ypos << 8) / zoom; o = (char*)Image + (xpos >> 3) + (ypos * ImageWid8); *o |= (1 << (xpos & 7)); } if (++Xpos == idesc.id_Width) { Xpos = 0; Ypos += LeaveStep[interleave]; if (Ypos >= idesc.id_Height) Ypos = LeaveFirst[++interleave]; } } void parse_gif(GIFfh) FILE *GIFfh; { char sig[7]; register int index; long size; int error; int colours; int cmdcode; sig[6] = '\0'; if (fread(sig,1,6,GIFfh) == 6 && !strcmp("GIF89a", sig) && !strcmp("GIF87a", sig)) ERROR("Not a GIF image.\n"); gdesc.gd_Width = fgetc(GIFfh); gdesc.gd_Width += 256 * fgetc(GIFfh); /* doncha just hate C's expression eval? */ gdesc.gd_Height = fgetc(GIFfh); gdesc.gd_Height += 256 * fgetc(GIFfh); gdesc.gd_ColInfo = fgetc(GIFfh); gdesc.gd_BackGround = fgetc(GIFfh); gdesc.gd_PixelAspect = fgetc(GIFfh); colours = 1 << ((gdesc.gd_ColInfo & 7) + 1); if (!(gdesc.gd_ColInfo & (1 << 7))) { for (index = 0; index < colours; index++) /* use greymap */ Intensity[index] = index; } else { for (index = 0; index < colours; index++) /* use colourmap */ Intensity[index] = (fgetc(GIFfh)+fgetc(GIFfh)+fgetc(GIFfh)) / 3; } fprintf(stderr, "Signature=\"%s\", Width=%u, Height=%u, Colours=%d\n", sig, gdesc.gd_Width, gdesc.gd_Height, colours); Backcol = Intensity[0]; ImageWidth = (256L * (long)gdesc.gd_Width) / zoom; ImageWid8 = ImageWidth >> 3; ImageHeight = (256L * (long)gdesc.gd_Height) / zoom; iheight = ImageHeight / 3; iwidth = ImageWidth >> 1; size = ((long)iheight * (1L + (long)iwidth)) - 1L; fprintf(stderr, "Attempting to allocate %lu bytes.\n", size); if (((size_t)size != (long)size) || !(ImageBase = (unsigned char *) malloc(size))) ERROR("Cannot allocate.\n"); Image = ImageBase + (unsigned int)size - (ImageHeight*ImageWid8); for (error = FALSE; error == FALSE;) { if ((cmdcode = fgetc(GIFfh)) == EOF) break; else if (cmdcode == GIF_IMAGE) error = DoImage(GIFfh); else if (cmdcode == GIF_EXTENSION) { if ((size = fgetc(GIFfh)) == EOF) /* snarf label */ { fprintf (stderr, "Warning: bad extension block\n"); break; } while (1) { if ((size = fgetc(GIFfh)) == EOF) { fprintf (stderr, "Warning: bad extension block\n"); break; } if (size == 0) break; if ((fread(CompData,1,size,GIFfh)) != size) { fprintf (stderr, "Warning: short extension block\n"); break; } } } else if (cmdcode == GIF_TERMINATOR) break; else { fprintf(stderr, "Unknown directive encountered (%u).\n", cmdcode); error = TRUE; } } } int DoImage (FILE *fh) { register int index; register ULONG colours; UBYTE InitCodeSize, FinChar, BitMask; int MaxCode, ClearCode, CurCode, OldCode, InCode, FreeCode; int OutCount, FirstFree, ByteIn, Code; idesc.id_Left = fgetc(GIFfh); idesc.id_Left += 256 * fgetc(GIFfh); idesc.id_Top = fgetc(GIFfh); idesc.id_Top += 256 * fgetc(GIFfh); idesc.id_Width = fgetc(GIFfh); idesc.id_Width += 256 * fgetc(GIFfh); idesc.id_Height = fgetc(GIFfh); idesc.id_Height += 256 * fgetc(GIFfh); idesc.id_Info = fgetc(GIFfh); interleave = idesc.id_Info & (1 << 6); if (interleave) interleave = 1; if (idesc.id_Info & (1 << 7)) { colours = 1 << ((idesc.id_Info & 7) + 1); fprintf(stderr, "Local colour map contains %lu entries.\n", colours); for (index = 0; index < colours; index++) Intensity[index] = (fgetc(GIFfh)+fgetc(GIFfh)+fgetc(GIFfh)) / 3; } else colours = 1 << ((gdesc.gd_ColInfo & 7) + 1); Xpos = Ypos = 0; if ((ByteIn = fgetc(fh)) == EOF) ERROR("I/O Error during decompression.\n"); else CodeSize = ByteIn; ClearCode = 1 << CodeSize; EOFCode = ClearCode + 1; FreeCode = FirstFree = ClearCode + 2; CodeSize++; InitCodeSize = CodeSize; MaxCode = 1 << CodeSize; ReadError = OutCount = 0; BitMask = colours - 1; CompDataPointer = CompDataCount = 0; Code = ReadCode(fh); while (Code != EOFCode) { if (ReadError) return TRUE; if (Code == ClearCode) { CodeSize = InitCodeSize; MaxCode = 1 << CodeSize; FreeCode = FirstFree; Code = ReadCode(fh); CurCode = Code; OldCode = Code; FinChar = Code; AddPixel(FinChar); } else { CurCode = InCode = Code; if (CurCode >= FreeCode) { CurCode = OldCode; OutCode[OutCount++] = FinChar; } while (CurCode > BitMask) { if (OutCount > OUTCOUNTMAX) { fprintf(stderr, "\nCorrupt GIF file (OutCount)\n"); return TRUE; } OutCode[OutCount++] = Suffix[CurCode]; CurCode = Prefix[CurCode]; } FinChar = CurCode; AddPixel(FinChar); for (index = OutCount - 1; index >= 0; index--) AddPixel(OutCode[index]); OutCount = 0; Prefix[FreeCode] = OldCode; Suffix[FreeCode] = FinChar; OldCode = InCode; if (++FreeCode >= MaxCode && CodeSize < 12) { CodeSize++; MaxCode <<= 1; } } Code = ReadCode(fh); } if ((Code = fgetc(fh)) == EOF) return TRUE; if (Code != 0) fprintf(stderr, "Warning: Unaligned packet.\n"); return FALSE; } /* This comes from the before-mentioned ASCII art FAQ by Jorn Barger. * -- * Happily, as I was working on this faq, I ran across Rob Harley * (robert@vlsi.cs.caltech.edu), who had some nice code for converting * b&w bitmaps according to a mapping like this: * * .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. * .. .. .. .. .@ .@ .@ .@ @. @. @. @. @@ @@ @@ @@ * .. .@ @. @@ .. .@ @. @@ .. .@ @. @@ .. .@ @. @@ * * , . _ - i v g - c i s = e z m * * .@ .@ .@ .@ .@ .@ .@ .@ .@ .@ .@ .@ .@ .@ .@ .@ * .. .. .. .. .@ .@ .@ .@ @. @. @. @. @@ @@ @@ @@ * .. .@ @. @@ .. .@ @. @@ .. .@ @. @@ .. .@ @. @@ * * ' ! / 2 ! ] / J / ( / K Y 4 Z W * * @. @. @. @. @. @. @. @. @. @. @. @. @. @. @. @. * .. .. .. .. .@ .@ .@ .@ @. @. @. @. @@ @@ @@ @@ * .. .@ @. @@ .. .@ @. @@ .. .@ @. @@ .. .@ @. @@ * * ` \ | L \ \ ) G ! t [ L + N D W * * @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ * .. .. .. .. .@ .@ .@ .@ @. @. @. @. @@ @@ @@ @@ * .. .@ @. @@ .. .@ @. @@ .. .@ @. @@ .. .@ @. @@ * * ~ T 7 X V Y Z 8 f 5 P K * M A @ */