/* * tkUnixColor.c -- * * This file contains the platform specific color routines * needed for X support. * * Copyright (c) 1996 by Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * RCS: @(#) $Id: tkUnixColor.c,v 1.2 1998/09/14 18:23:55 stanton Exp $ */ #include /* * If a colormap fills up, attempts to allocate new colors from that * colormap will fail. When that happens, we'll just choose the * closest color from those that are available in the colormap. * One of the following structures will be created for each "stressed" * colormap to keep track of the colors that are available in the * colormap (otherwise we would have to re-query from the server on * each allocation, which would be very slow). These entries are * flushed after a few seconds, since other clients may release or * reallocate colors over time. */ struct TkStressedCmap { Colormap colormap; /* X's token for the colormap. */ int numColors; /* Number of entries currently active * at *colorPtr. */ XColor *colorPtr; /* Pointer to malloc'ed array of all * colors that seem to be available in * the colormap. Some may not actually * be available, e.g. because they are * read-write for another client; when * we find this out, we remove them * from the array. */ struct TkStressedCmap *nextPtr; /* Next in list of all stressed * colormaps for the display. */ }; /* * Forward declarations for procedures defined in this file: */ static void DeleteStressedCmap _ANSI_ARGS_((Display *display, Colormap colormap)); static void FindClosestColor _ANSI_ARGS_((Tk_Window tkwin, XColor *desiredColorPtr, XColor *actualColorPtr)); /* *---------------------------------------------------------------------- * * TkpFreeColor -- * * Release the specified color back to the system. * * Results: * None * * Side effects: * Invalidates the colormap cache for the colormap associated with * the given color. * *---------------------------------------------------------------------- */ void TkpFreeColor(tkColPtr) TkColor *tkColPtr; /* Color to be released. Must have been * allocated by TkpGetColor or * TkpGetColorByValue. */ { Visual *visual; Screen *screen = tkColPtr->screen; /* * Careful! Don't free black or white, since this will * make some servers very unhappy. Also, there is a bug in * some servers (such Sun's X11/NeWS server) where reference * counting is performed incorrectly, so that if a color is * allocated twice in different places and then freed twice, * the second free generates an error (this bug existed as of * 10/1/92). To get around this problem, ignore errors that * occur during the free operation. */ visual = tkColPtr->visual; if ((visual->class != StaticGray) && (visual->class != StaticColor) && (tkColPtr->color.pixel != BlackPixelOfScreen(screen)) && (tkColPtr->color.pixel != WhitePixelOfScreen(screen))) { Tk_ErrorHandler handler; handler = Tk_CreateErrorHandler(DisplayOfScreen(screen), -1, -1, -1, (Tk_ErrorProc *) NULL, (ClientData) NULL); XFreeColors(DisplayOfScreen(screen), tkColPtr->colormap, &tkColPtr->color.pixel, 1, 0L); Tk_DeleteErrorHandler(handler); } DeleteStressedCmap(DisplayOfScreen(screen), tkColPtr->colormap); } /* *---------------------------------------------------------------------- * * TkpGetColor -- * * Allocate a new TkColor for the color with the given name. * * Results: * Returns a newly allocated TkColor, or NULL on failure. * * Side effects: * May invalidate the colormap cache associated with tkwin upon * allocating a new colormap entry. Allocates a new TkColor * structure. * *---------------------------------------------------------------------- */ TkColor * TkpGetColor(tkwin, name) Tk_Window tkwin; /* Window in which color will be used. */ Tk_Uid name; /* Name of color to allocated (in form * suitable for passing to XParseColor). */ { Display *display = Tk_Display(tkwin); Colormap colormap = Tk_Colormap(tkwin); XColor color; TkColor *tkColPtr; /* * Map from the name to a pixel value. Call XAllocNamedColor rather than * XParseColor for non-# names: this saves a server round-trip for those * names. */ if (*name != '#') { XColor screen; if (XAllocNamedColor(display, colormap, name, &screen, &color) != 0) { DeleteStressedCmap(display, colormap); } else { /* * Couldn't allocate the color. Try translating the name to * a color value, to see whether the problem is a bad color * name or a full colormap. If the colormap is full, then * pick an approximation to the desired color. */ if (XLookupColor(display, colormap, name, &color, &screen) == 0) { return (TkColor *) NULL; } FindClosestColor(tkwin, &screen, &color); } } else { if (XParseColor(display, colormap, name, &color) == 0) { return (TkColor *) NULL; } if (XAllocColor(display, colormap, &color) != 0) { DeleteStressedCmap(display, colormap); } else { FindClosestColor(tkwin, &color, &color); } } tkColPtr = (TkColor *) ckalloc(sizeof(TkColor)); tkColPtr->color = color; return tkColPtr; } /* *---------------------------------------------------------------------- * * TkpGetColorByValue -- * * Given a desired set of red-green-blue intensities for a color, * locate a pixel value to use to draw that color in a given * window. * * Results: * The return value is a pointer to an TkColor structure that * indicates the closest red, blue, and green intensities available * to those specified in colorPtr, and also specifies a pixel * value to use to draw in that color. * * Side effects: * May invalidate the colormap cache for the specified window. * Allocates a new TkColor structure. * *---------------------------------------------------------------------- */ TkColor * TkpGetColorByValue(tkwin, colorPtr) Tk_Window tkwin; /* Window in which color will be used. */ XColor *colorPtr; /* Red, green, and blue fields indicate * desired color. */ { Display *display = Tk_Display(tkwin); Colormap colormap = Tk_Colormap(tkwin); TkColor *tkColPtr = (TkColor *) ckalloc(sizeof(TkColor)); tkColPtr->color.red = colorPtr->red; tkColPtr->color.green = colorPtr->green; tkColPtr->color.blue = colorPtr->blue; if (XAllocColor(display, colormap, &tkColPtr->color) != 0) { DeleteStressedCmap(display, colormap); } else { FindClosestColor(tkwin, &tkColPtr->color, &tkColPtr->color); } return tkColPtr; } /* *---------------------------------------------------------------------- * * FindClosestColor -- * * When Tk can't allocate a color because a colormap has filled * up, this procedure is called to find and allocate the closest * available color in the colormap. * * Results: * There is no return value, but *actualColorPtr is filled in * with information about the closest available color in tkwin's * colormap. This color has been allocated via X, so it must * be released by the caller when the caller is done with it. * * Side effects: * A color is allocated. * *---------------------------------------------------------------------- */ static void FindClosestColor(tkwin, desiredColorPtr, actualColorPtr) Tk_Window tkwin; /* Window where color will be used. */ XColor *desiredColorPtr; /* RGB values of color that was * wanted (but unavailable). */ XColor *actualColorPtr; /* Structure to fill in with RGB and * pixel for closest available * color. */ { TkStressedCmap *stressPtr; double tmp, distance, closestDistance; int i, closest, numFound; XColor *colorPtr; TkDisplay *dispPtr = ((TkWindow *) tkwin)->dispPtr; Colormap colormap = Tk_Colormap(tkwin); XVisualInfo template, *visInfoPtr; /* * Find the TkStressedCmap structure for this colormap, or create * a new one if needed. */ for (stressPtr = dispPtr->stressPtr; ; stressPtr = stressPtr->nextPtr) { if (stressPtr == NULL) { stressPtr = (TkStressedCmap *) ckalloc(sizeof(TkStressedCmap)); stressPtr->colormap = colormap; template.visualid = XVisualIDFromVisual(Tk_Visual(tkwin)); visInfoPtr = XGetVisualInfo(Tk_Display(tkwin), VisualIDMask, &template, &numFound); if (numFound < 1) { panic("FindClosestColor couldn't lookup visual"); } stressPtr->numColors = visInfoPtr->colormap_size; XFree((char *) visInfoPtr); stressPtr->colorPtr = (XColor *) ckalloc((unsigned) (stressPtr->numColors * sizeof(XColor))); for (i = 0; i < stressPtr->numColors; i++) { stressPtr->colorPtr[i].pixel = (unsigned long) i; } XQueryColors(dispPtr->display, colormap, stressPtr->colorPtr, stressPtr->numColors); stressPtr->nextPtr = dispPtr->stressPtr; dispPtr->stressPtr = stressPtr; break; } if (stressPtr->colormap == colormap) { break; } } /* * Find the color that best approximates the desired one, then * try to allocate that color. If that fails, it must mean that * the color was read-write (so we can't use it, since it's owner * might change it) or else it was already freed. Try again, * over and over again, until something succeeds. */ while (1) { if (stressPtr->numColors == 0) { panic("FindClosestColor ran out of colors"); } closestDistance = 1e30; closest = 0; for (colorPtr = stressPtr->colorPtr, i = 0; i < stressPtr->numColors; colorPtr++, i++) { /* * Use Euclidean distance in RGB space, weighted by Y (of YIQ) * as the objective function; this accounts for differences * in the color sensitivity of the eye. */ tmp = .30*(((int) desiredColorPtr->red) - (int) colorPtr->red); distance = tmp*tmp; tmp = .61*(((int) desiredColorPtr->green) - (int) colorPtr->green); distance += tmp*tmp; tmp = .11*(((int) desiredColorPtr->blue) - (int) colorPtr->blue); distance += tmp*tmp; if (distance < closestDistance) { closest = i; closestDistance = distance; } } if (XAllocColor(dispPtr->display, colormap, &stressPtr->colorPtr[closest]) != 0) { *actualColorPtr = stressPtr->colorPtr[closest]; return; } /* * Couldn't allocate the color. Remove it from the table and * go back to look for the next best color. */ stressPtr->colorPtr[closest] = stressPtr->colorPtr[stressPtr->numColors-1]; stressPtr->numColors -= 1; } } /* *---------------------------------------------------------------------- * * DeleteStressedCmap -- * * This procedure releases the information cached for "colormap" * so that it will be refetched from the X server the next time * it is needed. * * Results: * None. * * Side effects: * The TkStressedCmap structure for colormap is deleted; the * colormap is no longer considered to be "stressed". * * Note: * This procedure is invoked whenever a color in a colormap is * freed, and whenever a color allocation in a colormap succeeds. * This guarantees that TkStressedCmap structures are always * deleted before the corresponding Colormap is freed. * *---------------------------------------------------------------------- */ static void DeleteStressedCmap(display, colormap) Display *display; /* Xlib's handle for the display * containing the colormap. */ Colormap colormap; /* Colormap to flush. */ { TkStressedCmap *prevPtr, *stressPtr; TkDisplay *dispPtr = TkGetDisplay(display); for (prevPtr = NULL, stressPtr = dispPtr->stressPtr; stressPtr != NULL; prevPtr = stressPtr, stressPtr = stressPtr->nextPtr) { if (stressPtr->colormap == colormap) { if (prevPtr == NULL) { dispPtr->stressPtr = stressPtr->nextPtr; } else { prevPtr->nextPtr = stressPtr->nextPtr; } ckfree((char *) stressPtr->colorPtr); ckfree((char *) stressPtr); return; } } } /* *---------------------------------------------------------------------- * * TkpCmapStressed -- * * Check to see whether a given colormap is known to be out * of entries. * * Results: * 1 is returned if "colormap" is stressed (i.e. it has run out * of entries recently), 0 otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TkpCmapStressed(tkwin, colormap) Tk_Window tkwin; /* Window that identifies the display * containing the colormap. */ Colormap colormap; /* Colormap to check for stress. */ { TkStressedCmap *stressPtr; for (stressPtr = ((TkWindow *) tkwin)->dispPtr->stressPtr; stressPtr != NULL; stressPtr = stressPtr->nextPtr) { if (stressPtr->colormap == colormap) { return 1; } } return 0; }