add the config header and emacs comment to all the .cc's
[mikachu/openbox.git] / otk / imagecontrol.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #ifdef    HAVE_CONFIG_H
4 #  include "../config.h"
5 #endif // HAVE_CONFIG_H
6
7 extern "C" {
8 #ifdef    HAVE_STDIO_H
9 #  include <stdio.h>
10 #endif // HAVE_STDIO_H
11
12 #ifdef    HAVE_CTYPE_H
13 #  include <ctype.h>
14 #endif // HAVE_CTYPE_H
15
16 #include <X11/Xlib.h>
17 }
18
19 #include <algorithm>
20
21 #include "display.hh"
22 #include "color.hh"
23 #include "image.hh"
24 #include "texture.hh"
25
26 namespace otk {
27
28 static unsigned long bsqrt(unsigned long x) {
29   if (x <= 0) return 0;
30   if (x == 1) return 1;
31
32   unsigned long r = x >> 1;
33   unsigned long q;
34
35   while (1) {
36     q = x / r;
37     if (q >= r) return r;
38     r = (r + q) >> 1;
39   }
40 }
41
42 BImageControl *ctrl = 0;
43
44 BImageControl::BImageControl(OBTimerQueueManager *timermanager,
45                              const ScreenInfo *scrn,
46                              bool _dither, int _cpc,
47                              unsigned long cache_timeout,
48                              unsigned long cmax) {
49   if (! ctrl) ctrl = this;
50
51   screeninfo = scrn;
52   setDither(_dither);
53   setColorsPerChannel(_cpc);
54
55   cache_max = cmax;
56   if (cache_timeout) {
57     timer = new OBTimer(timermanager, (OBTimeoutHandler)timeout, this);
58     timer->setTimeout(cache_timeout);
59     timer->start();
60   } else {
61     timer = (OBTimer *) 0;
62   }
63
64   colors = (XColor *) 0;
65   ncolors = 0;
66
67   grad_xbuffer = grad_ybuffer = (unsigned int *) 0;
68   grad_buffer_width = grad_buffer_height = 0;
69
70   sqrt_table = (unsigned long *) 0;
71
72   screen_depth = screeninfo->getDepth();
73   window = screeninfo->getRootWindow();
74   screen_number = screeninfo->getScreenNumber();
75   colormap = screeninfo->getColormap();
76
77   int count;
78   XPixmapFormatValues *pmv = XListPixmapFormats(OBDisplay::display,
79                                                 &count);
80   if (pmv) {
81     bits_per_pixel = 0;
82     for (int i = 0; i < count; i++)
83       if (pmv[i].depth == screen_depth) {
84         bits_per_pixel = pmv[i].bits_per_pixel;
85         break;
86       }
87
88     XFree(pmv);
89   }
90
91   if (bits_per_pixel == 0) bits_per_pixel = screen_depth;
92   if (bits_per_pixel >= 24) setDither(False);
93
94   red_offset = green_offset = blue_offset = 0;
95
96   switch (getVisual()->c_class) {
97   case TrueColor: {
98     int i;
99
100     // compute color tables
101     unsigned long red_mask = getVisual()->red_mask,
102       green_mask = getVisual()->green_mask,
103       blue_mask = getVisual()->blue_mask;
104
105     while (! (red_mask & 1)) { red_offset++; red_mask >>= 1; }
106     while (! (green_mask & 1)) { green_offset++; green_mask >>= 1; }
107     while (! (blue_mask & 1)) { blue_offset++; blue_mask >>= 1; }
108
109     red_bits = 255 / red_mask;
110     green_bits = 255 / green_mask;
111     blue_bits = 255 / blue_mask;
112
113     for (i = 0; i < 256; i++) {
114       red_color_table[i] = i / red_bits;
115       green_color_table[i] = i / green_bits;
116       blue_color_table[i] = i / blue_bits;
117     }
118     break;
119   }
120
121   case PseudoColor:
122   case StaticColor: {
123     ncolors = colors_per_channel * colors_per_channel * colors_per_channel;
124
125     if (ncolors > (1 << screen_depth)) {
126       colors_per_channel = (1 << screen_depth) / 3;
127       ncolors = colors_per_channel * colors_per_channel * colors_per_channel;
128     }
129
130     if (colors_per_channel < 2 || ncolors > (1 << screen_depth)) {
131       fprintf(stderr,
132               "BImageControl::BImageControl: invalid colormap size %d "
133               "(%d/%d/%d) - reducing",
134               ncolors, colors_per_channel, colors_per_channel,
135               colors_per_channel);
136
137       colors_per_channel = (1 << screen_depth) / 3;
138     }
139
140     colors = new XColor[ncolors];
141     if (! colors) {
142       fprintf(stderr, "BImageControl::BImageControl: error allocating "
143               "colormap\n");
144       exit(1);
145     }
146
147     int i = 0, ii, p, r, g, b,
148
149 #ifdef ORDEREDPSEUDO
150       bits = 256 / colors_per_channel;
151 #else // !ORDEREDPSEUDO
152     bits = 255 / (colors_per_channel - 1);
153 #endif // ORDEREDPSEUDO
154
155     red_bits = green_bits = blue_bits = bits;
156
157     for (i = 0; i < 256; i++)
158       red_color_table[i] = green_color_table[i] = blue_color_table[i] =
159         i / bits;
160
161     for (r = 0, i = 0; r < colors_per_channel; r++)
162       for (g = 0; g < colors_per_channel; g++)
163         for (b = 0; b < colors_per_channel; b++, i++) {
164           colors[i].red = (r * 0xffff) / (colors_per_channel - 1);
165           colors[i].green = (g * 0xffff) / (colors_per_channel - 1);
166           colors[i].blue = (b * 0xffff) / (colors_per_channel - 1);;
167           colors[i].flags = DoRed|DoGreen|DoBlue;
168         }
169
170     for (i = 0; i < ncolors; i++) {
171       if (! XAllocColor(OBDisplay::display, colormap, &colors[i])) {
172         fprintf(stderr, "couldn't alloc color %i %i %i\n",
173                 colors[i].red, colors[i].green, colors[i].blue);
174         colors[i].flags = 0;
175       } else {
176         colors[i].flags = DoRed|DoGreen|DoBlue;
177       }
178     }
179
180     XColor icolors[256];
181     int incolors = (((1 << screen_depth) > 256) ? 256 : (1 << screen_depth));
182
183     for (i = 0; i < incolors; i++)
184       icolors[i].pixel = i;
185
186     XQueryColors(OBDisplay::display, colormap, icolors, incolors);
187     for (i = 0; i < ncolors; i++) {
188       if (! colors[i].flags) {
189         unsigned long chk = 0xffffffff, pixel, close = 0;
190
191         p = 2;
192         while (p--) {
193           for (ii = 0; ii < incolors; ii++) {
194             r = (colors[i].red - icolors[i].red) >> 8;
195             g = (colors[i].green - icolors[i].green) >> 8;
196             b = (colors[i].blue - icolors[i].blue) >> 8;
197             pixel = (r * r) + (g * g) + (b * b);
198
199             if (pixel < chk) {
200               chk = pixel;
201               close = ii;
202             }
203
204             colors[i].red = icolors[close].red;
205             colors[i].green = icolors[close].green;
206             colors[i].blue = icolors[close].blue;
207
208             if (XAllocColor(OBDisplay::display, colormap,
209                             &colors[i])) {
210               colors[i].flags = DoRed|DoGreen|DoBlue;
211               break;
212             }
213           }
214         }
215       }
216     }
217
218     break;
219   }
220
221   case GrayScale:
222   case StaticGray: {
223     if (getVisual()->c_class == StaticGray) {
224       ncolors = 1 << screen_depth;
225     } else {
226       ncolors = colors_per_channel * colors_per_channel * colors_per_channel;
227
228       if (ncolors > (1 << screen_depth)) {
229         colors_per_channel = (1 << screen_depth) / 3;
230         ncolors =
231           colors_per_channel * colors_per_channel * colors_per_channel;
232       }
233     }
234
235     if (colors_per_channel < 2 || ncolors > (1 << screen_depth)) {
236       fprintf(stderr,
237               "BImageControl::BImageControl: invalid colormap size %d "
238               "(%d/%d/%d) - reducing",
239               ncolors, colors_per_channel, colors_per_channel,
240               colors_per_channel);
241
242       colors_per_channel = (1 << screen_depth) / 3;
243     }
244
245     colors = new XColor[ncolors];
246     if (! colors) {
247       fprintf(stderr,
248               "BImageControl::BImageControl: error allocating colormap\n");
249       exit(1);
250     }
251
252     int i = 0, ii, p, bits = 255 / (colors_per_channel - 1);
253     red_bits = green_bits = blue_bits = bits;
254
255     for (i = 0; i < 256; i++)
256       red_color_table[i] = green_color_table[i] = blue_color_table[i] =
257         i / bits;
258
259     for (i = 0; i < ncolors; i++) {
260       colors[i].red = (i * 0xffff) / (colors_per_channel - 1);
261       colors[i].green = (i * 0xffff) / (colors_per_channel - 1);
262       colors[i].blue = (i * 0xffff) / (colors_per_channel - 1);;
263       colors[i].flags = DoRed|DoGreen|DoBlue;
264
265       if (! XAllocColor(OBDisplay::display, colormap,
266                         &colors[i])) {
267         fprintf(stderr, "couldn't alloc color %i %i %i\n",
268                 colors[i].red, colors[i].green, colors[i].blue);
269         colors[i].flags = 0;
270       } else {
271         colors[i].flags = DoRed|DoGreen|DoBlue;
272       }
273     }
274
275     XColor icolors[256];
276     int incolors = (((1 << screen_depth) > 256) ? 256 :
277                     (1 << screen_depth));
278
279     for (i = 0; i < incolors; i++)
280       icolors[i].pixel = i;
281
282     XQueryColors(OBDisplay::display, colormap, icolors, incolors);
283     for (i = 0; i < ncolors; i++) {
284       if (! colors[i].flags) {
285         unsigned long chk = 0xffffffff, pixel, close = 0;
286
287         p = 2;
288         while (p--) {
289           for (ii = 0; ii < incolors; ii++) {
290             int r = (colors[i].red - icolors[i].red) >> 8;
291             int g = (colors[i].green - icolors[i].green) >> 8;
292             int b = (colors[i].blue - icolors[i].blue) >> 8;
293             pixel = (r * r) + (g * g) + (b * b);
294
295             if (pixel < chk) {
296               chk = pixel;
297               close = ii;
298             }
299
300             colors[i].red = icolors[close].red;
301             colors[i].green = icolors[close].green;
302             colors[i].blue = icolors[close].blue;
303
304             if (XAllocColor(OBDisplay::display, colormap,
305                             &colors[i])) {
306               colors[i].flags = DoRed|DoGreen|DoBlue;
307               break;
308             }
309           }
310         }
311       }
312     }
313
314     break;
315   }
316
317   default:
318     fprintf(stderr, "BImageControl::BImageControl: unsupported visual %d\n",
319             getVisual()->c_class);
320     exit(1);
321   }
322 }
323
324
325 BImageControl::~BImageControl(void) {
326   delete [] sqrt_table;
327
328   delete [] grad_xbuffer;
329
330   delete [] grad_ybuffer;
331
332   if (colors) {
333     unsigned long *pixels = new unsigned long [ncolors];
334
335     for (int i = 0; i < ncolors; i++)
336       *(pixels + i) = (*(colors + i)).pixel;
337
338     XFreeColors(OBDisplay::display, colormap, pixels, ncolors, 0);
339
340     delete [] colors;
341   }
342
343   if (! cache.empty()) {
344     //#ifdef DEBUG
345     fprintf(stderr, "BImageContol::~BImageControl: pixmap cache - "
346             "releasing %d pixmaps\n", cache.size());
347     //#endif
348     CacheContainer::iterator it = cache.begin();
349     const CacheContainer::iterator end = cache.end();
350     for (; it != end; ++it)
351       XFreePixmap(OBDisplay::display, it->pixmap);
352   }
353   if (timer) {
354     timer->stop();
355     delete timer;
356   }
357 }
358
359
360 Pixmap BImageControl::searchCache(const unsigned int width,
361                                   const unsigned int height,
362                                   const unsigned long texture,
363                                   const BColor &c1, const BColor &c2) {
364   if (cache.empty())
365     return None;
366
367   CacheContainer::iterator it = cache.begin();
368   const CacheContainer::iterator end = cache.end();
369   for (; it != end; ++it) {
370     CachedImage& tmp = *it;
371     if (tmp.width == width && tmp.height == height &&
372         tmp.texture == texture && tmp.pixel1 == c1.pixel())
373       if (texture & BTexture::Gradient) {
374         if (tmp.pixel2 == c2.pixel()) {
375           tmp.count++;
376           return tmp.pixmap;
377         }
378       } else {
379         tmp.count++;
380         return tmp.pixmap;
381       }
382   }
383   return None;
384 }
385
386
387 Pixmap BImageControl::renderImage(unsigned int width, unsigned int height,
388                                   const BTexture &texture) {
389   if (texture.texture() & BTexture::Parent_Relative) return ParentRelative;
390
391   Pixmap pixmap = searchCache(width, height, texture.texture(),
392                               texture.color(), texture.colorTo());
393   if (pixmap) return pixmap;
394
395   BImage image(this, width, height);
396   pixmap = image.render(texture);
397
398   if (! pixmap)
399     return None;
400
401   CachedImage tmp;
402
403   tmp.pixmap = pixmap;
404   tmp.width = width;
405   tmp.height = height;
406   tmp.count = 1;
407   tmp.texture = texture.texture();
408   tmp.pixel1 = texture.color().pixel();
409
410   if (texture.texture() & BTexture::Gradient)
411     tmp.pixel2 = texture.colorTo().pixel();
412   else
413     tmp.pixel2 = 0l;
414
415   cache.push_back(tmp);
416
417   if (cache.size() > cache_max) {
418 #ifdef    DEBUG
419     fprintf(stderr, "BImageControl::renderImage: cache is large, "
420       "forcing cleanout\n");
421 #endif // DEBUG
422
423     timeout(this);
424   }
425
426   return pixmap;
427 }
428
429
430 void BImageControl::removeImage(Pixmap pixmap) {
431   if (! pixmap)
432     return;
433
434   CacheContainer::iterator it = cache.begin();
435   const CacheContainer::iterator end = cache.end();
436   for (; it != end; ++it) {
437     CachedImage &tmp = *it;
438     if (tmp.pixmap == pixmap && tmp.count > 0)
439       tmp.count--;
440   }
441
442   if (! timer)
443     timeout(this);
444 }
445
446
447 void BImageControl::getColorTables(unsigned char **rmt, unsigned char **gmt,
448                                    unsigned char **bmt,
449                                    int *roff, int *goff, int *boff,
450                                    int *rbit, int *gbit, int *bbit) {
451   if (rmt) *rmt = red_color_table;
452   if (gmt) *gmt = green_color_table;
453   if (bmt) *bmt = blue_color_table;
454
455   if (roff) *roff = red_offset;
456   if (goff) *goff = green_offset;
457   if (boff) *boff = blue_offset;
458
459   if (rbit) *rbit = red_bits;
460   if (gbit) *gbit = green_bits;
461   if (bbit) *bbit = blue_bits;
462 }
463
464
465 void BImageControl::getXColorTable(XColor **c, int *n) {
466   if (c) *c = colors;
467   if (n) *n = ncolors;
468 }
469
470
471 void BImageControl::getGradientBuffers(unsigned int w,
472                                        unsigned int h,
473                                        unsigned int **xbuf,
474                                        unsigned int **ybuf)
475 {
476   if (w > grad_buffer_width) {
477     if (grad_xbuffer)
478       delete [] grad_xbuffer;
479
480     grad_buffer_width = w;
481
482     grad_xbuffer = new unsigned int[grad_buffer_width * 3];
483   }
484
485   if (h > grad_buffer_height) {
486     if (grad_ybuffer)
487       delete [] grad_ybuffer;
488
489     grad_buffer_height = h;
490
491     grad_ybuffer = new unsigned int[grad_buffer_height * 3];
492   }
493
494   *xbuf = grad_xbuffer;
495   *ybuf = grad_ybuffer;
496 }
497
498
499 void BImageControl::installRootColormap(void) {
500   int ncmap = 0;
501   Colormap *cmaps =
502     XListInstalledColormaps(OBDisplay::display, window, &ncmap);
503
504   if (cmaps) {
505     bool install = True;
506     for (int i = 0; i < ncmap; i++)
507       if (*(cmaps + i) == colormap)
508         install = False;
509
510     if (install)
511       XInstallColormap(OBDisplay::display, colormap);
512
513     XFree(cmaps);
514   }
515 }
516
517
518 void BImageControl::setColorsPerChannel(int cpc) {
519   if (cpc < 2) cpc = 2;
520   if (cpc > 6) cpc = 6;
521
522   colors_per_channel = cpc;
523 }
524
525
526 unsigned long BImageControl::getSqrt(unsigned int x) {
527   if (! sqrt_table) {
528     // build sqrt table for use with elliptic gradient
529
530     sqrt_table = new unsigned long[(256 * 256 * 2) + 1];
531
532     for (int i = 0; i < (256 * 256 * 2); i++)
533       *(sqrt_table + i) = bsqrt(i);
534   }
535
536   return (*(sqrt_table + x));
537 }
538
539
540 struct ZeroRefCheck {
541   inline bool operator()(const BImageControl::CachedImage &image) const {
542     return (image.count == 0);
543   }
544 };
545
546 struct CacheCleaner {
547   ZeroRefCheck ref_check;
548   CacheCleaner() {}
549   inline void operator()(const BImageControl::CachedImage& image) const {
550     if (ref_check(image))
551       XFreePixmap(OBDisplay::display, image.pixmap);
552   }
553 };
554
555
556 void BImageControl::timeout(BImageControl *t) {
557   CacheCleaner cleaner;
558   std::for_each(t->cache.begin(), t->cache.end(), cleaner);
559   t->cache.remove_if(cleaner.ref_check);
560 }
561
562 }