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