/* gifx.c - Functions to turn GIFs in memory into X Pixmaps. Copyright (C) 1997-2021 Eddie Kohler, ekohler@gmail.com This file is part of the LCDF GIF library. The LCDF GIF library is free software. It is distributed under the GNU General Public License, version 2; you can copy, distribute, or alter it at will, as long as this notice is kept intact and this source code is made available. There is no warranty, express or implied. */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define SAFELS(a,b) ((b) < 0 ? (a) >> -(b) : (a) << (b)) struct Gif_XColormap { Gif_XContext *x_context; Gif_Colormap *colormap; int allocated; int claimed; uint16_t npixels; unsigned long *pixels; Gif_XColormap *next; }; static unsigned long crap_pixels[256]; static void load_closest(Gif_XContext *gfx) { XColor *color; uint16_t ncolor; uint16_t ncolormap; int i; if (gfx->closest) return; ncolormap = ncolor = gfx->ncolormap; if (ncolor > 256) ncolor = 256; color = Gif_NewArray(XColor, ncolor); if (ncolormap > 256) for (i = 0; i < ncolor; i++) color[i].pixel = (rand() >> 4) % ncolormap; else for (i = 0; i < ncolor; i++) color[i].pixel = i; XQueryColors(gfx->display, gfx->colormap, color, ncolor); gfx->closest = Gif_NewArray(Gif_Color, ncolor); for (i = 0; i < ncolor; i++) { Gif_Color *c = &gfx->closest[i]; c->haspixel = 1; c->gfc_red = color[i].red >> 8; c->gfc_green = color[i].green >> 8; c->gfc_blue = color[i].blue >> 8; c->pixel = color[i].pixel; } gfx->nclosest = ncolor; Gif_DeleteArray(color); } static unsigned long allocate_closest(Gif_XContext *gfx, Gif_Color *c) { Gif_Color *closer; Gif_Color *got = 0; uint32_t distance = 0x4000000; int i; load_closest(gfx); for (i = 0, closer = gfx->closest; i < gfx->nclosest; i++, closer++) { int redd = c->gfc_red - closer->gfc_red; int greend = c->gfc_green - closer->gfc_green; int blued = c->gfc_blue - closer->gfc_blue; uint32_t d = redd * redd + greend * greend + blued * blued; if (d < distance) { distance = d; got = closer; } } if (!got) return 0; if (!got->haspixel) { XColor xcol; xcol.red = got->gfc_red | (got->gfc_red << 8); xcol.green = got->gfc_green | (got->gfc_green << 8); xcol.blue = got->gfc_blue | (got->gfc_blue << 8); if (XAllocColor(gfx->display, gfx->colormap, &xcol) == 0) { /* Probably was a read/write color cell. Get rid of it!! */ *got = gfx->closest[gfx->nclosest - 1]; gfx->nclosest--; return allocate_closest(gfx, c); } got->pixel = xcol.pixel; got->haspixel = 1; } return got->pixel; } static void allocate_colors(Gif_XColormap *gfxc) { Gif_XContext *gfx = gfxc->x_context; uint16_t size = gfxc->colormap->ncol; Gif_Color *c = gfxc->colormap->col; unsigned long *pixels = gfxc->pixels; XColor xcol; int i; if (!gfxc->allocated) { if (size > gfxc->npixels) size = gfxc->npixels; for (i = 0; i < size; i++, c++) { xcol.red = c->gfc_red | (c->gfc_red << 8); xcol.green = c->gfc_green | (c->gfc_green << 8); xcol.blue = c->gfc_blue | (c->gfc_blue << 8); if (XAllocColor(gfx->display, gfx->colormap, &xcol)) pixels[i] = xcol.pixel; else pixels[i] = allocate_closest(gfx, c); } gfxc->allocated = 1; gfxc->claimed = 0; } } static void deallocate_colors(Gif_XColormap *gfxc) { Gif_XContext *gfx = gfxc->x_context; if (gfxc->allocated && !gfxc->claimed) { XFreeColors(gfx->display, gfx->colormap, gfxc->pixels, gfxc->npixels, 0); gfxc->allocated = 0; } } static Gif_XColormap * create_x_colormap_extension(Gif_XContext *gfx, Gif_Colormap *gfcm) { Gif_XColormap *gfxc; unsigned long *pixels; if (!gfcm) return 0; gfxc = Gif_New(Gif_XColormap); pixels = gfxc ? Gif_NewArray(unsigned long, 256) : 0; if (pixels) { gfxc->x_context = gfx; gfxc->colormap = gfcm; gfxc->allocated = 0; gfxc->npixels = gfcm->ncol; gfxc->pixels = pixels; gfxc->next = gfx->xcolormap; gfx->xcolormap = gfxc; return gfxc; } else { Gif_Delete(gfxc); Gif_DeleteArray(pixels); return 0; } } static Gif_XColormap * find_x_colormap_extension(Gif_XContext *gfx, Gif_Colormap *gfcm, int create) { Gif_XColormap *gfxc = gfx->xcolormap; if (!gfcm) return 0; while (gfxc) { if (gfxc->colormap == gfcm) return gfxc; gfxc = gfxc->next; } if (create) return create_x_colormap_extension(gfx, gfcm); else return 0; } int Gif_XAllocateColors(Gif_XContext *gfx, Gif_Colormap *gfcm) { Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 1); if (gfxc) { allocate_colors(gfxc); return 1; } else return 0; } void Gif_XDeallocateColors(Gif_XContext *gfx, Gif_Colormap *gfcm) { Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 0); if (gfxc) deallocate_colors(gfxc); } unsigned long * Gif_XClaimStreamColors(Gif_XContext *gfx, Gif_Stream *gfs, int *np_store) { int i; int npixels = 0; unsigned long *pixels; Gif_Colormap *global = gfs->global; *np_store = 0; for (i = 0; i < gfs->nimages; i++) { Gif_Image *gfi = gfs->images[i]; Gif_Colormap *gfcm = (gfi->local ? gfi->local : global); Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 0); if (gfxc && gfxc->allocated && gfxc->claimed == 0) { gfxc->claimed = 2; npixels += gfxc->npixels; if (gfcm == global) global = 0; } } if (!npixels) return 0; pixels = Gif_NewArray(unsigned long, npixels); if (!pixels) return 0; *np_store = npixels; npixels = 0; global = gfs->global; for (i = 0; i < gfs->nimages; i++) { Gif_Image *gfi = gfs->images[i]; Gif_Colormap *gfcm = (gfi->local ? gfi->local : global); Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 0); if (gfxc && gfxc->allocated && gfxc->claimed == 2) { memcpy(pixels + npixels, gfxc->pixels, gfxc->npixels); npixels += gfxc->npixels; gfxc->claimed = 1; if (gfcm == global) global = 0; } } return pixels; } /* Getting pixmaps */ #define BYTESIZE 8 static int put_sub_image_colormap(Gif_XContext *gfx, Gif_Stream* gfs, Gif_Image *gfi, Gif_Colormap *gfcm, int left, int top, int width, int height, Pixmap pixmap, int pixmap_x, int pixmap_y) { XImage *ximage; uint8_t *xdata; int i, j, k; size_t bytes_per_line; unsigned long saved_transparent = 0; int release_uncompressed = 0; uint16_t nct; unsigned long *pixels; /* Find the correct image and colormap */ if (!gfi) return 0; if (!gfx->image_gc) gfx->image_gc = XCreateGC(gfx->display, pixmap, 0, 0); if (!gfx->image_gc) return 0; /* Make sure the image is uncompressed */ if (!gfi->img && !gfi->image_data && gfi->compressed) { Gif_UncompressImage(gfs, gfi); release_uncompressed = 1; } /* Check subimage dimensions */ if (width <= 0 || height <= 0 || left < 0 || top < 0 || left+width <= 0 || top+height <= 0 || left+width > gfi->width || top+height > gfi->height) return 0; /* Allocate colors from the colormap; make sure the transparent color * has the given pixel value */ if (gfcm) { Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 1); if (!gfxc) return 0; allocate_colors(gfxc); pixels = gfxc->pixels; nct = gfxc->npixels; } else { for (i = 0; i < 256; i++) crap_pixels[i] = gfx->foreground_pixel; pixels = crap_pixels; nct = 256; } if (gfi->transparent >= 0 && gfi->transparent < 256) { saved_transparent = pixels[ gfi->transparent ]; pixels[ gfi->transparent ] = gfx->transparent_pixel; } /* Set up the X image */ if (gfx->depth <= 8) i = 8; else if (gfx->depth <= 16) i = 16; else i = 32; ximage = XCreateImage(gfx->display, gfx->visual, gfx->depth, gfx->depth == 1 ? XYBitmap : ZPixmap, 0, NULL, width, height, i, 0); ximage->bitmap_bit_order = ximage->byte_order = LSBFirst; bytes_per_line = ximage->bytes_per_line; xdata = Gif_NewArray(uint8_t, bytes_per_line * height); ximage->data = (char *)xdata; /* The main loop */ if (ximage->bits_per_pixel % 8 == 0) { /* Optimize for cases where a pixel is exactly one or more bytes */ int bytes_per_pixel = ximage->bits_per_pixel / 8; for (j = 0; j < height; j++) { uint8_t *line = gfi->img[top + j] + left; uint8_t *writer = xdata + bytes_per_line * j; for (i = 0; i < width; i++) { unsigned long pixel; if (line[i] < nct) pixel = pixels[line[i]]; else pixel = pixels[0]; for (k = 0; k < bytes_per_pixel; k++) { *writer++ = pixel; pixel >>= 8; } } } } else { /* Other bits-per-pixel */ int bits_per_pixel = ximage->bits_per_pixel; uint32_t bits_per_pixel_mask = (1UL << bits_per_pixel) - 1; for (j = 0; j < height; j++) { int imshift = 0; uint32_t impixel = 0; uint8_t *line = gfi->img[top + j] + left; uint8_t *writer = xdata + bytes_per_line * j; for (i = 0; i < width; i++) { unsigned long pixel; if (line[i] < nct) pixel = pixels[line[i]]; else pixel = pixels[0]; impixel |= SAFELS(pixel & bits_per_pixel_mask, imshift); while (imshift + bits_per_pixel >= BYTESIZE) { *writer++ = impixel; imshift -= BYTESIZE; impixel = SAFELS(pixel, imshift); } imshift += bits_per_pixel; } if (imshift) *writer++ = impixel; } } /* Restore saved transparent pixel value */ if (gfi->transparent >= 0 && gfi->transparent < 256) pixels[ gfi->transparent ] = saved_transparent; /* Put it onto the pixmap */ XPutImage(gfx->display, pixmap, gfx->image_gc, ximage, 0, 0, pixmap_x, pixmap_y, width, height); Gif_DeleteArray(xdata); ximage->data = 0; /* avoid freeing it again in XDestroyImage */ XDestroyImage(ximage); if (release_uncompressed) Gif_ReleaseUncompressedImage(gfi); return 1; } Pixmap Gif_XSubImageColormap(Gif_XContext *gfx, Gif_Stream* gfs, Gif_Image *gfi, Gif_Colormap *gfcm, int left, int top, int width, int height) { Pixmap pixmap = XCreatePixmap(gfx->display, gfx->drawable, width ? width : 1, height ? height : 1, gfx->depth); if (pixmap) { if (put_sub_image_colormap(gfx, gfs, gfi, gfcm, left, top, width, height, pixmap, 0, 0)) return pixmap; else XFreePixmap(gfx->display, pixmap); } return None; } Pixmap Gif_XImage(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Image *gfi) { Gif_Colormap *gfcm; if (!gfi && gfs->nimages) gfi = gfs->images[0]; if (!gfi) return None; gfcm = gfi->local; if (!gfcm) gfcm = gfs->global; return Gif_XSubImageColormap(gfx, gfs, gfi, gfcm, 0, 0, gfi->width, gfi->height); } Pixmap Gif_XImageColormap(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Colormap *gfcm, Gif_Image *gfi) { if (!gfi && gfs->nimages) gfi = gfs->images[0]; if (!gfi) return None; return Gif_XSubImageColormap(gfx, gfs, gfi, gfcm, 0, 0, gfi->width, gfi->height); } Pixmap Gif_XSubImage(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Image *gfi, int left, int top, int width, int height) { Gif_Colormap *gfcm; if (!gfi && gfs->nimages) gfi = gfs->images[0]; if (!gfi) return None; gfcm = gfi->local; if (!gfcm) gfcm = gfs->global; return Gif_XSubImageColormap(gfx, gfs, gfi, gfcm, left, top, width, height); } Pixmap Gif_XSubMask(Gif_XContext* gfx, Gif_Stream* gfs, Gif_Image* gfi, int left, int top, int width, int height) { Pixmap pixmap = None; XImage *ximage; uint8_t *xdata; int i, j; int transparent; size_t bytes_per_line; int release_uncompressed = 0; /* Find the correct image */ if (!gfi) return None; /* Check subimage dimensions */ if (width <= 0 || height <= 0 || left < 0 || top < 0 || left+width <= 0 || top+height <= 0 || left+width > gfi->width || top+height > gfi->height) return None; /* Make sure the image is uncompressed */ if (!gfi->img && !gfi->image_data && gfi->compressed) { Gif_UncompressImage(gfs, gfi); release_uncompressed = 1; } /* Create the X image */ ximage = XCreateImage(gfx->display, gfx->visual, 1, XYBitmap, 0, NULL, width, height, 8, 0); ximage->bitmap_bit_order = ximage->byte_order = LSBFirst; bytes_per_line = ximage->bytes_per_line; xdata = Gif_NewArray(uint8_t, bytes_per_line * height); ximage->data = (char *)xdata; transparent = gfi->transparent; /* The main loop */ for (j = 0; j < height; j++) { int imshift = 0; uint32_t impixel = 0; uint8_t *line = gfi->img[top + j] + left; uint8_t *writer = xdata + bytes_per_line * j; for (i = 0; i < width; i++) { if (line[i] == transparent) impixel |= 1 << imshift; if (++imshift >= BYTESIZE) { *writer++ = impixel; imshift = 0; impixel = 0; } } if (imshift) *writer++ = impixel; } /* Create the pixmap */ pixmap = XCreatePixmap(gfx->display, gfx->drawable, width ? width : 1, height ? height : 1, 1); if (!gfx->mask_gc) gfx->mask_gc = XCreateGC(gfx->display, pixmap, 0, 0); if (pixmap && gfx->mask_gc) XPutImage(gfx->display, pixmap, gfx->mask_gc, ximage, 0, 0, 0, 0, width, height); Gif_DeleteArray(xdata); ximage->data = 0; /* avoid freeing it again in XDestroyImage */ XDestroyImage(ximage); if (release_uncompressed) Gif_ReleaseUncompressedImage(gfi); return pixmap; } Pixmap Gif_XMask(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Image *gfi) { if (!gfi && gfs->nimages) gfi = gfs->images[0]; if (!gfi) return None; return Gif_XSubMask(gfx, gfs, gfi, 0, 0, gfi->width, gfi->height); } static Pixmap screen_pixmap(Gif_XContext *gfx, Gif_Stream *gfs) { return XCreatePixmap(gfx->display, gfx->drawable, gfs->screen_width, gfs->screen_height, gfx->depth); } static int apply_background(Gif_XContext *gfx, Gif_Stream *gfs, int i, Pixmap pixmap) { Gif_Image *gfi = gfs->images[i >= 0 ? i : 0]; Gif_Colormap *gfcm = (gfi->local ? gfi->local : gfs->global); unsigned long bg_pixel; /* find bg_pixel */ if (gfs->global && gfs->background < gfs->global->ncol && gfs->images[0]->transparent < 0) { Gif_XColormap *gfxc = find_x_colormap_extension(gfx, gfcm, 1); if (!gfxc) return -1; allocate_colors(gfxc); bg_pixel = gfxc->pixels[gfs->background]; } else bg_pixel = gfx->transparent_pixel; /* install it as the foreground color on gfx->image_gc */ if (!gfx->image_gc) gfx->image_gc = XCreateGC(gfx->display, pixmap, 0, 0); if (!gfx->image_gc) return -1; XSetForeground(gfx->display, gfx->image_gc, bg_pixel); gfx->transparent_pixel = bg_pixel; /* clear the image portion */ if (i < 0) XFillRectangle(gfx->display, pixmap, gfx->image_gc, 0, 0, gfs->screen_width, gfs->screen_height); else /*if (gfi->transparent < 0)*/ XFillRectangle(gfx->display, pixmap, gfx->image_gc, gfi->left, gfi->top, gfi->width, gfi->height); /*else { Pixmap mask = Gif_XMask(gfx, gfs, gfi); if (mask == None) return -1; XSetClipMask(gfx->display, gfx->image_gc, mask); XSetClipOrigin(gfx->display, gfx->image_gc, gfi->left, gfi->top); XFillRectangle(gfx->display, pixmap, gfx->image_gc, gfi->left, gfi->top, gfi->width, gfi->height); XSetClipMask(gfx->display, gfx->image_gc, None); XFreePixmap(gfx->display, mask); }*/ return 0; } static int apply_image(Gif_XContext *gfx, Gif_Stream *gfs, Gif_Image *gfi, Pixmap pixmap) { Pixmap image = Gif_XImage(gfx, gfs, gfi), mask; if (image == None) return -1; if (gfi->transparent >= 0) { mask = Gif_XMask(gfx, gfs, gfi); if (mask == None) { XFreePixmap(gfx->display, image); return -1; } XSetClipMask(gfx->display, gfx->image_gc, mask); XSetClipOrigin(gfx->display, gfx->image_gc, gfi->left, gfi->top); XCopyArea(gfx->display, image, pixmap, gfx->image_gc, 0, 0, gfi->width, gfi->height, gfi->left, gfi->top); XSetClipMask(gfx->display, gfx->image_gc, None); XFreePixmap(gfx->display, mask); } else { XCopyArea(gfx->display, image, pixmap, gfx->image_gc, 0, 0, gfi->width, gfi->height, gfi->left, gfi->top); } XFreePixmap(gfx->display, image); return 0; } static int fullscreen(Gif_Stream *gfs, Gif_Image *gfi, int require_opaque) { return (gfi->left == 0 && gfi->top == 0 && gfi->width == gfs->screen_width && gfi->height == gfs->screen_height && (!require_opaque || gfi->transparent < 0)); } Gif_XFrame * Gif_NewXFrames(Gif_Stream *gfs) { int i, last_postdisposal = -1; Gif_XFrame *fs = Gif_NewArray(Gif_XFrame, gfs->nimages); if (!fs) return 0; for (i = 0; i < gfs->nimages; ++i) { Gif_Image *gfi = gfs->images[i]; fs[i].pixmap = None; if (gfi->disposal == GIF_DISPOSAL_PREVIOUS) fs[i].postdisposal = last_postdisposal; else fs[i].postdisposal = i; last_postdisposal = fs[i].postdisposal; } return fs; } void Gif_DeleteXFrames(Gif_XContext *gfx, Gif_Stream *gfs, Gif_XFrame *fs) { int i; for (i = 0; i < gfs->nimages; ++i) if (fs[i].pixmap) XFreePixmap(gfx->display, fs[i].pixmap); Gif_DeleteArray(fs); } Pixmap Gif_XNextImage(Gif_XContext *gfx, Gif_Stream *gfs, int i, Gif_XFrame *frames) { Pixmap result = None; unsigned long old_transparent = gfx->transparent_pixel; Gif_Image *gfi; int previ, scani; /* return already rendered pixmap if any */ if (frames[i].pixmap != None) return frames[i].pixmap; /* render fullscreen image */ gfi = gfs->images[i]; if (fullscreen(gfs, gfi, 1)) { frames[i].pixmap = Gif_XImage(gfx, gfs, gfi); return frames[i].pixmap; } /* image is not full screen, need to find background */ previ = i - 1; if (previ >= 0) previ = frames[previ].postdisposal; /* scan backwards for a renderable image */ scani = previ; while (scani >= 0 && frames[scani].pixmap == None && !fullscreen(gfs, gfs->images[scani], 1)) --scani; /* create the pixmap */ result = screen_pixmap(gfx, gfs); if (result == None) return None; /* scan forward to produce background */ gfi = (scani >= 0 ? gfs->images[scani] : 0); if (gfi && (gfi->disposal != GIF_DISPOSAL_BACKGROUND || !fullscreen(gfs, gfi, 1))) { /* perhaps we need to create an image (if so, must be fullscreen) */ if (frames[scani].pixmap == None) { frames[scani].pixmap = Gif_XImage(gfx, gfs, gfi); if (frames[scani].pixmap == None) goto error_exit; } XCopyArea(gfx->display, frames[scani].pixmap, result, gfx->image_gc, 0, 0, gfs->screen_width, gfs->screen_height, 0, 0); } if (!gfi || gfi->disposal == GIF_DISPOSAL_BACKGROUND) { if (apply_background(gfx, gfs, scani, result) < 0) goto error_exit; } while (scani < previ) { ++scani; gfi = gfs->images[scani]; if (gfi->disposal == GIF_DISPOSAL_BACKGROUND) { if (apply_background(gfx, gfs, scani, result) < 0) goto error_exit; } else if (gfi->disposal != GIF_DISPOSAL_PREVIOUS) { if (apply_image(gfx, gfs, gfs->images[scani], result) < 0) goto error_exit; } } /* apply image */ if (gfs->screen_width != 0 && gfs->screen_height != 0) { if (apply_image(gfx, gfs, gfs->images[i], result) < 0) goto error_exit; } frames[i].pixmap = result; return frames[i].pixmap; error_exit: XFreePixmap(gfx->display, result); gfx->transparent_pixel = old_transparent; return None; } /** CREATING AND DESTROYING XCONTEXTS **/ static void delete_xcolormap(Gif_XColormap *gfxc) { Gif_XContext *gfx = gfxc->x_context; Gif_XColormap *prev = 0, *trav = gfx->xcolormap; while (trav != gfxc && trav) { prev = trav; trav = trav->next; } if (gfx->free_deleted_colormap_pixels) deallocate_colors(gfxc); if (prev) prev->next = gfxc->next; else gfx->xcolormap = gfxc->next; Gif_DeleteArray(gfxc->pixels); Gif_Delete(gfxc); } static void delete_colormap_hook(int dummy, void *colormap_x, void *callback_x) { Gif_Colormap *gfcm = (Gif_Colormap *)colormap_x; Gif_XContext *gfx = (Gif_XContext *)callback_x; Gif_XColormap *gfxc; (void) dummy; for (gfxc = gfx->xcolormap; gfxc; gfxc = gfxc->next) if (gfxc->colormap == gfcm) { delete_xcolormap(gfxc); return; } } Gif_XContext * Gif_NewXContextFromVisual(Display *display, int screen_number, Visual *visual, int depth, Colormap colormap) { Gif_XContext *gfx; gfx = Gif_New(Gif_XContext); gfx->display = display; gfx->screen_number = screen_number; gfx->drawable = RootWindow(display, screen_number); gfx->visual = visual; gfx->colormap = colormap; gfx->ncolormap = visual->map_entries; gfx->depth = depth; gfx->closest = 0; gfx->nclosest = 0; gfx->free_deleted_colormap_pixels = 0; gfx->xcolormap = 0; gfx->image_gc = None; gfx->mask_gc = None; gfx->transparent_pixel = 0UL; gfx->foreground_pixel = 1UL; gfx->refcount = 0; Gif_AddDeletionHook(GIF_T_COLORMAP, delete_colormap_hook, gfx); return gfx; } Gif_XContext * Gif_NewXContext(Display *display, Window window) { XWindowAttributes attr; XGetWindowAttributes(display, window, &attr); return Gif_NewXContextFromVisual(display, XScreenNumberOfScreen(attr.screen), attr.visual, attr.depth, attr.colormap); } void Gif_DeleteXContext(Gif_XContext *gfx) { if (!gfx) return; if (--gfx->refcount > 0) return; while (gfx->xcolormap) delete_xcolormap(gfx->xcolormap); if (gfx->image_gc) XFreeGC(gfx->display, gfx->image_gc); if (gfx->mask_gc) XFreeGC(gfx->display, gfx->mask_gc); Gif_DeleteArray(gfx->closest); Gif_RemoveDeletionHook(GIF_T_COLORMAP, delete_colormap_hook, gfx); Gif_Delete(gfx); } #ifdef __cplusplus } #endif