1/* 2 * tkUnixColor.c -- 3 * 4 * This file contains the platform specific color routines needed for X 5 * support. 6 * 7 * Copyright (c) 1996 by Sun Microsystems, Inc. 8 * 9 * See the file "license.terms" for information on usage and redistribution of 10 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 11 * 12 * RCS: @(#) $Id$ 13 */ 14 15#include "tkInt.h" 16#include "tkColor.h" 17 18/* 19 * If a colormap fills up, attempts to allocate new colors from that colormap 20 * will fail. When that happens, we'll just choose the closest color from 21 * those that are available in the colormap. One of the following structures 22 * will be created for each "stressed" colormap to keep track of the colors 23 * that are available in the colormap (otherwise we would have to re-query 24 * from the server on each allocation, which would be very slow). These 25 * entries are flushed after a few seconds, since other clients may release or 26 * reallocate colors over time. 27 */ 28 29struct TkStressedCmap { 30 Colormap colormap; /* X's token for the colormap. */ 31 int numColors; /* Number of entries currently active at 32 * *colorPtr. */ 33 XColor *colorPtr; /* Pointer to malloc'ed array of all colors 34 * that seem to be available in the colormap. 35 * Some may not actually be available, e.g. 36 * because they are read-write for another 37 * client; when we find this out, we remove 38 * them from the array. */ 39 struct TkStressedCmap *nextPtr; 40 /* Next in list of all stressed colormaps for 41 * the display. */ 42}; 43 44/* 45 * Forward declarations for functions defined in this file: 46 */ 47 48static void DeleteStressedCmap(Display *display, 49 Colormap colormap); 50static void FindClosestColor(Tk_Window tkwin, 51 XColor *desiredColorPtr, XColor *actualColorPtr); 52 53/* 54 *---------------------------------------------------------------------- 55 * 56 * TkpFreeColor -- 57 * 58 * Release the specified color back to the system. 59 * 60 * Results: 61 * None 62 * 63 * Side effects: 64 * Invalidates the colormap cache for the colormap associated with the 65 * given color. 66 * 67 *---------------------------------------------------------------------- 68 */ 69 70void 71TkpFreeColor( 72 TkColor *tkColPtr) /* Color to be released. Must have been 73 * allocated by TkpGetColor or 74 * TkpGetColorByValue. */ 75{ 76 Visual *visual; 77 Screen *screen = tkColPtr->screen; 78 79 /* 80 * Careful! Don't free black or white, since this will make some servers 81 * very unhappy. Also, there is a bug in some servers (such Sun's X11/NeWS 82 * server) where reference counting is performed incorrectly, so that if a 83 * color is allocated twice in different places and then freed twice, the 84 * second free generates an error (this bug existed as of 10/1/92). To get 85 * around this problem, ignore errors that occur during the free 86 * operation. 87 */ 88 89 visual = tkColPtr->visual; 90 if ((visual->class != StaticGray) && (visual->class != StaticColor) 91 && (tkColPtr->color.pixel != BlackPixelOfScreen(screen)) 92 && (tkColPtr->color.pixel != WhitePixelOfScreen(screen))) { 93 Tk_ErrorHandler handler; 94 95 handler = Tk_CreateErrorHandler(DisplayOfScreen(screen), 96 -1, -1, -1, NULL, NULL); 97 XFreeColors(DisplayOfScreen(screen), tkColPtr->colormap, 98 &tkColPtr->color.pixel, 1, 0L); 99 Tk_DeleteErrorHandler(handler); 100 } 101 DeleteStressedCmap(DisplayOfScreen(screen), tkColPtr->colormap); 102} 103 104/* 105 *---------------------------------------------------------------------- 106 * 107 * TkpGetColor -- 108 * 109 * Allocate a new TkColor for the color with the given name. 110 * 111 * Results: 112 * Returns a newly allocated TkColor, or NULL on failure. 113 * 114 * Side effects: 115 * May invalidate the colormap cache associated with tkwin upon 116 * allocating a new colormap entry. Allocates a new TkColor structure. 117 * 118 *---------------------------------------------------------------------- 119 */ 120 121TkColor * 122TkpGetColor( 123 Tk_Window tkwin, /* Window in which color will be used. */ 124 Tk_Uid name) /* Name of color to allocated (in form 125 * suitable for passing to XParseColor). */ 126{ 127 Display *display = Tk_Display(tkwin); 128 Colormap colormap = Tk_Colormap(tkwin); 129 XColor color; 130 TkColor *tkColPtr; 131 char buf[100]; 132 unsigned len = strlen(name); 133 134 /* 135 * Make sure that we never exceed a reasonable length of color name. A 136 * good maximum length is 99, arbitrary, but larger than any known color 137 * name. [Bug 2809525] 138 */ 139 140 if (len > 99) { 141 len = 99; 142 } 143 memcpy(buf, name, len); 144 buf[len] = '\0'; 145 146 /* 147 * Map from the name to a pixel value. Call XAllocNamedColor rather than 148 * XParseColor for non-# names: this saves a server round-trip for those 149 * names. 150 */ 151 152 if (*name != '#') { 153 XColor screen; 154 155 if (XAllocNamedColor(display, colormap, buf, &screen, &color) != 0) { 156 DeleteStressedCmap(display, colormap); 157 } else { 158 /* 159 * Couldn't allocate the color. Try translating the name to a 160 * color value, to see whether the problem is a bad color name or 161 * a full colormap. If the colormap is full, then pick an 162 * approximation to the desired color. 163 */ 164 165 if (XLookupColor(display, colormap, buf, &color, &screen) == 0) { 166 return NULL; 167 } 168 FindClosestColor(tkwin, &screen, &color); 169 } 170 } else { 171 if (XParseColor(display, colormap, buf, &color) == 0) { 172 return NULL; 173 } 174 if (XAllocColor(display, colormap, &color) != 0) { 175 DeleteStressedCmap(display, colormap); 176 } else { 177 FindClosestColor(tkwin, &color, &color); 178 } 179 } 180 181 tkColPtr = (TkColor *) ckalloc(sizeof(TkColor)); 182 tkColPtr->color = color; 183 184 return tkColPtr; 185} 186 187/* 188 *---------------------------------------------------------------------- 189 * 190 * TkpGetColorByValue -- 191 * 192 * Given a desired set of red-green-blue intensities for a color, locate 193 * a pixel value to use to draw that color in a given window. 194 * 195 * Results: 196 * The return value is a pointer to an TkColor structure that indicates 197 * the closest red, blue, and green intensities available to those 198 * specified in colorPtr, and also specifies a pixel value to use to draw 199 * in that color. 200 * 201 * Side effects: 202 * May invalidate the colormap cache for the specified window. Allocates 203 * a new TkColor structure. 204 * 205 *---------------------------------------------------------------------- 206 */ 207 208TkColor * 209TkpGetColorByValue( 210 Tk_Window tkwin, /* Window in which color will be used. */ 211 XColor *colorPtr) /* Red, green, and blue fields indicate 212 * desired color. */ 213{ 214 Display *display = Tk_Display(tkwin); 215 Colormap colormap = Tk_Colormap(tkwin); 216 TkColor *tkColPtr = (TkColor *) ckalloc(sizeof(TkColor)); 217 218 tkColPtr->color.red = colorPtr->red; 219 tkColPtr->color.green = colorPtr->green; 220 tkColPtr->color.blue = colorPtr->blue; 221 if (XAllocColor(display, colormap, &tkColPtr->color) != 0) { 222 DeleteStressedCmap(display, colormap); 223 } else { 224 FindClosestColor(tkwin, &tkColPtr->color, &tkColPtr->color); 225 } 226 227 return tkColPtr; 228} 229 230/* 231 *---------------------------------------------------------------------- 232 * 233 * FindClosestColor -- 234 * 235 * When Tk can't allocate a color because a colormap has filled up, this 236 * function is called to find and allocate the closest available color in 237 * the colormap. 238 * 239 * Results: 240 * There is no return value, but *actualColorPtr is filled in with 241 * information about the closest available color in tkwin's colormap. 242 * This color has been allocated via X, so it must be released by the 243 * caller when the caller is done with it. 244 * 245 * Side effects: 246 * A color is allocated. 247 * 248 *---------------------------------------------------------------------- 249 */ 250 251static void 252FindClosestColor( 253 Tk_Window tkwin, /* Window where color will be used. */ 254 XColor *desiredColorPtr, /* RGB values of color that was wanted (but 255 * unavailable). */ 256 XColor *actualColorPtr) /* Structure to fill in with RGB and pixel for 257 * closest available color. */ 258{ 259 TkStressedCmap *stressPtr; 260 double tmp, distance, closestDistance; 261 int i, closest, numFound; 262 XColor *colorPtr; 263 TkDisplay *dispPtr = ((TkWindow *) tkwin)->dispPtr; 264 Colormap colormap = Tk_Colormap(tkwin); 265 XVisualInfo template, *visInfoPtr; 266 267 /* 268 * Find the TkStressedCmap structure for this colormap, or create a new 269 * one if needed. 270 */ 271 272 for (stressPtr = dispPtr->stressPtr; ; stressPtr = stressPtr->nextPtr) { 273 if (stressPtr == NULL) { 274 stressPtr = (TkStressedCmap *) ckalloc(sizeof(TkStressedCmap)); 275 stressPtr->colormap = colormap; 276 template.visualid = XVisualIDFromVisual(Tk_Visual(tkwin)); 277 278 visInfoPtr = XGetVisualInfo(Tk_Display(tkwin), 279 VisualIDMask, &template, &numFound); 280 if (numFound < 1) { 281 Tcl_Panic("FindClosestColor couldn't lookup visual"); 282 } 283 284 stressPtr->numColors = visInfoPtr->colormap_size; 285 XFree((char *) visInfoPtr); 286 stressPtr->colorPtr = (XColor *) ckalloc((unsigned) 287 (stressPtr->numColors * sizeof(XColor))); 288 for (i = 0; i < stressPtr->numColors; i++) { 289 stressPtr->colorPtr[i].pixel = (unsigned long) i; 290 } 291 292 XQueryColors(dispPtr->display, colormap, stressPtr->colorPtr, 293 stressPtr->numColors); 294 295 stressPtr->nextPtr = dispPtr->stressPtr; 296 dispPtr->stressPtr = stressPtr; 297 break; 298 } 299 if (stressPtr->colormap == colormap) { 300 break; 301 } 302 } 303 304 /* 305 * Find the color that best approximates the desired one, then try to 306 * allocate that color. If that fails, it must mean that the color was 307 * read-write (so we can't use it, since it's owner might change it) or 308 * else it was already freed. Try again, over and over again, until 309 * something succeeds. 310 */ 311 312 while (1) { 313 if (stressPtr->numColors == 0) { 314 Tcl_Panic("FindClosestColor ran out of colors"); 315 } 316 closestDistance = 1e30; 317 closest = 0; 318 for (colorPtr = stressPtr->colorPtr, i = 0; i < stressPtr->numColors; 319 colorPtr++, i++) { 320 /* 321 * Use Euclidean distance in RGB space, weighted by Y (of YIQ) as 322 * the objective function; this accounts for differences in the 323 * color sensitivity of the eye. 324 */ 325 326 tmp = .30*(((int) desiredColorPtr->red) - (int) colorPtr->red); 327 distance = tmp*tmp; 328 tmp = .61*(((int) desiredColorPtr->green) - (int) colorPtr->green); 329 distance += tmp*tmp; 330 tmp = .11*(((int) desiredColorPtr->blue) - (int) colorPtr->blue); 331 distance += tmp*tmp; 332 if (distance < closestDistance) { 333 closest = i; 334 closestDistance = distance; 335 } 336 } 337 if (XAllocColor(dispPtr->display, colormap, 338 &stressPtr->colorPtr[closest]) != 0) { 339 *actualColorPtr = stressPtr->colorPtr[closest]; 340 return; 341 } 342 343 /* 344 * Couldn't allocate the color. Remove it from the table and go back 345 * to look for the next best color. 346 */ 347 348 stressPtr->colorPtr[closest] = 349 stressPtr->colorPtr[stressPtr->numColors-1]; 350 stressPtr->numColors -= 1; 351 } 352} 353 354/* 355 *---------------------------------------------------------------------- 356 * 357 * DeleteStressedCmap -- 358 * 359 * This function releases the information cached for "colormap" so that 360 * it will be refetched from the X server the next time it is needed. 361 * 362 * Results: 363 * None. 364 * 365 * Side effects: 366 * The TkStressedCmap structure for colormap is deleted; the colormap is 367 * no longer considered to be "stressed". 368 * 369 * Note: 370 * This function is invoked whenever a color in a colormap is freed, and 371 * whenever a color allocation in a colormap succeeds. This guarantees 372 * that TkStressedCmap structures are always deleted before the 373 * corresponding Colormap is freed. 374 * 375 *---------------------------------------------------------------------- 376 */ 377 378static void 379DeleteStressedCmap( 380 Display *display, /* Xlib's handle for the display containing 381 * the colormap. */ 382 Colormap colormap) /* Colormap to flush. */ 383{ 384 TkStressedCmap *prevPtr, *stressPtr; 385 TkDisplay *dispPtr = TkGetDisplay(display); 386 387 for (prevPtr = NULL, stressPtr = dispPtr->stressPtr; stressPtr != NULL; 388 prevPtr = stressPtr, stressPtr = stressPtr->nextPtr) { 389 if (stressPtr->colormap == colormap) { 390 if (prevPtr == NULL) { 391 dispPtr->stressPtr = stressPtr->nextPtr; 392 } else { 393 prevPtr->nextPtr = stressPtr->nextPtr; 394 } 395 ckfree((char *) stressPtr->colorPtr); 396 ckfree((char *) stressPtr); 397 return; 398 } 399 } 400} 401 402/* 403 *---------------------------------------------------------------------- 404 * 405 * TkpCmapStressed -- 406 * 407 * Check to see whether a given colormap is known to be out of entries. 408 * 409 * Results: 410 * 1 is returned if "colormap" is stressed (i.e. it has run out of 411 * entries recently), 0 otherwise. 412 * 413 * Side effects: 414 * None. 415 * 416 *---------------------------------------------------------------------- 417 */ 418 419int 420TkpCmapStressed( 421 Tk_Window tkwin, /* Window that identifies the display 422 * containing the colormap. */ 423 Colormap colormap) /* Colormap to check for stress. */ 424{ 425 TkStressedCmap *stressPtr; 426 427 for (stressPtr = ((TkWindow *) tkwin)->dispPtr->stressPtr; 428 stressPtr != NULL; stressPtr = stressPtr->nextPtr) { 429 if (stressPtr->colormap == colormap) { 430 return 1; 431 } 432 } 433 return 0; 434} 435 436/* 437 * Local Variables: 438 * mode: c 439 * c-basic-offset: 4 440 * fill-column: 78 441 * End: 442 */ 443