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