xref: /openbsd-src/lib/libcurses/base/lib_color.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: lib_color.c,v 1.10 2001/01/22 18:01:38 millert Exp $	*/
2 
3 /****************************************************************************
4  * Copyright (c) 1998,1999,2000 Free Software Foundation, Inc.              *
5  *                                                                          *
6  * Permission is hereby granted, free of charge, to any person obtaining a  *
7  * copy of this software and associated documentation files (the            *
8  * "Software"), to deal in the Software without restriction, including      *
9  * without limitation the rights to use, copy, modify, merge, publish,      *
10  * distribute, distribute with modifications, sublicense, and/or sell       *
11  * copies of the Software, and to permit persons to whom the Software is    *
12  * furnished to do so, subject to the following conditions:                 *
13  *                                                                          *
14  * The above copyright notice and this permission notice shall be included  *
15  * in all copies or substantial portions of the Software.                   *
16  *                                                                          *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
20  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
21  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
22  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
23  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
24  *                                                                          *
25  * Except as contained in this notice, the name(s) of the above copyright   *
26  * holders shall not be used in advertising or otherwise to promote the     *
27  * sale, use or other dealings in this Software without prior written       *
28  * authorization.                                                           *
29  ****************************************************************************/
30 
31 /****************************************************************************
32  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
33  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
34  ****************************************************************************/
35 
36 /* lib_color.c
37  *
38  * Handles color emulation of SYS V curses
39  */
40 
41 #include <curses.priv.h>
42 
43 #include <term.h>
44 #include <tic.h>
45 
46 MODULE_ID("$From: lib_color.c,v 1.55 2000/12/10 02:43:27 tom Exp $")
47 
48 /*
49  * These should be screen structure members.  They need to be globals for
50  * historical reasons.  So we assign them in start_color() and also in
51  * set_term()'s screen-switching logic.
52  */
53 NCURSES_EXPORT_VAR(int)
54 COLOR_PAIRS = 0;
55 NCURSES_EXPORT_VAR(int)
56 COLORS = 0;
57 
58 /*
59  * Given a RGB range of 0..1000, we'll normally set the individual values
60  * to about 2/3 of the maximum, leaving full-range for bold/bright colors.
61  */
62 #define RGB_ON  680
63 #define RGB_OFF 0
64 /* *INDENT-OFF* */
65 static const color_t cga_palette[] =
66 {
67     /*  R               G               B */
68     {RGB_OFF,		RGB_OFF,	RGB_OFF},	/* COLOR_BLACK */
69     {RGB_ON,		RGB_OFF,	RGB_OFF},	/* COLOR_RED */
70     {RGB_OFF,		RGB_ON,		RGB_OFF},	/* COLOR_GREEN */
71     {RGB_ON,		RGB_ON,		RGB_OFF},	/* COLOR_YELLOW */
72     {RGB_OFF,		RGB_OFF,	RGB_ON},	/* COLOR_BLUE */
73     {RGB_ON,		RGB_OFF,	RGB_ON},	/* COLOR_MAGENTA */
74     {RGB_OFF,		RGB_ON,		RGB_ON},	/* COLOR_CYAN */
75     {RGB_ON,		RGB_ON,		RGB_ON},	/* COLOR_WHITE */
76 };
77 
78 static const color_t hls_palette[] =
79 {
80     /*  H       L       S */
81     {	0,	0,	0},		/* COLOR_BLACK */
82     {	120,	50,	100},		/* COLOR_RED */
83     {	240,	50,	100},		/* COLOR_GREEN */
84     {	180,	50,	100},		/* COLOR_YELLOW */
85     {	330,	50,	100},		/* COLOR_BLUE */
86     {	60,	50,	100},		/* COLOR_MAGENTA */
87     {	300,	50,	100},		/* COLOR_CYAN */
88     {	0,	50,	100},		/* COLOR_WHITE */
89 };
90 /* *INDENT-ON* */
91 
92 #if NCURSES_EXT_FUNCS
93 /*
94  * These are called from _nc_do_color(), which in turn is called from
95  * vidattr - so we have to assume that SP may be null.
96  */
97      static int
98        default_fg(void)
99 {
100     return (SP != 0) ? SP->_default_fg : COLOR_WHITE;
101 }
102 
103 static int
104 default_bg(void)
105 {
106     return SP != 0 ? SP->_default_bg : COLOR_BLACK;
107 }
108 #else
109 #define default_fg() COLOR_WHITE
110 #define default_bg() COLOR_BLACK
111 #endif
112 
113 /*
114  * SVr4 curses is known to interchange color codes (1,4) and (3,6), possibly
115  * to maintain compatibility with a pre-ANSI scheme.  The same scheme is
116  * also used in the FreeBSD syscons.
117  */
118      static int
119        toggled_colors(int c)
120 {
121     if (c < 16) {
122 	static const int table[] =
123 	{0, 4, 2, 6, 1, 5, 3, 7,
124 	 8, 12, 10, 14, 9, 13, 11, 15};
125 	c = table[c];
126     }
127     return c;
128 }
129 
130 static void
131 set_background_color(int bg, int (*outc) (int))
132 {
133     if (set_a_background) {
134 	TPUTS_TRACE("set_a_background");
135 	tputs(tparm(set_a_background, bg), 1, outc);
136     } else {
137 	TPUTS_TRACE("set_background");
138 	tputs(tparm(set_background, toggled_colors(bg)), 1, outc);
139     }
140 }
141 
142 static void
143 set_foreground_color(int fg, int (*outc) (int))
144 {
145     if (set_a_foreground) {
146 	TPUTS_TRACE("set_a_foreground");
147 	tputs(tparm(set_a_foreground, fg), 1, outc);
148     } else {
149 	TPUTS_TRACE("set_foreground");
150 	tputs(tparm(set_foreground, toggled_colors(fg)), 1, outc);
151     }
152 }
153 
154 static bool
155 set_original_colors(void)
156 {
157     if (orig_pair != 0) {
158 	TPUTS_TRACE("orig_pair");
159 	putp(orig_pair);
160 	return TRUE;
161     } else if (orig_colors != NULL) {
162 	TPUTS_TRACE("orig_colors");
163 	putp(orig_colors);
164 	return TRUE;
165     }
166     return FALSE;
167 }
168 
169 NCURSES_EXPORT(int)
170 start_color(void)
171 {
172     int n;
173     const color_t *tp;
174 
175     T((T_CALLED("start_color()")));
176 
177     if (set_original_colors() != TRUE) {
178 	set_foreground_color(default_fg(), _nc_outch);
179 	set_background_color(default_bg(), _nc_outch);
180     }
181 
182     if (VALID_NUMERIC(max_pairs))
183 	COLOR_PAIRS = SP->_pair_count = max_pairs;
184     else
185 	returnCode(ERR);
186     if ((SP->_color_pairs = typeCalloc(unsigned short, max_pairs)) == 0)
187 	  returnCode(ERR);
188     SP->_color_pairs[0] = PAIR_OF(default_fg(), default_bg());
189     if (VALID_NUMERIC(max_colors))
190 	COLORS = SP->_color_count = max_colors;
191     else
192 	returnCode(ERR);
193     SP->_coloron = 1;
194 
195     if ((SP->_color_table = typeMalloc(color_t, COLORS)) == 0)
196 	returnCode(ERR);
197     tp = (hue_lightness_saturation) ? hls_palette : cga_palette;
198     for (n = 0; n < COLORS; n++) {
199 	if (n < 8) {
200 	    SP->_color_table[n] = tp[n];
201 	} else {
202 	    SP->_color_table[n] = tp[n % 8];
203 	    if (hue_lightness_saturation) {
204 		SP->_color_table[n].green = 100;
205 	    } else {
206 		if (SP->_color_table[n].red)
207 		    SP->_color_table[n].red = 1000;
208 		if (SP->_color_table[n].green)
209 		    SP->_color_table[n].green = 1000;
210 		if (SP->_color_table[n].blue)
211 		    SP->_color_table[n].blue = 1000;
212 	    }
213 	}
214     }
215 
216     T(("started color: COLORS = %d, COLOR_PAIRS = %d", COLORS, COLOR_PAIRS));
217 
218     returnCode(OK);
219 }
220 
221 /* This function was originally written by Daniel Weaver <danw@znyx.com> */
222 static void
223 rgb2hls(short r, short g, short b, short *h, short *l, short *s)
224 /* convert RGB to HLS system */
225 {
226     short min, max, t;
227 
228     if ((min = g < r ? g : r) > b)
229 	min = b;
230     if ((max = g > r ? g : r) < b)
231 	max = b;
232 
233     /* calculate lightness */
234     *l = (min + max) / 20;
235 
236     if (min == max) {		/* black, white and all shades of gray */
237 	*h = 0;
238 	*s = 0;
239 	return;
240     }
241 
242     /* calculate saturation */
243     if (*l < 50)
244 	*s = ((max - min) * 100) / (max + min);
245     else
246 	*s = ((max - min) * 100) / (2000 - max - min);
247 
248     /* calculate hue */
249     if (r == max)
250 	t = 120 + ((g - b) * 60) / (max - min);
251     else if (g == max)
252 	t = 240 + ((b - r) * 60) / (max - min);
253     else
254 	t = 360 + ((r - g) * 60) / (max - min);
255 
256     *h = t % 360;
257 }
258 
259 /*
260  * Extension (1997/1/18) - Allow negative f/b values to set default color
261  * values.
262  */
263 NCURSES_EXPORT(int)
264 init_pair
265 (short pair, short f, short b)
266 {
267     unsigned result;
268 
269     T((T_CALLED("init_pair(%d,%d,%d)"), pair, f, b));
270 
271     if ((pair < 0) || (pair >= COLOR_PAIRS))
272 	returnCode(ERR);
273 #if NCURSES_EXT_FUNCS
274     if (SP->_default_color) {
275 	if (f < 0)
276 	    f = C_MASK;
277 	if (b < 0)
278 	    b = C_MASK;
279 	if (f >= COLORS && f != C_MASK)
280 	    returnCode(ERR);
281 	if (b >= COLORS && b != C_MASK)
282 	    returnCode(ERR);
283     } else
284 #endif
285     {
286 	if ((f < 0) || (f >= COLORS)
287 	    || (b < 0) || (b >= COLORS)
288 	    || (pair < 1))
289 	    returnCode(ERR);
290     }
291 
292     /*
293      * When a pair's content is changed, replace its colors (if pair was
294      * initialized before a screen update is performed replacing original
295      * pair colors with the new ones).
296      */
297     result = PAIR_OF(f, b);
298     if (SP->_color_pairs[pair] != 0
299 	&& SP->_color_pairs[pair] != result) {
300 	int y, x;
301 	attr_t z = COLOR_PAIR(pair);
302 
303 	for (y = 0; y <= curscr->_maxy; y++) {
304 	    struct ldat *ptr = &(curscr->_line[y]);
305 	    bool changed = FALSE;
306 	    for (x = 0; x <= curscr->_maxx; x++) {
307 		if ((ptr->text[x] & A_COLOR) == z) {
308 		    /* Set the old cell to zero to ensure it will be
309 		       updated on the next doupdate() */
310 		    ptr->text[x] = 0;
311 		    CHANGED_CELL(ptr, x);
312 		    changed = TRUE;
313 		}
314 	    }
315 	    if (changed)
316 		_nc_make_oldhash(y);
317 	}
318     }
319     SP->_color_pairs[pair] = result;
320     if ((int) (SP->_current_attr & A_COLOR) == COLOR_PAIR(pair))
321 	SP->_current_attr |= A_COLOR;	/* force attribute update */
322 
323     if (initialize_pair) {
324 	const color_t *tp = hue_lightness_saturation ? hls_palette : cga_palette;
325 
326 	T(("initializing pair: pair = %d, fg=(%d,%d,%d), bg=(%d,%d,%d)",
327 	   pair,
328 	   tp[f].red, tp[f].green, tp[f].blue,
329 	   tp[b].red, tp[b].green, tp[b].blue));
330 
331 	if (initialize_pair) {
332 	    TPUTS_TRACE("initialize_pair");
333 	    putp(tparm(initialize_pair,
334 		       pair,
335 		       tp[f].red, tp[f].green, tp[f].blue,
336 		       tp[b].red, tp[b].green, tp[b].blue));
337 	}
338     }
339 
340     returnCode(OK);
341 }
342 
343 NCURSES_EXPORT(int)
344 init_color
345 (short color, short r, short g, short b)
346 {
347     T((T_CALLED("init_color(%d,%d,%d,%d)"), color, r, g, b));
348 
349     if (initialize_color == NULL)
350 	returnCode(ERR);
351 
352     if (color < 0 || color >= COLORS)
353 	returnCode(ERR);
354     if (r < 0 || r > 1000 || g < 0 || g > 1000 || b < 0 || b > 1000)
355 	returnCode(ERR);
356 
357     if (hue_lightness_saturation)
358 	rgb2hls(r, g, b,
359 		&SP->_color_table[color].red,
360 		&SP->_color_table[color].green,
361 		&SP->_color_table[color].blue);
362     else {
363 	SP->_color_table[color].red = r;
364 	SP->_color_table[color].green = g;
365 	SP->_color_table[color].blue = b;
366     }
367 
368     if (initialize_color) {
369 	TPUTS_TRACE("initialize_color");
370 	putp(tparm(initialize_color, color, r, g, b));
371     }
372     returnCode(OK);
373 }
374 
375 NCURSES_EXPORT(bool)
376 can_change_color(void)
377 {
378     T((T_CALLED("can_change_color()")));
379     returnCode((can_change != 0) ? TRUE : FALSE);
380 }
381 
382 NCURSES_EXPORT(bool)
383 has_colors(void)
384 {
385     T((T_CALLED("has_colors()")));
386     returnCode((VALID_NUMERIC(max_colors) && VALID_NUMERIC(max_pairs)
387 		&& (((set_foreground != NULL)
388 		     && (set_background != NULL))
389 		    || ((set_a_foreground != NULL)
390 			&& (set_a_background != NULL))
391 		    || set_color_pair)) ? TRUE : FALSE);
392 }
393 
394 NCURSES_EXPORT(int)
395 color_content
396 (short color, short *r, short *g, short *b)
397 {
398     T((T_CALLED("color_content(%d,%p,%p,%p)"), color, r, g, b));
399     if (color < 0 || color >= COLORS)
400 	returnCode(ERR);
401 
402     if (r)
403 	*r = SP->_color_table[color].red;
404     if (g)
405 	*g = SP->_color_table[color].green;
406     if (b)
407 	*b = SP->_color_table[color].blue;
408     returnCode(OK);
409 }
410 
411 NCURSES_EXPORT(int)
412 pair_content
413 (short pair, short *f, short *b)
414 {
415     T((T_CALLED("pair_content(%d,%p,%p)"), pair, f, b));
416 
417     if ((pair < 0) || (pair >= COLOR_PAIRS))
418 	returnCode(ERR);
419     if (f)
420 	*f = ((SP->_color_pairs[pair] >> C_SHIFT) & C_MASK);
421     if (b)
422 	*b = (SP->_color_pairs[pair] & C_MASK);
423 
424     returnCode(OK);
425 }
426 
427 NCURSES_EXPORT(void)
428 _nc_do_color
429 (int old_pair, int pair, bool reverse, int (*outc) (int))
430 {
431     NCURSES_COLOR_T fg = C_MASK, bg = C_MASK;
432     NCURSES_COLOR_T old_fg, old_bg;
433 
434     if (pair < 0 || pair >= COLOR_PAIRS) {
435 	return;
436     } else if (pair != 0) {
437 	if (set_color_pair) {
438 	    TPUTS_TRACE("set_color_pair");
439 	    tputs(tparm(set_color_pair, pair), 1, outc);
440 	    return;
441 	} else if (SP != 0) {
442 	    pair_content(pair, &fg, &bg);
443 	}
444     }
445 
446     if (old_pair >= 0 && SP != 0) {
447 	pair_content(old_pair, &old_fg, &old_bg);
448 	if ((fg == C_MASK && old_fg != C_MASK)
449 	    || (bg == C_MASK && old_bg != C_MASK)) {
450 #if NCURSES_EXT_FUNCS
451 	    /*
452 	     * A minor optimization - but extension.  If "AX" is specified in
453 	     * the terminal description, treat it as screen's indicator of ECMA
454 	     * SGR 39 and SGR 49, and assume the two sequences are independent.
455 	     */
456 	    if (SP->_has_sgr_39_49 && old_bg == C_MASK && old_fg != C_MASK) {
457 		tputs("\033[39m", 1, outc);
458 	    } else if (SP->_has_sgr_39_49 && old_fg == C_MASK && old_bg != C_MASK) {
459 		tputs("\033[49m", 1, outc);
460 	    } else
461 #endif
462 		set_original_colors();
463 	}
464     } else {
465 	set_original_colors();
466 	if (old_pair < 0)
467 	    return;
468     }
469 
470 #if NCURSES_EXT_FUNCS
471     if (fg == C_MASK)
472 	fg = default_fg();
473     if (bg == C_MASK)
474 	bg = default_bg();
475 #endif
476 
477     if (reverse) {
478 	NCURSES_COLOR_T xx = fg;
479 	fg = bg;
480 	bg = xx;
481     }
482 
483     TR(TRACE_ATTRS, ("setting colors: pair = %d, fg = %d, bg = %d", pair,
484 		     fg, bg));
485 
486     if (fg != C_MASK) {
487 	set_foreground_color(fg, outc);
488     }
489     if (bg != C_MASK) {
490 	set_background_color(bg, outc);
491     }
492 }
493