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