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