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