/* * 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$ */ #include "tkInt.h" #include "tkColor.h" /* * 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 functions defined in this file: */ static void DeleteStressedCmap(Display *display, Colormap colormap); static void FindClosestColor(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( 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, NULL, 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( 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; char buf[100]; unsigned len = strlen(name); /* * Make sure that we never exceed a reasonable length of color name. A * good maximum length is 99, arbitrary, but larger than any known color * name. [Bug 2809525] */ if (len > 99) { len = 99; } memcpy(buf, name, len); buf[len] = '\0'; /* * 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, buf, &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, buf, &color, &screen) == 0) { return NULL; } FindClosestColor(tkwin, &screen, &color); } } else { if (XParseColor(display, colormap, buf, &color) == 0) { return 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( 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 * function 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( 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) { Tcl_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) { Tcl_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 function 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 function 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 *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( 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; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */