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