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