#line 60 "gif2.c-nw" #include #include #include #include #include #include #define LIVE_IMAGES 1 /* Defined in w3a.h: */ /* #define POINT_SELECT 5000 /* W3A event type */ #define CMAP 256 /* Max. GIF color table */ #define MAX_LZW_BITS 12 /* Code can grow to this */ #define MAXSTACK 4096 /* Maximum with 12 bits */ #define GIF_SUCCESS 0 /* Decoded successfully */ #define NO_GIF 1 /* Wrong magic number */ #define SHORT_DATA 2 /* Unexpected end of data */ #define IMG_START 3 /* Image seperator missing */ #define GRAYMARGIN 10000 /* Max. distance from grayrey */ #define abs(a) ((a) < 0 ? -(a) : (a)) #define min(a, b) ((a) < (b) ? (a) : (b)) static char *err_msg[] = { "OK", "GIF viewer failed: image is not a GIF87a or GIF89a\n(%s)", "GIF viewer failed: image is incomplete\n(%s)", "GIF viewer failed: start of image not found\n(%s)", }; typedef struct { /* A viewer instance */ char *url; /* Only used for error msg */ unsigned char buf[2*BUFSIZ]; /* Temporary GIF image data */ size_t buflen; /* Length of buf in bytes */ Widget canvas; /* The widget to draw into */ GC gc; /* GC for image */ int width, height; /* Image size */ int red[CMAP], green[CMAP], blue[CMAP]; /* Color map table */ Bool use_gray; /* Use std. grayrey map? */ int transparent; /* Transp. color index or -1 */ int xpos, ypos, step, pass; /* Coords. & interlace step */ int rowstart; /* == ypos * width */ int maskrowstart; /* == ypos * ((width+7)/8) */ char *mask; /* Transparency mask */ unsigned char *image; /* Decoded image */ int state; Bool ready; /* Image fully decoded */ Bool has_color_table; int color_table_size; int clear_code, eoi_code, min_code_size, free_code; int nrworkbits, code_size, code_mask; int prev_code, bufidx, blocklen, first; int prefix[MAXSTACK], extnsn[MAXSTACK]; unsigned long workbits; } *Buffer; #define discard(b, n) memmove((b)->buf, (b)->buf + (n), (b)->buflen -= (n)) static XscmInfoRec xscm_info, xscm_gray_info; static Bool default_stdcmap, gray_stdcmap; #line 129 "gif2.c-nw" typedef struct _Assoc {long id; Buffer b; struct _Assoc *next;} *Assoc; static Assoc assoclist = NULL; /* store -- store an ID/Buffer combination */ static void store(Buffer b, long id) { Assoc h; new(h); h->id = id; h->b = b; h->next = assoclist; assoclist = h; } /* delete -- delete an ID/Buffer combination */ static void delete(long id) { Assoc g, h; assert(assoclist); if (assoclist->id == id) { h = assoclist; assoclist = assoclist->next; dispose(h); } else { assert(assoclist->next); for (h = assoclist; h->next->id != id; h = h->next) assert(h->next); g = h->next; h->next = g->next; dispose(g); } } /* find -- find the Buffer associated with an ID */ static Buffer find(long id) { Assoc h; assert(assoclist); for (h = assoclist; h->id != id; h = h->next) assert(h->next); return h->b; } #line 173 "gif2.c-nw" /* store_pixel -- add pixel to decoded image */ static void store_pixel(Buffer b, int index) { static int stepsize[] = {8, 8, 4, 2, 0}; static int startrow[] = {0, 4, 2, 1, 0}; assert(0 <= index && index <= 255); assert(b->rowstart == b->ypos * b->width); assert(b->maskrowstart == b->ypos * ((b->width + 7)/8)); if (index == b->transparent) b->mask[b->maskrowstart + b->xpos/8] &= ~(1 << (b->xpos % 8)); b->image[b->rowstart + b->xpos] = index; if (++b->xpos == b->width) { /* At the end of a row... */ #if LIVE_IMAGES if (XtIsRealized(b->canvas)) { /* ...display 1 screen row */ if (gray_stdcmap && (b->use_gray || ! default_stdcmap)) XscmDisplay(&xscm_gray_info, b->image + b->ypos * b->width, b->width, b->width, 1, b->red, b->green, b->blue, XtWindow(b->canvas), b->gc, 0, b->ypos); else if (default_stdcmap) XscmDisplay(&xscm_info, b->image + b->ypos * b->width, b->width, b->width, 1, b->red, b->green, b->blue, XtWindow(b->canvas), b->gc, 0, b->ypos); } #endif /* LIVE_IMAGES */ b->xpos = 0; b->ypos += b->step; b->rowstart += b->step * b->width; b->maskrowstart += b->step * ((b->width + 7)/8); if (b->ypos >= b->height) { b->pass++; b->step = stepsize[b->pass]; b->ypos = startrow[b->pass]; b->rowstart = b->ypos * b->width; b->maskrowstart = b->ypos * ((b->width + 7)/8); } } } #line 223 "gif2.c-nw" /* parseGIF -- decode GIF image data and create an XImage from it */ static int parseGIF(Buffer b) { int stack[MAXSTACK]; Bool is_interlaced; Dimension dum1, dum2; /* Dummy arguments */ int i, j, drb, dgb, drg; int code, sp = 0; for (;;) { /* Interrupted by a return */ switch (b->state) { case 0: /* Initialize */ b->transparent = -1; b->state++; /* Fall through */ case 1: /* Read magic number */ if (b->buflen < 6) /* Insufficient data */ return TRUE; if (!memcmp(b->buf, "GIF87a", 6) && !memcmp(b->buf, "GIF89a", 6)) return FALSE; /* Error */ discard(b, 6); b->state++; /* Fall through */ case 2: /* Logical Screen Descriptor */ if (b->buflen < 7) return TRUE; b->has_color_table = (b->buf[4] & 0x80) == 0x80; b->color_table_size = 2 << (b->buf[4] & 0x07); discard(b, 7); b->state++; /* Fall through */ case 3: /* Global Color Table */ if (b->has_color_table) { if (b->buflen < 3 * b->color_table_size) return TRUE; b->use_gray = TRUE; for (i = 0, j = 0; i < b->color_table_size; i++) { /* Expand 0..255 --> 0..65535 */ b->red[i] = ((int) b->buf[j++]) << 8; b->green[i] = ((int) b->buf[j++]) << 8; b->blue[i] = ((int) b->buf[j++]) << 8; if (b->use_gray) { drb = abs(b->blue[i] - b->red[i]); dgb = abs(b->blue[i] - b->green[i]); drg = abs(b->green[i] - b->red[i]); if (drb > GRAYMARGIN || dgb > GRAYMARGIN || drg > GRAYMARGIN) b->use_gray = FALSE; } } } discard(b, 3 * b->color_table_size); b->state++; /* Fall through */ case 4: /* Extensions */ if (b->buflen < 1) return TRUE; if (b->buf[0] == 0x21) { if (b->buflen < 2) return TRUE; else if (b->buf[1] == 0xf9) b->state = 400; else b->state = 410; discard(b, 2); break; } b->state++; /* Fall through */ case 5: /* Image Descriptor */ if (b->buflen < 10) return TRUE; if (b->buf[0] != 0x2c) return FALSE; /* No Image Seperator */ b->width = (b->buf[6] << 8) | b->buf[5]; b->height = (b->buf[8] << 8) | b->buf[7]; b->has_color_table = (b->buf[9] & 0x80) == 0x80; is_interlaced = (b->buf[9] & 0x40) == 0x40; b->color_table_size = 2 << (b->buf[9] & 0x07); b->xpos = 0; b->ypos = 0; b->step = is_interlaced ? 8 : 1; b->pass = is_interlaced ? 0 : 3; b->rowstart = 0; b->maskrowstart = 0; newarray(b->image, b->height * b->width); if (b->transparent >= 0) { newarray(b->mask, b->height * ((b->width + 7)/8)); memset(b->mask, 0xff, b->height * ((b->width + 7)/8)); } #if 1 XtMakeResizeRequest(b->canvas, b->width, b->height, &dum1, &dum2); #endif discard(b, 10); b->state++; /* Fall through */ case 6: /* Local Color Table */ if (b->has_color_table) { if (b->buflen < 3 * b->color_table_size) return TRUE; b->use_gray = TRUE; for (i = 0, j = 0; i < b->color_table_size; i++) { /* Expand 0..255 --> 0..65535 */ b->red[i] = ((int) b->buf[j++]) << 8; b->green[i] = ((int) b->buf[j++]) << 8; b->blue[i] = ((int) b->buf[j++]) << 8; if (b->use_gray) { drb = abs(b->blue[i] - b->red[i]); dgb = abs(b->blue[i] - b->green[i]); drg = abs(b->green[i] - b->red[i]); if (drb > GRAYMARGIN || dgb > GRAYMARGIN || drg > GRAYMARGIN) b->use_gray = FALSE; } } } b->state++; /* Fall through */ case 7: /* Special codes */ assert(b->state == 7); if (b->buflen < 1) return TRUE; b->min_code_size = b->buf[0]; b->clear_code = 1 << b->min_code_size; b->eoi_code = b->clear_code + 1; b->code_size = b->min_code_size + 1; b->free_code = b->clear_code + 2; b->code_mask = (1 << b->code_size) - 1; b->nrworkbits = 0; b->workbits = 0; b->bufidx = 0; b->blocklen = 0; b->prev_code = -2; discard(b, 1); b->state++; case 8: /* Decode LZW */ /* Add bytes until we have enough bits for next code */ while (b->nrworkbits < b->code_size) { /* Read new data block if needed */ if (b->bufidx == b->blocklen) { discard(b, b->blocklen); b->bufidx = b->blocklen = 0; if (b->buflen < 1 || b->buflen < 1 + (size_t) b->buf[0]) return TRUE; b->blocklen = 1 + b->buf[0]; b->bufidx = 1; } b->workbits |= (unsigned long) b->buf[b->bufidx++] << b->nrworkbits; b->nrworkbits += 8; } /* Get next code */ code = b->workbits & b->code_mask; b->workbits >>= b->code_size; b->nrworkbits -= b->code_size; /* Force first code of image to be a clear code */ if (b->prev_code == -2) code = b->clear_code; /* Branch on type of code */ if (code == b->clear_code) { /* Reset the decoder */ b->code_size = b->min_code_size + 1; /* Original size */ b->code_mask = (1 << b->code_size) - 1; b->free_code = b->clear_code + 2; /* First pos in tables */ b->prev_code = -1; /* Next code is a root code */ } else if (code == b->eoi_code) { /* End of Information */ b->state++; break; } else if (b->prev_code == -1) { /* 1st code after clearcode */ store_pixel(b, code); /* Add to image */ b->first = b->prev_code = code; } else { /* We've got a normal code */ assert(sp == 0); if (code >= b->free_code) { /* It's a new code */ stack[sp++] = b->first; b->first = b->prev_code; } else /* It's an existing code */ b->first = code; while (b->first >= b->clear_code) { /* Push string of pixels */ stack[sp++] = b->extnsn[b->first]; b->first = b->prefix[b->first]; } stack[sp++] = b->first; /* Push string's root code */ while (sp != 0) /* Now add pixels to image */ store_pixel(b, stack[--sp]); b->prefix[b->free_code] = b->prev_code; b->extnsn[b->free_code++] = b->first; b->prev_code = code; /* Check if code_size needs to increase */ if (b->free_code > b->code_mask && b->code_size != MAX_LZW_BITS) { b->code_size++; b->code_mask = (1 << b->code_size) - 1; } } break; case 9: /* Ignore additional images */ discard(b, b->buflen); b->ready = TRUE; return TRUE; case 400: /* Graphic Control Extension */ if (b->buflen < 6) return TRUE; b->transparent = b->buf[4]; /* Index in colortable */ discard(b, 6); b->state = 4; break; case 410: /* Other extension */ if (b->buflen < 1) return TRUE; if (b->buf[0] == 0) { /* Last block */ discard(b, 1); b->state = 4; } else if (b->buflen < 1 + (size_t) b->buf[0]) { return TRUE; } else { discard(b, 1 + b->buf[0]); /* Remove one block */ } break; default: assert(! "Cannot happen"); } } } /* GIF_redraw -- redraw the indicated area of a GIF image */ static void GIF_redraw2(Buffer b, int x, int y, int wd, int ht) { Widget w = b->canvas; #if 0 XtMakeResizeRequest(b->canvas, b->width, b->height, &dum1, &dum2); #endif /* * Only once: set the window shape */ if (b->mask != NULL && b->ready) { Pixmap mask; mask = XCreateBitmapFromData(XtDisplay(w), XtWindow(w), b->mask, b->width, b->height); if (mask != None) { XShapeCombineMask(XtDisplay(w), XtWindow(w), ShapeBounding, 0, 0, mask, ShapeSet); XFreePixmap(XtDisplay(w), mask); } dispose(b->mask); b->mask = NULL; } /* Draw image to exposed rectangle */ if (gray_stdcmap && (b->use_gray || ! default_stdcmap)) XscmDisplay(&xscm_gray_info, b->image + y * b->width + x, b->width, wd, ht, b->red, b->green, b->blue, XtWindow(w), b->gc, x, y); else if (default_stdcmap) XscmDisplay(&xscm_info, b->image + y * b->width + x, b->width, wd, ht, b->red, b->green, b->blue, XtWindow(w), b->gc, x, y); else assert(! "Cannot happen"); } /* GIF_redraw -- action proc: redraw the exposed area of a GIF image */ static void GIF_redraw(Widget w, XEvent *ev, String *parms, Cardinal *nparms) { Buffer b; int wd, ht, x, y; /* Decode string argument as a Buffer pointer */ assert(*nparms == 1); if (sscanf(parms[0], "%ld", &b) != 1) assert(!"Missing parameter"); if (! XtIsRealized(w) || b->image == NULL) return; assert(b->canvas == w); x = ev ? ev->xexpose.x : 0; y = ev ? ev->xexpose.y : 0; if (x >= b->width || y >= b->height) return; /* Nothing to draw */ wd = ev ? min(ev->xexpose.width, b->width - x) : b->width; ht = ev ? min(ev->xexpose.height, b->height - y) : b->height; GIF_redraw2(b, x, y, wd, ht); } /* GIF_click -- action for mouse clicks on canvas */ /* ARGSUSED */ static void GIF_click(Widget w, XEvent *ev, String *params, Cardinal *nparams) { long id; struct {int x, y;} coords; assert(*nparams == 1); if (sscanf(params[0], "%ld", &id) != 1) assert(!"Missing parameter"); coords.x = ev->xbutton.x; coords.y = ev->xbutton.y; #if 0 debug("Clicked on image: (%d,%d)\n", coords.x, coords.y); #endif W3Aevent(id, POINT_SELECT, &coords); #ifndef NDEBUG { Buffer b = find(id); assert(b->canvas == w); } #endif } static XtActionsRec actions[] = { {"GIF_expose", GIF_redraw}, {"GIF_click", GIF_click} }; /* * The 1st %ld points to the Buffer, the 2nd is the ID */ static char translations[] = ": GIF_expose(%ld)\n\ ,: GIF_click(%ld)"; /* initGIF -- initialize the GIF viewer class */ EXPORT Bool initGIF(char ***mime_types, int *nrtypes, float **prefs) { static char *types[] = {"image/gif"}; static float pref[] = {1.0}; Widget toplevel = W3Atoplevel(); Display *dpy = XtDisplay(toplevel); int screen = XScreenNumberOfScreen(XtScreen(toplevel)); *mime_types = types; *nrtypes = 1; *prefs = pref; XtAppAddActions(XtWidgetToApplicationContext(toplevel), actions, XtNumber(actions)); default_stdcmap = XscmFindColorSCM(dpy, screen, XSCM_ANY, &xscm_info); gray_stdcmap = XscmFindGraySCM(dpy, screen, &xscm_gray_info); if (! default_stdcmap && ! gray_stdcmap) XtAppWarning(XtWidgetToApplicationContext(W3Atoplevel()), "GIF viewer: Could find neither a standard color map \ nor a standard gray map"); return default_stdcmap || gray_stdcmap; } /* openGIF -- start a new GIF viewer, return its ID */ EXPORT Bool openGIF(const W3ADocumentInfo doc, W3AWindow window, long id) { int screen = XScreenNumberOfScreen(XtScreen(window)); Display *dpy = XtDisplay(window); char s[256]; Buffer b; new(b); store(b, id); b->url = newstring(doc.url); b->buflen = 0; b->image = NULL; b->mask = NULL; b->canvas = window; b->state = 0; b->ready = FALSE; sprintf(s, translations, b, id); XtOverrideTranslations(b->canvas, XtParseTranslationTable(s)); b->gc = DefaultGC(dpy, screen); return TRUE; } /* writeGIF -- add image data to the buffer, decode when complete */ EXPORT int writeGIF(long id, const char *buf, size_t nbytes) { Buffer b = find(id); int n, rest = nbytes; while (rest) { n = min(rest, sizeof(b->buf) - b->buflen); memcpy(b->buf + b->buflen, buf, n); b->buflen += n; rest -= n; buf += n; if (! parseGIF(b)) {errno = EFORMAT; return -1;} } /* When image complete, redraw it, to correct the dithering */ if (nbytes == 0 && XtIsRealized(b->canvas)) { #if 0 XClearArea(XtDisplay(b->canvas), XtWindow(b->canvas), 0, 0, 0, 0, TRUE); #else GIF_redraw2(b, 0, 0, b->width, b->height); #endif } return nbytes; } /* closeGIF -- close a GIF viewer */ EXPORT Bool closeGIF(long id) { Buffer b = find(id); dispose(b->url); dispose(b->image); delete(id); return TRUE; } /* eventGIF -- react to events happening elsewhere */ /* ARGSUSED */ EXPORT void eventGIF(long id, long source, long eventtype, void *params) { #if 0 debug("GIF viewer %ld received event %ld from %ld\n", id, eventtype, source); #endif /* Doesn't handle events */ } /* infoGIF -- a chance to modify the document info based on the contents */ /* ARGSUSED */ EXPORT Bool infoGIF(long id, W3ADocumentInfo *doc) { /* No modifications */ return TRUE; }