1 /* This file is part of the GNU plotutils package.  Copyright (C) 1995,
2    1996, 1997, 1998, 1999, 2000, 2005, 2008, Free Software Foundation, Inc.
3 
4    The GNU plotutils package is free software.  You may redistribute it
5    and/or modify it under the terms of the GNU General Public License as
6    published by the Free Software foundation; either version 2, or (at your
7    option) any later version.
8 
9    The GNU plotutils package is distributed in the hope that it will be
10    useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License along
15    with the GNU plotutils package; see the file COPYING.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin St., Fifth Floor,
17    Boston, MA 02110-1301, USA. */
18 
19 /* This file contains color methods that are GNU extensions to libplot:
20    pencolor, fillcolor, bgcolor, and the convenience function, color.
21 
22    It also contains _grayscale_approx(), which computes grayscale
23    approximations to RGB colors. */
24 
25 /* It also includes the pencolorname, fillcolorname, and bgcolorname
26    methods, which are GNU extensions to libplot; also the convenience
27    function colorname.  They search a database of known names (stored in
28    g_colorname.h) for a specified color name.  If the name is found, its
29    interpretation as a 48-bit RGB color is determined, and pencolor,
30    fillcolor, or bgcolor is called to set the color.  If the name is not
31    found, a default color (black for pen and fill, white for bg) is
32    substituted. */
33 
34 #include "sys-defines.h"
35 #include "extern.h"
36 #include "g_colorname.h"
37 
38 /* forward references */
39 static bool string_to_precise_color (const char *name, plColor *color_p);
40 
41 /* The pencolor method, which is a GNU extension to libplot.  It sets a
42    drawing attribute: the pen color (``foreground color'') of objects
43    created in the drawing operations that follow.  The fill color may be
44    set separately, by invoking fillcolor() and filltype().
45 
46    We use the RGB color model.  In principle we support 48-bit color (16
47    bits, i.e. 0x0000 through 0xffff, for each of red, green, and blue). */
48 
49 int
_API_pencolor(R___ (Plotter * _plotter)int red,int green,int blue)50 _API_pencolor (R___(Plotter *_plotter) int red, int green, int blue)
51 {
52   if (!_plotter->data->open)
53     {
54       _plotter->error (R___(_plotter)
55 		       "pencolor: invalid operation");
56       return -1;
57     }
58 
59   _API_endpath (S___(_plotter)); /* flush path if any */
60 
61   if ((red > 0xffff) || (green > 0xffff) || (blue > 0xffff))
62     /* OOB switches to default */
63     {
64       red = _default_drawstate.fgcolor.red;
65       green = _default_drawstate.fgcolor.green;
66       blue = _default_drawstate.fgcolor.blue;
67     }
68 
69   if (_plotter->data->emulate_color)
70     /* replace by grayscale approximation */
71     red = green = blue = _grayscale_approx (red, green, blue);
72 
73   /* save our notion of foreground color */
74   _plotter->drawstate->fgcolor.red = red;
75   _plotter->drawstate->fgcolor.green = green;
76   _plotter->drawstate->fgcolor.blue = blue;
77 
78   return 0;
79 }
80 
81 /* The fillcolor method, which is a GNU extension to libplot.  It sets a
82    drawing attribute: the fill color of objects created in the following
83    drawing operations.  Actually the true fill color (if filling is not
84    disabled) will be a desaturated version of the user-specified fill
85    color.  The desaturation level is set by invoking filltype().
86 
87    In principle we support 48-bit color (16 bits, i.e. 0x0000 through
88    0xffff, for each of red, green, and blue). */
89 
90 int
_API_fillcolor(R___ (Plotter * _plotter)int red,int green,int blue)91 _API_fillcolor (R___(Plotter *_plotter) int red, int green, int blue)
92 {
93   double red_d, green_d, blue_d;
94   double desaturate;
95   plColor new_rgb;
96 
97   if (!_plotter->data->open)
98     {
99       _plotter->error (R___(_plotter)
100 		       "fillcolor: invalid operation");
101       return -1;
102     }
103 
104   _API_endpath (S___(_plotter)); /* flush path if any */
105 
106   if ((red > 0xffff) || (green > 0xffff) || (blue > 0xffff))
107     /* OOB switches to default */
108     {
109       red = _default_drawstate.fillcolor.red;
110       green = _default_drawstate.fillcolor.green;
111       blue = _default_drawstate.fillcolor.blue;
112     }
113 
114   if (_plotter->data->emulate_color)
115     /* replace by grayscale approximation */
116     red = green = blue = _grayscale_approx (red, green, blue);
117 
118   /* save the basic fillcolor (i.e. the base fillcolor, unaffected by the
119      fill type) */
120   _plotter->drawstate->fillcolor_base.red = red;
121   _plotter->drawstate->fillcolor_base.green = green;
122   _plotter->drawstate->fillcolor_base.blue = blue;
123 
124   if (_plotter->drawstate->fill_type == 0)
125     /* won't be doing filling, so stop right here */
126     return 0;
127 
128   /* update fillcolor, taking fill type into account */
129 
130   /* scale each RGB from a 16-bit quantity to range [0.0,1.0] */
131   red_d = ((double)red)/0xFFFF;
132   green_d = ((double)green)/0xFFFF;
133   blue_d = ((double)blue)/0xFFFF;
134 
135   /* fill_type, if nonzero, specifies the extent to which the nominal fill
136      color should be desaturated.  1 means no desaturation, 0xffff means
137      complete desaturation (white). */
138   desaturate = ((double)_plotter->drawstate->fill_type - 1.)/0xFFFE;
139   red_d = red_d + desaturate * (1.0 - red_d);
140   green_d = green_d + desaturate * (1.0 - green_d);
141   blue_d = blue_d + desaturate * (1.0 - blue_d);
142 
143   /* restore each RGB to a 16-bit quantity (48 bits in all) */
144   new_rgb.red = IROUND(0xFFFF * red_d);
145   new_rgb.green = IROUND(0xFFFF * green_d);
146   new_rgb.blue = IROUND(0xFFFF * blue_d);
147 
148   /* store actual fill color in drawing state */
149   _plotter->drawstate->fillcolor = new_rgb;
150 
151   return 0;
152 }
153 
154 /* convenience function */
155 
156 int
_API_color(R___ (Plotter * _plotter)int red,int green,int blue)157 _API_color (R___(Plotter *_plotter) int red, int green, int blue)
158 {
159   if (!_plotter->data->open)
160     {
161       _plotter->error (R___(_plotter)
162 		       "color: invalid operation");
163       return -1;
164     }
165 
166   _API_pencolor (R___(_plotter) red, green, blue);
167   _API_fillcolor (R___(_plotter) red, green, blue);
168 
169   return 0;
170 }
171 
172 /* The bgcolor method, which is a GNU extension to libplot.  It sets a
173    drawing attribute: the background color.
174 
175    We use the RGB color model.  In principle we support 48-bit color (16
176    bits, i.e. 0x0000 through 0xffff, for each of red, green, and blue). */
177 
178 int
_API_bgcolor(R___ (Plotter * _plotter)int red,int green,int blue)179 _API_bgcolor (R___(Plotter *_plotter) int red, int green, int blue)
180 {
181   if (!_plotter->data->open)
182     {
183       _plotter->error (R___(_plotter)
184 		       "bgcolor: invalid operation");
185       return -1;
186     }
187 
188   if ((red > 0xffff) || (green > 0xffff) || (blue > 0xffff))
189     /* OOB switches to default */
190     {
191       red = _default_drawstate.bgcolor.red;
192       green = _default_drawstate.bgcolor.green;
193       blue = _default_drawstate.bgcolor.blue;
194     }
195 
196   if (_plotter->data->emulate_color)
197     /* replace by grayscale approximation */
198     red = green = blue = _grayscale_approx (red, green, blue);
199 
200   /* save our notion of background color */
201   _plotter->drawstate->bgcolor.red = red;
202   _plotter->drawstate->bgcolor.green = green;
203   _plotter->drawstate->bgcolor.blue = blue;
204 
205   return 0;
206 }
207 
208 /* compute a 16-bit grayscale approximation to a 48-bit RGB; optionally
209    used by pencolorname, fillcolorname, bgcolorname methods */
210 int
_grayscale_approx(int red,int green,int blue)211 _grayscale_approx (int red, int green, int blue)
212 {
213   double gray;
214 
215   /* compute CIE luminance according to Rec. 709 */
216   gray = 0.212671 * red + 0.715160 * green + 0.072169 * blue;
217   return IROUND(gray);
218 }
219 
220 /* Below are the pencolorname, fillcolorname, and bgcolorname methods,
221    which are GNU extensions to libplot.  They search a database of known
222    names (stored in g_colorname.h) for a specified color name.  If the name
223    is found, its interpretation as a 48-bit RGB color is determined, and
224    pencolor, fillcolor, or bgcolor is called to set the color.  If the name
225    is not found, a default color (black for pen and fill, white for bg) is
226    substituted.
227 
228    The lowest-level routine is _string_to_color(). */
229 
230 int
_API_pencolorname(R___ (Plotter * _plotter)const char * name)231 _API_pencolorname (R___(Plotter *_plotter) const char *name)
232 {
233   plColor color;
234   int intred, intgreen, intblue;
235 
236   if (!_plotter->data->open)
237     {
238       _plotter->error (R___(_plotter)
239 		       "pencolorname: invalid operation");
240       return -1;
241     }
242 
243   /* null pointer ignored */
244   if (!name)
245     return 0;
246 
247   /* RGB values for default pen color */
248   intred = _default_drawstate.fgcolor.red;
249   intgreen = _default_drawstate.fgcolor.green;
250   intblue = _default_drawstate.fgcolor.blue;
251 
252   if (_string_to_color (name, &color, _plotter->data->color_name_cache))
253     {
254       unsigned int red, green, blue;
255 
256       red = color.red;
257       green = color.green;
258       blue = color.blue;
259       /* to convert from 24-bit to 48-bit color, double bytes */
260       intred = (red << 8) | red;
261       intgreen = (green << 8) | green;
262       intblue = (blue << 8) | blue;
263     }
264   else if (_plotter->data->pen_color_warning_issued == false)
265     {
266       char *buf;
267 
268       buf = (char *)_pl_xmalloc (strlen (name) + 100);
269       sprintf (buf, "substituting \"black\" for undefined pen color \"%s\"",
270 	       name);
271       _plotter->warning (R___(_plotter) buf);
272       free (buf);
273       _plotter->data->pen_color_warning_issued = true;
274     }
275 
276   _API_pencolor (R___(_plotter) intred, intgreen, intblue);
277 
278   return 0;
279 }
280 
281 int
_API_fillcolorname(R___ (Plotter * _plotter)const char * name)282 _API_fillcolorname (R___(Plotter *_plotter) const char *name)
283 {
284   plColor color;
285   int intred, intgreen, intblue;
286 
287   if (!_plotter->data->open)
288     {
289       _plotter->error (R___(_plotter)
290 		       "fillcolorname: invalid operation");
291       return -1;
292     }
293 
294   /* null pointer ignored */
295   if (!name)
296     return 0;
297 
298   /* RGB values for default fill color */
299   intred = _default_drawstate.fillcolor.red;
300   intgreen = _default_drawstate.fillcolor.green;
301   intblue = _default_drawstate.fillcolor.blue;
302 
303   if (_string_to_color (name, &color, _plotter->data->color_name_cache))
304     {
305       unsigned int red, green, blue;
306 
307       red = color.red;
308       green = color.green;
309       blue = color.blue;
310       /* to convert from 24-bit to 48-bit color, double bytes */
311       intred = (red << 8) | red;
312       intgreen = (green << 8) | green;
313       intblue = (blue << 8) | blue;
314     }
315   else if (_plotter->data->fill_color_warning_issued == false)
316     {
317       char *buf;
318 
319       buf = (char *)_pl_xmalloc (strlen (name) + 100);
320       sprintf (buf, "substituting \"black\" for undefined fill color \"%s\"",
321 	       name);
322       _plotter->warning (R___(_plotter) buf);
323       free (buf);
324       _plotter->data->fill_color_warning_issued = true;
325     }
326 
327   _API_fillcolor (R___(_plotter) intred, intgreen, intblue);
328 
329   return 0;
330 }
331 
332 /* convenience function */
333 
334 int
_API_colorname(R___ (Plotter * _plotter)const char * name)335 _API_colorname (R___(Plotter *_plotter) const char *name)
336 {
337   if (!_plotter->data->open)
338     {
339       _plotter->error (R___(_plotter)
340 		       "colorname: invalid operation");
341       return -1;
342     }
343 
344   _API_pencolorname (R___(_plotter) name);
345   _API_fillcolorname (R___(_plotter) name);
346 
347   return 0;
348 }
349 
350 int
_API_bgcolorname(R___ (Plotter * _plotter)const char * name)351 _API_bgcolorname (R___(Plotter *_plotter) const char *name)
352 {
353   plColor color;
354   int intred, intgreen, intblue;
355 
356   if (!_plotter->data->open)
357     {
358       _plotter->error (R___(_plotter)
359 		       "bgcolorname: invalid operation");
360       return -1;
361     }
362 
363   /* null pointer ignored */
364   if (!name)
365     return 0;
366 
367   if (strcmp (name, "none") == 0)
368     /* turn off background (some Plotters can implement this) */
369     {
370       _plotter->drawstate->bgcolor_suppressed = true;
371       /* treat as default, for benefit of Plotters that can't */
372       name = "white";
373     }
374   else
375     _plotter->drawstate->bgcolor_suppressed = false;
376 
377   /* RGB values for default color [white, presumably] */
378   intred = _default_drawstate.bgcolor.red;
379   intgreen = _default_drawstate.bgcolor.green;
380   intblue = _default_drawstate.bgcolor.blue;
381 
382   if (_string_to_color (name, &color, _plotter->data->color_name_cache))
383     {
384       unsigned int red, green, blue;
385 
386       red = color.red;
387       green = color.green;
388       blue = color.blue;
389       /* to convert from 24-bit to 48-bit color, double bytes */
390       intred = (red << 8) | red;
391       intgreen = (green << 8) | green;
392       intblue = (blue << 8) | blue;
393     }
394   else if (_plotter->data->bg_color_warning_issued == false)
395     {
396       char *buf;
397 
398       buf = (char *)_pl_xmalloc (strlen (name) + 100);
399       sprintf (buf, "substituting \"white\" for undefined background color \"%s\"",
400 	       name);
401       _plotter->warning (R___(_plotter) buf);
402       free (buf);
403       _plotter->data->bg_color_warning_issued = true;
404     }
405 
406   _API_bgcolor (R___(_plotter) intred, intgreen, intblue);
407 
408   return 0;
409 }
410 
411 /* _string_to_color() searches a database of known color names, in
412    g_colorname.h, for a specified string.  Matches are case-insensitive and
413    ignore spaces.  The retrieved RGB components are returned via a pointer.
414 
415    We don't wish to search through the entire (long) color database.  (It
416    contains 600+ color name strings.)  So any Plotter maintains a cache
417    of previously found colors. */
418 
419 bool
_string_to_color(const char * name,plColor * color_p,plColorNameCache * color_name_cache)420 _string_to_color (const char *name, plColor *color_p, plColorNameCache *color_name_cache)
421 {
422   plColor color;
423   plCachedColorNameInfo **cached_colors_p;
424   bool found = false;
425   char *squeezed_name, *nptr;
426   const plColorNameInfo *info, *found_info = NULL;
427   const char *optr;
428   plCachedColorNameInfo *cached_info;
429 
430   if (name == NULL)		/* avoid core dumps */
431     return false;
432 
433   if (color_name_cache == NULL)	/* avoid core dumps */
434     return false;
435 
436   /* first check whether string is of the form "#ffffff" */
437   if (string_to_precise_color (name, &color))
438     {
439       *color_p = color;
440       return true;
441     }
442 
443   /* copy string, removing spaces */
444   squeezed_name = (char *)_pl_xmalloc (strlen (name) + 1);
445   optr = name, nptr = squeezed_name;
446   while (*optr)
447     {
448       if (*optr == '\0')
449 	break;
450       if (*optr != ' ')
451 	*nptr++ = *optr;
452       optr++;
453     }
454   *nptr = '\0';
455 
456   /* Search our list of cached, previously used color names, doing string
457      comparison.  If this were only for use by the X11 driver, we'd use
458      XrmPermStringToQuark to get a faster-compared representation. */
459 
460   cached_colors_p = &color_name_cache->cached_colors;
461   cached_info = *cached_colors_p;
462   while (cached_info)
463     {
464       if (strcasecmp (cached_info->info->name, squeezed_name) == 0)
465 	{
466 	  found = true;
467 	  found_info = cached_info->info;
468 	  break;
469 	}
470       cached_info = cached_info->next;
471     }
472 
473   if (!found)
474    /* not previously used, so search master colorname table (this is slower) */
475     {
476       info = _pl_g_colornames; /* start at head of list in g_colorname.h */
477       while (info->name)
478 	{
479 	  if (strcasecmp (info->name, squeezed_name) == 0)
480 	    {
481 	      found = true;
482 	      found_info = info;
483 	      break;
484 	    }
485 	  info++;
486 	}
487 
488       if (found)
489 	/* copy to head of cached color list */
490 	{
491 	  plCachedColorNameInfo *old_cached_colors, *cached_colors;
492 
493 	  old_cached_colors = *cached_colors_p;
494 	  cached_colors =
495 	    (plCachedColorNameInfo *)_pl_xmalloc (sizeof (plCachedColorNameInfo));
496 	  cached_colors->next = old_cached_colors;
497 	  cached_colors->info = found_info;
498 	  *cached_colors_p = cached_colors;
499 	}
500     }
501 
502   free (squeezed_name);
503   if (found)
504     {
505       color_p->red = found_info->red;
506       color_p->green = found_info->green;
507       color_p->blue = found_info->blue;
508     }
509 
510   return found;
511 }
512 
513 /* Attempt to map a string to a 24-bit RGB; this will work if the string is
514    of the form "#ffffff". */
515 
516 static bool
string_to_precise_color(const char * name,plColor * color_p)517 string_to_precise_color (const char *name, plColor *color_p)
518 {
519   const char *good_hex_digits = "0123456789abcdefABCDEF";
520   int i, num_assigned;
521 
522   if (name == (const char *)NULL || *name != '#')
523     return false;
524 
525   for (i = 1; i <= 8 ; i++)
526     {
527       bool found;
528       const char *cp;
529 
530       if (name[i] == '\0')
531 	break;
532       cp = good_hex_digits;
533       found = false;
534       while (*cp)
535 	{
536 	  if (name[i] == *cp)
537 	    {
538 	      found = true;
539 	      break;
540 	    }
541 	  cp++;
542 	}
543       if (found == false)
544 	return false;
545     }
546   if (i != 7)
547     return false;
548 
549   /* okay, have something like "#ffffff"; can now safely use scanf() */
550 
551   num_assigned = sscanf (name, "#%2x%2x%2x",
552 			 &(color_p->red), &(color_p->green), &(color_p->blue));
553 
554   return (num_assigned == 3 ? true : false);
555 }
556 
557 
558 /* The cache of color names is currently implemented as a linked list. */
559 
560 plColorNameCache *
_create_color_name_cache(void)561 _create_color_name_cache (void)
562 {
563   plColorNameCache *new_cache;
564 
565   new_cache = (plColorNameCache *)_pl_xmalloc(sizeof(plColorNameCache));
566   new_cache->cached_colors = NULL;
567   return new_cache;
568 }
569 
570 void
_delete_color_name_cache(plColorNameCache * color_name_cache)571 _delete_color_name_cache (plColorNameCache *color_name_cache)
572 {
573   plCachedColorNameInfo *colorptr;
574 
575   if (color_name_cache == (plColorNameCache *)NULL)
576     return;
577 
578   colorptr = color_name_cache->cached_colors;
579   while (colorptr != NULL)	/* free linked list */
580     {
581       plCachedColorNameInfo *next_colorptr;
582 
583       next_colorptr = colorptr->next;
584       free (colorptr);
585       colorptr = next_colorptr;
586     }
587 
588   free (color_name_cache);	/* free structure itself */
589 }
590