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