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 device-specific color computation routines.  These
20    routines are called by various XDrawablePlotter (and XPlotter)
21    methods. */
22 
23 #include "sys-defines.h"
24 #include "extern.h"
25 
26 /* we call this routine to set the foreground color in the X GC used for
27    drawing, only when needed (just before an object is written out) */
28 
29 void
_pl_x_set_pen_color(S___ (Plotter * _plotter))30 _pl_x_set_pen_color(S___(Plotter *_plotter))
31 {
32   plColor old, new1;
33   XColor rgb;
34 
35   new1 = _plotter->drawstate->fgcolor;
36   old = _plotter->drawstate->x_current_fgcolor; /* i.e. as stored in gc */
37   if (new1.red == old.red && new1.green == old.green && new1.blue == old.blue
38       && _plotter->drawstate->x_gc_fgcolor_status)
39     /* can use current color cell */
40     return;
41 
42   rgb.red = new1.red;
43   rgb.green = new1.green;
44   rgb.blue = new1.blue;
45 
46   /* retrieve matching color cell, if possible */
47   if (_pl_x_retrieve_color (R___(_plotter) &rgb) == false)
48     return;
49 
50   /* select pen color as foreground color in GC used for drawing */
51   XSetForeground (_plotter->x_dpy, _plotter->drawstate->x_gc_fg, rgb.pixel);
52 
53   /* save the new pixel value */
54   _plotter->drawstate->x_gc_fgcolor = rgb.pixel;
55 
56   /* flag this as a genuine pixel value */
57   _plotter->drawstate->x_gc_fgcolor_status = true;
58 
59   /* update non-opaque representation of stored foreground color */
60   _plotter->drawstate->x_current_fgcolor = new1;
61 }
62 
63 /* we call this routine to set the foreground color in the X GC used for
64    filling, only when needed (just before an object is written out) */
65 
66 void
_pl_x_set_fill_color(S___ (Plotter * _plotter))67 _pl_x_set_fill_color(S___(Plotter *_plotter))
68 {
69   plColor old, new1;
70   XColor rgb;
71 
72   if (_plotter->drawstate->fill_type == 0) /* transparent */
73     /* don't do anything, fill color will be ignored when writing objects*/
74     return;
75 
76   new1 = _plotter->drawstate->fillcolor;
77   old = _plotter->drawstate->x_current_fillcolor; /* as used in GC */
78   if (new1.red == old.red && new1.green == old.green && new1.blue == old.blue
79       && _plotter->drawstate->x_gc_fillcolor_status)
80     /* can use current color cell */
81     return;
82 
83   rgb.red = (short)_plotter->drawstate->fillcolor.red;
84   rgb.green = (short)_plotter->drawstate->fillcolor.green;
85   rgb.blue = (short)_plotter->drawstate->fillcolor.blue;
86 
87   /* retrieve matching color cell, if possible */
88   if (_pl_x_retrieve_color (R___(_plotter) &rgb) == false)
89     return;
90 
91   /* select fill color as foreground color in GC used for filling */
92   XSetForeground (_plotter->x_dpy, _plotter->drawstate->x_gc_fill, rgb.pixel);
93 
94   /* save the new pixel value */
95   _plotter->drawstate->x_gc_fillcolor = rgb.pixel;
96 
97   /* flag this as a genuine pixel value */
98   _plotter->drawstate->x_gc_fillcolor_status = true;
99 
100   /* update non-opaque representation of stored fill color */
101   _plotter->drawstate->x_current_fillcolor = new1;
102 }
103 
104 /* we call this routine to set the foreground color in the X GC used for
105    erasing, only when needed (just before an erasure takes place) */
106 
107 void
_pl_x_set_bg_color(S___ (Plotter * _plotter))108 _pl_x_set_bg_color(S___(Plotter *_plotter))
109 {
110   plColor old, new1;
111   XColor rgb;
112 
113   new1 = _plotter->drawstate->bgcolor;
114   old = _plotter->drawstate->x_current_bgcolor; /* i.e. as stored in gc */
115   if (new1.red == old.red && new1.green == old.green && new1.blue == old.blue
116       && _plotter->drawstate->x_gc_bgcolor_status)
117     /* can use current color cell */
118     return;
119 
120   rgb.red = new1.red;
121   rgb.green = new1.green;
122   rgb.blue = new1.blue;
123 
124   /* retrieve matching color cell, if possible */
125   if (_pl_x_retrieve_color (R___(_plotter) &rgb) == false)
126     return;
127 
128   /* select background color as foreground color in GC used for erasing */
129   XSetForeground (_plotter->x_dpy, _plotter->drawstate->x_gc_bg, rgb.pixel);
130 
131   /* save the new pixel value */
132   _plotter->drawstate->x_gc_bgcolor = rgb.pixel;
133 
134   /* flag this as a genuine pixel value */
135   _plotter->drawstate->x_gc_bgcolor_status = true;
136 
137   /* update non-opaque representation of stored background color */
138   _plotter->drawstate->x_current_bgcolor = new1;
139 }
140 
141 /* This is the internal X color retrieval routine.  If the visual class
142    is known and is TrueColor, it computes the X pixel value from a 48-bit
143    RGB without invoking XAllocColor(), which would require a round trip
144    to the server.
145 
146    Otherwise, it first searches for a specified RGB in a cache of
147    previously retrieved color cells, and if that fails, tries to allocate a
148    new color cell by calling XAllocColor().  If that fails, and a new
149    colormap can be switched to, it switches to a new colormap and tries
150    again.  If that attempt also fails, it searches the cache for the
151    colorcell with an RGB that's closest to the specified RGB.  Only if that
152    fails as well (i.e. the cache is empty), does it return false.
153 
154    Cache is maintained as a linked list (not optimal, but it facilitates
155    color cell management; see comment in x_erase.c). */
156 
157 bool
_pl_x_retrieve_color(R___ (Plotter * _plotter)XColor * rgb_ptr)158 _pl_x_retrieve_color (R___(Plotter *_plotter) XColor *rgb_ptr)
159 {
160   plColorRecord *cptr;
161   int rgb_red = rgb_ptr->red;
162   int rgb_green = rgb_ptr->green;
163   int rgb_blue = rgb_ptr->blue;
164   int xretval;
165 
166 #ifdef LIBPLOTTER
167   if (_plotter->x_visual && _plotter->x_visual->c_class == TrueColor)
168 #else
169 #ifdef __cplusplus
170   if (_plotter->x_visual && _plotter->x_visual->c_class == TrueColor)
171 #else
172   if (_plotter->x_visual && _plotter->x_visual->class == TrueColor)
173 #endif
174 #endif
175     /* can compute pixel value from RGB without calling XAllocColor(), by
176        bit-twiddling */
177     {
178       unsigned long red_mask, green_mask, blue_mask;
179       int red_shift, green_shift, blue_shift;
180       int red_bits, green_bits, blue_bits;
181 
182       /* first, compute {R,G,B}_bits and {R,G,B}_shift (should be precomputed) */
183 
184       red_mask = _plotter->x_visual->red_mask; red_shift = red_bits = 0;
185       while (!(red_mask & 1))
186         {
187 	  red_mask >>= 1; red_shift++;
188         }
189       while (red_mask & 1)
190         {
191 	  red_mask >>= 1; red_bits++;
192         }
193       green_mask = _plotter->x_visual->green_mask; green_shift = green_bits = 0;
194       while (!(green_mask & 1))
195         {
196 	  green_mask >>= 1; green_shift++;
197         }
198       while (green_mask & 1)
199         {
200 	  green_mask >>= 1; green_bits++;
201         }
202       blue_mask = _plotter->x_visual->blue_mask; blue_shift = blue_bits = 0;
203       while (!(blue_mask & 1))
204         {
205 	  blue_mask >>= 1; blue_shift++;
206         }
207       while (blue_mask & 1)
208         {
209 	  blue_mask >>= 1; blue_bits++;
210         }
211 
212       /* compute and pass back pixel, as a 32-bit unsigned long */
213       rgb_red = rgb_red >> (16 - red_bits);
214       rgb_green = rgb_green >> (16 - green_bits);
215       rgb_blue = rgb_blue >> (16 - blue_bits);
216       rgb_ptr->pixel = ((rgb_red << red_shift) & _plotter->x_visual->red_mask)
217 			 | ((rgb_green << green_shift) & _plotter->x_visual->green_mask)
218 			 | ((rgb_blue << blue_shift) & _plotter->x_visual->blue_mask);
219 #if 0
220       fprintf (stderr, "pixel=0x%lx, R=0x%hx, G=0x%hx, B=0x%hx\n",
221 	       rgb_ptr->pixel, rgb_ptr->red, rgb_ptr->green, rgb_ptr->blue);
222 #endif
223 
224       return true;
225     }
226 
227   /* If we got here, we weren't able to compute the pixel value from the
228      RGB without calling XAllocColor().  So may have to do that, but first
229      we consult a list of previously allocated color cells. */
230 
231   /* search cache list */
232   for (cptr = _plotter->x_colorlist; cptr; cptr = cptr->next)
233     {
234       XColor cached_rgb;
235 
236       cached_rgb = cptr->rgb;
237       if (cached_rgb.red == rgb_red
238 	  && cached_rgb.green == rgb_green
239 	  && cached_rgb.blue == rgb_blue)
240 	/* found in cache */
241 	{
242 	  /* keep track of page, frame number in which cell was most
243 	     recently accessed */
244 	  cptr->page_number = _plotter->data->page_number;
245 	  cptr->frame_number = _plotter->data->frame_number;
246 	  /* return stored pixel value */
247 	  *rgb_ptr = cached_rgb;
248 	  return true;
249 	}
250     }
251 
252   /* not in cache, so try to allocate a new color cell, if colormap hasn't
253      been flagged as bad (i.e. full) */
254   if (_plotter->x_cmap_type != X_CMAP_BAD)
255     {
256       xretval = XAllocColor (_plotter->x_dpy, _plotter->x_cmap, rgb_ptr);
257 
258       if (xretval == 0)
259 	/* failure */
260 	{
261 	  if (_plotter->x_cmap_type == X_CMAP_ORIG)
262 	    /* colormap is the one we started with, so try switching and
263 	       reallocating */
264 	    {
265 	      /* Which method is invoked here depends on the type of
266 		 Plotter.  If this is an X Plotter, replace its colormap by
267 		 a copied, private colormap if we can; otherwise we flag
268 		 the colormap as bad (i.e. filled up).  If this is an
269 		 XDrawable Plotter, this method doesn't do anything, so
270 		 colormap just gets flagged as bad. */
271 	      _maybe_get_new_colormap (S___(_plotter));
272 	      if (_plotter->x_cmap_type != X_CMAP_NEW)
273 		_plotter->x_cmap_type = X_CMAP_BAD;
274 
275 	      if (_plotter->x_cmap_type != X_CMAP_BAD)
276 		/* got a new colormap; try again to allocate color cell */
277 		xretval = XAllocColor (_plotter->x_dpy, _plotter->x_cmap, rgb_ptr);
278 	    }
279 	}
280     }
281   else
282     /* colormap is bad, i.e. full; no hope of allocating a new colorcell */
283     xretval = 0;
284 
285   if (xretval == 0)
286     /* allocation failed, and no switching or further switching of
287        colormaps is possible; so simply search cache list for closest
288        color, among previously allocated cells */
289     {
290       XColor cached_rgb;
291       plColorRecord *best_cptr = NULL;
292       double distance = DBL_MAX;
293 
294       /* flag colormap as bad, i.e. full; no further color cell allocations
295          will be attempted */
296       _plotter->x_cmap_type = X_CMAP_BAD;
297 
298       if (_plotter->x_colormap_warning_issued == false)
299 	{
300 	  _plotter->warning(R___(_plotter)
301 			    "color supply exhausted, can't create new colors");
302 	  _plotter->x_colormap_warning_issued = true;
303 	}
304 
305       for (cptr = _plotter->x_colorlist; cptr; cptr = cptr->next)
306 	{
307 	  double newdistance;
308 
309 	  cached_rgb = cptr->rgb;
310 	  newdistance = (((rgb_red - cached_rgb.red)
311 			  * (rgb_red - cached_rgb.red))
312 			 + ((rgb_green - cached_rgb.green)
313 			    * (rgb_green - cached_rgb.green))
314 			 + ((rgb_blue - cached_rgb.blue)
315 			    * (rgb_blue - cached_rgb.blue)));
316 	  if (newdistance < distance)
317 	    {
318 	      distance = newdistance;
319 	      best_cptr = cptr;
320 	    }
321 	}
322 
323       if (best_cptr != (plColorRecord *)NULL)
324 	{
325 	  /* keep track of page, frame number in which cell was most
326 	     recently accessed */
327 	  best_cptr->page_number = _plotter->data->page_number;
328 	  best_cptr->frame_number = _plotter->data->frame_number;
329 	  /* return pixel value via pointer */
330 	  *rgb_ptr = best_cptr->rgb;
331 	  return true;
332 	}
333       else
334 	/* cache must be empty; bad news */
335 	return false;
336     }
337 
338   else
339     /* allocation succeeded, add new color cell to head of cache list */
340     {
341       cptr = (plColorRecord *)_pl_xmalloc (sizeof (plColorRecord));
342       cptr->rgb = *rgb_ptr;
343       /* include unquantized RGB values */
344       cptr->rgb.red = rgb_red;
345       cptr->rgb.green = rgb_green;
346       cptr->rgb.blue = rgb_blue;
347       cptr->allocated = true;	/* vestigial field */
348       /* keep track of page, frame number in which cell was allocated */
349       cptr->page_number = _plotter->data->page_number;
350       cptr->frame_number = _plotter->data->frame_number;
351       cptr->next = _plotter->x_colorlist;
352       _plotter->x_colorlist = cptr;
353 #if 0
354       fprintf (stderr, "pixel=0x%lx, R=0x%hx, G=0x%hx, B=0x%hx\n",
355 	       cptr->rgb.pixel, cptr->rgb.red, cptr->rgb.green, cptr->rgb.blue);
356 #endif
357       return true;
358     }
359 }
360