/* * Shows a GIF file. * * The GIF file is read from stdin. * * Requires either a PseudoColor visual with at least 256 colourmap * entries or a TrueColor visual. (Actually, at the moment, requires * a TrueColor visual.) */ #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; typedef unsigned long long int TIME; typedef struct pixinfo PIXINFO; struct pixinfo { unsigned char rshift; unsigned char lshift; } ; static XrmDatabase db; static const char *defaults = "\ *BorderColor: white\n\ *BorderWidth: 1\n\ *Name: xshowgif\n\ *IconName: xshowgif\n\ "; static char *displayname; static char *visualstr; static char *bordercstr; static char *borderwstr; static char *winname; static char *iconname; static char *gammastr; static int synch = 0; static int min_delay = 0; static const char *resname = "xshowgif"; static const char *resclass = "XShowGIF"; static char *xrmstr = 0; static int argc; static char **argv; static Display *disp; static Screen *scr; static int width; static int height; static Colormap wincmap; static Window rootwin; static XVisualInfo visinfo; static XColor bdcolour; static int borderwidth; static int (*preverr)(Display *, XErrorEvent *); static int (*prevIOerr)(Display *); static Window topwin; static GC wingc; static XImage *img = 0; static int bytes_per_pixel; static unsigned char *imgdata = 0; static PIXINFO pi_r; static PIXINFO pi_g; static PIXINFO pi_b; static GIFREADER *gr; static GIFREADER *gr; static const char *broken_ext_name; static int scr_w; static int scr_h; static int scr_m; static int scr_bpp; static int scr_bg; static unsigned char gcdata[256][3]; static unsigned char lcdata[256][3]; static unsigned char (*gcmap)[3]; static unsigned char (*lcmap)[3]; static unsigned char (*cmap)[3]; static int gx_disp; static int gx_input; static int gx_delay; static int gx_transp; static int img_w; static int img_h; static int img_x; static int img_y; static int img_cw; static int img_ch; static int img_i; static int px; static int py; static int ipy; static TIME t0 = 0; static TIME tend; static TIME tawait; static char *deconst_(int dummy, ...) { va_list ap; char *rv; va_start(ap,dummy); rv = va_arg(ap,char *); va_end(ap); return(rv); } static char *deconst(const char *s) { return(deconst_(0,s)); } static TIME now(void) { struct timeval tv; gettimeofday(&tv,0); return((tv.tv_sec*(TIME)1000000)+(TIME)tv.tv_usec); } static void await_time(TIME t) { TIME n; unsigned long long int us; int ms; while (1) { n = now(); if (n >= t) return; us = t - n; if (us > 60000000) { ms = 60000; } else { ms = us / 1000; if (ms < 1) ms = 1; } poll(0,0,ms); } } static void saveargv(int ac, char **av) { int i; int nc; char *abuf; argc = ac; argv = malloc((ac+1)*sizeof(char *)); nc = 1; for (i=0;i 0) { skip --; continue; } if (**av != '-') { fprintf(stderr,"%s: stray argument `%s'\n",__progname,*av); errs ++; continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-display")) { WANTARG(); displayname = av[skip]; continue; } if (!strcmp(*av,"-bordercolor") || !strcmp(*av,"-bd")) { WANTARG(); bordercstr = av[skip]; continue; } if (!strcmp(*av,"-borderwidth") || !strcmp(*av,"-bw")) { WANTARG(); borderwstr = av[skip]; continue; } if (!strcmp(*av,"-visual")) { WANTARG(); visualstr = av[skip]; continue; } if (!strcmp(*av,"-name")) { WANTARG(); winname = av[skip]; continue; } if (!strcmp(*av,"-iconname")) { WANTARG(); iconname = av[skip]; continue; } if (!strcmp(*av,"-resname")) { WANTARG(); resname = av[skip]; continue; } if (!strcmp(*av,"-resclass")) { WANTARG(); resclass = av[skip]; continue; } if (!strcmp(*av,"-xrm")) { WANTARG(); strappend(&xrmstr,"\n"); strappend(&xrmstr,av[skip]); continue; } if (!strcmp(*av,"-sync")) { synch = 1; continue; } if (!strcmp(*av,"-gamma")) { WANTARG(); gammastr = av[skip]; continue; } if (!strcmp(*av,"-min-delay")) { WANTARG(); min_delay = atoi(av[skip]); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } return(errs); } static int err(Display *d, XErrorEvent *ee) { return((*preverr)(d,ee)); } static int ioerr(Display *d) { return((*prevIOerr)(d)); } static void setup_pix_info(unsigned long int mask0, PIXINFO *pi) { unsigned long int mask; int nb; int r; mask = mask0; r = 0; while (! (mask & 1)) { r ++; mask >>= 1; } nb = 0; while (mask & 1) { nb ++; mask >>= 1; } if (mask) { fprintf(stderr,"%s: mask 0x%lx is noncontiguous\n",__progname,mask0); exit(1); } if (nb < 8) { pi->rshift = 8 - nb; pi->lshift = r; } else { pi->rshift = 0; pi->lshift = r + nb - 8; } } static void setup_visual(void) { XVisualInfo *xvi; int nvi; XVisualInfo template; int best; int best_onscr; int i; if (visualstr) { unsigned long int id; char *cp; id = strtol(visualstr,&cp,0); if (*cp) { fprintf(stderr,"%s: %s: invalid visual option\n",__progname,visualstr); exit(1); } template.visualid = (VisualID) id; xvi = XGetVisualInfo(disp,VisualIDMask,&template,&nvi); if (xvi == 0) { fprintf(stderr,"%s: no such visual found\n",__progname); exit(1); } } else { xvi = XGetVisualInfo(disp,0,0,&nvi); if (xvi == 0) { fprintf(stderr,"%s: no visuals found?!\n",__progname); exit(1); } } if (nvi == 0) { fprintf(stderr,"%s: what? XGetVisualInfo returned non-nil but zero count?\n",__progname); exit(1); } best = -1; best_onscr = -1; for (i=0;i xvi[best_onscr].depth) || ( (xvi[i].depth == xvi[best_onscr].depth) && (xvi[i].visual == XDefaultVisual(disp,xvi[i].screen)) ) ) { best_onscr = i; } } if ( (best < 0) || (xvi[i].depth > xvi[best].depth) || ( (xvi[i].depth == xvi[best].depth) && (xvi[i].visual == XDefaultVisual(disp,xvi[i].screen)) ) ) { best = i; } } if (best < 0) { if (visualstr) { fprintf(stderr,"%s: visual %s not suitable\n",__progname,visualstr); } else { fprintf(stderr,"%s: no suitable visual found\n",__progname); } } if (best_onscr < 0) { if (visualstr) { fprintf(stderr,"%s: visual %s is on a different screen\n",__progname,visualstr); } else { fprintf(stderr,"%s: no suitable visual found on default screen (best is on screen %d)\n",__progname,xvi[best_onscr].screen); } exit(1); } visinfo = xvi[best_onscr]; setup_pix_info(visinfo.red_mask,&pi_r); setup_pix_info(visinfo.green_mask,&pi_g); setup_pix_info(visinfo.blue_mask,&pi_b); } static void setup_db(void) { char *str; XrmDatabase db2; db = XrmGetStringDatabase(defaults); str = XResourceManagerString(disp); if (str) { db2 = XrmGetStringDatabase(str); XrmMergeDatabases(db2,&db); } #if defined(XlibSpecificationRelease) && (XlibSpecificationRelease >= 5) str = XScreenResourceString(scr); if (str) { db2 = XrmGetStringDatabase(str); XrmMergeDatabases(db2,&db); } #endif if (xrmstr) { db2 = XrmGetStringDatabase(xrmstr); XrmMergeDatabases(db2,&db); } } static char *get_default_value(const char *name, const char *class) { char *type; XrmValue value; int nl; int cl; static int buflen = -1; static int rnlen = 0; static int rclen = 0; static char *nbuf; static char *cbuf; nl = strlen(name); cl = strlen(class); if ((nl+1+rnlen >= buflen) || (cl+1+rclen >= buflen)) { if (buflen < 0) { rnlen = strlen(resname); rclen = strlen(resclass); buflen = 10; nbuf = malloc(buflen); cbuf = malloc(buflen); } if (buflen <= nl+1+rnlen) buflen = nl+1+rnlen + 1; if (buflen <= cl+1+rclen) buflen = cl+1+rclen + 1; nbuf = realloc(nbuf,buflen); cbuf = realloc(cbuf,buflen); } sprintf(nbuf,"%s.%s",resname,name); sprintf(cbuf,"%s.%s",resclass,class); if (XrmGetResource(db,nbuf,cbuf,&type,&value) == False) return(0); return(value.addr); } static void maybeset(char **strp, char *str) { if (str && !*strp) *strp = strdup(str); } static void setup_numbers(void) { borderwidth = atoi(borderwstr); if (borderwidth < 0) borderwidth = 0; } static void setup_cmap(void) { if (visinfo.visual == XDefaultVisualOfScreen(scr)) { wincmap = XDefaultColormapOfScreen(scr); } else { wincmap = XCreateColormap(disp,rootwin,visinfo.visual,AllocNone); } } static void setup_colour(const char *str, XColor *c) { XColor junk; if (! XLookupColor(disp,wincmap,str,&junk,c)) { fprintf(stderr,"%s: bad colour name `%s'\n",__progname,str); exit(1); } if (! XAllocColor(disp,wincmap,c)) { fprintf(stderr,"%s: can't setup colourmap cell for `%s'\n",__progname,str); exit(1); } } static void setup_colours(void) { setup_colour(bordercstr,&bdcolour); } static void setup_window(void) { unsigned long int attrmask; XSetWindowAttributes attr; XTextProperty wn_prop; XTextProperty in_prop; XWMHints *wm_hints; XSizeHints *normal_hints; XClassHint *class_hints; wm_hints = XAllocWMHints(); normal_hints = XAllocSizeHints(); class_hints = XAllocClassHint(); wm_hints->flags = InputHint; wm_hints->input = True; normal_hints->min_width = 1; normal_hints->min_height = 1; normal_hints->width_inc = 1; normal_hints->height_inc = 1; normal_hints->base_width = 1; normal_hints->base_height = 1; normal_hints->flags = PMinSize | PBaseSize | PResizeInc; class_hints->res_name = deconst(resname); class_hints->res_class = deconst(resclass); normal_hints->x = 0; normal_hints->y = 0; normal_hints->width = 1; normal_hints->height = 1; normal_hints->win_gravity = NorthWestGravity; normal_hints->flags |= PWinGravity; normal_hints->flags |= PSize; attrmask = 0; attr.background_pixmap = None; attrmask |= CWBackPixmap; attr.border_pixel = bdcolour.pixel; attrmask |= CWBorderPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.colormap = wincmap; attrmask |= CWColormap; topwin = XCreateWindow(disp,rootwin,0,0,1,1,borderwidth,visinfo.depth,InputOutput,visinfo.visual,attrmask,&attr); wn_prop.value = (unsigned char *) winname; wn_prop.encoding = XA_STRING; wn_prop.format = 8; wn_prop.nitems = strlen(winname); in_prop.value = (unsigned char *) iconname; in_prop.encoding = XA_STRING; in_prop.format = 8; in_prop.nitems = strlen(iconname); XSetWMProperties(disp,topwin,&wn_prop,&in_prop,argv,argc,normal_hints,wm_hints,class_hints); wingc = XCreateGC(disp,topwin,0,0); XMapRaised(disp,topwin); } static int gif_read_input(void *cookie __attribute__((__unused__)), void *buf, int len) { int i; i = fread(buf,1,len,stdin); if (i < 0) return(-1); return(i); } static void gif_error(void *cookie __attribute__((__unused__)), int err, ...) { va_list ap; va_start(ap,err); switch (err) { default: printf("Impossible error %d\n",err); abort(); break; case GIFREAD_ERR_READERROR: printf("*** Read error reading %s\n",va_arg(ap,const char *)); exit(1); break; case GIFREAD_ERR_UNXEOF: printf("*** Unexpected EOF reading %s\n",va_arg(ap,const char *)); exit(1); break; case GIFREAD_ERR_BADSIG: printf("*** Invalid signature\n"); exit(1); break; case GIFREAD_ERR_87a_RESERVED: if (va_arg(ap,int)) printf("*** Reserved bit set in word 5 of screen descriptor (ignoring)\n"); if (va_arg(ap,int)) printf("*** Word 7 of screen descriptor is not zero (ignoring)\n"); break; case GIFREAD_ERR_TEXTEXT_HDRSIZE: printf("*** plain text extension block length %d wrong\n",va_arg(ap,int)); // broken_ext_name = "plain text"; break; case GIFREAD_ERR_GFXCTLEXT_HDRSIZE: printf("*** graphic control extension block length %d wrong\n",va_arg(ap,int)); // broken_ext_name = "graphic control"; break; case GIFREAD_ERR_GFXCTLEXT_MBZ: printf("*** Reserved bits not zero in graphic control extension header (ignoring)\n"); break; case GIFREAD_ERR_GFXCTLEXT_BADTERM: printf("*** graphic control extension block terminator wrong\n"); // broken_ext_name = "graphic control"; break; case GIFREAD_ERR_GFXCTLEXT_BADDISP: printf("*** Reserved disposal value %d graphic control extension byte 0 (treating as 0)\n",va_arg(ap,int)); break; case GIFREAD_ERR_APPEXT_HDRSIZE: printf("*** application extension block length %d wrong\n",va_arg(ap,int)); // broken_ext_name = "application"; break; case GIFREAD_ERR_IMGDESC_RESERVED: printf("*** Reserved bits set in image descriptor (ignoring)\n"); break; case GIFREAD_ERR_IMGDESC_CODESIZE: printf("***\tcodesize %d ",va_arg(ap,int)); printf("not expected for bpp %d, using codesize\n",va_arg(ap,int)); break; case GIFREAD_ERR_LZW_OVERFLOW: printf("***\t\tError, lzw table overflow (forcing clear)\n"); break; case GIFREAD_ERR_LZW_BAD_KWKWK: printf("***\t\tError, KwKwK code with no previous code (ignoring)\n"); break; case GIFREAD_ERR_LZW_BAD_CODE: printf("***\t\tError, LZW code %d out of range (ignoring)\n",va_arg(ap,int)); break; case GIFREAD_ERR_SKIPJUNK: printf("*** Skipping junk in file...\n"); break; case GIFREAD_ERR_TRAILJUNK: printf("*** Skipping trailing junk...\n"); break; } va_end(ap); } static void clear_gx(void) { gx_disp = GIFREAD_GX_DISP_NONE; gx_input = 0; gx_delay = 0; gx_transp = -1; } static unsigned long int build_pixel(unsigned char r, unsigned char g, unsigned char b) { return( ((unsigned long int)(r >> pi_r.rshift) << pi_r.lshift) | ((unsigned long int)(g >> pi_g.rshift) << pi_g.lshift) | ((unsigned long int)(b >> pi_b.rshift) << pi_b.lshift) ); } static void set_image_pixel(int x, int y, unsigned long int p) { XPutPixel(img,x,y,p); } static void fill_whole_image(unsigned long int p) { int x; int y; for (y=0;ydata = 0; XDestroyImage(img); free(imgdata); } img = XCreateImage(disp,visinfo.visual,visinfo.depth,ZPixmap,0,0,scr_w,scr_h,8,0); bytes_per_pixel = (img->bits_per_pixel + 7) >> 3; imgdata = malloc(scr_h*img->bytes_per_line); img->data = imgdata; scr_m = va_arg(ap,int); switch (scr_m) { case GIFREAD_CMAP_NONE: case GIFREAD_CMAP_UNSORTED: case GIFREAD_CMAP_SORTED: break; default: abort(); break; } scr_bpp = va_arg(ap,int); scr_bg = va_arg(ap,int); (void)va_arg(ap,int); clear_gx(); break; case GIFREAD_DETAIL_UNKEXT: printf("Extension block:\n"); printf("\tExtension code %02x\n",va_arg(ap,int)); break; case GIFREAD_DETAIL_UNKEXT_DATA: { const unsigned char *dp; int len; dp = va_arg(ap,const unsigned char *); len = va_arg(ap,int); printf("\tData %d:",len); for (;len>0;len--) printf(" %02x",*dp++); printf("\n"); } break; case GIFREAD_DETAIL_BROKEN_EXT: { const unsigned char *dp; int len; dp = va_arg(ap,const unsigned char *); len = va_arg(ap,int); printf("Broken %s extension data %d:",broken_ext_name,len); for (;len>0;len--) printf(" %02x",*dp++); printf("\n"); } break; case GIFREAD_DETAIL_TEXTEXT: { unsigned int gx; unsigned int gy; unsigned int gw; unsigned int gh; unsigned int ccw; unsigned int cch; unsigned int fg; unsigned int bg; gx = va_arg(ap,unsigned int); gy = va_arg(ap,unsigned int); gw = va_arg(ap,unsigned int); gh = va_arg(ap,unsigned int); ccw = va_arg(ap,unsigned int); cch = va_arg(ap,unsigned int); fg = va_arg(ap,unsigned int); bg = va_arg(ap,unsigned int); printf("\tPlain text extension:\n"); printf("\t\tGrid area: %ux%u+%u+%u\n",gw,gh,gx,gy); printf("\t\tCharcell: %ux%u\n",ccw,cch); if (ccw && (gw % ccw)) printf("*** Grid width is not a multiple of charcell width (ignoring)\n"); if (cch && (gh % cch)) printf("*** Grid height is not a multiple of charcell height (ignoring)\n"); printf("\t\tPixel values: fg=%u bg=%u\n",fg,bg); } break; case GIFREAD_DETAIL_TEXTEXT_DATA: { const unsigned char *dp; int len; dp = va_arg(ap,const unsigned char *); len = va_arg(ap,int); printf("Plain text extension data %d:",len); for (;len>0;len--) printf(" %02x",*dp++); printf("\n"); } break; case GIFREAD_DETAIL_GFXCTLEXT: gx_disp = va_arg(ap,int); gx_input = va_arg(ap,int); gx_delay = va_arg(ap,int); gx_transp = va_arg(ap,int); switch (gx_disp) { case GIFREAD_GX_DISP_NONE: case GIFREAD_GX_DISP_LEAVE: case GIFREAD_GX_DISP_BG: case GIFREAD_GX_DISP_PREV: break; default: abort(); break; } if (t0 == 0) { t0 = now(); tend = t0; tawait = t0; } if (gx_delay < min_delay) gx_delay = min_delay; tend += gx_delay * (TIME)10000; break; case GIFREAD_DETAIL_COMMEXT: printf("Comment extension begins\n"); break; case GIFREAD_DETAIL_COMMEXT_DATA: { const unsigned char *dp; int len; dp = va_arg(ap,const unsigned char *); len = va_arg(ap,int); printf("Comment extension data %d:",len); for (;len>0;len--) printf(" %02x",*dp++); printf("\n"); } break; case GIFREAD_DETAIL_IMGDESC: { int m; img_x = va_arg(ap,int); img_y = va_arg(ap,int); img_w = va_arg(ap,int); img_h = va_arg(ap,int); m = va_arg(ap,int); img_i = va_arg(ap,int); (void)va_arg(ap,int); // printf("Image is %dx%d+%d+%d\n",img_w,img_h,img_x,img_y); if ((img_x+img_w > scr_w) || (img_y+img_h > scr_h)) { printf("Clipping image out of bounds: = %dx%d+%d+%d falls outside %dx%d", img_w, img_h, img_x, img_y, scr_w, scr_h); img_cw = (img_x+img_w > scr_w) ? scr_w - img_x : img_w; img_ch = (img_y+img_h > scr_h) ? scr_h - img_y : img_h; } else { img_cw = img_w; img_ch = img_h; } switch (m) { case GIFREAD_CMAP_NONE: case GIFREAD_CMAP_UNSORTED: case GIFREAD_CMAP_SORTED: break; default: abort(); break; } lcmap = 0; cmap = gcmap; px = 0; py = 0; ipy = 0; } break; case GIFREAD_DETAIL_PIXELS: { int v; v = va_arg(ap,int); if (py < 0) { printf("Too much data for image (ignoring)\n"); return; } if (v == gx_transp) { // do nothing for transparent pixels } else if ((px <= img_cw) && (py <= img_ch)) { if (cmap) { set_image_pixel(px+img_x,ipy+img_y,build_pixel(cmap[v][0],cmap[v][1],cmap[v][2])); } else { set_image_pixel(px+img_x,ipy+img_y,build_pixel(v,v,v)); } } px ++; if (px >= img_w) { px = 0; py ++; if (img_i) { if (! (ipy & 7)) { ipy += 8; if (ipy >= img_h) { ipy = 4; } } else if (! (ipy & 3)) { ipy += 8; if (ipy >= img_h) { ipy = 2; } } else if (! (ipy & 1)) { ipy += 4; if (ipy >= img_h) { ipy = 1; } } else { ipy += 2; } } else { ipy ++; } if (py >= img_h) { await_time(tawait); XPutImage(disp,topwin,wingc,img,0,0,0,0,scr_w,scr_h); XFlush(disp); py = -1; tawait = tend; } } } break; case GIFREAD_DETAIL_BACKGROUND: { int v; v = va_arg(ap,int); if (cmap) { fill_whole_image(build_pixel(cmap[v][0],cmap[v][1],cmap[v][2])); } else { fill_whole_image(build_pixel(v,v,v)); } } break; case GIFREAD_DETAIL_CMAP: { int gbl; int lgents; unsigned char *ents; unsigned char (*map)[3]; gbl = va_arg(ap,int); lgents = va_arg(ap,int); if (lgents > 8) { printf("cmap has lg(entries)=%d, too large\n",lgents); exit(1); } ents = va_arg(ap,unsigned char *); map = (void *)ents; if (gbl) { bcopy(map,&gcdata[0][0],3<